Fork me on GitHub

Keep Learning Conhecimento nunca é o bastante

Postado em
30 November 2010 @ 14:33

Tag(s)
Desenvolvimento, Ruby, Tutoriais

Back-end caching com Ruby, parte 1 – Introdução

Depois de pesquisar muito código e dar duas palestras em que falei um pouco sobre o tema, posso afirmar com convicção que caching é um dos aspectos mais negligenciados pelos desenvolvedores em nossa comunidade – não por preguiça ou algo do tipo, mas por falta de prática e exposição ao assunto.

Resolvi contribuir para que o assunto seja mais discutido escrevendo alguns posts. Como a parte de cache em front-end já é mais difundida, vou focar no back-end.

Para começar, precisamos clarificar essa distinção importante: front-end cache e back-end cache. O primeiro tipo é o mais comum de encontrar em tutoriais e posts em blogs. Trata-se do cache de camadas relacionadas à interface da aplicação. No Rails, por exemplo, engloba: page, action e fragment cache. Já o segundo tipo, em geral, envolve fazer cache de resultados de operações pesadas (como queries complexas a banco de dados) utlizando algum storage como memcached. É desse tipo que vamos falar.

Sabemos que, quanto mais cedo no ciclo do request for feito o cache, mais eficiente ele é. Daí o cache no front-end ser o que traz mais retorno. No entanto, há momentos em que não é possível utilizar cache no front-end. Além disso, há situações em que outros tipos de cache são necessários, por exemplo quando utilizamos um back-end comum para a aplicação web e API (com clientes mobile e desktop, por exemplo) e queremos ter o benefício de desempenho do cache nessa parte também.

De modo geral, o que precisamos para o caching no back-end é um storage e um cliente para ele. Um dos storages mais comuns (em diversas plataformas) é o memcached – tão comum e consistente que vários frameworks o suportam out of the box, como é o caso do Rails.

Vamos a um simples exemplo utilizando a gem memcached para o caching de um cálculo feito em cima do resultado de uma hipotética query a um banco de dados.

Primeiro, vamos iniciar um servidor do memcached localmente:

$ memcached -vv -l 127.0.0.1 -p 11211

Isso vai iniciar o servidor localmente na porta 11211 em modo very verbose para que possamos acompanhar o que está acontecendo.

Agora, vamos ao código:

require "mysql"
require "memcached"
 
connection = Mysql.real_connect("127.0.0.1", "root", "pass", "my_database")
 
results = connection.query("select * from amazingly_big_table where non_indexed_column = 'some_value'")
 
some_calc = 0
 
# no inject on Mysql::Result :(
results.each do |row|
   # do some processing using some_calc
end
 
puts some_calc

Toda vez que esse código for executado, a query irá ser feita no banco de dados e o resultado será calculado novamente. Nesse exemplo, vamos adicionar cache para evitar que isso ocorra tantas vezes:

require "mysql"
require "memcached"
 
connection = Mysql.real_connect("127.0.0.1", "root", "pass", "my_database")
cache = Memcached.new("localhost:11211")
 
some_calc = cache.get "some_calc" #busca o valor no cache
 
unless some_calc #caso não esteja em cache
  results = connection.query("select * from amazingly_big_table where non_indexed_column = 'some_value'")
  some_calc = 0
 
  # no inject on Mysql::Result :(
  results.each do |row|
    # do some processing using some_calc
  end
 
  cache.set "some_calc", some_calc, 3600 # coloca o valor no cache por 3600 segundos
end
 
puts some_calc

As linhas importantes aqui são as que manipulam o cache. No caso do memcached (e na grande maioria dos outros storages para cache) utilizamos o esquema de chave-valor. Armazenamos um valor em uma determinada chave, que deve ser conhecida para que possamos recuperar os dados. O memcached também suporta um time to live, que é um tempo em que o dado deve ser mantido no cache. Após esse tempo, ele é considerado expirado e não será encontrado quando tentarmos um get.

Dessa forma, nosso resultado permanecerá em cache por 3600 segundos. Durante esse tempo, toda vez que o código for executado, o valor será trazido do memcached e mostrado ao final, sem execução da query e do cálculo novamente.

No terminal podemos ver a saída do memcached, mostrando quando armazenamos e buscamos o valor:

memcached

Bom, para começar é só isso. Bem simples e não lá muito útil, mas precisamos começar do mais básico, certo? 🙂

No próximo artigo vou mostrar alguns exemplos mais práticos, utilizando frameworks como Rails e Sinatra. Após isso vamos explorar áreas como: expiração de dados e coleções interdependentes, dog pile effect e outras coisas interessantes.


6 Comentários

[…] This post was mentioned on Twitter by Tino Gomes, Lucas Húngaro and Raul Souza Lima, Willian Fernandes. Willian Fernandes said: RT @lucashungaro: Keep Learning – Back-end caching com Ruby, parte 1 – Introdução http://is.gd/i0dTa #blog […]


Comentário por
Fábio T. da Costa
1 December 2010 @ 0:47

Muito bacana o artigo,

Mas não consegui instalar a gem ‘memcached’ no Ruby 1.9.2, dá erro na hora de fazer o build das extensões nativas.

Estou usando o Mac (Snow Leopard)


Comentário por
Lucas Húngaro
1 December 2010 @ 0:52

Fábio, amanhã vou dar uma olhada sobre a compatibilidade dessa gem com o Ruby 1.9.

No exemplo utilizei o REE.


Comentário por
Rinaldi Fonseca
4 January 2011 @ 21:17

Muito bom o exemplo! Parabéns!


Comentário por
Bruno Barros @bkether
19 February 2011 @ 21:11

@fabio : Se for no snow leopard usar o arch flags correto para plataforma 64 bits. =) Provavelmente funcionará seguindo o caso do mysql.

@lucas fico no aguardo da continuação, pois minha saga até que foi legal pra aprender, vide sua palestra na rubyconfbr, resolvi meter a cara de verdade e hj meu projeto está usando memcache com libmemcached e quando possível kasket. Troquei e-mails com o autor do kasket e me indicou ferramentas importantes, como o uso do libmemcached. O assunto é complexo e o meu projeto também vide que de 1s em 1s tinha que fazer uma consulta da DB. Hj resolvemos esse problema, otimizamos e está 100% melhor, acho que se não fosse pelo cache de backend távamos down a muito tempo! Valeu pelo incentivo. Só parei pra ler agora.


Comentário por
Lucas Húngaro
19 February 2011 @ 22:43

Bruno, estou devendo algumas continuações mesmo. Por enquanto só tenho rascunhos. 🙂

Bom saber que as coisas funcionaram pra você. Cache é sempre uma parte que ajuda muito no desempenho e, fora do básico, dá uma boa dose de trabalho.


Deixe um comentário