| name | redis-diagnose |
| description | Troubleshoot common StackExchange.Redis.Extensions issues — timeouts, connection failures, serialization problems, pool exhaustion, distributed locks, Lua scripting |
Redis Diagnose
Diagnose and fix common issues with StackExchange.Redis.Extensions.
When to use
When the user reports:
- Timeout exceptions
- Connection failures
- Serialization errors
- Pool exhaustion
- Pub/Sub messages not being received
- Distributed lock failures
- Lua scripting errors
- Performance issues
Diagnostic Tree
RedisTimeoutException
Symptoms: StackExchange.Redis.RedisTimeoutException
Check in order:
- SyncTimeout too low — default is 5000ms, increase for slow networks
config.SyncTimeout = 10000;
- Pool size too small — default 5, increase for high-throughput
config.PoolSize = 10;
- Connection strategy — switch to LeastLoaded if using RoundRobin
config.ConnectionSelectionStrategy = ConnectionSelectionStrategy.LeastLoaded;
- Large values — enable compression to reduce payload size
services.AddRedisCompression<LZ4Compressor>();
- ThreadPool starvation — check with
ThreadPool.GetAvailableThreads(), increase min threads
RedisConnectionException
Symptoms: SocketClosed, ConnectionFailed
Check:
- Redis server reachable? —
redis-cli -h <host> -p <port> ping
- Firewall/NSG rules — port 6379 (or 6380 for TLS) open?
- TLS misconfiguration — if using Ssl=true, check certificate callbacks
- Sentinel misconfiguration — ServiceName must match Redis master name exactly
- Azure Cache for Redis — ensure Managed Identity is configured if not using password
Serialization Errors
Symptoms: JsonException, InvalidOperationException, corrupt data
Check:
- Type mismatch — GetAsync must use same T as AddAsync
- String values are JSON-encoded — "hello" is stored as ""hello"", this is by design
- Compression migration — enabling compression makes old (uncompressed) data unreadable
- Error:
InvalidOperationException: Failed to decompress data from Redis
- Fix: flush the database or read old data without compression first
- Value type quirk —
GetAsync<int>() returns 0 (not null) for missing keys because default(int) is 0. Use GetAsync<int?>() to distinguish missing keys from actual zero values.
Pub/Sub Not Receiving Messages
Check:
- KeyPrefix — channels are automatically prefixed. Don't add prefix manually.
- Serializer mismatch — publisher and subscriber must use the same serializer
- Handler exceptions — check logs for EventId 4001 errors. Handlers that throw don't crash but the message is lost.
- Different connection pools — ensure pub and sub use the same IRedisDatabase instance
Pool Exhaustion / All Connections Down
Symptoms: All operations fail, logs show EventId 1003
Check:
- Pool health — inject
IRedisClient and call client.ConnectionPoolManager.GetConnectionInformation()
- Use health check — register
builder.Services.AddHealthChecks().AddRedisExtensionsHealthCheck() to monitor pool status automatically (returns Healthy/Degraded/Unhealthy)
- Redis server overloaded — check
INFO clients on Redis
- Network partition — the pool skips disconnected connections automatically and logs warnings
- Dispose pattern — ensure IRedisConnectionPoolManager is not disposed prematurely
IDistributedCache Issues
Symptoms: Data not found, expiration not working, migration issues
Check:
- Registration order —
AddRedisDistributedCache() must be called after AddStackExchangeRedisExtensions<T>()
- KeyPrefix applies — IDistributedCache goes through
IRedisDatabase.Database which uses WithKeyPrefix. Cache keys are prefixed automatically.
- Migration from Microsoft provider — hash schema is compatible (
data/absexp/sldexp fields), but key prefix format may differ (this library uses KeyPrefix, Microsoft uses InstanceName)
- Sliding expiration not refreshing —
Get and Refresh both refresh the TTL. Check that SlidingExpiration was set in DistributedCacheEntryOptions
Keyed DI Not Resolving
Symptoms: [FromKeyedServices("name")] returns null
Check:
- Config must have a Name —
RedisConfiguration.Name must be non-empty for keyed registration
- Use eager overloads — keyed services are only registered with the overloads that receive
RedisConfiguration directly, NOT the Func<IServiceProvider, ...> overload
- Name must match exactly —
[FromKeyedServices("cache")] must match config.Name = "cache" (case-sensitive)
Distributed Lock Issues
Symptoms: Lock not acquired, deadlocks, lock lost during processing
Check:
- LockAcquireAsync returns null — increase
maxRetries or retryDelay, the resource may be legitimately contended
- Lock expires during processing — use
lockObj.ExtendAsync(TimeSpan) to extend the TTL before it expires
- Lock not released on error — always use
await using pattern, never try/finally with manual release
- Deadlock — ensure lock expiry is always set. If the holder crashes, the lock auto-expires
- This is a single-instance lock — for Redis Cluster, consider Redlock algorithm
Lua Script Errors
Symptoms: RedisServerException, NOSCRIPT, wrong return type
Check:
- NOSCRIPT error — the script was cached but the server restarted. SE.Redis retries with EVAL automatically, but check for transient failures
- Wrong return type — Lua
return 1 returns long, return "hello" returns string. Use explicit casts on RedisResult
- CROSSSLOT error in Cluster — all KEYS[] must hash to the same slot. Use hash tags:
{user}:counter, {user}:data
- Script blocks Redis — Lua runs atomically, blocking the event loop. Keep scripts short (<1ms). Use read-only variant (
ScriptEvaluateReadOnlyAsync) for read operations to route to replicas
- Typed deserialization fails —
ScriptEvaluateAsync<T> passes the raw bytes through ISerializer. Ensure the script returns a value serialized in the same format
Performance Issues
Check:
- Enable logging — set log level to Debug to see connection selection
{ "Logging": { "LogLevel": { "StackExchange.Redis.Extensions.Core": "Debug" } } }
- Check outstanding commands — pool info shows outstanding count per connection
- Use compression for large objects — LZ4 adds ~1ms latency but reduces network 5-10x
- Use AddAllAsync for bulk writes instead of loop of AddAsync
- Use GetAllAsync for bulk reads (note: requires
HashSet<string> for keys, not arrays)
Logging Reference
| EventId | Level | Meaning |
|---|
| 1001 | Info | Pool initialized successfully |
| 1003 | Warning | All connections disconnected — degraded mode |
| 1006 | Error | Pool initialization failed |
| 2001 | Error | Connection failed |
| 2002 | Warning | Connection restored |
| 4001 | Error | Pub/Sub handler threw exception |
Enable with:
{ "Logging": { "LogLevel": { "StackExchange.Redis.Extensions.Core": "Information" } } }