by Irakli Nadareishvili, Director of Product Development at Phase2, @inadarei on Twitter.
Erik Summerfield, Software Developer at Phase2
You will probably have a number of contrib modules, and the queries that these modules execute can have a huge impact on the performance of your site.
Performance on the web is usually described by speed and scalability.
Speed is measured in seconds, and tells you how long it takes a single user to load a single page and all the assets that make up that page.
Scalability is another metric which describes the maximum number of concurrent users who can access your system.
PHP Performance can be affected by:
- Number of source files
- Memory utilization
- Database queries
When PHP is parsed and converted to opcode and run, it takes time. If you have a lot of source files that need to be pieced together, your site will be slower. There are solutions to that in terms of opcode caches which can pre-parse your code and cache it into memory, which saves some of the execution steps. However, this is not a silver bullet: you're moving the bottleneck from parsing into memory, which is another resource you need to be careful with. Also, Drupal is an open source system, and code that you write may be deployed to many other sites which may or may not have opcode caches. Your module should run fast with or without opcode caches.
Drupal is known for using a lot of memory. You have to set your memory limit relatively high, and way higher than the default that is set in php.ini and what some other systems like Wordpress require.
Garbage collection is resource intensive, and there's a lot of CPU that goes into managing memory for you. Also, garbage collection is not instantaneous, it runs at intervals, and something marked for garbage collection may not be freed from memory for some time. PHP 5.3 has new garbage collection, but many modules may not be compatible. Make sure your modules run on PHP 5.3.
For people who come from non-LAMP backgrounds, one of the things that stuns them is the number of queries that a typical Drupal website issues to load a simple page, as opposed to the five or ten from a custom system. The modularity of Drupal leads to this situation, since everybody is trying to do things separately.
Another thing you'll notice quickly is that a lot of queries being issued are being repeated.
Best Practices: Execute Fewer Queries
You can solve the non-unique query problem by putting that content into a static variable. A good place to look at where this is done the best is Ctools or Features, where they have functions that wrap static variables with GETters and SETters to allow other modules to access these variables.
If the variable can be persisted past one session, you could use something like memcache. Don't target memcache specifically; it's not guaranteed that someone will always have memcache when they use your router. Use something like cache_router so that developers can plug and play their own caching systems.
Ctools Exportables also works very well, even though this was not the intention of Ctools. It was created with the idea of exporting database configurations to code, so that it could be versioned.
Best Practices: Use Less Memory
To avoid using a lot of memory, use the unset() function. This tells PHP that you don't need the data structure anymore. We don't necessarily need these huge data structures, but we don't want to use the garbage collector because it's not particularly reliable and it uses CPU time.
Also, use lean data structures. Use associative arrays if you need them, but indexed arrays are much more performant and can be spun up and down much quicker. When you use a lot of memory, you run into issues with Apache, which is a blocking web server. When Apache makes a request to PHP, it waits until the entire execution is finished before the response is sent back to the browser. nginx is a non-blocking server, which is why it's becoming popular. So, if your PHP max memory is set to 200MB, and you have 2GB free memory, you can only have 10 requests at the same time.
Best Practices: Less Runtime Code
Drupal provides us with some nice tools to do this. In a lot of core hooks, you can say where the code is going to live, such as with the theme hook and menu. When you specify your page callback, you can specify the
In Drupal 7 we get an extra bonus: if you're doing any class definitions, you tell Drupal where those classes live, and when people try to use those classes Drupal will load the proper files.
Conditional Includes let you wait until the time that you need code, and then only include the file at that point. So:
This way, your code will only be included if it's needed.
Next, how do we write our modules so other people do the same thing? In Drupal 7, there's a
hook_hook_info() that specifies which hooks you're going to use, and that other people can implement to enhance your module. Features uses this to allow developers to put their Features hooks into modulename.features.inc.
Register hooks vs. Function hooks
- Request static info
- Can be cached
- Need to only be called when data is stale
- Allow dynamic returns
- Can't be cached
- Best used when one knows which module to call
If you use registration hooks a lot, you should check out Ctools plugins. They're an alternative to register hooks. If something's going to register more than one item, that's when I strongly look at ctools, especially if the items are separate entities. Most recently, I wrote a module that let us start a site with default content. We're using Features to export nodes, which is messy. Ctools plugins was perfect for that. It's well organized and I can easily iterate through our items.
A lot of modules split module functionality int core, ui, and admin modules. Bad idea! One pattern I see developing is to make Features plugins which you can turn off on your production site.
Finally, do not put business logic code, such as CRUD or service calls, in:
- Form validation functions
- Form submit functions
Also, do not end your code with drupal_goto() or HTTP redirects.