II. Advanced Drupal Performance Practices

Avoid Using Slow (Node Access) Modules

This is an obvious performance tip: avoid installing modules that are known to drag down Drupal performance. If you do not need them, do not use them. This espcially relates to any kind of node access related modules. Such modules add extra MySQL database "LEFT JOINS" to match each node table item to the node_access table, to check if the user may access the node.

If you do not need such access rights control, avoid using node access modules. Simply disable and uninstall them. Even if you need to use a node access module, mind the following advice:

Avoid Calls to node_load() Where Possible, Use Fields

Drupal's node_load() function fires a great deal of queries and functions. The more module you use, the more node hooks will be involved in this call. Wherever possible it is advised to avoid calls to node_load(). Instead, try to fetch the specific fields you need directly. Best example: Views. Using the Views module you can build a page that lists all nodes. Each item requires a node_load() call. To avoid this, switch to using Fields in Views instead and call the fields that you really need. Then use custom Views theming to output the data.

Avoid Calls to taxonomy_get_tree()

Drupal's taxonomy system, to categorize and tag content, has one bad functiontaxonomy_get_tree(). It works fine on small sites with few tags. But on large sites with over tens of thousands of taxonomy terms, you should avoid it at all cost. The function namely stores all terms into the PHP RAM memory, quickly blocking further PHP execution.

Avoid Calls to Third Party Web 2.0 Services

Digg, Twitter, Facebook Connect, OpenCalais and what not can be configured to submit a new node or comment upon submission. This works for small sites with low volumes, but even then it slows down the form submission: you have to wait for the third party server to process your request and send back an "OK". If the remote servers are slow or down, it may even cause a local "traffic jam" in your system. Apache processes will hang until the remote server replies.

Avoid Calls to variable_set() on Page Loads

The Drupal variable are cached as one string. But upon calling variable_set() on any page requires this variable cache to be refreshed. This leads to a heavy MySQL query, SELECT * FROM variables. Best practice is to only use this call on administrative tasks. Avoid it on any other page.

If you really need to set values on every page, think about Drupal's caching functions, or even create a custom Drupal caching table for your module.

Avoid Using the PHP Input Filter

Drupal provides a PHP input filter out of the box. It lets you use custom PHP inside nodes and blocks. The downside is that this PHP code is stored in the database. Upon a new page request this code must be retrieved from the database and executed through PHP's eval() function.

But the biggest drawback is this: the PHP input filter is not cached. The solution is to write a custom Drupal module to do the task.

Disable Developer Modules

A number of modules have no functionality other than to provide an Administation UI. Examples are Views UI, Panels UI, ImageCache UI, Feeds UI and several others. On production servers, once you are done with them, you probably do not need keep them enabled. By just disabling these modules you can save Drupal resources. Do not unintall these modules! Just keep them disabled in the Drupal modules list.

Remember, this tweak only makes sense once you are done building your site!

Disable the Database Logging Module

For the same reason as disabling the Drupal core statistics module, one can disable the database logging module. It also fire MySQL queries on each page load. For truly optimal performance, for example on shared hosting or on hig traffic sites, you should definitely turn it off. This means you will have to use the server's error logs system. An alternative module is the Drupal core syslog module, which lets you log Drupal message to the Apache logs.

Enable .htaccess ExpiresByType Directives

Using Google Page Speed tools, one of the recommendations is to enabled Expire headers for Apache. The ExpireByType directives in your Drupal .htaccess file can set the duration which files should be stored locally. For example, if you set ExpiresByType for jpg files to 1 year, that means your file will not be re-downloaded by a user. The jpg file will be stored in the Temporary Internet Folder.

Implement Fast 404s for Static Files

A drawback of dynamic CMS like Drupal is the handling of 404 errors, or Page Not Found errors. Drupal handles every request through index.php. If a file or path does not exist, Drupal has already bootstrapped the entire LAMP and modules stack. 2Bits came up with a solution. Below I show the code I use at the bottom of settings.php. You may need to carefully inspect the code, to see whether it fits to your situation. Find the original discussions via the references below.

Improve Theme Performance with CSS Sprites

CSS sprites are multiple images merged into a single image. This reduces the number of HTTP request to your server. Using CSS style rules, you can position the sprite in such a way that it show different graphics for differenet sections of the site. For example, you can merge the header, logo and footer into one CSS sprite image.

CSS Sprites are part of Google's Page Speed and Yahoo!'s YSlow recommendations for performance.

Install the Boost Module (on Shared Hosts)

For shared hosts, the best caching option availabe is the Boost module. Boost creates HTML copies of rendered Drupal pages and stores them in a cache folder. Using .htaccess rules Boost then checks if a file already exists. If so, it will load the static HTML file, while fully avoiding Drupal, PHP and MySQL. If not, it will generate the file. Old HTML files are purged on cron runs, to keep the content fresh.

Move All JavaScript to the Page Footer

Part of Yahoo!'s YSlow recommendations is to load JavaScript in the footer of the page. Drupal's theming system usually has two variables, $scripts (in the ) and $closure (before the closing tag). According to Wim Leers, the quicket way to move all Drupal's JS to the footer is by moving $scripts just before $closure.

Why is this useful? Because whatever comes after the JS must wait for the JS to load. That's why it makes sense to put JS at the bottom.

Move Third Party Libraries outside the Modules Folder

Every file in the modules folder is read for inclusion in the Drupal stack. If you store large third party libraries, such as teh YUI library or third party WYSIWYG editors, this will slow down Drupal. The modules folder is usually in /sites/all/modules or /sites/all/modules/contrib. To avoid such unnecessary file reading, move all your third party libraries to a special folder at/sites/all/libraries.

Simplify Your Drupal Theme

A number of aspects influence theme performance: its size in kilobytes, number of total files, flash and silverlight, the number of CSS styles and stylesheets, the number of jQuery functions and other JavaScript files included, the size of the images and so on. Your theme may have complex regions and visibility logic.

Simplifying a theme is not easy, it requires you to re-think your decisions. If you downloaded someone else's theme, you may want to rebuild a custom theme from scratch, leaving out unneeded assets.

Use the Devel Module to Analyze Performance

The Devel module provides a set of developer UI tools and code hooks to analyze performance. It also includes a Performance Logging tool to measure PHP's RAM usage per Drupal page.

Note: use Devel on development sites, not on production environments. This module is of course a resource hog and should not be active all the time. You may temporarily use it on live sites to identify problems of course.