quarta-feira, 30 de junho de 2010

Doctrine - Operações em lote ou "batch updates" - algums dicas

O Doctrine não é uma ferramenta feita para operações em lote, ele é um ORM. Mas tem alguns momentos que já temos o método na nossa camada de negócio e queremos chama-lo um monte de vezes para executar alguma manutenção no banco de dados ou algo assim.

Nestes momentos nem sempre é necessário reescrever tudo em um sql otimizado, em casos mais simples ou não tão críticos, podemos usar o que já temos. Porém, fazer esse tipo de operação no Doctrine requer alguns cuidados que temos aprendido com o tempo. Coloco aqui algumas dicas para quem quiser pular a fase do aprendizado pelo erro. Mais abaixo também coloco um exemplo de código para o tão famigerado e entretanto tão usado copy and paste.

Dicas:

1 - Faça um looping iniciando e "commitando" a transação. Isso aumenta em muito a velocidade de insert/update no banco. A quantidade de registros atualizados a cada iteração vai ter que ser definida na tentativa e erro, mas quanto mais simples a operação, maior a quantidade de itens que podem ser incluidos. Vale perceber que mesmo que a transação funcione, se forem muitos itens, o resultado final pode ficar mais lento mesmo assim.

2 - Sempre chame o método free() passando o parâmetro true para seus objetos instanciados pelo doctrine para que ele libere a memória usada por aquele objeto. Eu chamo também um unset() logo em seguida.

3 - Se você usar uma action no symfony (isso é, se é que você usa o symfony), ele tem uma mania de querer repetir a operação depois que termina. Por algum motivo que ainda não descobri, ele faz um redirect ou forward para a mesma action, então eu coloco aquele die() no final. Sei que ideal seria uma task rodando do shell, mas faço assim pq acho mais simples, principalmente para operações que vão rodar uma vez só. Se for algo que vai ser ativado pelo cron, aí não tem como fugir das tasks.

Para recortar e colar (divirta-se):


function executeRecalculateAllRatings() {
set_time_limit(3600);
$min = 0;
$increment = 1000;
$last = 50000;
$count = 0;
while ($min <= $last) { echo "<>NEW TRANSACTION - $min <>";
Doctrine_Manager::connection()->beginTransaction();
$comments = CommentTable::build()->findByIdRange($min, $min + $increment);
foreach($comments as $comment) {
$res = RatingLogTable::build()->calculateCommentRatingTotal($comment['id']);
$comment->setRating($res[0],$res[1]);
$comment->save();
$comment->free(true);
unset($comment);
echo $count++ . ' - ';
}
$min = $min + $increment;
Doctrine_Manager::connection()->commit();
}
die();
}

2 comentários:

  1. se quiser fazer um batch com o symfony, recebi esta dica: http://www.symfonybr.com/2008/07/02/enviando-uma-newsletter-com-swift-e-symfony/

    ResponderExcluir
  2. se precisar...ini_set('memory_limit','2056M');

    ResponderExcluir