Fiz uma pesquisa, recentemente, para entender como as pessoas estão usando cache com banco de dados, logicamente, com MySQL. O resultado, confesso que foi muito diferente do que eu imaginava.

 31% dos usuários de MySQL usam MemCache. 19% ficam o Query Cache, o cache nativo do MySQL. Isto significa que 50% usam algum tipo de cache, o que faz muito sentido para mim.

 Uma surpresa espantosa foi aqueles que disseram que “não sabem para que serve cache” e os que “não sabem dizer se usam ou não”. Estamos falando de 19% da amostragem! Tudo bem, ninguém nasce sabendo. Cheguei a conclusão que seria muito bancana intensificar a criação de artigos sobre o assunto.

Cache é para todos? Não, não é. Obviamente, todo tipo de aplicação pode tirar proveito de um cache. No entanto, às vezes o benefício trazido pelo cache é tão pequeno que não compensa usá-lo, vez que algumas novas linhas de código seriam necessárias em sua aplicação. Certamente, alguns irão discordar deste parágrafo. Mas, depois de tantos anos de consultoria, o titio chegou a conclusão que esta é uma verdade inexorável.

Para aqueles 19% que não sabem o que é cache, precisamos evitar a confusão (super freqüente) com buffer.Buffer é uma área de memória volátil que pretende reduzir os problemas de latência entre dois recursos. Ficou chato? Não entendeu? Titio sabe que palavras bonitas só atrapalham. Então vamos lá. Quem é mais rápido processador ou Disco? Processador, certo? Muito bem! Imagine que o processador precisa processar uma grande massa de dados, e gravar o resultado deste processamento em disco. O processador consegue processar blocos de dados, muito mais rápido do que o disco consegue gravar. Aí entram os buffers! Como o disco não conta do recado, ele processa os dados e já que se livrar deles, para continuar processando. Então ele envia isto para o disco… na verdade, estes dados vão para os buffers da controladora de disco, e, para o buffer do próprio disco. Isto existe para evitar que o processador pare de processar aguardando o lerdo disco gravar e dizer, ok, mande mais dados para mim. Então, os dados ficam em buffers aguardando que o disco peça por eles. Isto funciona para leitura e para gravação. Usei o exemplo do disco, mas, existem diversas aplicações para buffers: buffers de gravação, de leitura, de disco, de rede, de impressão, etc. Um buffer é sempre usado com dispostivos rápidos (memória ram, flash, etc), e pode ser implementado como um hardware ou software.

Cache e buffer tem peculiaridades e diferenças tão sutis que sempre são alvo de confusão. Um cache tem exatamente as mesmas características de um buffer. Ambos são memórias temporárias, voláteis. A diferença está no seu uso.

Enquanto o buffer é mais utilizado durante o processamento de uma informação. O Cache é usado para armazenar o resultado de um processamento por determinado tempo.

Exemplos de Buffer dentro do MySQL

No Mysql existem buffers globais e de sessão. Exemplo típico de buffer, implementado via software, são os seguintes buffers de sessão do MySQL:

a/ Read Buffer (variável read_buffer_size): É um buffer utilizado quando um SELECT (leitura) é feita de forma sequencial, do primeiro registro até o último registro de uma tabela;

b/ Read Random Buffer (variável read_rnd_buffer_size): Outro buffer usado em leitura, porém, quando há uso de índice e a leitura dos registros (ou linhas) da tabela será feita de forma zoneada… oops, aleatória.

c/ Bulk Insert Buffer (variável bulk_insert_buffer_size): Este já é um buffer de escrita, ao contrário dos anteriores que são de leitura. Este buffer é usado pelo MySQL quando uma grande quantidade de INSERTS são demandados no servidor MySQL. Nota: usado pelo comando LOAD DATA, mysqlimport (aplicativo comando de linha) e comandos INSERT do tipo: INSERT…VALUES… (linha 1), (linha 2), (linha n).

Todos os buffers acima existem porque é muito mais demorado para o MySQL interagir com os dispositivos de armazenamento do que com a memória ram e o processador (latência).

Exemplo de Cache dentro do MySQL:

Estamos nos entendendo até agora? Espero que sim! Em sala de aula eu gosto muito de esgotar este assunto, pois, uma vez entendido nunca mais esquecemos.

O Cache (que é primo de primeiro grau do buffer) é usado para armazenarmos resultados de processamento para que este resultado possa ser disponibilizado de forma instantânea, muito rápida, e com o menor overhead (custo de processamento) possível.

Normalmente, Caches são estruturas NOSQL. Muito simples. E armazenam estes resultados em pares do tipo (chave, resultado). Onde:

– Chave: Normalmente é um hash (como MD5, por exemplo), que permite identificar de forma única o resultado armazenado

– Resultado: É o dado que foi armazenado dentro do Cache

No MySQL temos um Cache NoSQL chamado de Query Cache. E funciona da seguinte forma: Todo comando SELECT que atenda às premissas de configuração do Query Cache é armazenado dentro do Query Cache. As configurações e as possibilidades sobre o que é armazenado no Query Cache ou não, como ligar ou não o Query Cache, como fazer o expurgo dos dados vencidos (invalidação dos dados) são muitos e não são objeto deste artigo.

O que precisamos saber sobre o Query Cache? Primeiro, como ele armazena os dados. O SELECT que será armazenado é “hasheado”, ou seja, todo o comando SELECT, passando pelo FROM, JOIN, WHERE até o LIMIT é passado em um algoritmo que irá converter sua linha SQL em uma chave única. É então, armazenado o hash do SELECT e seu resultado.

Imagine o SELECT * FROM tabela_de_paises. Este SELECT, passado no algoritmo de hash será transformado em 310905b644f0844c78037c104716e6bd, que é a chave. Esta chave será armazenada junto com o resultado do SELECT, que consequentemente, são todos os dados da tabela de países.

Todo SELECT igual a este que for enviado ao servidor não causará processamento, tão pouco, ida ao disco para buscar os dados. Será retornado os dados armazenados no Query Cache. Uma vez que não tem disco, não tem processamento, apenas uma busca NoSQL dentro de um CACHE o resultado é uma entrega de dados muito… muito rápida e com um custo para o MySQL próximo a zero.

Cuidados com Query Cache:

Hash para SELECT * FROM tabela_de_paises => 310905b644f0844c78037c104716e6bd

Hash para sELECT * FROM tabela_de_paises => 6ce0a48206e12beb563ad7f2f823fe4b

Note que ao mudar uma única letra de S para s já causou um hash diferente. Isto significa, uma chave diferente. Chaves diferentes, dois registros ou entradas no Query Cache.

O Query Cache é um cache do tipo LRU (Least Recent Used). Isto é, os dados eliminados do Cache, quando é necessário livrar espaço para novas entradas, é baseado na eliminação dos registros (ou entradas) menos usadas, menos lidas. O algoritmos para isso é complexo e inteligente, e, muito eficiente embora cause fragmentações dentro do Cache.

Outra coisa muito inteligente no Query Cache, que também é sua maldição é forma como acontecem as invalidações dos dados lá armazenados.

Imagine que um SELECT no estoque foi feito. E, o resultado é Capirinha = 10. Tem-se, então, 10 caipirinhas no estoque. E cuidado, álcool nunca pode faltar no estoque! Se isto acontecer dá stack overflow! Na sequência outro SELECT idêntico foi requisito. O resultado retornado do Cache é, Sim temos 10 Caipirinhas. Mas, e se todas foram vendidas?

Pensando nisso, toda vez que uma tabela sofre um UPDATE, DELETE ou até mesmo INSERT, todos os dados daquela tabela armazendados no Query Cache são invalidados, e não serão mais retornados. É claro que o espaço fique livre para novos armazenamentos.

O MemCache: O melhor Cache e NoSQL para MySQL

O MemCache é o Cache externo, e, também NoSQL natural para o MySQL. Tanto que, na pesquisa que realizei, não custa lembrar: 31% dos usuários de MySQL usam o MemCache.

Ele é um Cache implementado via software em memória RAM através de um “daemon”. Extremamente veloz, eficiente, robusto e já está na estrada desde 2003. Inicialmente, foi desenvolvido pelo Brad Fitzpatrick para o LiveJournal. Hoje é uma comunidade extremamente ativa. O MemCache é um FOSS – Free Open Source Software. Ou seja, é de grátis e tem código aberto, entre e fique a vontade.

Eu uso MemCache desde 2005, faço parte da comunidade (com pouquíssima contribuição, até por falta de tempo), e posso dar meu depoimento sobre ele: É bom para C….huchu! Ô gente de mente poluída.

Atualmente, tenho ele instalado em mais de 50 clientes. Alguns, são aplicações de altíssima demanda, e, o comportamento do MemCache é notável, às vezes, até assustador de tão perfeito.

Extremamente simples de usar, basicamente, tem 2 comandos:

– GET (chave): Busca dentro do Cache se existe a chave informada. Se existir já retorna o valor armazenado (que em geral é o resultado de um SELECT, se estamos falando de MySQL)

– SET (chave, o_quê_será_armazenado, tempo_de_vida): Grava dentro do Cache. Chave, em se tratando de MySQL, normalmente é um MD5 do SELECT (do comando inteiro); _o_quê_será_armazenado é o resultado do SELECT, e, tempo_de_vida é por quanto tempo esta informação deve viver.

Este é o melhor Cache, e, o melhor NoSQL que eu conheço. Simples, Robusto, Rápido e agüenta qualquer pauleira.

O MemCache é um Cache/NoSQL tão sério que a partir da versão 5.6 do MySQL ele será incorporado ao MySQL. A Oracle não faria isso se o MemCache não fosse, realmente, sério.

Paralelos entre Query Cache e MemCache

– Ambos são NoSQL, sim senhor. No Query Cache isto não fica evidente pois ele é automático. Uma vez assim configurado, todo SELECT (que atenda às premissas de configuração) é, automaticametne, dentro do Query Cache. Antes de executar qualquer SELECT contra uma tabela, antes, é verificado contra o Query Cache se aquele SELECT já foi executado e se está armazenado dentro do Query Cache. Tudo sem nenhuma preocupação para o desenvolvedor.

– Query Cache é baseado em LRU, e, os dados armazenados no Cache são, automaticamente, invalidados em caso de qualquer alteração na tabela. Isto é perfeito, pois, evita de servir-se de um informação desatualizada ou incorreta, previamente, armazenada no Cache. O ruim é que, qualquer alteração na tabela, ainda que não sejam as linhas armazenadas no Cache, gerará invalidação total de todas as entradas no Query Cache daquela tabela. Em outras palavras, tem-se no Query Cache todas as vendas do estado de São Paulo, hipoteticamente: WHERE estado = ‘SP’. Uma nova venda para o estado do Amazonas irá invalidar todas as entradas do Cache do estado de São Paulo. Ruinzão né? Sem falar que isto gera um certo overhead.

– MemCache é baseado em TTL (time to live) que define um tempo de vida para o conteúdo (resultado) lá depositado. Por exemplo, ao se enviar alguma coisa para o MemCache define-se quanto tempo aquele dado, informação, conteúdo deve viver, se preferir, o seu tempo de expiração. Enquanto o tempo de expiração ou de vida não for atingido, aquele dado é servido. Significa, que não se deve usar MemCache, por exemplo, para estoque, movimentações, etc, em outras palavras, MemCache só serve para dados estáticos ou com pouquíssima atualização.

– Query Cache só guarda resultados de queries (SELECT) do MySQL, já o MemCache pode ser usado, literalmente, para armazenar qualquer informação no formato (chave, valor) e NoSQL. No MemCache pode-se, por exemplo, além de resultado de queries (SELECT) pode-se usá-lo para armazenar fotos, imagens, pdf, etc.

Cache uma salva-vidas para bancos de dados em geral?

Tenho clientes usando um único servidor MySQL rodando com 28.000 queries por segundo, sem problemas, e sem qualquer uso de Cache. Nenhum mesmo. Trata-se de uma aplicação ERP homemade com uma base de dados extremamente bem modelada. Por isso, insisto tanto que o sucesso e/ou fracasso de uma aplicação começa, lá atrás, na definição das entidades e na modelagem da base.

Em contra-partida tenho clientes que não conseguem ultrapassar as 500 queries por segundo sem problemas, e, muitas vezes usando Query Cache, ao menos.

A verdade por detrás desta história é que, Query Cache só faz sentido ser utilizado em algumas tabelas da base, aquelas com pouquíssima atualização. O grande incomodo que me causa o Query Cache é que não se consegue gerenciá-lo de forma adequada. Não se consegue saber, por exemplo, quais queries estão lá dentro. Embora eu conheça bem o seu funcionamento, não deixo de vê-lo como uma grande, preta, e feia caixa de pandora. Em última instância, também é um tomador de recursos do MySQL.

Já o MemCache, e aqui não se trata de defesa prévia, de largada me apraz o fato de ser um recurso externo ao MySQL, que poupa-lhe a vida. Não gosto de TTL, mas, verdade seja dita, é fantástico quando se define a existência de um Cache junto à regra de negócios da companhia. MemCache deve ser usado, somente, e coisas muito estáticas. Alguns exemplos práticos para uso de MemCache:

– Tabelas que contenham CEP’s, bairros, cidades, estados, países, dados históricos, enciclopédias, dados D-1 (dados com mais de 1 dia de inserção/atualização e que não podem mais ser alterados)

– Pode-se definir com o negócio que produtos, promoções, categorias de produtos só ficam disponíveis após 5 minutos, e, estas tabelas ficam com TTL = 5 minutos

– Pode-se definir com o negócio que vitrines de lojas de e-commerce, atualização de perfil de usuário em sites de relacionamento, são atualizados a cada minuto, logo, TTL = 1 minuto

Titio diz:

Use Cache, Query Cache, MemCache, Jboss Cache, não importa. Use! Em aplicações com intenso uso de banco de dados os Caches podem cuidar da performance e da saúde do seu MySQL.

Usem sem total moderação!