You must have read plenty of articles on how to tweak your Drupal site to improve its page load times. This post assembles an exhaustive list of all the configuration changes you can do that help in Drupal performance tuning.
- Enable block cache. Blocks that don't change from one user to another can be cached and served.
- Enable Drupal cache for anonymous users. Use this if you are not using any other caching solution such as front-end cache (Varnish) or Boost.
- Cache views. There are plenty of options:Use this patch if you want to cache filtered results based on exposed filter selections as well.
- Time-based (built into core). Use this if you want to expire the Views cache after a specific period of time and if you are fine serving stale content for some period of time.
- Content cache. Use this if you want to expire the Views every time a content is inserted, updated or deleted. Use this if you expect content changes to happen rather infrequently on your site.
- Row cache. This caches the rendered output of each row separately but the query is not cached. Row results from the query are converted into a hash to store the rendered output of each row. If the row result changes, then the result is rendered again otherwise it is fetched from cache. Use this if you expect content changes to happen frequently on your site.
- Cache panels. There are plenty of options:
- Content cache. Use this if you want to expire the Panel cache whenever content is inserted, updated or deleted. Use this if you expect content changes to happen rather infrequently on your site.
- Page cache. Use this if you want to cache panel based on Drupal path and URL.
- Hash cache. Use this if you want panel to be cached with more granularity than just Drupal path and URL. This module allows you to create a custom hash for the cache based on contexts, arguments, Drupal path, URL, users and roles. Cache is expired automatically whenever any element of the hash changes.
- Cache rendered entities using Render cache module. You could also use Display cache module instead.
- Install distributed cache that can store cache tables of the database in memory or file. Drupal creates and uses its own cache. By default, it is stored in database in tables whose names start with "cache_". A distributed cache can instead store these caches in memory or file. The popular ones are:I prefer memcache since it's easy to install and configure. A lot of developers prefer Redis. I believe it's a tie between memcache and Redis. File Cache will be the slowest of the three so don't go with that.
- Cache entities using Entity cache module. For warming up the entity cache, you can use Entity Cache Loader module.
- Enable Far Future Expiration for static assets. See the Webserver section of this article below for more details.
- Install a front-end cache. It stores the HTML response in memory. The next time, the same page is requested, it returns the HTML from memory. As a result, on the second request, web server and PHP are completely bypassed and the page load time is much faster. There are two good open-source front-end caches: Varnish and Squid. Drupal has much better support for Varnish such as expiring particular URLs using Rules. That's why I prefer Varnish.
- Use cache_get() and cache_set() functions in your custom modules to cache results from your custom functions.
- Use cache warmer to maintain fresh pages in cache. Use this for homepage and other Views pages that you don't want to clear on any content update.
- Use Content Delivery Networks (CDN). Cache static content (CSS, JS, fonts, files and images) using CDN such as AWS Cloudfront, Akamai, Fastly or Cloudflare. There are two advantages to using a CDN:
- Since CDNs have a different domain than your site, browsers load content from CDN in parallel to the requests to your domain. This speeds up page load even more.
- CDNs have endpoints throughout the world so the network delay is reduced.
- Use Asynchronous Prefetch Database Query Cache module for prefetching cache calls.
Contributed modules that improve performance
- Use Fast 404 module to return pages with 404 error without full Drupal bootstrap.
- Enable CSS/JS aggregation.
- Install and enable Advanced CSS/JS Aggregation module. Here is what you should do using this module:
- Create image sprites so that multiple background images can be served in the same request. Here are some ways to create sprites:
- Lazy load images so that only the images above the fold are requested initially. When user scrolls down, additional images are requested. There are multiple Drupal modules available:
- Laze load images in Views using Views Lazy Load module.
- Use Imageinfo Cache module to pre-generate image styles before they are requested for the first time. By default, Drupal generates image styles only on the first request. Using this module will ensure that the image style has already been created beofre the first request and even the first request is fast. In general, this module won't decrease your page load time much so use this only if you really really need 5 ms of reduction in page load time.
- Optimize images and reduce their size. There are multiple ways to do this:
Convert non-transparent pngs to jpgs since jpgs are smaller.
- Disable logging using Performance module.
- Use Shadow module to create shadow tables for slow queries.
- Use Views Litepager module to eliminate COUNT queries from Views. InnoDB tables in MySQL do not store total count and hence a count query requires the engine to actually count the number of rows and that can slow down the query.
- Use Picture module for rendering responsive images. This ensures that on small screens, smaller images are downloaded which saves bandwidth and decreases page load time.
- Disable and uninstall modules that are not required for your application such as Update Manager and Statistics.
- On production, disable and uninstall modules that only provide UI functionality. Most common ones are Views UI and Rules UI.
- Disable Database log (dblog) module so that logs don't write to database. Enable syslog instead.
- Fix PHP code so that notices, warnings and errors are not generated and as a result, logging is minimized.
- Disable and uninstall modules that are active in the dabase but missing from the file system using Missing module.
- Use suggestions from Performance and Scalability Checklist module to improve performance.
- Rewrite your custom modules so that only the hooks are present in .module file. All the other functions should be in other files. Include these files as needed using module_load_include() and form_load_include() functions.
- Disable PHP Filter. It uses eval() function to run the code and that is slower than code that is compiled.
- Reduce the number of times that cron is run. By default, cron flushes Drupal's cache and as a result, frequent cron runs will trigger frequent cache flushes.
- Cache pages for authenticated users. Front-end caches generally cache pages for anonymous users. If your site is being used mostly by authenticated users, choose one of the following two options:
- Cache pages using front-end cache such as Varnish after removing all the cookies. All blocks that have user-specific or page-specific content are loaded using AJAX via AJAX Blocks module. The advantage of this approach is that webserver and PHP are not called when returning base HTML to the browser and as a result page load time decreases dramatically.
- Cache pages for authenticated users using Authcache. Use authcache if most blocks in your pages has user-specific or role-specific content. Although this is an excellent module and it works very well, it requires some configuration and maintenance. Also since it uses webserver and PHP, page times are slower than using a front-end cache such as Varnish.
- Speed up DNS requests. When you type a website URL in the browser, browser first contacts a DNS service to find the IP address. Typically this request takes anywhere from 50 ms to 200 ms. You can use DNS Anycast network to reduce this time. Some services that can help with this are: AWS Route 53, easyDNS, DNS Made Easy, Akamai, Dyn and Neustar UltraDNS.
- Use CDNs for serving dynamic content such as your complete HTML page. With this, you get the above two benefits even for your normal pages and not just for static assets. This requires a lot of configuration and we'll post another article on this soon.
- Inline critical CSS/JS. Browsers need to send multiple requests to the server before it can start rendering a page. Once it receives the HTML from the browser, it needs to request all the CSS and JS files present in the header. These are blocking requests and the page won't start rendering till they are received. By inlining the CSS/JS that you need to render the content that is above the fold, browsers don't need to make any more request to start rendering and hence the user perceives the page load times to be faster. This is a tool that is extremely useful for mobile devices since their bandwidth is generally limited and hence easy response takes a long time to arrive. There are multiple ways to do this:
- Tune MySQL for better performance.
- Index slow queries.
- Optimize Views so that they use INNER JOIN instead of LEFT JOIN.
- Enable and configure MySQL query cache. It stores the results of past queries in memory. Next time the same query comes, it first sees if the results are already in memory. If yes, it returns the results from memory. If not, the query is passed to the MySQL query engine to fetch the results. MySQL query cache is always up-to-date, i.e. if any of the tables on which the query depends is modified, then the cache is cleared automatically. As a result, there is no need to clear MySQL query cache manually.
- Replace MySQL with Percona Server, a drop-in replacement that offers better performance
- Use MongoDB for storing key-value pairs.Using MongoDB does provide performance improvement of about 5% over MySQL. I prefer using Percona Server and then tuning it for better performance.
There are plenty of open-source webservers. The two most frequent ones used with Drupal are Apache and Nginx. I have also seen IIS being used on Windows.
I prefer using nginx since its event-driven model makes it much faster and more memory-efficient than Apache.
- Install PHP Opcode Cache. PHP is an interpreted language and it compiles during run-time. Compiler compiles it in two passes. In the first pass, PHP code is converted to an intermediate code (Opcode) which is platform-independent. In the next pass, this code gets converted to binary. Opcode caches store the intermediate code (Opode) in memory so that the next time code runs, compiler can fetch it from memory and skip the first pass. There are plenty of open-source Opcode caches available. Following are the popular ones. I prefer APC because it's easy to install and configure. Starting with PHP 5.5, PHP comes with Zend Optimizer by default so there won't be any need to install Opcode cache separately.
- Zend Optimizer
- Use PHP-FPM. It is a high-performance FastCGI Process Manager.
- Compile PHP into binary. There are two options:
If you are compiling PHP, make sure to test the application thoroughly before deploying it to production.
Improve the performance of:
- Replace disk drive by SSD (solid-state drives). I have seen a huge gain in performance using SSD.
- Increase RAM to make sure that swap never gets used.
- Increase CPU speed.
If I have missed out on any technique, please let me know by writing a comment below and I'll prompty include it in this list.