Implementing Caching Strategies
Add effective caching to improve application performance.
By EMEPublished: February 20, 2025
cachingredisdistributed cachememoization
A Simple Analogy
Caching is like keeping frequently-used items within reach. Don't go to the warehouse every time; grab from nearby.
Why Cache?
- Performance: Avoid expensive operations
- Scalability: Reduce database load
- UX: Faster response times
- Cost: Cheaper compute resources
- Reliability: Handle traffic spikes
In-Memory Cache
public class ProductService
{
private readonly IMemoryCache _cache;
private readonly IProductRepository _repository;
public async Task<Product> GetProductAsync(int id)
{
var cacheKey = $"product-{id}";
if (_cache.TryGetValue(cacheKey, out var cached))
return cached as Product;
var product = await _repository.GetAsync(id);
var options = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromHours(1))
.SetSlidingExpiration(TimeSpan.FromMinutes(20));
_cache.Set(cacheKey, product, options);
return product;
}
}
Distributed Cache (Redis)
public class ProductService
{
private readonly IDistributedCache _cache;
private readonly IProductRepository _repository;
public async Task<Product> GetProductAsync(int id)
{
var cacheKey = $"product-{id}";
var cached = await _cache.GetStringAsync(cacheKey);
if (cached != null)
return JsonSerializer.Deserialize<Product>(cached);
var product = await _repository.GetAsync(id);
await _cache.SetStringAsync(
cacheKey,
JsonSerializer.Serialize(product),
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
}
);
return product;
}
}
Cache Invalidation
public async Task UpdateProductAsync(int id, UpdateProductRequest request)
{
var product = new Product { Id = id, Name = request.Name };
await _repository.UpdateAsync(product);
// Invalidate cache
await _cache.RemoveAsync($"product-{id}");
await _cache.RemoveAsync("products-list");
}
HTTP Caching Headers
[HttpGet("{id}")]
public async Task<IActionResult> GetProduct(int id)
{
var product = await _service.GetProductAsync(id);
// Cache in browser for 1 hour
Response.Headers.CacheControl = "public, max-age=3600";
Response.Headers.ETag = $"\"{product.UpdatedAt.GetHashCode()}\"";
return Ok(product);
}
Best Practices
- TTL: Set reasonable expiration times
- Invalidation: Remove when data changes
- Keys: Use consistent naming
- Size: Don't cache too much
- Monitoring: Track hit rates
Related Concepts
- Cache-aside pattern
- Write-through cache
- Cache warming
- Cache stampede
Summary
Implement multi-level caching with in-memory and distributed caches. Use appropriate TTLs and invalidation strategies.