Riscos de implementações ingênuas quando utilizamos Memory Cache em .NET — Tiago Tartari

Tiago Tartari
3 min readOct 28, 2020

Não raro, nós, desenvolvedores, gerenciamos os riscos de implementações ingênuas nas entregas que estamos fazendo. O que, muitas vezes, geram experiências ruins para os usuários, além, de diminuir a confiabilidade das nossas aplicações.

Deste modo, abordo uma das implementações ingênuas que frequentemente adicionamos em nossas aplicações, a utilização do Memory Cache.

Ao adotamos uma abordagem de cache em memória, não medimos os impactos dessa decisão. Não temos noção do comportamento da nossa aplicação em relação aos recursos computacionais.

Assim sendo, nesse post, utilizaremos um serviço produtivo de produtos criado em .NET Core 3.1 e uma base de dados noSQL com 39.861 documentos. Ferramentas de monitoramento e métricas como Grafana, Prometheus-Net, InfluxDb, Prometheus-Net.DotNetRuntime foram utilizados, além do JMeter para teste de carga.

Embora, com número razoável de documentos, conseguimos perceber como nossa aplicação se comportará com três abordagens, sendo:

  • Criando as chaves de cache;
  • Consumindo os dados do cache criado;
  • Acesso direto a base de dados, sem implementação de cache;

Em todos os testes adicionamos carga ao nosso serviço para percorrer todos códigos de produtos disponibilizados em um csv, o ramp-up foi a cada 10 segundos com incremento de 20 threads a cada ramp-up.

1º Abordagem: Criando as chaves de Cache

Notamos que, a quantidade de requests no início do teste chegaram em um pouco mais 500 requisições por segundo, pelo fato da pressão dos requests, mais requisições de GC são realizadas, entretanto não o suficiente para conter o crescimento de memória, chegando a um pouco mais de 600Mb alocados, o que é esperado.

Os resultados dessa primeira abordagem foram:

  • 90% do Response Time em 110ms;
  • Total de 174 reqps
  • Tempo para percorrer 39.861 registros em 3 minutos e 45 segundos

2º Abordagem: Consumindo os dados em cache

Notamos que, a quantidade de requests no início do teste chegaram em um pouco mais 1.800 requisições por segundo, não há incidencia de coletas do GC, e a memória permanece estabilizada em um pouco mais de 600Mb durante o teste.

Os resultados dessa primeira abordagem foram:

  • 90% do Response Time em 32ms;
  • Total de 1874 reqps
  • Tempo para percorrer 39.861 registros em 21 segundos

3º Abordagem: Acesso direto a base de dados, sem implementação de cache

Notamos que, a quantidade de requests no início do teste chegaram em um pouco menos 500 requisições por segundo, pelo fato da pressão dos requests, mais requisições de GC são realizadas, um pouco diferente do primeiro teste, a memória manteve-se em um pouco menos de 30Mb alocadas.

Os resultados dessa primeira abordagem foram:

  • 90% do Response Time em 107ms;
  • Total de 180 reqps
  • Tempo para percorrer 39.861 registros em 4 minutos e 23 segundos

Conclusão

Logo percebemos que a pressão do Garbage Collector tem grande influência positiva no controle das alocações em memória, entretanto, tem uma influência negativa na performance. Se repararmos os cenários 2 e 3 podemos comprovar isso.

Em um próximo artigo, vou abordar as estratégias de caching nas perpectivas de confiabilidade, escalabilidade e disponibilidade.

Originally published at https://www.tiagotartari.net on October 28, 2020.

--

--

Tiago Tartari

Microsoft MVP, programador por mais de 18 anos onde 10 deles atuando como arquiteto de soluções para e-commerce, palestrante técnico, apaixonado por performance