Redis Caché vs Retrieve and Retrieve Multiple

Redis Caché

Azure Redis Cache es un servicio de caché completamente administrado que se basa en Redis, una popular base de datos en memoria de código abierto. Ofrecido por Microsoft Azure, este servicio está diseñado para acelerar aplicaciones web y móviles al permitirles almacenar y recuperar datos de manera ultrarrápida, lo que a su vez reduce la carga en las bases de datos como Dataverse en nuestro caso, y mejora significativamente el rendimiento.

Lo que vamos a intentar demostrar aquí es, primero, como se crea una base de datos en Redis y como podemos almacenar información. Por otro lado, como podemos crear elementos en esta base de datos de Redis y luego proveeremos algo de información para que se vea el Benchmark entre recuperar un dato en Redis y recuperar un dato desde Dataverse con .NET.

Crear Azure Caché for Redis

Iremos dentro de Azure y crearemos un grupo de recursos, a mi siempre me gusta crear cuál va a ser mi contenedor donde voy a alojar mis recursos, en vez de crearlo directamente desde la herramienta a usar.

Creacion grupo de recursos

Una vez que el grupo de recursos esté listo, crearemos nuestro Azure Caché for Redis.

Creacion Azure Cache for Redis Creacion Azure Cache for Redis 2

Cuando esté creado nuestro Azure Caché for Redis, tendremos que ir a Claves de Acceso y ahí cogeremos la cadena de conexión.

Get connection string

Código Redis

Una de las cosas que a lo mejor frustran un poco más de Redis es que desde Azure no podemos visualizar los valores como tal que tenemos almacenados en nuestra base de datos. Por ello, hay bastantes herramientas que nos permiten visualizar estos datos de una manera rápida. La que yo utilizo es dentro de VSCode, una extensión que se llama Azure Caché.

Esta herramienta me permite visualizar de una forma muy cómoda todas las bases de datos que tengo dentro de Redis.

En la siguiente imagen podemos ver como se visualizaría cada uno de los elementos en nuestra base de datos de Redis.

VS Code Redis

Conexión de código con Redis

Ahora, para poder realizar las conexiones a Redis utilizo el siguiente código

//Primero creo una clase de crear conexion.
//Esto podemos meterlo dentro de un singleton si tuvieramos alguna Azure Function / App Service
private IDatabase CreateConnection()
{
    var cacheConnection = "abc.redis.cache.windows.net:6380,password=123456789=,ssl=True,abortConnect=False";
    var connection = new Lazy<ConnectionMultiplexer>(() => ConnectionMultiplexer.Connect(cacheConnection));
    var connectionRedis = connection.Value;
    return connectionRedis.GetDatabase();
}

En el siguiente código lo que vamos a hacer es obtener el valor de Redis, y si por un casual no existiera, lo que hacemos es hacerle un Set a ese valor. La primera función es un constructor a mi función principal para crear la conexión con la funcionalidad anterior.

//Constructor
public RetrievalMethods()
{
    databaseRedis = CreateConnection();
}

public decimal GetValueFromRedis(IDatabase databaseRedis, string key)
{
    RedisValue redisValue = databaseRedis.StringGet(key);
    var redisValueString = redisValue.IsNullOrEmpty == true ? "-1" : redisValue.ToString();
    if (redisValue.IsNullOrEmpty == true)
    {
        //En esta linea decimos si el valor de Redis es nulo hazme un Set
        databaseRedis.StringSet(key, "1.12");
    }
    return decimal.Parse(redisValueString);
}

## Explicación caso de uso

En el caso de uso que he propuesto, lo que vamos a hacer es comparar el Benchmarking entre:

  1. Caché de redis
  2. RetrieveMultiple con top 1 con FetchExpression
  3. RetrieveMultiple con top 1 con QueryExpression
  4. Retrieve con Guid estático

Para poder realizar las funcionalidades de arriba las funciones de c# que hemos usado son las siguientes.

//Obtener valor de Redis
public decimal GetValueFromRedis(IDatabase databaseRedis, string key)
{
    RedisValue redisValue = databaseRedis.StringGet(key);
    var redisValueString = redisValue.IsNullOrEmpty == true ? "-1" : redisValue.ToString();
    if (redisValue.IsNullOrEmpty == true)
    {
        databaseRedis.StringSet(key, "1.12");
    }
    return decimal.Parse(redisValueString);
}

