Redis  

Redis Caching in ASP.NET Core: Strategies That Actually Improve Performance

Database queries, external API calls, and expensive computations can quickly become performance bottlenecks as application traffic grows. Even well-optimized SQL queries introduce latency when executed repeatedly for the same data.

Redis is a high-performance, in-memory data store commonly used as a distributed cache. When integrated with ASP.NET Core, Redis can significantly reduce response times, lower database load, and improve application scalability. However, caching the wrong data—or using an inappropriate caching strategy—can create stale data, unnecessary complexity, and difficult-to-debug consistency issues.

Rather than caching everything, this article explains practical Redis caching strategies, when to use them, and how to implement them effectively in ASP.NET Core.

Note: Caching improves performance only when cache hits significantly outnumber cache misses. Always measure cache effectiveness using metrics such as hit ratio and latency.

Why Use Redis Caching?

Every request that reaches the database consumes CPU, memory, network bandwidth, and I/O resources.

Caching frequently accessed data reduces repeated database queries and improves response times.

Typical benefits include:

  • Faster API responses

  • Reduced database load

  • Lower infrastructure costs

  • Improved scalability

  • Better user experience

  • Reduced latency for frequently accessed data

Not every piece of data should be cached. Frequently changing data may benefit more from direct database access.

What Should Be Cached?

Redis works best for data that is frequently read but changes infrequently.

Common candidates include:

  • Product catalogs

  • User profiles

  • Application configuration

  • Country and currency lists

  • Dashboard summaries

  • Frequently accessed reports

  • API responses

  • Session data

Avoid caching highly volatile or sensitive information unless there is a clear business requirement.

Caching Architecture

flowchart LR

A[Client]
B[ASP.NET Core API]
C{Cache Hit?}
D[(Redis)]
E[(SQL Server)]

A --> B
B --> C
C -->|Yes| D
C -->|No| E
E --> D
D --> B
B --> A

When cached data exists, Redis responds immediately. Otherwise, the application retrieves the data from the database, stores it in Redis, and returns the response.

Installing Redis

Install the Redis caching package.

dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis

This package integrates Redis with the ASP.NET Core dependency injection system.

Configuring Redis

Register Redis during application startup.

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration =
        "localhost:6379";

    options.InstanceName = "MyApp:";
});

In production, replace the local connection string with your managed Redis endpoint.

Reading from the Cache

Inject IDistributedCache into your service.

public class ProductService
{
    private readonly IDistributedCache _cache;

    public ProductService(
        IDistributedCache cache)
    {
        _cache = cache;
    }
}

IDistributedCache provides a consistent API regardless of the underlying cache provider.

Cache-Aside Pattern

The Cache-Aside pattern is the most common Redis strategy.

var cacheKey = $"product:{id}";

var cached =
    await _cache.GetStringAsync(cacheKey);

if (cached != null)
{
    return JsonSerializer.Deserialize<Product>(cached);
}

var product =
    await repository.GetByIdAsync(id);

await _cache.SetStringAsync(
    cacheKey,
    JsonSerializer.Serialize(product),
    new DistributedCacheEntryOptions
    {
        AbsoluteExpirationRelativeToNow =
            TimeSpan.FromMinutes(10)
    });

return product;

The application first checks Redis. If the data isn't found, it retrieves it from the database and stores it in the cache for future requests.

Setting Expiration Policies

Expiration prevents stale data from remaining in the cache indefinitely.

var options =
    new DistributedCacheEntryOptions
    {
        SlidingExpiration =
            TimeSpan.FromMinutes(15),

        AbsoluteExpirationRelativeToNow =
            TimeSpan.FromHours(1)
    };

Combining sliding and absolute expiration provides a balance between freshness and performance.

Cache Invalidation

Whenever cached data changes, invalidate the associated cache entry.

await repository.UpdateAsync(product);

await _cache.RemoveAsync(
    $"product:{product.Id}");

Failing to invalidate outdated entries is one of the most common causes of stale data.

Choosing an Expiration Strategy

Different data requires different expiration periods.

Data TypeSuggested Expiration
Product Catalog30–60 minutes
User Profile10–30 minutes
Dashboard Data5–15 minutes
ConfigurationSeveral hours
Reference Data24 hours or longer

Choose expiration values based on how frequently the underlying data changes.

Common Redis Data Structures

Redis supports multiple data types.

Data StructureCommon Use Case
StringCached objects
HashUser profiles
ListActivity feeds
SetUnique values
Sorted SetLeaderboards
StreamEvent processing

Selecting the appropriate data structure can improve both performance and memory efficiency.

Monitoring Cache Performance

Monitor these metrics regularly:

  • Cache hit ratio

  • Cache miss ratio

  • Average response time

  • Memory utilization

  • Key expiration rate

  • Network latency

  • Eviction count

A low cache hit ratio often indicates ineffective caching or overly aggressive expiration settings.

Common Production Mistakes

ProblemRoot Cause
Stale dataCache not invalidated after updates
Low cache hit ratioExpiration time too short
Excessive memory usageLarge objects cached unnecessarily
Cache stampedeMultiple requests rebuilding the same cache entry
Serialization errorsIncompatible object versions
Performance degradationCaching frequently changing data

Many caching issues originate from poor cache design rather than Redis itself.

Best Practices

  • Cache only frequently accessed data.

  • Use meaningful cache keys.

  • Configure appropriate expiration policies.

  • Invalidate cache entries after updates.

  • Monitor cache hit ratios continuously.

  • Keep cached objects reasonably small.

  • Protect against cache stampedes for expensive operations.

Common Anti-Patterns

Avoid these common mistakes:

  • Caching every database query.

  • Using extremely long expiration times for changing data.

  • Ignoring cache invalidation.

  • Storing very large objects in Redis.

  • Using identical expiration values for every cache entry.

  • Assuming cached data is always current.

FAQ

Should every ASP.NET Core application use Redis?

Not necessarily. Applications with low traffic or minimal repeated reads may not benefit enough to justify the additional infrastructure.

What is the Cache-Aside pattern?

Cache-Aside is a strategy where the application first checks the cache. If the requested data isn't found, it retrieves the data from the database, stores it in the cache, and returns the result.

Is Redis a database replacement?

No. Redis is typically used as a caching layer in front of a primary database rather than as a replacement for transactional data storage.

How long should cached data live?

It depends on how frequently the data changes. Frequently updated information should have shorter expiration times, while relatively static reference data can remain cached much longer.

Conclusion

Redis is one of the most effective ways to improve ASP.NET Core application performance when used appropriately. By reducing repetitive database queries and serving frequently accessed data directly from memory, Redis helps lower latency, reduce infrastructure load, and improve scalability.

Successful caching is not about storing as much data as possible. It requires selecting the right data, choosing appropriate expiration policies, implementing reliable cache invalidation, and continuously monitoring cache performance. When applied thoughtfully, Redis becomes a valuable component of a fast, scalable, and production-ready ASP.NET Core architecture.