We have written before about Drupal Memory usage by modules, and the Open Buffet binge syndrome.
But this time, it was different. Modules were not to blame.
While inspecting a site that had several performance problems for a client, we noticed is that memory usage was very high. From the "top" command, the RES (resident set) field was 159 MB, far more than what it should be.
We narrowed down the problem to a view that is in a block that is visible on most pages of the site.
But the puzzling part is that the view was configured to only returned 5 rows. It did not make sense for it to use that much memory.
However, when we traced the query, it was like so:
SELECT node.nid, .... FROM node INNER JOIN ... ORDER BY ... DESC
No LIMIT clause was in the query!
When executing the query manually, we found that it returned 35,254 rows, with 5 columns each!
Using the script at the end of this article, we were able to measure memory usage at different steps. We inserted a views embed in the script and measured memory usage:
Before boot 0.63 MB Boot time 445.1 ms After Boot 51.74 MB Boot Peak 51.80 MB Module count 136 After query 66.37 MB Query Peak 67.37 MB After fetch 78.96 MB Fetch Peak 148.69 MB
So, indeed, that view was the cause of the inflated memory usage! With memory jumping from 67 MB to 148 MB.
In this case, it turns out that the module "views_php" was definitely the culprit. Once it was disabled, the query did not have that huge memory foot print any more.
Here are the results after disabling views_php:
Before boot 0.63MB Boot time 427.1 ms After Boot 51.68MB Boot Peak 51.74MB Module count 135 After query 66.31MB Query Peak 67.31MB After fetch 74.71MB Fetch Peak 74.89MB
A more reasonable 75MB.
We did not dig further, but it could be that because a field of type "Global: PHP" was used, views wanted to return the entire data set and then apply the PHP to it, rather than add a LIMIT to the query before executing it.
So, watch out for those blocks that are shown on many web pages.
Baseline Memory Usage
As a general comparative reference, here are some baseline figures. These are worse case scenarios, and assume APC is off, or that this measurement is running from the command line, where APC is disabled or non-persistent. The figures would be less from Apache when APC is enabled.
These figures will vary from site to site, and they depend on many factors. For example, what modules are enabled in Apache, what modules are enabled in PHP, ...etc.
Drupal 6 with 73 modules
Before boot: 0.63 MB After boot: 22.52 MB Peak memory: 22,52 MB
Drupal 7 site, with 105 modules
Before boot: 0.63 MB After boot: 57.03 MB Peak memory: 58.39 MB
Drupal 7 site, with 134 modules
Before boot: 0.63 MB After boot: 58.79 MB Peak memory: 60.28 MB
Drupal 6 site, with 381 modules
Before boot: 0.63 MB After boot: 66.02 MB
Drupal 7 site, pristine default install, 29 modules
Now compare all the above to a pristine Drupal 7 install, which has 29 core modules installed.
Before boot 0.63 MB Boot time 227.40 ms After Boot 20.03 MB Boot Peak 20.07 MB Module count 29
Effect of APC on boot memory footprint
To see how much APC, and other opcode caches, improves these figures, compare the following:
First access after Apache restarted, for a Drupal 6 site:
Before boot 0.63 MB Boot time 802.5 ms After Boot 62.85 MB Boot Peak 63.11 MB Module count 210
Subsequent accesses, with APC caching the code:
Before boot 0.61 MB Boot time 163.80 ms After Boot 17.24 MB Boot Peak 18.41 MB Module count 210
Also, for a default Drupal 7 install, with 29 core modules. Compare to the above figures for the same site without APC.
Before boot 0.61 MB Boot time 60.4 ms After Boot 3.36 MB Boot Peak 3.41 MB Module count 29
A marked improvement! Not only in bootup time, but also reduced memory foot print.
So always install APC, and configure it correctly on your site.
Read more at: PHP opcode caches/accelerators a must for large Drupal sites and Benchmarking Drupal with PHP op-code caches: APC, eAccelerator and Xcache compared.
Memory Measurement Script
Here is a script to measure your memory usage. You can run it from the command line as:
$ cd /your/document_root $ php mem.php
Add whatever part you think is causing memory usage to sky rocket in place of the commented out section, and you can see how much is being used.
You can also add HTML line breaks to the print statement, and run it from a browser to see the effect of APC code caching as well.
preview('block'); */ $results['After fetch'] = fmt_mem(memory_get_usage()); $results['Fetch Peak '] = fmt_mem(memory_get_peak_usage()); foreach($results as $k => $v) { print $k . "\t\t" . $v . "\n"; }
Comments
ksenzee (not verified)
Views != Views PHP
Thu, 2012/11/01 - 23:15I wouldn't say Views itself was to blame here. That's like saying the text format system was to blame for a bad PHP node. Views PHP is similarly dangerous and should be avoided similarly.
Khalid
I agree
Fri, 2012/11/02 - 10:26I agree, and that is what I am saying. It was "Views PHP" that was the culprit here, and disabling it solved the issue.
The lesson here is hidden stuff that is not obvious can come to haunt you. Despite saying we need only 5 rows, it was 35,000+ that were being returned.