HybridCache in ASP.NET Core

HybridCache in ASP.NET Core

  • avatar
    Name
    Meysam Hadeli
  • Caching is a powerful way to make your web applications faster and more efficient. In ASP.NET Core, HybridCache is a modern caching solution that combines the speed of in-memory caching with the consistency of distributed caching. This article will walk you through what HybridCache is, how to set it up, and how to use its key features to build a modern caching system for your applications.

    What is HybridCache?

    HybridCache is a caching library in ASP.NET Core that merges two types of caching:

    1. In-Memory Cache: This is super fast because it stores data directly in your application’s memory.
    2. Distributed Cache: This is slower but ensures consistency across multiple servers. Examples include Redis or SQL Server.

    Here’s how it works:

    • When you request data, HybridCache first checks the in-memory cache (primary cache). If the data is there, it returns it immediately.
    • If the data isn’t in the in-memory cache (primary cache), it checks the distributed cache (secondary cache). If found, it returns the data and also stores it in the in-memory cache for faster access next time.
    • If the data isn’t in either cache, HybridCache fetches it from the original source (like a database), stores it in both caches, and then returns it.

    This combination gives you the best of both worlds: speed and consistency.

    Using HybridCache in Practice

    To use HybridCache in your ASP.NET Core app, follow these steps:

    1. Install the Microsoft.Extensions.Caching.Hybrid package using the .NET CLI or NuGet Package Manager.
    2. Register HybridCache with AddHybridCache in your Program.cs file:
    var builder = WebApplication.CreateBuilder(args);
    
    // Register HybridCache with custom options
    builder.Services.AddHybridCache(options =>
    {
        options.DefaultEntryOptions = new HybridCacheEntryOptions
        {
            Expiration = TimeSpan.FromMinutes(5), // Default expiration for both caches
            LocalCacheExpiration = TimeSpan.FromMinutes(2) // Expiration for in-memory cache only
        };
    });
    
    // Register Redis as the distributed cache (optional)
    builder.Services.AddStackExchangeRedisCache(options =>
    {
        options.Configuration = builder.Configuration.GetConnectionString("Redis");
    });
    
    // Register the UserService
    builder.Services.AddScoped<UserService>();
    builder.Services.AddScoped<ProductService>();
    
    // Add Swagger
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new() { Title = "HybridCache Demo API", Version = "v1" }); });
    
    var app = builder.Build();
    
    // Enable Swagger UI in development environment
    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "HybridCache Demo API v1"); });
    }
    
    // Map endpoints
    app.MapEndpoints();
    
    app.Run();
    

    Note: If you don’t configure a distributed cache, HybridCache will only use in-memory caching.

    Let’s say you have a service that fetches user information from a database. Instead of hitting the database every time, you can use HybridCache to store the results. Here’s an example:

    public class UserService(HybridCache cache)
    {
        public async Task<string> GetUserInfoAsync(string userId, CancellationToken token = default)
        {
            return await cache.GetOrCreateAsync(
                $"user_{userId}", // Cache key
                async cancel =>
                {
                    // Simulate a slow database call
                    Console.WriteLine($"Fetching user info for {userId} from the database...");
                    await Task.Delay(2000, cancel); // Simulate a 2-second delay
                    return $"User info for {userId}";
                },
                new HybridCacheEntryOptions { Expiration = TimeSpan.FromMinutes(5) }, // Cache for 5 minutes
                cancellationToken: token
            );
        }
    }
    

    In this example:

    1. The GetOrCreateAsync method checks the cache for the data.
    2. If the data isn’t found, It fetches from the database and stores it in both caches, primary and secondary caches.
    3. The next time the data is requested, it comes from the cache instead of the database.

    Key Features of HybridCache

    Tags for Grouping Cache Entries

    Tags allow you to group related cache entries together. This is useful when you want to remove multiple cache entries at once. For example, if you have a list of products in a category, you can tag them with the category name and remove all products in that category when the category changes.

    public class ProductService(HybridCache cache)
    {
        // Example of using tags
        public async Task<string> GetProductInfoAsync(string productId, CancellationToken token = default)
        {
            var tags = new List<string> { "category_1" };
            return await cache.GetOrCreateAsync(
                $"product_{productId}", // Cache key
                async cancel =>
                {
                    // Simulate a slow database call
                    Console.WriteLine($"Fetching product info for {productId} from the database...");
                    await Task.Delay(2000, cancel); // Simulate a 2-second delay
                    return $"Product info for {productId}";
                },
                new HybridCacheEntryOptions { Expiration = TimeSpan.FromMinutes(5) },
                tags: tags,
                cancellationToken: token
            );
        }
    }
    

    Removing Cache Entries with RemoveAsync

    If you want to remove a specific cache entry manually, you can use the RemoveAsync method:

    await _cache.RemoveAsync("user_123");
    

    This removes the cache entry with the key user_123 from both the in-memory and distributed caches.

    Setting Cache Entries with SetAsync

    If you already have the data and want to store it in the cache, you can use the SetAsync method:

    await _cache.SetAsync("user_123", "User info for 123");
    

    This stores the value User info for 123 in the cache with the key user_123.

    Configuring HybridCache Options

    You can configure global caching options when registering HybridCache. For example, you can set default expiration times for cache entries:

    builder.Services.AddHybridCache(options =>
    {
        options.DefaultEntryOptions = new HybridCacheEntryOptions
        {
            Expiration = TimeSpan.FromMinutes(5), // Default expiration time for both caches
            LocalCacheExpiration = TimeSpan.FromMinutes(2) // Expiration time for in-memory cache only
        };
    });
    

    In this example:

    • With Expiration option, The cache entry will expire from both the distributed cache and the in-memory cache after 5 minutes.
    • With LocalCacheExpiration option, The cache entry will expire from the in-memory cache after 2 minutes.

    Stampede Protection

    HybridCache also provides stampede protection. This prevents multiple requests from overwhelming your database when a popular piece of data expires in the cache. Only one request fetches the data, while the others wait for the result.

    app.MapGet("/test-stampede", async (UserService userService) =>
    {
        var userId = "123";
        var tasks = new List<Task<string>>();
    
        // Simulate 10 concurrent requests
        for (int i = 0; i < 10; i++)
        {
            tasks.Add(userService.GetUserInfoAsync(userId));
        }
    
        // Wait for all tasks to complete
        var results = await Task.WhenAll(tasks);
    
        return Results.Ok(results);
    });
    

    When you run this, only one request will fetch the data from the database, while the other nine requests will wait for the result.

    You can find the sample code in this repository:

    🔗 https://github.com/meysamhadeli/blog-samples/tree/main/src/hybrid-cache-sample

    Conclusion

    HybridCache is a powerful tool for improving the performance of your ASP.NET Core applications. It combines the speed of in-memory caching with the consistency of distributed caching and offers advanced features to manage your cache effectively. By following the examples in this article, you can start using HybridCache to build a modern and efficient caching system for your apps.

    If you found this guide helpful, feel free to share it with others. Happy coding! 🚀

    Reference

    https://learn.microsoft.com/en-us/aspnet/core/performance/caching/hybrid?view=aspnetcore-9.0