Skip to content
bitzorcas
中EN

Reference

Multi-Level Caching and Stampede Protection with FusionCache

Explore the BitzOrcas.Modern caching architecture. Learn how FusionCache L1 in-memory and L2 Redis caching, CacheScope isolation, and tag invalidation prevent cache stampedes.

Last updated

In high-concurrency systems, naive caching strategies quickly lead to catastrophic outages:

  • Redis Network Bottlenecks: Every read makes an external network hop to Redis, saturating network interfaces;
  • Cache Stampedes: When hot keys expire, thousands of concurrent requests hammer the database at once, causing CPU spikes;
  • Cross-Tenant Data Leakage: Inconsistent key naming risks leaking Tenant A’s private data to Tenant B!

BitzOrcas.Modern solves this with FusionCache L1 (in-memory) + L2 (Redis) caching: Providing sub-millisecond local reads, single-flight stampede protection, and automated tenant key generation via ICacheKeyBuilder.

Multi-Level Caching Architecture

Microsecond readL1 MissL2 MissWrite back & broadcast

1. Application Handler

2. ICacheKeyBuilder (Injects Tenant & Env)

3. ICacheStore (Unified Gateway)

4. FusionCache L1 (Local In-Memory Cache)

5. Redis L2 (Distributed Shared Cache)

6. Database (Protected by Single-Flight Lock)

7. Redis Backplane Syncs Invalidation


Step 1: Building Tenant-Safe Keys with ICacheKeyBuilder

Manual string concatenation for cache keys is strictly forbidden. All keys are built via ICacheKeyBuilder, formatting {app}:{env}:v{version}:{area}:{scope}:{parts}:

Building Isolated Cache Keys
using BitzOrcas.Application.Abstractions.Caching;
public sealed class CatalogOfferingCacheService(
ICacheKeyBuilder cacheKeys,
ICacheStore cache)
{
public async Task<OfferingView?> GetOfferingAsync(string offeringId, CancellationToken ct)
{
// ① Explicitly assign tenant scope; tenant ID is injected from ICurrentUserAccessor
var key = cacheKeys.Build(
area: "catalog-offering",
scope: CacheScope.Tenant,
version: 1,
offeringId);
// ② Configure policy: 10m TTL, 20s negative TTL, 10% jitter to prevent thundering herd
var policy = new CachePolicy(TimeSpan.FromMinutes(10))
{
NegativeTtl = TimeSpan.FromSeconds(20),
JitterRatio = 0.10,
AreaTag = "catalog",
Tags = [$"offering:{offeringId}"]
};
// ③ Read-through with single-flight execution to eliminate stampedes
var result = await cache.GetOrCreateAsync(
key,
async token => await LoadFromDbAsync(offeringId, token),
policy,
ct);
return result.Value;
}
private Task<OfferingView?> LoadFromDbAsync(string id, CancellationToken ct) => Task.FromResult<OfferingView?>(null);
}

Step 2: Three-State Reads and Negative Caching

BitzOrcas handles Hit, Negative, and Miss states explicitly to prevent penetration on non-existent records:

Three-State Cache Flow
var cached = await cache.TryGetAsync<OfferingView>(key, cancellationToken);
if (cached.HasValue)
{
// 1. Cache hit with valid entity
return Result<OfferingView>.Success(cached.Value!);
}
if (cached.IsNegative)
{
// 2. Negative hit: entity is known to not exist, short-circuit 404
return Result<OfferingView>.Failure(OfferingErrors.NotFound);
}
// 3. True Miss: execute query against database
return await LoadFromDbAndPopulateAsync(offeringId, cancellationToken);

Step 3: Precise Post-Commit Tag Invalidation

Never invalidate cache before the database transaction commits. If the transaction rolls back, the cache would be erroneously purged:

UpdateOfferingCommandHandler.cs: Post-Commit Invalidation
public async ValueTask<Result> Handle(UpdateOfferingCommand command, CancellationToken ct)
{
// 1. Persist aggregate changes (TransactionPipeline auto-commits transaction)
var saveResult = await repository.SaveAsync(offering, ct);
if (saveResult.IsFailure) return saveResult;
// 2. Broadcast invalidation across instances using tag descriptors
await cache.RemoveByTagAsync($"offering:{command.Id}", ct);
return Result.Success();
}

Summary

FusionCache multi-level caching gives BitzOrcas speed and resilience:

  • L1 Microsecond Response: Sub-millisecond reads straight from process memory;
  • Single-Flight Lock: Prevents database stampedes during high-load cold starts;
  • Strict Tenant Safety: KeyBuilder enforces multi-tenant boundary compliance.

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%