VapeCache is a Redis-protocol-first caching runtime for ASP.NET Core and .NET services. It is designed for predictable behavior under load, Redis trouble, and high-throughput API traffic.
-
Website:
https://vapecache.net -
Source:
https://github.com/haxxornulled/VapeCache -
Redis/KeyDB-compatible transport tuned for cache workloads
-
Circuit-breaker and in-memory fallback for outage tolerance
-
Stampede controls for hot keys
-
OpenTelemetry metrics + traces
-
ASP.NET Core and Aspire integrations
When you register the native runtime with AddVapecacheCaching(), VapeCache is not just acting like a thin Redis wrapper.
The hybrid runtime is a production cache execution model with Redis as the primary backend and node-local memory as the failover path.
Native hybrid behavior includes:
- typed cache-aside APIs through
IVapeCache - named cache scopes with logical clear boundaries
- Redis-primary reads and writes with in-memory fallback routing during outages
- circuit-breaker state and manual failover controls for drills and incident handling
- stampede protection on hot keys
- write mirroring and read warming so fallback memory stays useful when Redis is healthy
- tag and zone version invalidation
- ASP.NET Core output-cache storage integration
- OpenTelemetry metrics and tracing for the runtime path
If you want the detailed contract, see docs/HYBRID_CACHING_API_SURFACE.md.
VapeCache overlaps with FusionCache in important places, but the products are not trying to be the exact same thing.
- FusionCache is focused on application-facing cache orchestration.
- VapeCache is focused on a Redis-first runtime with cache APIs, transport control, failover behavior, and framework integrations.
- If you want FusionCache-style L2 interoperability, VapeCache ships an
IDistributedCache/IBufferDistributedCachebridge for that migration path. - If you want the full VapeCache model, use the native runtime APIs instead of stopping at the distributed-cache bridge.
The practical message is: VapeCache is not "just another FusionCache". It is a Redis-first runtime choice for teams that care about the whole runtime path (transport, failover, observability, and framework integration), not only the call-site cache abstraction.
See docs/FUSIONCACHE_POSITIONING.md.
The production VapeCache runtime packages in this repo do not depend on StackExchange.Redis.
VapeCache.Runtime ships its own Redis transport/runtime implementation and composes with Microsoft.Extensions.*, Polly, and VapeCache packages.
StackExchange.Redis does appear in benchmark and test surfaces in this repository for interoperability validation.
That distinction matters: comparison and validation tooling may depend on it, the runtime does not.
Performance data and methodology are tracked in-repo with reproducible benchmark artifacts and disclosure rules.
- Benchmark methodology: docs/BENCHMARKING.md
- Claims policy: docs/BENCHMARK_CLAIMS_POLICY.md
- Latest benchmark reports: docs/BENCHMARK_RESULTS.md
OSS scope in this repository: production-ready runtime packages for core caching, invalidation, ASP.NET Core integration, and Aspire integration. For OSS/Enterprise boundaries, see docs/OSS_VS_ENTERPRISE.md.
Keep Redis versioning boring:
- The Redis version line used by the Aspire host is owned by
Aspire.Hosting.Redis. - The Aspire package version is owned in
Directory.Build.propsviaVapeCacheAspireVersion. - Manual Docker examples and test scripts should follow the same Redis major/minor line that Aspire defaults to.
- Use exact patch pinning only as a deliberate temporary freeze, not as the normal repo default.
Current project status: Production-Capable.
- Production runtime features are in place, including failover paths, stampede controls, reconnect handling, and observability.
- Stability, compatibility, and release gates are documented and enforced in-repo.
- Benchmark claims follow strict disclosure rules in docs/BENCHMARK_CLAIMS_POLICY.md.
- Release and compatibility governance is documented in:
- Install packages
dotnet add package VapeCache.Runtime
dotnet add package VapeCache.Extensions.AspireIf you need ASP.NET Core output-cache middleware integration:
dotnet add package VapeCache.Extensions.AspNetCoreIf you want a DI composition facade for clean architecture wiring:
dotnet add package VapeCache.Extensions.DependencyInjectionIf you want an explicit KeyDB package boundary and default KeyDbConnection section binding:
dotnet add package VapeCache.Extensions.KeyDBIf you need an IDistributedCache / IBufferDistributedCache bridge for interoperability or migration:
dotnet add package VapeCache.Extensions.DistributedCacheIf you want centralized Serilog + OTEL logging wiring with rolling file sink support and optional JSON formatting:
dotnet add package VapeCache.Extensions.LoggingIf you need Redis pub/sub support:
dotnet add package VapeCache.Extensions.PubSubIf you need Redis 8.6 stream idempotent producer support:
dotnet add package VapeCache.Extensions.StreamsIf you need HASH-backed RediSearch projections for operational lookup/search workloads:
dotnet add package VapeCache.Features.SearchIf you need EF Core second-level cache interceptor contracts and invalidation bridge wiring:
dotnet add package VapeCache.Extensions.EntityFrameworkCoreIf you need EF Core cache OpenTelemetry signals (Aspire/OTEL ready):
dotnet add package VapeCache.Extensions.EntityFrameworkCore.OpenTelemetry- Run Redis (or KeyDB)
docker run --name vapecache-redis -p 6379:6379 -d redis:8.6Manual Docker examples intentionally track the same Redis 8.6 line that the Aspire host gets from Aspire.Hosting.Redis.
If we move supported Redis forward, update the Aspire package first and keep the manual examples on that same line.
KeyDB local alternative:
docker run --name vapecache-keydb -p 6379:6379 -d eqalpha/keydb:latestIf you do not have Redis and want a local/lightweight runtime, skip Redis and use AddVapeCacheInMemory(...) instead.
- Configure
appsettings.json
{
"RedisConnection": {
"Host": "localhost",
"Port": 6379,
"Database": 0
}
}- Register VapeCache in
Program.cs
using VapeCache.Abstractions.Caching;
using VapeCache.Abstractions.Connections;
using VapeCache.Extensions.Logging;
using VapeCache.Extensions.PubSub;
using VapeCache.Extensions.Streams;
using VapeCache.Infrastructure.Caching;
using VapeCache.Infrastructure.Connections;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOptions<RedisConnectionOptions>()
.Bind(builder.Configuration.GetSection("RedisConnection"));
builder.Services.AddVapecacheRedisConnections();
builder.Services.AddVapecacheCaching();
builder.Services.AddVapeCachePubSub(); // optional: only when pub/sub is needed
builder.Services.AddVapeCacheStreams(); // optional: only when stream idempotent producer support is needed
builder.Services.AddOptions<CacheStampedeOptions>()
.UseCacheStampedeProfile(CacheStampedeProfile.Balanced)
.Bind(builder.Configuration.GetSection("CacheStampede"));Memory-only alternative for local dev or lightweight single-node hosts:
using VapeCache.Abstractions.Caching;
using VapeCache.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddVapeCacheInMemory(builder.Configuration)
.WithCacheStampedeProfile(CacheStampedeProfile.Balanced);- Add one endpoint
var app = builder.Build();
app.MapGet("/products/{id:int}", async (int id, IVapeCache cache, CancellationToken ct) =>
{
var key = CacheKey<string>.From($"products:{id}");
var value = await cache.GetOrCreateAsync(
key,
_ => new ValueTask<string>($"product-{id}"),
new CacheEntryOptions(TimeSpan.FromMinutes(5)),
ct);
return Results.Ok(value);
});
app.Run();Named cache scopes are also available:
var catalogCache = app.Services.GetRequiredService<IVapeCache>().NamedCache("catalog");
await catalogCache.ClearAsync();- Go deeper: docs/QUICKSTART.md
VapeCache now supports native ASP.NET Core output-cache ergonomics for both minimal APIs and MVC while keeping the runtime engine untouched.
builder.Services.AddVapeCacheOutputCaching();
builder.Services.AddVapeCacheAspNetPolicies(policies =>
{
policies.AddPolicy("products", policy => policy
.Ttl(TimeSpan.FromMinutes(5))
.VaryByQuery()
.Tags("products"));
});
app.MapGet("/products/{id:int}", (int id) => Results.Ok(new { id }))
.CacheWithVapeCache("products");MVC/controller attributes are also supported:
[VapeCachePolicy("products", TtlSeconds = 300, VaryByQuery = true, CacheTags = new[] { "products" })]
public IActionResult GetProduct(int id) => Ok(new { id });See:
- docs/ASPNETCORE_POLICY_EXTENSION.md
- docs/ASPNETCORE_PIPELINE_CACHING.md
- docs/HYBRID_CACHING_API_SURFACE.md
Already on IDistributedCache or using FusionCache with a distributed L2?
VapeCache ships a bridge package for that migration path.
using VapeCache.Extensions.DistributedCache;
builder.Services.AddVapeCache(builder.Configuration)
.UseDistributedCacheAdapter(options =>
{
options.KeyPrefix = "fusion:l2:";
});This is intentionally a compatibility layer, not the preferred headline integration. Native VapeCache remains the recommended path when you want the full runtime surface. Recommended framing: keep your current cache abstraction, route the distributed-cache layer through VapeCache, and migrate to native APIs later if you want the fuller runtime model. See docs/DISTRIBUTED_CACHE_BRIDGE.md for the interop positioning and FusionCache guidance.
VapeCache can also back Microsoft.Extensions.Caching.Hybrid.HybridCache.
using VapeCache.Extensions.DependencyInjection;
builder.Services.AddVapeCache(builder.Configuration)
.AddMicrosoftHybridCache(options =>
{
options.KeyPrefix = "ms-hc:";
});This gives you the Microsoft HybridCache API over the native VapeCache runtime, including tag invalidation and logical clear-all via RemoveByTagAsync("*").
See docs/MICROSOFT_HYBRIDCACHE.md.
IVapeCache supports named cache scopes and logical clear operations.
var root = app.Services.GetRequiredService<IVapeCache>();
var products = root.NamedCache("products");
await products.SetAsync(CacheKey<string>.From("42"), "espresso");
await products.ClearAsync();Named caches:
- prefix keys for that scope
- attach a reserved scope tag
- support logical clear via tag invalidation instead of backend scans
For workloads like grocery receipt verification, the recommended plan is to search denormalized HASH projections, not your full source aggregates.
VapeCache.Features.Search gives you:
- typed
TEXT,TAG, andNUMERICRediSearch schemas - generic HASH projection storage
- query-builder helpers for exact-match, text, and numeric range filters
- search-result cache key/tag conventions that fit
VapeCache.Features.Invalidation
That lets the front-door receipt check invalidate instantly without flattening the rest of the runtime behind a generic search abstraction. See docs/REDIS_SEARCH.md.
| Package | NuGet | GitHub Packages | Purpose | Docs |
|---|---|---|---|---|
VapeCache.Runtime |
VapeCache.Runtime | vapecache.runtime | Core runtime, Redis transport, fallback behavior, telemetry | API Reference |
VapeCache.Core |
VapeCache.Core | vapecache.core | Shared primitives package (transitive dependency, usually not installed directly) | Package Matrix |
VapeCache.Abstractions |
VapeCache.Abstractions | vapecache.abstractions | Public contracts and option/value types | API Reference |
VapeCache.Features.Invalidation |
VapeCache.Features.Invalidation | vapecache.features.invalidation | Optional key/tag/zone invalidation policies | Cache Invalidation |
VapeCache.Features.Search |
VapeCache.Features.Search | vapecache.features.search | Typed HASH-backed RediSearch projections, query helpers, and invalidation conventions for operational search | Redis Search |
VapeCache.Extensions.DependencyInjection |
VapeCache.Extensions.DependencyInjection | vapecache.extensions.dependencyinjection | One-call IServiceCollection wiring facade for runtime + config binding | Quickstart |
VapeCache.Extensions.KeyDB |
VapeCache.Extensions.KeyDB | vapecache.extensions.keydb | Explicit KeyDB registration facade with default KeyDbConnection binding |
Package README |
VapeCache.Extensions.DistributedCache |
VapeCache.Extensions.DistributedCache | vapecache.extensions.distributedcache | IDistributedCache / IBufferDistributedCache bridge for interoperability and migration |
Package README |
VapeCache.Extensions.Logging |
VapeCache.Extensions.Logging | vapecache.extensions.logging | Optional Serilog + OTEL logging wiring with file/Seq/console sinks and pluggable JSON formatting | Logging + Telemetry |
VapeCache.Extensions.PubSub |
VapeCache.Extensions.PubSub | vapecache.extensions.pubsub | Optional Redis pub/sub package (publish/subscribe, bounded queues, reconnect/resubscribe) | API Reference |
VapeCache.Extensions.Streams |
VapeCache.Extensions.Streams | vapecache.extensions.streams | Optional Redis 8.6 streams package for idempotent producers (XADD IDMP/IDMPAUTO, XCFGSET) |
Package README |
VapeCache.Extensions.EntityFrameworkCore |
VapeCache.Extensions.EntityFrameworkCore | vapecache.extensions.entityframeworkcore | EF Core second-level cache interceptor contracts, deterministic query-key builder, and SaveChanges invalidation bridge wiring | EF Core Second-Level Cache |
VapeCache.Extensions.EntityFrameworkCore.OpenTelemetry |
VapeCache.Extensions.EntityFrameworkCore.OpenTelemetry | vapecache.extensions.entityframeworkcore.opentelemetry | OpenTelemetry metrics/activity package for EF Core cache interceptor events and profiler correlation | EF Core package README |
VapeCache.Extensions.AspNetCore |
VapeCache.Extensions.AspNetCore | vapecache.extensions.aspnetcore | ASP.NET Core output-cache integration | ASP.NET Core Pipeline |
VapeCache.Extensions.Aspire |
VapeCache.Extensions.Aspire | vapecache.extensions.aspire | Aspire wiring, health checks, endpoint helpers | Aspire Integration |
Full package install matrix: docs/NUGET_PACKAGES.md
GitHub profile/About/topic/social branding: docs/GITHUB_BRANDING.md
Public website and doc home: https://vapecache.net
The following are not shipped from this OSS repository:
- adaptive autoscaling of multiplexed lanes
- enterprise licensing and control-plane features
- durable spill persistence package
- reconciliation package for post-outage write replay
Multiplexing itself is OSS; adaptive autoscaling is Enterprise.
- Start here: docs/INDEX.md
- Getting started: docs/QUICKSTART.md, docs/CONFIGURATION.md, docs/SETTINGS_REFERENCE.md, docs/NUGET_PACKAGES.md, docs/DISTRIBUTED_CACHE_BRIDGE.md, docs/FUSIONCACHE_POSITIONING.md, docs/GITHUB_BRANDING.md
- FusionCache comparison: docs/FUSIONCACHE_GAP_ANALYSIS.md
- Microsoft HybridCache: docs/MICROSOFT_HYBRIDCACHE.md
- Core runtime: docs/API_REFERENCE.md, docs/CACHE_INVALIDATION.md, docs/CACHE_TAGS_AND_ZONES.md
- ASP.NET Core: docs/ASPNETCORE_PIPELINE_CACHING.md, docs/ASPNETCORE_POLICY_EXTENSION.md
- Integrations: docs/ASPIRE_INTEGRATION.md, docs/LOGGING_TELEMETRY_CONFIGURATION.md
- Demo strategy: docs/DEMO_HOST_BLUEPRINT.md, docs/TRANSPORT_MUX_AUTOSCALER_DEEP_DIVE.md
- Ops and releases: docs/PRODUCTION_GUARDRAILS.md, docs/STABILITY_POLICY.md, docs/PRODUCTION_READINESS.md, docs/RELEASE_RUNBOOK.md
- Process model: docs/PROCESS_MODEL.md
- OSS and licensing: docs/OSS_VS_ENTERPRISE.md, docs/LICENSE_FAQ.md
dotnet build VapeCache.slnx -c Release
dotnet test VapeCache.Tests/VapeCache.Tests.csproj -c ReleaseVapeCache OSS is licensed under the MIT License.
That means you can use, modify, redistribute, and commercialize the code with very few conditions. The main obligations are to keep the copyright notice and license text with substantial portions of the software.
Important boundary:
- the code is MIT-licensed
- the
VapeCachename, logos, package identity, and brand assets are not granted to you under MIT
See LICENSE for the license text, docs/LICENSE_FAQ.md for quick answers, and docs/TRADEMARK_POLICY.md for brand and naming rules.
