Trabalhando com grandes collections no Magento

Esta semana me deparei com um problema aparentemente simples: precisava fazer um script que fazia um loop em todos os produtos de uma loja para remover algumas imagens duplicadas.

O problema é que a collection de produtos possuía 100.000 registros, mesmo após vários filtros. Ao fazer o ->count() da collection no Magento,  me deparei com estouro de memória (Exhausted X bytes in ..), antes mesmo de chegar no usual foreach.

$products = Mage::getModel('catalog/product')->getCollection();
$total = $products->count();

O Magento carrega a collection ao acionarmos oo método count(), jogando seus valores em array, ou seja, colocando em memória como podemos ver abaixo:

//Varien_Data_Collection::count
public function count()
{
    $this->load();
    return count($this->_items);
}

O problema do count() é conhecido e de simples solução. Basta usar o método getSize() em seu lugar.

$total = $products->getSize();

O getSize() é bem menos custoso, pois invocará uma chamada apenas com o count de registros. Veja:

//Varien_Data_Collection_Db::getSize
public function getSize()
    {
        if (is_null($this->_totalRecords)) {
            $sql = $this->getSelectCountSql();
            $this->_totalRecords = $this->getConnection()->fetchOne($sql, $this->_bindParams);
        }
        return intval($this->_totalRecords);
    }

Problema resolvido né?  Não. Ainda temos o foreach.

foreach($products as $product) {
 //F...!
}

Ao chamar o foreach a coleção é carregada no load() novamente em memória, estourando novamente o erro classico de memória do PHP.

A solução foi utilizar o Resource Iterator do Magento. Ele fará a leitura de cada linha do meu SQL chamando um método de callback.

Funcionou mais ou menos assim:


Mage::getSingleton('core/resource_iterator') 
    ->walk($products->getSelect(), 
    array('meuMetodo') 
); 

function meuMetodo($args){ 
    Zend_Debug::dump($args, 'Detalhes da linha atual'); 
} 

Desta forma os 100 mil registros da minha collection não são carregados de uma vez na memória, e consigo fazer o loop normalmente, registro por registro, sem me preocupar (muito) com a quantidade de registros.

Em resumo, aprendemos que:

  • É melhor getSize() do que count()
  • É melhor Mage::getSingleton(‘core/resource_iterator’) do que foreach

Ahh se eu soubesse disso antes…

Compartilhe com os amigos
  • Digg
  • StumbleUpon
  • Print
  • del.icio.us
  • Facebook
  • Twitter
  • Google Bookmarks
  • email
  • Google Buzz
  • LinkedIn
  • Live
  • MSN Reporter
  • Orkut
  • PDF
  • Reddit
  • Tumblr
Publicidade

Ninguém comentou sobre isso ainda. Seja o primeiro.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *