terça-feira, 14 de setembro de 2010

Cache, pra que te quero?

Essa semana ocorreu a qcon aqui em São Paulo. O evento foi muito bom, embora algumas palestras recomendo menos teatro e piadas e mais objetividade no assunto.

De qualquer forma procurei ir em todas apresentações que falavam de escalabilidade, e um ponto me chamou atenção, um tema presente em praticamente todas apresentações e que curiosamente já estava preparando um post aqui no blog. É a utilização de cache.

Muitas vezes eu vejo pessoas falando em cache e na maioria das vezes vinculando cache apenas a dados armazenados. Quando envolve banco de dados, os dados armazenados estão em tabelas, então cacheiam as linhas da banco de dados. Bem, não que isso esteja errado, geralmente um banco de dados é remoto a aplicação e se o cache for local, obviamente a aplicação vai ficar mais rápida. Mas eu acho que tem que pensar em cache não para cachear apenas dados armazenados e sim para cachear dados processados.

Logo na primeira apresentação da qcon isso ficou bem claro, nela Nick Kallen descreveu a arquitetura de dados no twitter.

Você deve imaginar que o twitter tem uma base gigantesca de dados, e que não é um banco de dados relacional comum, eles usam muito estratégias de particionamento. Porém mesmo assim a tarefa de mostrar os tweets de uma pessoa é muito difícil. Quando você acessa o twitter do seu colega você vê os últimos tweets dele junto com os últimos tweets dos usuários que ele segue. Essa lista de tweets eles chamam de timeline.

Isso não é muito simples a medida que seu colega segue vários outros usuários e que esses usuários tem vários tweets postados. Imagina se para cada visualização de página de um usuário o twitter tivesse que buscar do banco e calcular o timeline?

Para isso eles usam cache, mas veja bem, não adianta cachear um tweet, ou os tweets de cada usuário, pois dessa forma eles estariam apenas mudando o ponto onde iria buscar o dado, ao invés do disco estariam buscando na memória em um cache. Ok, isso iria melhorar a performance, mas não elimina a necessidade de montar o timeline, o que implicaria em buscar também no cache os últimos tweets de cada usuário que seu colega segue.

O que o twitter faz é cachear o timeline, ou seja, os tweets em uma ordem já definida para cada usuário. Eles não cacheiam apenas os dados da forma como estão armazenados no modelo, ele cacheia dados processados, o timeline já calculado, pois se cacheasse somente tweets, eles teriam o trabalho imenso de processar e montar o timeline para cada usuário a cada request.

Mas como assim? para todos usuários? Isso mesmo, o timeline esta calculado e processado em um cache para todos usuários do twitter. O cache é imenso, mas compensa.

Mas se um usuário postar qualquer coisa, o timeline fica inválido? Em teoria sim, a menos que ao postar algo, o twitter atualize todos timelines dos usuários que seguem o usuário que postou o tweet. Algo como, usuário José e João seguem o usuário Luiz, quando usuário Luiz posta nova mensagem, o timeline em cache do usuário José e João são atualizados. E adivinha? é isso que o twitter faz, porém um usuário pode ter milhares de seguidores, e o processo de atualização do timeline de todos eles pode demorar. Por isso a atualização é feita de forma assíncrona, e por esse motivo que um post no twitter não aparece imediatamente no twitter dos seus seguidores (Luiz posta e em um momento do futuro esse post vai aparecer no timeline de João e José).

Mas e se o cache morrer? Nesse caso o twitter vai penar bastante, afinal buscar os tweets para montar o timeline a partir do base pode ser extremamente custoso, teria que buscar e processar os últimos tweets de todos os usuários que determinado usuário segue. De qualquer forma há uma forma, só é mais complicada e demorada. Ou seja, o twitter usa cache, mas se der qualquer problema, os dados que ali estavam podem ser totalmente reconstruídos através de informações da base de dados.

Enfim, o que realmente me chamou atenção nisso foi o exemplo claro de uma abordagem de cache que não apenas cacheia uma entidade do banco de dados, um registro de tabela, o resultado de uma query, o dado como ele está armazenado. Eles economizam não apenas queries mas também processamento, o processamento que seria necessário para criar o timeline, isso está lá cacheado também e é dessa forma que eu acho que deve ser pensado os caches.

Para não ficar apenas com o twitter, um outro exemplo. Imagine um sistema como paypal ou pagseguro, onde um usuário é redirecionado para realizar o pagamento de uma compra. Nesses sistemas geralmente quando o usuário comprador vai efetuar o pagamento, a página de compra é customizada para a loja do vendedor, o que implica buscar os dados das várias configurações dele, como customizações de tela, meios de pagamentos aceitos, limites, nome, telefone, etc.

Um sistema sem cache iria realizar várias queries direto no banco para buscar todos os dados da loja do usuário que está vendendo.

Um sistema com cache não muito bem pensado iria cachear 'n' registros de 'n' queries no banco, isso iria resultar em 'n' buscas no cache, objetos pequenos que representavam o resultado de cada query e o processamento de iteração entre esses objetos precisaria ser feito sempre.

Finalmente uma abordagem mais inteligente iria cachear um objeto maior, que não existe na base de dados e que iria conter todas informações passiveis de serem cacheadas já processadas e prontas para serem usadas. Então 'n' consultas no banco iriam resultar em uma única busca no cache de um objeto já pronto para ser usado, economizando não apenas em buscas, mas no processamento que iria ser realizado.

Bem é isso, cache é uma das soluções mais eficientes para resolver problemas de escalabilidade, isso ficou bem claro nas palestras que fui. Mas ao mesmo tempo, para se tirar melhor proveito dele, deve se pensar no que está cacheado, nem sempre colocar o cache junto aos DAO's do seu sistema pode ser a melhor alternativa.