Cache em memória no .NET

Cache em memória no .NET

O cache em memória é uma técnica fundamental para melhorar o desempenho e a escalabilidade de aplicações. No contexto do .NET, o caching permite armazenar dados em memória para acessos rápidos e eficientes, evitando a necessidade de operações dispendiosas, como consultas a bancos de dados ou chamadas a serviços externos.

O que é cache em memória?

O cache em memória é um mecanismo que armazena dados temporariamente para reduzir o tempo de acesso a informações frequentemente utilizadas. Em vez de buscar os dados em uma fonte mais lenta (como um banco de dados ou serviço web), o cache armazena uma cópia local dos dados, neste caso na memória, permitindo acessos mais rápidos e eficientes. O caching é especialmente útil para melhorar o desempenho em sistemas que realizam muitas operações de leitura.

Benefícios do cache em memória

O cache em memória oferece diversas vantagens para melhorar o desempenho e a escalabilidade das aplicações:

Redução do tempo de resposta: o cache fornece dados rapidamente a partir da memória, o que é muito mais rápido do que acessos a disco ou rede.

Redução da carga no banco de dados: evita a sobrecarga em consultas frequentes ao banco de dados, reduzindo a quantidade de operações necessárias.

Melhoria na escalabilidade: com menos chamadas externas, o sistema pode suportar um maior número de usuários simultâneos.

Tipos de cache

O cache pode ser implementado de várias formas, dependendo das necessidades da aplicação:

Cache local: armazenado na memória do processo que está executando a aplicação. Exemplo: MemoryCache no .NET.

Cache distribuído: armazenado em um servidor de cache separado, acessível por várias instâncias de aplicação. Exemplos incluem Redis e Memcached.

Neste artigo, focaremos no cache local usando MemoryCache.

Cuidados ao utilizar o cache

Consistência dos dados: o cache pode armazenar dados desatualizados. Para evitar problemas de consistência, é crucial implementar políticas de expiração e invalidação adequadas. Isso significa definir um tempo de vida para os dados no cache e garantir que eles sejam atualizados ou removidos quando necessário. 

Gerenciamento de memória: o uso excessivo de cache pode levar a problemas de memória, especialmente em sistemas com recursos limitados. É essencial monitorar o uso de memória e implementar estratégias de gerenciamento, como a limitação do tamanho do cache e a remoção de itens menos utilizados. 

Segurança: o cache pode armazenar dados sensíveis, o que representa um risco de segurança se não for gerenciado corretamente. É importante garantir que os dados armazenados no cache sejam criptografados e que o acesso ao cache seja restrito apenas a usuários e processos autorizados.

Implementando o cache

Para ilustrar a implementação de cache em memória, tomaremos como exemplo um projeto de API construído em ASP.NET Core.Nele, temos um controller que retorna uma lista de produtos, consultando sempre o banco de dados, da seguinte forma:

				
					[HttpGet]
public IActionResult Get()
{
    return Ok(_productsRepository.GetAllProducts());
}

				
			

Considerando que esta listagem de produtos seja consultada com frequência, e que os dados retornados não sofram muita variação, concluímos que teremos várias consultas sendo realizadas no banco de dados para retornar “sempre” as mesmas informações. Este é então um cenário viável para uso de cache.

Ao aplicarmos uma técnica de caching neste endpoint, o número de consultas ao banco de dados reduzirá, pois na maioria das vezes os dados retornados estarão em memória.

Para utilizar o cache em memória no ASP.NET, o primeiro passo é configurar este serviço na classe Program (ou Startup) do projeto. A seguinte linha de código deve ser adicionada após a chamada do “AddControllers()”:

				
					builder.Services.AddMemoryCache(); 
				
			

Em seguida, precisaremos criar uma instância de IMemoryCache no nosso controller, o que pode ser feito por meio de Injeção de Dependência. Partindo do nosso exemplo anterior, o controller agora terá a seguinte estrutura:

				
					using CacheDemo.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;

[ApiController]
[Route("[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IMemoryCache _memoryCache;
    private readonly ProductsRepository _productsRepository;

    public ProductsController(IMemoryCache memoryCache, ProductsRepository productsRepository)
    {
        _memoryCache = memoryCache;
        _productsRepository = productsRepository;
    }

    [HttpGet]
    public IActionResult Get()
    {
        return Ok(_productsRepository.GetAllProducts());
    }
}

				
			

Note, no código acima, que declaramos um atributo readonly do tipo IMemoryCache e o injetamos no construtor, prática comum em projetos ASP.NET. Além disso vemos o mesmo sendo feito para o repositório de produtos, que já existia em nosso projeto e que no seu caso pode ser um service, query ou mesmo o próprio DbContext.

Agora vamos alterar a action “Get()” para não apenas consultar o repositório diretamente, mas antes tentar buscar as informações no cache:

				
					[HttpGet]
public IActionResult Get()
{
    const string cacheKey = "products";
    
    if (!_memoryCache.TryGetValue(cacheKey, out List<Product> products))
    {
    	products = _productsRepository.GetAllProducts();
        var cacheEntryOptions = new MemoryCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
        };

        _memoryCache.Set(cacheKey, products, cacheEntryOptions);
    }

    return Ok(products);
}
				
			

No código acima, o primeiro passo foi definir uma chave para essa entrada do cache. Ou seja, a lista de produtos será identificada pela chave “products”, e precisaremos utilizar essa identificação quando quisermos ler, atualizar ou remover essa entrada do cache.

Em seguida usamos o método “TryGetValue” do “_memoryCache”. Este método recebe a chave que desejamos ler e um parâmetro do tipo output que será preenchido com o valor contido no cache, caso ele exista. Se o valor não existir no cache, o código contido no corpo do “if” será executado. É neste ponto que devemos atualizar o cache, inserindo a lista de produtos para que na próxima chamada ela seja localizada.

Neste ponto, fazemos a chamada normalmente ao repositório (ou outra fonte de informações) e preenchemos o parâmetro de saída “products”. Em seguida, declaramos uma variável do tipo “MemoryCacheEntryOptions”, que contém as configurações que devem ser aplicadas nesta entrada do cache, principalmente o tempo seu tempo de expiração. No exemplo, “AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)” especifica que o valor armazenado no cache deve expirar 5 minutos após ser armazenado, garantindo que os dados sejam atualizados periodicamente.

Finalmente, “_memoryCache.Set(cacheKey, products, cacheEntryOptions);” armazena a lista de produtos no cache usando a chave “cacheKey” e as opções definidas no “cacheEntryOptions”. Assim, pelos próximos 5 minutos, as chamadas a esse endpoint retornarão as informações a partir do cache, não precisando consultar no banco de dados.

Aqui é importante ressaltar que o tempo de expiração do cache é uma das principais configurações para que essa estratégia seja bem sucedida. Além disso, às vezes também é importante garantir que o cache seja expirado caso as informações sejam alteradas na fonte, garantindo que nenhuma requisição retorne dados desatualizados.

Acelere a sua carreira conosco!

Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET StartClique aqui

Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET ExpertClique aqui

Considerações finais

Implementar caching de memória de forma eficiente pode melhorar significativamente o desempenho de aplicações. É importante escolher a estratégia de cache apropriada com base nos requisitos da aplicação e monitorar o uso para evitar problemas de memória. Com as ferramentas e técnicas discutidas, você pode começar a aplicar caching em seus projetos .NET para obter um desempenho mais rápido e escalável.