Olá meus amigos nerds, hoje estou aqui para falar de problemas do mundo real, que vocês podem ter que lidar quando estiverem desenvolvendo algum sistema de grande porte.
Outro dia eu precisava fazer uma atualização em algumas tabelas de um banco de dados, para acomodar algumas melhorias em um dos sistemas que eu ajudo a desenvolver.
Eu tinha a opção de gerar o sql de atualização na unha ou usar o Hibernate para isso. Como a atualização não era simples, optei por fazê-la usando o Hibernate (sou preguiçoso como todo bom cientista da computação)
(Se você não conhece o Hibernate, ele é um framework muito útil para desenvolvimento de aplicações (java) que precisam se comunicar com bancos de dados relacionais. A grosso modo, ele faz o mapeamento dos objetos da sua aplicação para as tabelas do seu banco de dados, gerando para você o sql das consultas de modo transparente.)
Você deve estar se perguntando qual o problema. Isso qualquer peão programador consegue fazer. O problema estava no número de objetos (linhas das tabelas) que eu tinha que atualizar (alguns milhares). É claro que, como um bom nerd, eu estava atento a esse fato. E para evitar problemas de memória, eu havia implementado meu código de modo a fazer a atualização de forma paginada, ou seja, eu carregava apenas uma parte dos objetos a serem atualizados de cada vez na memória.
Testei meu código usando um dump do banco de dados e para minha surpresa ele começou a demorar muito a executar. Meu instinto nerd me dizia, deu merda! Então eu voltei ao código e o instrumentei (adicionei informações de tempo ao log da execução) de modo a obter informações de onde estava o problema. Eis que encontrei o seguinte comportamento:
Tempo de execução do método de atualização sobre a 1ª página de dados 1s
Tempo de execução do método de atualização sobre a 2ª página de dados 2s
Tempo de execução do método de atualização sobre a 3ª página de dados 4s
Tempo de execução do método de atualização sobre a 4ª página de dados 6s
...
Você é capaz de me dizer onde está o problema? Tanto o tamanho das páginas, quanto a natureza dos dados que o método de atualização lidava não mudavam. Sendo assim, o tempo de execução de uma página deveria ser mais ou menos constante. Então como explicar esse resultado? Além do mais, não poderia ser problema de paginação de memória, já que eu havia tomado esse cuidado de antemão. Correto? Então o que poderia ser?
Minha análize do problema me dizia: isso cheira a problema de memória! Para confirmar ou refutar essa hipótese, verifiquei o consumo de memória do java. Estava mais alto do que seria esperado! Era problema de memória. Mas não deveria ser meu código que o está causando o problema, pois eu havia sido cuidadoso, inclusive eu havia incluído instruções de flush para garantir que eu teria apenas os dados de uma página na memória.
Então formulei a seguinte hipótese: O Hibernate esta se tornando mais lento ao executar uma página, pois ele não deve estar fazendo o flush dos objetos já atualizados. Para confirmar ou refutar essa hipótese, recorri ao google e pesquisei o comportamento do Hibernate. Eis que em algum site eu encontrei a informação de que se você der um flush no Hibernate, fica a critério dele liberar memória ou não! Se eu quisesse forçar o Hibernate a fazer o flush e limpar sua memória interna eu deveria usar o método session.clear().
Adicionei uma chamada a session.clear(), testei novamente meu atualizador e voilá. Problema resolvido!
Lembra da primeira execução do meu atualizador, pois é, ela ainda estava rodando quando terminei de resolver o problema. Ela gastou no final das contas uma hora e pouco para executar, enquanto a versão corrigida gastou menos de um minuto. (Deixei terminar de executar só para poder me gabar depois do speedup da alteração que eu fiz no código do meu atualizador :P)
A lição que fica aqui é que você sempre deve conhecer a arquitetura interna dos frameworks, bibliotecas, etc, que você usa. Esse conhecimento salva vidas e te poupa muita dor de cabeça.
P.S.: Observe que em nenhum momento eu saí colocando breakpoints no meu código e dando step in ou step out em algum debuger visual. A técnica de debug que eu usei para resolver esse problema (de fazer suposições, colher dados e rejeitar ou confirmar as suposições até encontrar o bug) é muito útil na solução de diversos problemas. Em uma outra oportunidade irei falar mais sobre ela.
Nenhum comentário:
Postar um comentário