Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Memory leak in Magento/Zend Framework

When running this simple script I get the output posted below. It makes me think that there is a memory leak in either my code or the Zend Framework/Magento stack. This issue occurs when iterating any kind of Magento collection. Is there anything that I am missing or doing wrong?

Script:

$customersCollection = Mage::getModel('customer/customer')->getCollection();

foreach($customersCollection as $customer) {
  $customer->load();
  $customer = null;
  echo memory_get_usage(). "\n";
}

Output:

102389104
102392920
...
110542528
110544744
like image 512
rafarr Avatar asked Apr 13 '13 15:04

rafarr


2 Answers

Your issue is that you are issuing fairly expensive queries with each iteration, when you could load the necessary data via the collection queries:

$collection = Mage::getResourceModel('customer/customer_collection')->addAttributeToSelect('*');

will do the same, but all in one query. The caveat to this approach is that if there are any custom event observers for customer_load_before or customer_load_after events (there are no core observers for these), the observer will need to be run manually for each data model.

Edit: credit to osonodoar for spotting an incorrect class reference (customer/customer vs customer/customer_collection)

like image 132
benmarks Avatar answered Nov 02 '22 17:11

benmarks


The memory for an object (or other value) can only be freed when there are no references to it anywhere in the PHP process. In your case, the line $customer = null only decreases the number of references to that object by one, but it doesn't make it reach zero.

If you consider a simpler loop, this may become clearer:

$test = array('a' => 'hello');
foreach ( $test as $key => $value )
{
    // $value points at the same memory location as $test['a']
    // internally, that "zval" has a "refcount" of 2

    $value = null;
    // $value now points to a new memory location, but $test['a'] is unnaffected
    // the refcount drops to 1, but no memory is freed
}

Because you are using objects, there is an added twist - you can modify the object inside the loop without creating a new copy of it:

$test = array('a' => new __stdClass);
// $test['a'] is an empty object

foreach ( $test as $key => $value )
{
    // $value points at the same object as $test['a']
    // internally, that object has a "refcount" of 2

    $value->foo = "Some data that wasn't there before";
    // $value is still the same object as $test['a'], but that object now has extra data
    // This requires additional memory to store that object

    $value = null;
    // $value now points to a new memory location, but $test['a'] is unnaffected
    // the refcount drops to 1, but no memory is freed
}

// $test['a']->foo now contains the string assigned in the loop, consuming extra memory

In your case, the ->load() method is presumably expanding the amount of data in each of the members of $customersCollection in turn, requiring more memory for each. Inspecting $customersCollection before and after the loop would probably confirm this.

like image 44
IMSoP Avatar answered Nov 02 '22 19:11

IMSoP