|
Sem dúvida este é o ano da promessa para as Ruby Machines. Este assunto já me interessava, mas quando tivemos solucionar o problema de consumo de memória do site de um cliente em Ruby on Rails o assunto teve que ser estudado a fundo. Gargabe Collector, Tuning no Rails, etc. E não só isso, vários testes sobre o MRI (versão oficial do Ruby 1.8.6) e sobre o JRuby foram feitos a ponto de massacrar um servidor de testes. A partir dai ganhamos uma experiência nova, sempre aprendemos mais com os problemas e quero compartilhar essa experiência com você !
|
|
|
Bom, tudo começou quando o servidor de um cliente que possue nosso ERP (que atualmente está 40% migrado para Rails), esteve com problemas de uso absurdo de memória. É um servidor Linux, e a swap chegava a ser usada a ponto de inviabilizar seu uso. Era uma daquelas situações que só dando um restart nos servidos resolveria o problema. Além do ERP o servidor possuia o E-commerce integrado a ele. Este totalmente escrito em Ruby on Rails.
|
|
|
Resolvi então examinar onde era o problema, e após colocar o site na minha máquina utilizei o monit (vou falar dele logo a frente) para verificar o consumo de memória. Com um script bem simples carregava a index do site várias vezes. E para minha surpresa o site em apenas 10 minutos ele consumia 100 mb. O mesmo site coloquei em nosso servidor de testes e após 44 minutos ele consumia 300 mega de swap. A máquina possuia 512 mb de ram.
|
|
|
Não havia dúvidas que o problema era o site, então criei um index em branco com um render :text => 'alguma coisa' e efetuei os testes. A memória simplesmente não subia. Minha idéia era verificar se havia algum prolbema com o mongrel ou alguma customização do rails feita por nós. Então depois de vários testes descobrimos os gargalos. Uma consulta no ActiveRecord fazia uma busca de Produto onde possuia vários relacionamentos e o pior é que o Produto possuia uma relação has_many (1 para muitos) para Preco e Estoque. O resultado de nossa consulta era perfeita para trabalharmos pois ele retorna uma instancia de produto que podiamos fazer produto.precos[0].custo, ou, produto.estoques[0].quantidade. E a consulta garantia que viria apenas um registro de Preco/Estoque.
|
|
|
Outro gargalo era uma consulta pelo ActiveRecord de Categoria e Sub-Categoria onde era feita um each dentro de outro each para localizar as categorias.
|
|
Esta parte foi até simples, substituimos a consulta feita pelo ActiveRecor::Base.find, por uma em SQL. Mas também NÃO utilizamos find_by_sql. Por quê ? Porque em find_by_sql ele retorna uma instancia do objeto que você está consultando exemplo: Produto.find_by_sql "SELECT *...", o resultado é um array contendo nele instâncias de Produto. Caso exista um relacionamento de belongs_to ou has_many você não conseguirá acessar valores que na consulta sql tenham o mesmo nome do relacionamento, exemplo:
class Produto
has_many :estoque
end
Produto.find_by_sql "SELECT produto.id, estoque.quantidade as estoque"
O campo nomeado como estoque possue o mesmo nome do relacionamento assim eles "trobam", isso pode parecer contornável, mas em query extremamentes complexas isto sim pode gerar problemas.
|
|
|
Hash ao invés de instâncias
|
Além do problema de colisão de nomes, existe o overhead de instanciar um objeto Produto para cada registro encontrado, então utilizamos:
ActiveRecord::Base.connection.select_all
você pode utilizar internamente em suas classes fazendo:
class Produto
def find_em_promocao(data)
connection.select_all 'SELECT * ...'
end
end
Isso retornará um Array, e dentro de cada array uma hash com uma string como indice.
|
|
|
|