Relato de Caso: Uso de cache em aplicação ASP.NET

Olá, pessoal.

Hoje vou falar sobre cache, mas quero fazer algo diferente dos muitos materiais que se encontra na Internet. Não numa visão de codificação prática, mas sim conceitualmente, explicando o resultado que tive ao implementá-lo em uma aplicação ASP.NET que possuía sérios problemas de desempenho.

Primeiramente, qual é o conceito de cache? A idéia do cache é ter uma cópia de um conjunto de dados para acesso rápido. Dessa forma não oneramos o repositório original, cujo acesso é mais lento que o cache, ao fazer buscas.

A seguinte imagem mostra como um algoritmo de cache comumente é feito. Alguém faz uma requisição ao cache, buscando determinada informação. Caso essa informação não exista no cache, ele vai ao repositório original para carregá-la e o insere no cache. A partir disso, qualquer busca por esta informação no cache vai trazer o dado armazenado, ao invés de efetuar a pesquisa no repositório “lento”.



A vantagem do cache está em melhorar o desempenho de pesquisas. O principal desafio dessa abordagem é controlar a validade do cache: pode ser que logo após a informação ser armazenada no cache, ela ser atualizada no repositório original. Nesse caso, que for acessar o cache irá receber uma versão antiga. Isso pode ser problema para algumas situações, e existem diversas formas de contornar este problema. Uma delas que minimiza esse efeito indesejado é estabelecer um tempo máximo de vida do cache, ou seja, depois de determinado tempo, um objeto armazenado no cache é descartado e uma nova consulta no repositório original quando o objeto for novamente requisitado. Essa é a abordagem que utilizei com o cache do ASP.NET, e que serviu bem para a situação que trabalhei.

O cenário que tínhamos era o seguinte. Uma aplicação era disponibilizada na Internet para acesso de vários estabelecimentos comerciais e seus procuradores para receber avisos e notificações. Pelo grande volume atual de acessos, esse sistema não estava mais escalando e ficava indisponível constantemente. Quando cheguei, a primeira coisa que fiz foi tirar um dump de memória, e com isso constatei que havia muitos objetos do tipo Estabelecimento carregados no processo do IIS. Depois de uma análise de código, entendemos que essa quantidade excessiva de objetos era porque se fazia muitas consultas ao banco de dados buscando este tipo de objeto. As informações armazenadas neles, por exemplo CNPJ, razão social e data de fundação, não eram dados que recebiam muitas atualizações (alias, é bem difícil que recebam), e por isso optamos por colocar uma rotina de cache para esses objetos. Utilizamos o CNPJ do estabelecimento como índice de busca. Outro detalhe importante é que essas informações de estabelecimentos eram para somente leitura. Com isso, problemas de concorrência no uso desses objetos praticamente não existem.

Como o sistema já estava dividido numa arquitetura que, apesar de anêmica, possuía a divisão clara de componentes na qual as classes de acesso a dados já estavam bem separadas, decidimos colocar o cache neste ponto. Usamos sim o cache do ASP.NET dentro da camada de acesso a dados, no entanto isolamos essa chamada à infra-estrutura do ASP.NET de forma que se esse sistema mude de plataforma, possamos acoplar outra solução de cache (como o AppFabric ou NCache, por exemplo) de forma simples.

Feito isso, testamos e implantamos em produção. Felizmente deu tudo certo e a aplicação voltou a atender os usuários!

Fizemos algumas medições para ver como o uso do cache melhorou o desempenho. Podemos ver isso no gráfico abaixo.


Este gráfico é o Performance Monitor do Windows. Nós pedimos para ele acompanhar alguns contadores de desempenho referentes ao cache do ASP.NET: Cache API Entries, Cache API Hits e Cache API Misses (os três primeiros contadores que aparecem). Para poder explicar melhor o que cada um deles representa, vamos recorrer ao desenho que comentamos agora há pouco. Veja a nova versão abaixo.



  • Cache API Misses representa a quantidade de vezes que buscamos um determinado objeto no cache e ele não se encontra. Neste caso, temos que ir no repositório "lento".
  • Cache API Entries representa a quantidade de objetos que foram recuperados do repositório original e que estão atualmente armazenados no cache.
  • Cache API Hits representa quantas vezes fomos até o cache e achamos a informação que queríamos já pronta para nosso uso. É neste momento que vemos o ganho de desempenho.
Com essas informações na cabeça, vamos analisar o gráfico. Vejam que a linha azul (Cache API Hits) é a que mais cresce, enquanto as outras ficam relativamente constantes. Isso indica que o cache está sendo bem utilizado, pois temos uma mesma informação (linha vermelha, o Cache API Entries) que está sendo consumida inúmeras vezes.

Alguns podem perguntar da relação entre a linha vermelha e a linha rosa (Cache API Misses), pois na teoria toda vez que não encontramos uma informação no cache (miss), nós a carregados do repositório. Neste caso, os valores de ambos os contadores deveriam ser iguais (um para um). O que acontece nesse caso é que objetos do cache que já tiveram seu tempo de vida expirado são removidos, fazendo com que seja necessária uma nova busca para uma informação que já foi pesquisada há tempos.

O que eu gostaria de falar para vocês é que nesta situação que tivemos, o uso de cache foi imprescindível para um bom desempenho do sistema. Tenham sempre em mente que é muito provável que seja vantajoso utilizar uma estratégia de cache quando temos a mesma informação sendo consumida várias vezes. É claro que cada caso requer uma análise dos problemas colaterais que podemos ter, como maior uso de memória e consumo de dados desatualizados, mas eu acredito que o custo, ao menos da análise, valha a pena.

Aqui seguem alguns links com mais informações sobre cache:
Até a próxima!

Comentários

Postagens mais visitadas deste blog

Trocando configurações padrão do Live TIM

Uma proposta de Clean Architecure com Modelo de Atores

Testes automatizados em sistemas autenticados com certificados digitais, usando Selenium e PhantomJS