Caching is one of the most effective ways to improve application performance. Instead of repeatedly querying a database or calling external services, applications can retrieve frequently accessed data from a fast in-memory cache. This reduces response times, lowers database load, and improves scalability.
While the in-memory cache (IMemoryCache) works well for single-instance applications, it isn't suitable for load-balanced or distributed environments because each application instance maintains its own cache. Redis solves this problem by providing a centralized, high-performance distributed cache that can be shared across multiple application instances.
In this article, you'll learn how to implement distributed caching with Redis in ASP.NET Core 11, explore common caching patterns, and understand how to evaluate cache performance using a structured testing methodology.
Note: This article focuses on implementation and benchmark methodology. It does not include fabricated benchmark numbers.
Why Use Distributed Caching?
Without caching, every request may access the database.
Client Request
│
▼
ASP.NET Core API
│
▼
SQL Database
│
▼
Response
With Redis:
Client Request
│
▼
ASP.NET Core API
│
▼
Redis Cache
│ │
Hit Miss
│ │
▼ ▼
Response SQL Database
│
▼
Store in Redis
This significantly reduces database traffic for frequently requested data.
IMemoryCache vs Redis
| Feature | IMemoryCache | Redis |
|---|
| Shared across servers | No | Yes |
| Distributed | No | Yes |
| High availability | No | Yes |
| Persistent (optional) | No | Yes |
| Suitable for Web Farms | No | Yes |
| Performance | Excellent | Excellent |
For applications running on multiple servers or Kubernetes, Redis is generally the preferred choice.
Create the Project
dotnet new webapi -n RedisCachingDemo
Install the Redis caching package.
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
Configure Redis
Register Redis as the distributed cache provider.
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration =
"localhost:6379";
options.InstanceName = "DemoApp:";
});
Replace the connection string with your Redis server in production.
Inject the Cache
Inject IDistributedCache into your service.
public class ProductService
{
private readonly IDistributedCache _cache;
public ProductService(
IDistributedCache cache)
{
_cache = cache;
}
}
IDistributedCache provides a provider-independent API that works with Redis and other distributed cache implementations.
Store Data in the Cache
Serialize the object before storing it.
var json = JsonSerializer.Serialize(product);
await _cache.SetStringAsync(
$"product:{product.Id}",
json);
The cache key should be unique and descriptive.
Read Data from the Cache
var json = await _cache.GetStringAsync(
"product:1");
if (json is not null)
{
var product =
JsonSerializer.Deserialize<Product>(json);
}
If the value exists, the application avoids a database query.
Configure Expiration
Use expiration policies to prevent stale data.
await _cache.SetStringAsync(
"product:1",
json,
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromMinutes(30)
});
Common expiration options include:
Choose an expiration strategy based on how frequently the data changes.
Implement the Cache-Aside Pattern
The Cache-Aside pattern is the most common caching strategy.
public async Task<Product?> GetProductAsync(int id)
{
var key = $"product:{id}";
var cached =
await _cache.GetStringAsync(key);
if (cached is not null)
{
return JsonSerializer.Deserialize<Product>(
cached);
}
var product = await _repository.GetByIdAsync(id);
if (product is not null)
{
await _cache.SetStringAsync(
key,
JsonSerializer.Serialize(product));
}
return product;
}
The application checks Redis first. If the data is unavailable, it loads the data from the database and stores it in the cache.
Remove Cached Data
Whenever underlying data changes, invalidate the corresponding cache entry.
await _cache.RemoveAsync(
$"product:{product.Id}");
Cache invalidation helps ensure clients receive fresh data.
Cache Keys
Use consistent key naming conventions.
Examples:
product:10
customer:25
orders:active
category:laptops
Consistent key naming simplifies maintenance and troubleshooting.
End-to-End Request Flow
A typical request follows these steps:
Client requests a product.
API checks Redis.
If a cache hit occurs, the value is returned immediately.
If a cache miss occurs, the database is queried.
The result is stored in Redis.
The response is returned to the client.
This minimizes unnecessary database access while improving response times.
Common Caching Patterns
| Pattern | Description | Best For |
|---|
| Cache-Aside | Application manages cache | Most applications |
| Read-Through | Cache loads data automatically | Specialized cache providers |
| Write-Through | Cache updated with database | Consistent writes |
| Write-Behind | Database updated asynchronously | High-throughput workloads |
ASP.NET Core applications commonly use the Cache-Aside pattern.
Performance Evaluation Methodology
The research brief emphasizes performance improvements but does not include benchmark data. Use the following methodology to evaluate your implementation.
Test Environment
Keep these variables consistent:
.NET SDK version
Redis version
Database engine
Hardware
Network latency
Dataset size
Test Scenarios
Compare:
Database only
IMemoryCache
Redis distributed cache
Cache hit scenarios
Cache miss scenarios
Expired cache entries
Metrics to Measure
Collect:
Response time
Cache hit ratio
Cache miss ratio
Database queries
CPU utilization
Memory usage
Redis latency
Useful Tools
Useful tools include:
BenchmarkDotNet
Redis Insight
redis-cli
dotnet-counters
dotnet-trace
k6
SQL Server Query Store
Evaluate cache effectiveness using production-like workloads and realistic data sizes.
Best Practices
Cache frequently accessed, rarely changing data.
Use meaningful cache keys.
Configure appropriate expiration policies.
Invalidate cache entries after updates.
Monitor cache hit rates.
Avoid caching extremely large objects.
Compress large payloads when appropriate.
Protect against cache stampedes using locking or request coalescing techniques.
Common Mistakes
| Mistake | Impact |
|---|
| Never expiring cache entries | Stale data |
| Caching highly volatile data | Low cache effectiveness |
| Using inconsistent key names | Difficult maintenance |
| Ignoring cache invalidation | Incorrect responses |
| Storing oversized objects | Increased memory usage |
| Treating Redis as a database | Poor architecture |
Troubleshooting
Cache Misses Occur Frequently
Verify:
A low cache hit ratio often indicates ineffective caching or overly aggressive expiration.
Stale Data Is Returned
Review:
Cache invalidation logic
Expiration policies
Update workflows
Ensure cache entries are removed or refreshed whenever the underlying data changes.
Redis Connection Issues
Check:
Enable logging to identify intermittent connectivity issues.
FAQs
Why use Redis instead of IMemoryCache?
Redis provides a shared cache that works across multiple application instances, making it suitable for distributed and cloud-native deployments.
What is the Cache-Aside pattern?
The application first checks the cache. If the data is missing, it retrieves it from the database, stores it in the cache, and returns the result.
Should every database query be cached?
No. Cache data that is read frequently and changes infrequently. Highly dynamic data may not benefit from caching.
How long should cached data live?
The expiration period depends on how frequently the underlying data changes and how much stale data your application can tolerate.
Can Redis persist data?
Yes. Redis supports optional persistence mechanisms, although it is primarily designed as an in-memory data store.
Conclusion
Distributed caching with Redis is a proven way to improve the scalability and responsiveness of ASP.NET Core applications. By reducing database load and serving frequently requested data from memory, Redis helps applications handle higher traffic with lower latency.
Using patterns such as Cache-Aside, implementing sensible expiration policies, and monitoring cache performance ensures that your caching strategy remains effective as your application grows. Combined with realistic performance testing and ongoing monitoring, Redis becomes a valuable component of a production-ready .NET architecture.