

This document is some of my notes culled from a day of research into Drupal performance tuning. I've broken it down into several different levels that all impact a Drupal site's performance. For best results, apply some or all recommendations from each of these levels. They are ordered from project-specific to server-specific, with the final one being testing. There's also a lather-rinse-repeat cycle at play here.
Do not wait until day of launch to consider performance!
Staging (aka launch prep) needs to be a part of the process — don't go from dev to launch w/o analysis
Module development needs to use cache tables (or direct API access to memcache, etc.) when applicable
node_load()s, node_save()s, etc.Project managers need to build-in time for performance tuning on client server, plus a pre-launch analysis of their server environment
If setting up a new server, DO NOT LAUNCH WITH APACHE/MYSQL/PHP DEFAULTS FROM MEDIATEMPLE, ZOMG1!1
Best solution: Get those cache_* tables out of MySQL!
Turn built-in caching on (for anonymous users only)
page_fast_cache aggressive caching may work well for some casesalways add this directive to .htaccess:
<FilesMatch "\.(png|gif|jpe?g|s?html?|css|js|cgi|ico|swf|flv|dll)$">
ErrorDocument 404 default
</FilesMatch>
There is no need for Drupal to return a 404 error for these objects! Most of the time it is in the background anyway.
Decrease PHP's error reporting level to avoid unnecessary logging (or fix the warnings)
DB Logging
SHOW ENGINES;Syslog
Syslog-ng config (/etc/syslog-ng.conf on arch): https://wiki.archlinux.org/index.php/Syslog-ng
destination d_drupal { file("/var/log/drupal.log"); };
filter f_drupal { program("drupal"); };
log { source(src); filter(f_drupal); destination(d_drupal); };
Performance-enhanced replacement for standard Drupal distro
Supports db replication workflow, can make read only db calls
Disable modules you are not using
Prune Sessions table by adjusting max lifetime of sessions:
ini_set('session.gc_maxlifetime', 86400); i.e. 24 hours (in secs)
ini_set('session.cache_expire', 1440); i.e. 24 hours (in mins)
ini_set('session.cookie_lifetime', 86400); i.e. 24 hours (in secs)Increase apc.shm_size from default of 32MB to 48MB
/dev/zero fix for shm_size limits in OS layer
Consult apc.php which will output apc utilization stats (May be available in Drupal module?)
http://2bits.com/articles/importance-tuning-apc-sites-high-number-drupal-modules.html#comment-1228:
Those who have limited resources for APC may consider using apc.filters. Setting it to include only very common files results in very high hit percentage with a limited memory.
Example to exclude admin files and include only php and inc files
apc.filters = "+inc$,+php$,-admin"Or for very limited resources you can also consider excluding contributed modules so you can cache Drupal core only.
apc.filters = "+inc$,+php$,-admin,-sites/all/modules"
Requirements:
Memcache Drupal module:
cache_get/cache_setRecommendations
Previous to 6.x-1.5 of memcache module, need to use multiple memcached processes for each bin — but now one bin to rule them all
How much memory do you need [for memcache]? Look at your object sizes in the mysql cache tables and add them up. (hint: probably not a lot). You don’t need to be exact, you just need to get it within the correct order of magnitude.
mod_fastcgid)Under mod_php, every apache process includes the memory footprint of PHP regardless of whether it is serving PHP
Under FastCGI, PHP is separated from apache.
mod_php, which would be every apache processIn a 1:1 relationship, mod_php and fastCGI serve PHP with (roughly) the same resource usage — but when serving static content, PHP is skipped entirely
Downsides include:
Note: Under FastCGI, Apache MPM worker (threaded) is better than prefork (less memory usage per request)
mod_php ConfigApache prefork is the better choice for mod_php (over MPM worker)
When using prefork, immediately prefork a large pool of processes (as many as memory can support) to avoid churn
StartServers, MinSpareServers, MaxSpareServers, and MaxClients can all be set to the same amount to keep the PHP pool size constantGoal: Keep as many idle PHP interpreters hanging around for as long as possible.
It's better to keep a constant size, to fill server memory
Set MaxRequestsPerChild to a very high number, or 0
Note: Memory leaks may occur over time w/ MaxRequestsPerChild = 0
Example:
StartServers 40
MinSpareServers 40
MaxSpareServers 40
MaxClients 80
MaxRequestsPerChild 20000
i.e. Start at 40 child processes, stay at 40 even when idle, burst up to 80 in heavy load.
LOL: "There is no prize for having a lot of free memory.
'My server is slow, but look at all that free RAM!!!' If you have memory, then use it!"
Pro D7 Dev, Ch. 23, p. 505
Counterpoint: Memory usage in apache child processes tends to equal the largest page served by that child process, which means over time each of those
MinSpareServerscan become enormous. This may or may not be a good thing. Alternatively, setMaxRequestsPerChildto 2000 (or lower) to more frequently respawn while maintaining the same constant number of child processes.
Increasing MaxClients will only worsen a memory problem, as it will attempt to serve even more requests.
Timeout from 5 mins to 20 seconds or less.htaccess into server config files, disable searching for .htaccessSELECT statement will have to wait for that write to finish!InnoDB uses row-level locking which will only affect a given row during a write
Should you convert to InnoDB for all tables?
To convert a table to InnoDB: (take site offline first!)
ALTER TABLE {tablename} TYPE='InnoDB';
Analyze lock contention by checking the Table_locks_immediate and Table_locks_waited status variables:
SHOW STATUS LIKE 'Table%';
Table_locks_immediate = number of times a table lock was acquired w/o waiting
Table_locks_waited = number of times waiting was necessary to acquire a lock
Table_locks_waited value is high, and you're having performance issues, consider splitting large tables into multiple smaller tables (like for cache tables, etc.)Idea: MySQL Master/Slave replication and using it for read/write separation
Analyzing slow query log
mysqlslaUsing MySQL profiling
SET PROFILING = 1; will enable profiling for that sessionSHOW PROFILES; will show how those queries performedAdditional information is available, e.g.:
SELECT state, duration, cpu_user+cpu_system AS cpu,
block_ops_in+block_ops_out AS blocks_ops
FROM information_schema.profiling
WHERE query_id = 1 AND
duration > 0.000999;
Reduce the number of services that aren't directly supporting apache and mysql
The more memory available to apache and mysql, the better.
ab)
ab -c5 -n3000 http://example.com/Apache Bench (ab) is more of a raw-speed benchmarking tool.
Siege lets you build test cases (see the URLs File I mentioned) which try to simulate large numbers of real users browsing your site - with a list of GET and POST requests, including POST data. Rather than each concurrent user flooding the server, it inserts delays between requests for a more true-to-life effect.