DcsvIo.D2.Caching.Distributed.Redis
0.1.2
dotnet add package DcsvIo.D2.Caching.Distributed.Redis --version 0.1.2
NuGet\Install-Package DcsvIo.D2.Caching.Distributed.Redis -Version 0.1.2
<PackageReference Include="DcsvIo.D2.Caching.Distributed.Redis" Version="0.1.2" />
<PackageVersion Include="DcsvIo.D2.Caching.Distributed.Redis" Version="0.1.2" />
<PackageReference Include="DcsvIo.D2.Caching.Distributed.Redis" />
paket add DcsvIo.D2.Caching.Distributed.Redis --version 0.1.2
#r "nuget: DcsvIo.D2.Caching.Distributed.Redis, 0.1.2"
#:package DcsvIo.D2.Caching.Distributed.Redis@0.1.2
#addin nuget:?package=DcsvIo.D2.Caching.Distributed.Redis&version=0.1.2
#tool nuget:?package=DcsvIo.D2.Caching.Distributed.Redis&version=0.1.2
DcsvIo.D2.Caching.Distributed.Redis
Redis-backed implementation of IDistributedCache and
ICacheInvalidationBackplane. Wraps StackExchange.Redis.
Install
dotnet add package DcsvIo.D2.Caching.Distributed.Redis
Public surface
RedisDistributedCache : IDistributedCache — implements every building block
(ICacheBasic + ICacheAtomic + ICacheBroadcast + ICacheSet) over a
StackExchange.Redis.IConnectionMultiplexer.
RedisCacheInvalidationBackplane : ICacheInvalidationBackplane — pub/sub-backed
invalidation channel. Subscribes once at construction; dispatches to all registered
handlers per the universal "everyone acts" rule.
JsonCacheSerializer : ICacheSerializer — default value serializer.
System.Text.Json, dev-friendly (Redis CLI can inspect values directly).
Pluggable via ICacheSerializer registration.
RedisCacheOptions — connection string, default TTL, key prefix, channel name,
command/connect timeouts, retries.
services.AddD2DistributedCacheRedis(opts => …) — registers
RedisDistributedCache + the underlying IConnectionMultiplexer.
services.AddD2RedisCacheInvalidationBackplane() — opt-in registration of the
pub/sub backplane. Required for *AndBroadcast* methods on the cache (and on
tiered caches that compose it).
Lua scripts (internal)
Three Lua scripts live in RedisLuaScripts.cs. They make compound atomic ops into single
round-trips:
INCREMENT_WITH_OPTIONAL_TTL—INCRBY+ conditionalPEXPIRE. Used byIncrementAsync. Sets the TTL only when explicitly requested; existing TTLs survive subsequent increments.RELEASE_LOCK_IF_OWNER— compare-and-delete.ReleaseLockAsynconly DELs if the stored value matches the caller'slockId. Defeats the ABA pattern where someone else's lock would otherwise be released by a stale releaser.SET_ADD_WITH_OPTIONAL_TTL—SADD+ conditionalPEXPIREon first add. Sets TTL only when the set is being created; existing sets keep their TTL.
Lua is intentionally NOT exposed as a public surface (IRedisScriptExecutor etc.). If a future
caller genuinely needs custom scripts, they can take a private dep on this lib and add what they
need internally — keeping Lua as an implementation detail of the Redis impl preserves portability if
we ever swap to a different backing store.
Error handling
- Connection lost / timeout (
RedisException) —D2Result.ServiceUnavailable(graceful degradation; caller decides fail-open vs fail-closed via Result inspection). - WRONGTYPE on Increment (key holds a non-string data structure) —
D2Result.Conflict. - "value is not an integer or out of range" on Increment (key holds a
non-numeric string) —
D2Result.Conflict. - Serialization failure —
D2Resultwith error codeCOULD_NOT_BE_SERIALIZED/COULD_NOT_BE_DESERIALIZED.
Connection management
AddD2DistributedCacheRedis(opts) registers IConnectionMultiplexer as a singleton — one
connection pool shared across the app. The connection is built from
RedisCacheOptions.ConnectionString (StackExchange format:
host:port[,host:port],password=...,ssl=true). Sentinel and Cluster topologies are inferred from
the connection string.
Defaults that matter:
AbortOnConnectFail = false— graceful degradation if Redis isn't up at startup; cache calls returnServiceUnavailableuntil it comes back.CommandTimeout = 2s,ConnectTimeout = 5s,ConnectRetries = 3— tuneable perRedisCacheOptions.
Observability
Static Meter (DcsvIo.D2.Caching.Distributed.Redis) emits aggregate counters:
d2.cache.redis.{hits, misses, sets, removes, broadcasts, errors}. No per-call spans — Redis ops
are network-bound (1-5ms) and a per-call ActivitySource span would dominate at typical cache rates
without adding signal worth the noise. The aggregate counters are what tells you "is the cache
healthy" (hit rate, error rate); per-call observability is reserved for the consuming handler's own
pipeline.
Test coverage
Integration/Caching/Distributed/ (xunit collection "Redis", shared Testcontainers Redis
fixture):
RedisDistributedCacheTests— full op surface against real Redis. Includes:SetNxAsync_Concurrent_OnlyOneWinsAcrossCluster— 32 contenders, exactly one wins (cluster-wide atomic SET NX)IncrementAsync_Concurrent_AggregatesAtomically— 8×200 increments aggregate to exactly 1600 (cluster-wide atomic INCR via Lua)IncrementAsync_OnNonNumeric_ReturnsConflict— Redis WRONGTYPE / "not an integer" →Conflict(verifies both paths)SetAddAsync_Concurrent_BuildsCorrectCardinality— 100 concurrent SADDs with 30 distinct values → SCARD = 30AcquireReleaseLock_RoundTrip+ReleaseLockAsync_WrongOwner_DoesNotRelease— Lua compare-and-delete defeats ABA
RedisCacheInvalidationBackplaneTests— pub/sub backplane against real Redis. Verifies:- Universal "everyone acts" rule (publisher receives own messages)
- Multi-subscriber independence (each gets every message)
- Error isolation (one throwing handler doesn't break others)
- Disposal stops handler invocation
- Cross-instance delivery (publish on instance A → received on instance B)
The test container is launched once per test run via Testcontainers.Redis and shared across all
[Collection("Redis")] test classes. Each test uses a fresh key prefix / channel name for
isolation.
Dependencies
DcsvIo.D2.Caching.Abstractions—IDistributedCache,ICacheInvalidationBackplane,ICacheSerializerStackExchange.Redis— Redis clientMicrosoft.Extensions.Options,Microsoft.Extensions.DependencyInjection.Abstractions,Microsoft.Extensions.Logging.Abstractions
References
DcsvIo.D2.Caching.Abstractions— interface + result-mapping referenceDcsvIo.D2.Caching.Tiered— composes Local + this lib for L1+L2 cascades- Product Edge rate-limit middleware is the primary consumer of
ICacheSetfor FP-too-common detection (product surface; out of scope for this library README).
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- DcsvIo.D2.Caching.Abstractions (>= 0.1.1)
- DcsvIo.D2.I18n.Abstractions (>= 0.1.1)
- DcsvIo.D2.I18n.Keys (>= 0.1.1)
- DcsvIo.D2.Result (>= 0.1.1)
- DcsvIo.D2.Utilities (>= 0.1.1)
- dotenv.net (>= 4.0.2)
- JetBrains.Annotations (>= 2025.2.4)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Options (>= 10.0.7)
- StackExchange.Redis (>= 2.8.31)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on DcsvIo.D2.Caching.Distributed.Redis:
| Package | Downloads |
|---|---|
|
DcsvIo.D2.Telemetry
OpenTelemetry SDK setup for D2 — traces, metrics, logs, OTLP exporters, an IP-restricted Prometheus endpoint, and aggregation of every shared library's ActivitySource and Meter. |
GitHub repositories
This package is not used by any popular GitHub repositories.