Capturing memory exhaustion in Magento

Recently a customer was experiencing a peculiar issue whereby in some circumstances, a MySQL query on their store was unfortunately consuming so much memory that it hit the memory limit for the process. Identifying the cause of the query isn't easily done with Magento's native system.log and exception.log - so a little extra logging was put in place.

This was meant as a fast mechanism to debug the issue, not necessarily the most elegant, its certainly not recommended as a long-term/permanent tool for diagnosis.

The only error we had to work off was that presented in the browser to the customer,

Allowed memory size of 805306368 bytes exhausted (tried to allocate 65552 bytes) in lib/Zend/Db/Statement/Pdo.php on line 291

So to capture more information on the error, a couple of minor modifications were made. The bootstrap was edited to capture a fatal shutdown. Magento methods were deliberately avoided (because of memory saturation within the Mage god class)

In index.php,

#ini_set('display_errors', 1);
...

function shutdownCapture() {
    global $lastQueryString;
    $error = error_get_last();
    if ($error !== null && strpos($error['message'], 'Allowed memory size') !== false) {
      file_put_contents(MAGENTO_ROOT.'/var/log/oom.log', print_r(array($lastQueryString, $_REQUEST, $_SERVER, $error), true), FILE_APPEND);
      mail('info@examplecom', 'Server Memory Error', print_r(array($lastQueryString, $_REQUEST, $_SERVER, $error), true), 'Cc: mycolleague@example.com');
    }
}
register_shutdown_function('shutdownCapture');

...
umask(0);

As we knew the error originated from a greedy MySQL query (the error originated from , a global PHP variable was used to store the last MySQL query),

In lib/Zend/Db/Statement/Pdo.php

    public function fetchAll($style = null, $col = null)
    {
+       global $lastQueryString;
+       $lastQueryString = $this->_stmt->queryString;

Its not the most elegant code, but it should serve a purpose. On memory error, it should send an email and log the last query executed (which should be the memory intensive one).

Problem solved

Within 2 hours, the error had been triggered and the email went out. The memory leak was traced back to a mailing list module attempting to load every column and row of the customer entity table.

With the problem diagnosed, the problem was fixed and the debugging code could be removed. Quick, cheap and simple diagnosis.