In-memory & Distributed (Redis) Caching in ASP.NET Core
In this post, I will demonstrate how to use in-memory caching and Redis based distributed caching in an ASP.NET Core Web API.
I will use the following tools & technologies:
- ASP.NET Core 3.1
- Visual Studio 2019
- Redis
Let’s start with the definition of caching.
Caching
A cache is a hardware or software component that stores data so that future requests for that data can be served faster; the data stored in a cache might be the result of an earlier computation or a copy of data stored elsewhere. A cache hit occurs when the requested data can be found in a cache, while a cache miss occurs when it cannot. Cache hits are served by reading data from the cache, which is faster than recomputing a result or reading from a slower data store; thus, the more requests that can be served from the cache, the faster the system performs.[1]
Caching can significantly improve the performance and scalability of an app by reducing the work required to generate content. Caching works best with data that changes infrequently and is expensive to generate. Caching makes a copy of data that can be returned much faster than from the source. Apps should be written and tested to never depend on cached data.[2]
Now, I will present the API in which we will use caching.
About the API
The API returns the movies of an actor/actress given as a parameter and looks like below:
It makes an external API call to the TMDb API and the controller code is shown below:
As you see, an external API call is made for every query. However, there will be cases like the same actor/actress is queried several times by the same or different users. So, we can increase the performance of this application by using caching mechanism instead of calling the external API again and again for the same actors/actresses.
First, let’s see how we can implement in-memory caching in this API.
In-memory Caching
ASP.NET Core supports several different caches. The simplest cache is based on the IMemoryCache.
IMemoryCache
represents a cache stored in the memory of the web server. Apps running on a server farm (multiple servers) should ensure sessions are sticky when using the in-memory cache. Sticky sessions ensure that subsequent requests from a client all go to the same server.[3]
In-memory caching is a service that’s referenced from an app using Dependency Injection. So, we first need to register this service to the built-in IoC container of ASP.NET Core by modifying ConfigureServices
method of Startup.cs
as below:
Then we inject IMemoryCache
to the constructor:
And we change GetMovieList
method as below:
As you see in the above code, we first check if the movie list exists in the cache and return from there if it does. Otherwise, we call the external API and cache the result.
Besides, we set the cache expiration options which are explained below:
SlidingExpiration:
Gets or sets how long a cache entry can be inactive (e.g. not accessed) before it will be removed. This will not extend the entry lifetime beyond the absolute expiration (if set).
AbsoluteExpiration:
Gets or sets an absolute expiration date for the cache entry.
A cached item set with a sliding expiration only is at risk of becoming stale. If it’s accessed more frequently than the sliding expiration interval, the item will never expire. Combine a sliding expiration with an absolute expiration to guarantee that the item expires once its absolute expiration time passes.[4]
In the next section, we will implement distributed caching for the same controller.
Distributed Caching
A distributed cache is a cache shared by multiple app servers, typically maintained as an external service to the app servers that access it. A distributed cache can improve the performance and scalability of an ASP.NET Core app, especially when the app is hosted by a cloud service or a server farm.
A distributed cache has several advantages over other caching scenarios where cached data is stored on individual app servers. When cached data is distributed, the data:
- Is coherent (consistent) across requests to multiple servers.
- Survives server restarts and app deployments.
- Doesn’t use local memory.[5]
There are two main disadvantages of the shared caching approach:
- The cache is slower to access because it is no longer held locally to each application instance.
- The requirement to implement a separate cache service might add complexity to the solution.[6]
We will use Redis-based distributed caching in our API. So let’s continue with what Redis is and how to install it.
Redis
The name Redis means REmote DIctionary Server.
Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams.[7]
Now, I will show how to install Redis in a Windows machine using Chocolatey. (If you don’t have Chocolatey you can install it from here.)
First, install the Chocolatey Redis package.
Then run redis-server
from a command prompt.
Now, the Redis server is up and running. We can test it using the redis-cli
command.
Open a new command prompt and run redis-cli
on it and try the following commands:
Now that we installed the Redis-server and saw that it is working properly, we can modify the API to use Redis-based distributed caching.
Modify the API
ASP.NET Core provides IDistributedCache interface to interact with the distributed caches including Redis.
Get
,GetAsync
: Accepts a string key and retrieves a cached item as abyte[]
array if found in the cache.Set
,SetAsync
: Adds an item (asbyte[]
array) to the cache using a string key.Refresh
,RefreshAsync
: Refreshes an item in the cache based on its key, resetting its sliding expiration timeout (if any).Remove
,RemoveAsync
: Removes a cache item based on its string key.
To use a Redis distributed cache, we need to add a package reference to Microsoft.Extensions.Caching.StackExchangeRedis package in our project.
Then we add the following line to ConfigureServices
method of the Startup.cs
for configuration the cache implementation using a RedisCache instance:
Our approach in modifying the controller will be very similar to what we did in the in-memory caching section.
First, we inject IDistributedCache
to the controller:
Then we change the GetMovieList
method as follows:
As mentioned above, GetAsync
and SetASync
methods work with byte[]
arrays, so I encoded the serialized movies list to byte[]
array. Also, I want to mention that there are extension methods where you do not need to convert the value to byte[]
array and these are GetStringAsync
and SetStringAsync.
You can find the source code in this GitHub repository.
That’s the end of the post. I hope you found this post helpful and easy to follow. If you have any questions and/or comments, you can share them in the responses section below.
And if you liked this post, please clap your hands 👏👏👏
Bye!