Feed e Twitter

Feed RSS Twitter

Busca

Por Lustosa em 16/05/2009 às 13:01

Um projeto muito interessante, e que pode ser usado pra aumentar drasticamente o desempenho de aplicativos web é o Memcached. Pela descrição do site do projeto, o “memcached é um sistema distribuído de alto desempenho para o cacheamento de objetos na memória, genérico por natureza, mas feito para se aumentar a velocidade de sites dinâmicos diminuindo a carga no banco de dados”.
O memcached funciona como um grande dicionário, que armazena tuplas do tipo [chave, valor]. Para armazenar um objeto qualquer, basta conectar no memcached e passar uma chave para acessá-lo e o objeto (e opcionalmente outros parâmetros, como tempo de armazenamento). Para buscar o objeto, basta conectar ao memcached e pedir a ele o objeto que possui a chave previamente criada.
Digamos que uma página web precise fazer algumas consultas pesadas em um banco de dados. A idéia é justamente pegar o resultado da consulta e jogar pra dentro do memcached. Na próxima vez que a consulta precisar ser feita, o programa irá primeiro checar se o resultado já está disponível no memcached. Se estiver, ótimo, economizamos uma query pesada. Se não estiver, o programa faz a consulta e guarda o resultado no memcached. Simples assim!

Exemplo em PHP

O memcached pode ser acessado de muitas linguagens de alto nível, como C/C++, Java, Python, PHP, e até .NET. Abaixo, um exemplo em PHP.
Antes do memcache, uma página poderia ter uma consulta do seguinte tipo:

<?php
// A query SQL está abreviada, apenas como exemplo
// Digamos que essa consulte demore cerca de 1 segundo
// para ser completada
$sql = "SELECT * FROM tabela INNER JOIN ......";
$res = pg_query($sql);
$arr = pg_fetch_all($res); // array com todos os resultados
?>

Com esse código, a cada acesso na página, teremos uma consulta de 1 segundo feita ao banco de dados. Muitas vezes, a consulta será exatamente a mesma. Modificando a consulta para uso do memcached, teríamos algo assim:

<?php
// Conecta ao memcached
$mc = new Memcache;
$mc->addServer('ip.do.servidor');
 
$sql = "SELECT * FROM tabela INNER JOIN ......";
$cache = $mc->get(md5($sql)); // Usamos o md5() da query como chave
 
if ($cache === false) {
    // Resultado não está no cache
    $res = pg_query($sql);
    $arr = pg_fetch_all($res);
    $mc->set(md5($sql), $arr);
}
else {
    $arr = $cache;
}
?>

Pelo exemplo acima, temos algumas modificações. Primeiramente conectamos ao memcached. A chamada a addServer() pode ser feita múltiplas vezes, caso existam vários memcached rodando em máquinas separadas. Pode ser dado um “peso” pra cada servidor também, de acordo com a memória disponível em cada um.
Logo em seguida, é feita a verificação no memcached se o resultado da consulta já está lá. Como chave, usei o hash md5 da consulta, pra gerar uma identificação única. O método get() pode retornar false, caso a chave não esteja no memcached, ou retornar o objeto que foi guardado.
A checagem é simples. Se o get() retornou false, então o sistema faz a consulta como já fazia antes, e no fim guarda o resultado no memcached, pra agilizar as próximas consultas. E caso não tenha retornado false, temos já o resultado da consulta diretamente. Ao fim do bloco if, teremos, de uma forma ou de outra, o resultado da consulta em $arr.
A diferença é que rodando a consulta no banco, o tempo será de cerca de 1 segundo (tempo que estimamos para a execução da consulta), e pegando o resultado direto do memcached, a consulta demorará algo da ordem de milissegundos para ser completada.

Problemas

Um dos problemas clássicos em qualquer abordagem que use cache é como saber se as informações em cache ainda estão atuais. Com o memcached, isso não é diferente. Existem algumas formas de se lidar com o problema.
A primeira delas, mais simples, e que pode ser usada livremente caso não seja de suma importância que os dados estejam atuais, é mexer no tempo em que a informação ficará no cache. O tempo pode ser especificado na chamada ao método set(), da seguinte forma:

<?php
$mc->set(md5($sql), $arr, MEMCACHE_COMPRESSED, 60);
?>

Temos neste exemplo todos os parâmetros para a chamada de set(): a chave, o objeto a ser guardado, uma flag (pode ser usado MEMCACHE_COMPRESSED, que indica que o objeto será comprimido para ocupar menos espaço, ou 0 pra indicar que deve ser guardado sem compressão), e por último o tempo de vida do objeto, no caso, 60 segundos.
Ou seja, caso não haja problema das informações permanecerem desatualizadas por 60 segundos no máximo, essa abordagem pode ser usada.
Caso a informação tenha que estar sempre atualizada, a forma de tratamento terá que ser outra. O memcached possui método para apagar o objeto associado a uma chave. Tendo a chave, basta usar:

<?php
$mc->delete('chave');
?>

e a entrada que existia no memcached para aquela chave será invalidada.
A idéia, nesse segundo caso, é justamente localizar no código todos os pontos que modificam o banco de dados de forma que possa alterar o resultado da consulta feita, e adicionar código para invalidar o cache nesses pontos. Assim, quando for feita a consulta no memcached, caso alguma informação tenha sido modificada, a entrada correspondente no memcached já terá sido apagada, e a chamada a get() retornará false naturalmente.
É claro que essa segunda abordagem dá muito mais trabalho, por isso a sugestão de se usar a primeira delas nos casos em que pode ser usada. Lembre-se que em um site com 100 acessos por segundo, um tempo de vida de 60 segundos evitará 5999 consultas ao banco (a primeira será feita e guardada).

Mais informações

O site do projeto tem um Wiki que responde a maior parte das dúvidas que se pode ter, em relação a instalação e ao uso.
No caso do PHP, as funções são bem documentadas na parte do manual sobre memcache.
Eu uso em produção o memcached, com 2 servidores, um com 768mb de memória disponíveis para o memcached e outro com 256mb. Dei pesos 3 e 1 respectivamente, para que um objeto tenha 3 vezes mais chance de cair no primeiro servidor. A configuração está bastante satisfatória, e o banco de dados agradece.

Artigos relacionados

Arquivado em programação

Feed RSS para os comentários deste artigo.


4 comentários em “Otimização de sites com memcached”

  1. Diego Silva comentou:

    Olá Lustosa.

    Funciona com Super Cache para wordpress? É um complemento a mais para cache? Otimiza também o DB?

    Abraço.

  2. Bruno Lustosa comentou:

    Oi, Diego.
    O memcached funciona pra “economizar” o banco de dados, pois ao invés de se fazer consultas demoradas, pode-se buscar o resultado diretamente nele, caso essa consulta já tenha sido feita antes.
    O plugin WP-SuperCache funciona em outro nível. Ele monta em disco toda a estrutura de diretórios e arquivos, de forma que quando é feito um acesso, o conteúdo já está estático em disco, e com isso nem precisa ser interpretado pelo PHP. Mais rápido que isso, só se os arquivos de cache do WP-SuperCache estiverem na memória também.
    Abraço!

  3. Diego Silva comentou:

    Então é uma ótima combinação certo? Não há problemas?
    VocÊ começou utilizar o Memcached?

  4. Bruno Lustosa comentou:

    Diego, com o WP-SuperCache, não é preciso usar o memcached. Como disse, o memcached é mais adequado pra armazenar outros tipos de conteúdo. Para páginas, o super cache desse plugin é o que há de mais rápido.
    Aqui no blog eu não uso o memcached.


Copyright 2009 Ataraxia!   Sinopse