Introduction
As applications grow, repeatedly querying databases and external services can become a major performance bottleneck. High latency, increased infrastructure costs, and unnecessary load on backend systems often result from serving the same data repeatedly.
Distributed caching addresses this problem by storing frequently accessed data in a centralized, high-speed cache that can be shared across multiple application instances. Redis is the most widely adopted distributed cache for ASP.NET Core applications due to its speed, scalability, and rich feature set.
In this article, you'll learn how to implement distributed caching with Redis in ASP.NET Core 10, understand common caching patterns, and follow production-ready practices for building high-performance applications.
Why Use Distributed Caching?
Unlike in-memory caching, a distributed cache is shared across all application instances.
Benefits include:
Distributed caching is especially valuable in cloud-native and load-balanced environments.
In-Memory Cache vs Distributed Cache
| Feature | In-Memory Cache | Redis Distributed Cache |
|---|
| Shared Across Servers | No | Yes |
| Survives Application Restart | No | Yes |
| Suitable for Load Balancing | Limited | Yes |
| Scalability | Single Instance | Multiple Instances |
| Performance | Very Fast | Very Fast |
For enterprise applications running multiple instances, Redis is typically the preferred option.
Configuring Redis
Install the Redis cache package.
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
Register Redis during application startup.
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration =
builder.Configuration.GetConnectionString("Redis");
options.InstanceName = "ProductApi:";
});
This registers IDistributedCache, allowing the application to interact with Redis through a framework-provided abstraction.
Storing Cached Data
Inject IDistributedCache into your service and store serialized data.
public class ProductService
{
private readonly IDistributedCache _cache;
public ProductService(IDistributedCache cache)
{
_cache = cache;
}
public async Task CacheProductAsync(string key, string json)
{
await _cache.SetStringAsync(
key,
json,
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromMinutes(10)
});
}
}
Setting an expiration ensures stale data is automatically removed and prevents the cache from growing indefinitely.
Reading Cached Data
Before querying the database, check whether the requested data already exists in the cache.
var cachedProduct =
await _cache.GetStringAsync(cacheKey);
if (cachedProduct is not null)
{
return cachedProduct;
}
If the data isn't available, retrieve it from the database, return it to the client, and update the cache.
Common Caching Patterns
Choosing the right caching strategy depends on your application's requirements.
| Pattern | Description | Best For |
|---|
| Cache-Aside | Application loads and updates the cache manually | Most ASP.NET Core applications |
| Read-Through | Cache retrieves missing data automatically | Specialized caching layers |
| Write-Through | Updates cache and database together | Frequently updated data |
| Write-Behind | Cache writes changes asynchronously | High-throughput workloads |
The Cache-Aside pattern is the most commonly used approach in ASP.NET Core because it is simple, flexible, and easy to maintain.
Production Considerations
Dependency Injection
Always inject IDistributedCache or a dedicated caching service rather than interacting with Redis directly throughout your application.
Wrapping caching logic in a service:
Configuration
Store Redis configuration in appsettings.json.
{
"ConnectionStrings": {
"Redis": "localhost:6379"
}
}
In production, use environment variables or Azure Cache for Redis connection settings instead of hardcoding them.
Logging
Monitor cache behavior by logging:
Monitoring hit rates helps determine whether the cache is delivering meaningful performance improvements.
Error Handling
Your application should continue functioning even if Redis becomes temporarily unavailable.
Handle scenarios such as:
If the cache cannot be reached, retrieve the data directly from the primary data source rather than failing the request.
Security
Protect your cache infrastructure by:
Enabling TLS encryption.
Using authentication.
Restricting network access.
Rotating credentials regularly.
Avoiding storage of highly sensitive data unless encrypted.
Applying least-privilege access.
A compromised cache can expose valuable application data if it isn't properly secured.
Performance
Maximize Redis performance by:
Caching frequently requested data.
Keeping cached objects reasonably small.
Using sensible expiration policies.
Compressing large payloads when appropriate.
Avoiding unnecessary cache writes.
Monitoring memory usage and eviction rates.
Poor cache design can increase latency instead of reducing it.
Deployment
Redis works well with modern ASP.NET Core deployment options, including:
Azure Cache for Redis
Azure Container Apps
Docker
Kubernetes
Virtual Machines
Use a managed Redis service whenever possible to simplify maintenance, scaling, backups, and monitoring.
Cache Invalidation Strategies
One of the most challenging aspects of caching is keeping cached data synchronized with the underlying data source.
Common approaches include:
Choose an approach based on how frequently your data changes and how fresh it needs to remain.
Best Practices
Cache expensive database queries.
Use meaningful cache keys.
Set appropriate expiration times.
Monitor cache hit ratios.
Handle cache failures gracefully.
Keep cached objects small.
Centralize caching logic in dedicated services.
Common Mistakes
Avoid these common caching mistakes:
Caching everything indiscriminately.
Never expiring cached data.
Storing oversized objects.
Ignoring serialization costs.
Assuming cached data is always current.
Failing to invalidate stale entries after updates.
A well-designed cache should improve performance without compromising data consistency.
Troubleshooting
| Problem | Solution |
|---|
| Redis connection fails | Verify the connection string, authentication, firewall rules, and server availability. |
| Cache misses are too frequent | Review expiration settings and cache key generation. |
| Stale data is returned | Implement proper cache invalidation after data updates. |
| High memory usage | Reduce object size, configure eviction policies, and monitor cache growth. |
| Application slows during cache failures | Implement graceful fallback to the primary data source and retry transient failures. |
Conclusion
Distributed caching is one of the most effective techniques for improving the performance and scalability of ASP.NET Core applications. By using Redis as a centralized cache, applications can significantly reduce database load, improve response times, and support large-scale cloud deployments.
When combined with thoughtful cache invalidation, secure configuration, comprehensive monitoring, and resilient error handling, Redis becomes a powerful component of a production-ready ASP.NET Core 10 architecture that delivers fast, reliable experiences for users.