Redis Cache vs Retrieve and Retrieve Multiple

Redis Cache

Azure Redis Cache is a fully managed caching service based on Redis, a popular open source in-memory database. Offered by Microsoft Azure, this service is designed to accelerate web and mobile applications by allowing them to store and retrieve data ultra-fast, which in turn reduces the load on databases such as Dataverse in our case, and significantly improves performance.

What we are going to try to demonstrate here is, first, how a database is created in Redis and how we can store information. On the other hand, how we can create items in this Redis database and then we’ll provide some information so that you can see the Benchmark between retrieving a data in Redis and retrieving a data from Dataverse with .NET.

Create Azure Cache for Redis

We’ll go inside Azure and create a resource pool, I always like to create my container where I’m going to host my resources, instead of creating it directly from the tool to use.

Create resource group

Once the resource group is ready, we will create our Azure Cache for Redis.

Create Azure Cache for Redis](/assets/redis/create-azure-redis.png) Create Azure Cache for Redis 2](/assets/redis/create-azure-redis-2.png)

When our Azure Cache for Redis is created, we will have to go to Access Keys and there we will take the connection string.

Redis code

One of the most frustrating things about Redis is that from Azure we can’t visualise the values as such that we have stored in our database. For this reason, there are quite a few tools that allow us to visualise this data in a quick way. The one I use is within VSCode, an extension called Azure Caché.

This tool allows me to visualise in a very comfortable way all the databases that I have inside Redis.

In the following image we can see how each of the elements would be displayed in our Redis database.

VS Code Redis](/assets/redis/vscode_redis.png)

Code connection to Redis

Now, in order to make the connections to Redis I use the following code

// First I create a create connection class.
///This can be put inside a singleton if we have an 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();
}

In the following code what we are going to do is to get the value of Redis, and if by any chance it doesn’t exist, what we do is to set that value. The first function is a constructor to my main function to create the connection with the previous functionality.

//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)
    {
        //In this line we say if the value of Redis is null do me a Set
        databaseRedis.StringSet(key, "1.12");
    }
    return decimal.Parse(redisValueString);
}

Explanation of use case

In the use case I have proposed, what we are going to do is to compare the Benchmarking between:

  1. Redis cache
  2. RetrieveMultiple with top 1 with FetchExpression
  3. RetrieveMultiple with top 1 with QueryExpression
  4. Retrieve with static Guid

In order to realise the above functionalities the c# functions we have used are the following.

//GetValueFromRedis
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);
}

//GetValueFromD365 via 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;
}


// Get value from D365 via 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;
}

// Get the D365 value from a Retrieve with a static GUID.
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

In order to perform the benchmark, a Nuget package called BenchmarkDotNet has been used.

This package allows us to check the time performance of each of our methods, and also how many KB of memory are allocated with our methods. This way we can make our methods much better, faster and more efficient.

The result of the Benchmarking may surprise you, this was the result:

Benchmarking Results

As you can see, obtaining the redis value took us an average of 37 milliseconds, while obtaining the Dynamics values took us around 240 milliseconds each.

In fact, we might think that retrieving from Retrieve would be faster than RetrieveMultiple, but as we can see, this is not the case if the amount of data is not very high in my case. If you have a database with many more records than I have, you can make a comparison and see if this result varies. It will also depend on all the Plugins and/or backend processes you have in that entity.

Possible questions

But Victor… is it possible that we can do it with Plugins, or do we need to make an ILMerge?

The answer is: It can be done without ILMerge because now you can put dependent DLLs and it is in GA. This is written in this documentation https://learn.microsoft.com/en-us/power-apps/developer/data-platform/build-and-package#dependent-assemblies

Possible CAUTION; Beware!

When you use Redis, you are using Azure, that means a symbol. Be careful and check well in https://azure.microsoft.com/es-es/pricing/calculator/ the calculation of what you could get what you choose.

Redis, at the end of the day, works as a database that is operative for X hours.

Now for example, in Spain, as of today, it tells me that the monthly cost of the instance C0: 250 MB of Caché is 0.022$ per hour, that is to say, 16.06$ per month.

If you are going to use it in any project always take it into account for your estimates.

Conclusion

Well, this has been a small proof that we can build something fast and simple with Redis and we can apply it to Plugins.

This way we can optimise the response times of our more complex developments using Azure functionality.

And as always, if you have any doubt, you can send me an email to me@victorsolaya.com


Join the newsletter