0

Uso de cache en Memoria con .Net y C#

En muchos casos escuchamos sobre Cache. El almacenamiento en caché puede mejorar el rendimiento y la escalabilidad de las aplicaciones al reducir el trabajo en procesamiento y reducir en muchos casos el tráfico de red. Existen grandes herramientas de caché como Redis que nos ayudan a que nuestras aplicaciones tengan respuestas más rápidas. En este post no veremos Redis, veremos las opciones que tenemos dentro del framework de .Net para obtener esta funcionalidad.

Caché en Memoria

EN. Net tenemos 2 tipos de almacenamiento en caché disponibles en nuestras aplicaciones. El primero es el caché de almacenamiento de salida y nos permite almacenar dinámicamente controles y páginas en todo dispositivo que soporte HTTP 1.1. Luego en solicitudes posteriores, no se ejecutara todo el código nuevamente si no que será retornado el resultado almacenado en el caché.

El segundo tipo disponible es el almacenamiento de datos en caché. Por ejemplo, si consultamos una base de datos y obtenemos registros los almacenaremos en memoria. En la siguiente consulta, consultaremos si tenemos datos disponibles en caché para ello, si es así, se retornará, si no es así se volverán a consultar a la base de datos y serán almacenados en caché para futuras consultas.

En la memoria caché podemos almacenar cualquier tipo de objeto o colecciones de objetos que nos ayudarán a aumentar el rendimiento de nuestras aplicaciones. Tengamos presente que no es lo ideal para aplicaciones distribuidas ya que mantendrá el cache en la instancia que se está invocando, y si tenemos múltiples servicios balanceados, el cache no estará presente al mismo tiempo en todos.

Manos a la obra

Lo primero que haremos es crear un proyecto y agregar el desde el repositorio Nuget a la librería Microsoft.Extensions.Caching.Memory de la siguiente manera.

dotnet new webapp
dotnet add package Microsoft.Extensions.Caching.Memory

Una vez referido el paquete, para tener disponible el servicio de caché deberemos agregar el servicio por medio de inyección de dependencia. Para esto utilizaremos la interfaz IMemoryCache que será pasado por el constructor a los que deseen utilizarlos. Veamos se vería en una clase genérica:

using Microsoft.Extensions.Caching.Memory;

public class GenericClass
{
    private readonly IMemoryCache _memoryCache;
    
    public GenericClass(IMemoryCache memoryCache) =>
        _memoryCache = memoryCache;

...

Para inyectar debemos ir a nuestro program.cs y agrega la linea:

builder.Services.AddMemoryCache();

Ahora veamos un código de caché implementado en alto nivel.

   public async Task<List<Customer>> GetCustomersCacheAsync()
    {
        var output = _memoryCache.Get<List<Customer>>("customers");

        if (output is not null) return output;

        output = new()
        {
            new() { FirstName = "Homer", LastName = "Simpsons" },
            new() { FirstName = "Moe", LastName = "Sislak" },
            new() { FirstName = "Barny", LastName = "Gomez" }
        };

        _memoryCache.Set("customers", output, TimeSpan.FromMinutes(1));

        return output;
    }

Veamos paso a paso lo que hace nuestro método. La primera vez que se ejecuta intentara tomar del cache la lista de Customer (línea 3). Luego validará si no es null, como es la primera vez lo estará y seguirá para construir el objeto con los clientes, en nuestro caso el output. Aquí viene nuestra línea clave:

_memoryCache.Set("users", output, TimeSpan.FromMinutes(1));

En esta línea seteamos nuestro objeto en caché. Posee 3 partes, primero la key con la que reconoceremos a nuestro cache, luego lo que deseamos guardar, por último el tiempo que daemon guardarlo en memoria. Pasado este tiempo el caché se eliminará. Entonces, en una segunda invocación, al preguntar si es null, al no serlo, retorna directamente el output que almacenamos en la primera petición.

En un gráfico para ser más claros:

Existe un camino más eficiente para validar si un caché existe:

public async Task<List<Customer>> GetCustomersCacheAsync()
    {
        List<Customer> customers = null;

       if (_memoryCache.TryGetValue("customers", out customers))
       {
           return customers;
      }

        output = new()
        {
            new() { FirstName = "Homer", LastName = "Simpsons" },
            new() { FirstName = "Moe", LastName = "Sislak" },
            new() { FirstName = "Barny", LastName = "Gomez" }
        };

        _memoryCache.Set("customers", output, TimeSpan.FromMinutes(1));

        return output;
    }

Hablemos un poco del tiempo de expiración: Sliding Expiration y Absolute Expiration. Sliding Expiration cadura el caché si no es accedido durante el tiempo establecido. Absolute Expiration el cache caducará en el tiempo establecido sin importar si se consume o no.

Para poder configurar alguno de estos 2 tipos usaremos el objeto MemoryCacheEntryOptions  que nos permitirá configurar estas opciones veamos.

Sliding Expiration

// Configurar cache
var cacheOptions = new MemoryCacheEntryOptions()
    .SetSlidingExpiration(TimeSpan.FromSeconds(30));

Absolute Expiration

// Configurar cache
var cacheOptions = new MemoryCacheEntryOptions()
    .SetAbsoluteExpiration(TimeSpan.FromSeconds(30));

por último:

// Setear el objeto
_memoryCache.Set("customers", output, cacheOptions)

¿Puedo guardar todo lo que quiera en la memoria caché? Si, pero tenemos que tener presente que la memoria de nuestros servidores son limitadas. Si llenamos la memoria podremos tener algunos inconvenientes en las respuestas de nuestra aplicación. Por esto es posible establecer un límite de uso de memoria para no crecer sin límite.

{
    public MemoryCache Cache { get; } = new MemoryCache(
        new MemoryCacheOptions
        {
            SizeLimit = 1024
        });
}

Conclusiones

El uso de cache en memoria puede sernos muy útiles para aumentar el rendimiento de nuestra aplicación y reducir el tráfico de red hacia los repositorios de datos. Pero es un recurso que debemos utilizar con cautela por no ocupar toda nuestra memoria y también tener presente el momento correcto de la expiración del caché. Espero que te haya gustado el post. Déjame tus comentarios.

Fernando Sonego

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *