Redis  

Building Distributed Caching with Redis and ASP.NET Core 11

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

FeatureIMemoryCacheRedis
Shared across serversNoYes
DistributedNoYes
High availabilityNoYes
Persistent (optional)NoYes
Suitable for Web FarmsNoYes
PerformanceExcellentExcellent

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:

  • Absolute expiration

  • Sliding expiration

  • Combined expiration policies

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:

  1. Client requests a product.

  2. API checks Redis.

  3. If a cache hit occurs, the value is returned immediately.

  4. If a cache miss occurs, the database is queried.

  5. The result is stored in Redis.

  6. The response is returned to the client.

This minimizes unnecessary database access while improving response times.

Common Caching Patterns

PatternDescriptionBest For
Cache-AsideApplication manages cacheMost applications
Read-ThroughCache loads data automaticallySpecialized cache providers
Write-ThroughCache updated with databaseConsistent writes
Write-BehindDatabase updated asynchronouslyHigh-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

MistakeImpact
Never expiring cache entriesStale data
Caching highly volatile dataLow cache effectiveness
Using inconsistent key namesDifficult maintenance
Ignoring cache invalidationIncorrect responses
Storing oversized objectsIncreased memory usage
Treating Redis as a databasePoor architecture

Troubleshooting

Cache Misses Occur Frequently

Verify:

  • Cache expiration settings

  • Cache keys

  • Redis connectivity

  • Application restart behavior

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:

  • Connection string

  • Firewall configuration

  • Redis server availability

  • Network latency

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.