//Obtener el valor de D365 mediante QueryExpression
public decimal GetValueFromD365Query(IOrganizationService service, string key)
{

    // Instantiate QueryExpression query
    var query = new QueryExpression("vso_configurationcustom");
    query.TopCount = 1;
    // Add all columns to query.ColumnSet
    query.ColumnSet.Columns.Add("vso_value");

    // Add conditions to query.Criteria
    query.Criteria.AddCondition("vso_name", ConditionOperator.Equal, key);
    EntityCollection configurationItems = new EntityCollection();
    try
    {
        configurationItems = service.RetrieveMultiple(query);
    }
    catch (Exception ex)
    {
        string message = $"There was an error getting configuration with name {key} because {ex.Message}";
        throw new InvalidPluginExecutionException(message);
    }
    Entity configurationItem = configurationItems.Entities.FirstOrDefault();
    var configurationValue = (string)configurationItem.Attributes["vso_value"];
    var configurationDecimal = Convert.ToDecimal(configurationValue);
    return configurationDecimal;

}

// Obtener el valor de D365 mediante FetchExpression
public decimal GetValueFromD365Fetch(IOrganizationService service, string key)
{
    // Instantiate QueryExpression query
    var fetch = $@"<fetch top='1'>
<entity name='vso_configurationcustom'>
<filter>
<condition attribute='vso_name' operator='eq' value='{key}' />
</filter>
</entity>
</fetch>";
    EntityCollection configurationItems = new EntityCollection();
    try
    {
        configurationItems = service.RetrieveMultiple(new FetchExpression(fetch));
    }
    catch (Exception ex)
    {
        string message = $"There was an error getting configuration with name {key} because {ex.Message}";
        throw new InvalidPluginExecutionException(message);
    }
    Entity configurationItem = configurationItems.Entities.FirstOrDefault();
    var configurationValue = (string)configurationItem.Attributes["vso_value"];
    var configurationDecimal = Convert.ToDecimal(configurationValue);
    return configurationDecimal;

}

// Obtener el valor de D365 de un Retrieve con un GUID estático
public decimal GetValueFromD365SingleRetrieve(IOrganizationService service)
{
    Entity configurationItem = service.Retrieve("vso_configurationcustom", new Guid("d172c90a-397b-ee11-8179-000d3a5651f2"), new ColumnSet("vso_value"));
    var configurationValue = (string)configurationItem.Attributes["vso_value"];
    var configurationDecimal = Convert.ToDecimal(configurationValue);
    return configurationDecimal;
}

Benchmarking

Para poder realizar el Benchmark se ha usado un paquete Nuget que se denomina BenchmarkDotNet.

Este paquete nos permite comprobar el rendimiento en tiempo de cada uno de nuestros métodos, y también cuantos KB de memoria son alocados con nuestros métodos. De esta manera podemos hacer nuestros métodos mucho mejores, rápidos y eficientes.

El resultado del Benchmarking os puede sorprender, este ha sido el resultado:

Benchmarking Results

Como se puede comprobar, el obtener el valor de redis nos ha tardado una media de unos 37 milisegundos, mientras que obtener los valores de Dynamics nos ha tardado alrededor de 240 cada uno.

De hecho, podríamos pensar que obtener desde el Retrieve sería más rápido que un RetrieveMultiple, pero como vemos, no es así si la cantidad de datos no es muy alta en mi caso. Si se tiene una base de datos con muchísimos más registros de los que yo tengo, podéis hacer una comparación y ver si ese resultado os varía. También dependerá de todos los Plugins y/o procesos backend que tengáis en esa entidad.

Posibles preguntas

Pero Victor…¿es posible que podamos ponerlo con Plugins? ¿O necesitamos hacer un ILMerge?

La respuesta es: Se puede hacer sin ILMerge ya que ahora se pueden meter DLLs dependientes y está en GA. Esto está escrito en esta documentación https://learn.microsoft.com/en-us/power-apps/developer/data-platform/build-and-package#dependent-assemblies

Posibles OJO; Cuidado!

Cuando se usa Redis, estáis utilizando Azure, eso conlleva un símbolo \$\$\$. Tened cuidado y revisad bien en https://azure.microsoft.com/es-es/pricing/calculator/ el cálculo de lo que os podria salir lo que elijáis.

Redis al fin y al cabo funciona como una Base de datos que está operativa X horas.

Ahora por ejemplo, en España, a día de hoy, me dice que el costo mensual de la instancia C0: 250 MB de Caché me sale a 0.022$ la hora, es decir, 16.06$ al mes.

Si vais a usarlo en algún proyecto siempre tenedlo en cuenta para vuestras estimaciones.

Conclusión

Bien, esta ha sido una pequeña prueba de que se puede construir algo rápido y sencillo con Redis y podemos aplicarlo a Plugins.

De esta manera podemos optimizar los tiempos de respuesta de nuestros desarrollos más complejos pudiendo usar la funcionalidad de Azure.

Y como siempre, cualquier duda que podáis tener me la podéis mandar a me@victorsolaya.com


Únete a la newsletter