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.
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:
- Redis cache
- RetrieveMultiple with top 1 with FetchExpression
- RetrieveMultiple with top 1 with QueryExpression
- 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:
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