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; // 10 seconds
- 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" } } }
1---2name: redis-diagnose3description: Troubleshoot common StackExchange.Redis.Extensions issues — timeouts, connection failures, serialization problems, pool exhaustion, distributed locks, Lua scripting4---56# Redis Diagnose78Diagnose and fix common issues with StackExchange.Redis.Extensions.910## When to use1112When the user reports:13- Timeout exceptions14- Connection failures15- Serialization errors16- Pool exhaustion17- Pub/Sub messages not being received18- Distributed lock failures19- Lua scripting errors20- Performance issues2122## Diagnostic Tree2324### RedisTimeoutException25**Symptoms:** `StackExchange.Redis.RedisTimeoutException`2627**Check in order:**281. **SyncTimeout too low** — default is 5000ms, increase for slow networks29 ```csharp30 config.SyncTimeout = 10000; // 10 seconds31 ```322. **Pool size too small** — default 5, increase for high-throughput33 ```csharp34 config.PoolSize = 10;35 ```363. **Connection strategy** — switch to LeastLoaded if using RoundRobin37 ```csharp38 config.ConnectionSelectionStrategy = ConnectionSelectionStrategy.LeastLoaded;39 ```404. **Large values** — enable compression to reduce payload size41 ```csharp42 services.AddRedisCompression<LZ4Compressor>();43 ```445. **ThreadPool starvation** — check with `ThreadPool.GetAvailableThreads()`, increase min threads4546### RedisConnectionException47**Symptoms:** `SocketClosed`, `ConnectionFailed`4849**Check:**501. **Redis server reachable?** — `redis-cli -h <host> -p <port> ping`512. **Firewall/NSG rules** — port 6379 (or 6380 for TLS) open?523. **TLS misconfiguration** — if using Ssl=true, check certificate callbacks534. **Sentinel misconfiguration** — ServiceName must match Redis master name exactly545. **Azure Cache for Redis** — ensure Managed Identity is configured if not using password5556### Serialization Errors57**Symptoms:** `JsonException`, `InvalidOperationException`, corrupt data5859**Check:**601. **Type mismatch** — GetAsync<T> must use same T as AddAsync<T>612. **String values are JSON-encoded** — "hello" is stored as "\"hello\"", this is by design623. **Compression migration** — enabling compression makes old (uncompressed) data unreadable63 - Error: `InvalidOperationException: Failed to decompress data from Redis`64 - Fix: flush the database or read old data without compression first654. **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.6667### Pub/Sub Not Receiving Messages68**Check:**691. **KeyPrefix** — channels are automatically prefixed. Don't add prefix manually.702. **Serializer mismatch** — publisher and subscriber must use the same serializer713. **Handler exceptions** — check logs for EventId 4001 errors. Handlers that throw don't crash but the message is lost.724. **Different connection pools** — ensure pub and sub use the same IRedisDatabase instance7374### Pool Exhaustion / All Connections Down75**Symptoms:** All operations fail, logs show EventId 10037677**Check:**781. **Pool health** — inject `IRedisClient` and call `client.ConnectionPoolManager.GetConnectionInformation()`792. **Use health check** — register `builder.Services.AddHealthChecks().AddRedisExtensionsHealthCheck()` to monitor pool status automatically (returns Healthy/Degraded/Unhealthy)803. **Redis server overloaded** — check `INFO clients` on Redis814. **Network partition** — the pool skips disconnected connections automatically and logs warnings825. **Dispose pattern** — ensure IRedisConnectionPoolManager is not disposed prematurely8384### IDistributedCache Issues85**Symptoms:** Data not found, expiration not working, migration issues8687**Check:**881. **Registration order** — `AddRedisDistributedCache()` must be called after `AddStackExchangeRedisExtensions<T>()`892. **KeyPrefix applies** — IDistributedCache goes through `IRedisDatabase.Database` which uses `WithKeyPrefix`. Cache keys are prefixed automatically.903. **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`)914. **Sliding expiration not refreshing** — `Get` and `Refresh` both refresh the TTL. Check that `SlidingExpiration` was set in `DistributedCacheEntryOptions`9293### Keyed DI Not Resolving94**Symptoms:** `[FromKeyedServices("name")]` returns null9596**Check:**971. **Config must have a Name** — `RedisConfiguration.Name` must be non-empty for keyed registration982. **Use eager overloads** — keyed services are only registered with the overloads that receive `RedisConfiguration` directly, NOT the `Func<IServiceProvider, ...>` overload993. **Name must match exactly** — `[FromKeyedServices("cache")]` must match `config.Name = "cache"` (case-sensitive)100101### Distributed Lock Issues102**Symptoms:** Lock not acquired, deadlocks, lock lost during processing103104**Check:**1051. **LockAcquireAsync returns null** — increase `maxRetries` or `retryDelay`, the resource may be legitimately contended1062. **Lock expires during processing** — use `lockObj.ExtendAsync(TimeSpan)` to extend the TTL before it expires1073. **Lock not released on error** — always use `await using` pattern, never try/finally with manual release1084. **Deadlock** — ensure lock expiry is always set. If the holder crashes, the lock auto-expires1095. **This is a single-instance lock** — for Redis Cluster, consider Redlock algorithm110111### Lua Script Errors112**Symptoms:** `RedisServerException`, NOSCRIPT, wrong return type113114**Check:**1151. **NOSCRIPT error** — the script was cached but the server restarted. SE.Redis retries with EVAL automatically, but check for transient failures1162. **Wrong return type** — Lua `return 1` returns `long`, `return "hello"` returns `string`. Use explicit casts on RedisResult1173. **CROSSSLOT error in Cluster** — all KEYS[] must hash to the same slot. Use hash tags: `{user}:counter`, `{user}:data`1184. **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 replicas1195. **Typed deserialization fails** — `ScriptEvaluateAsync<T>` passes the raw bytes through ISerializer. Ensure the script returns a value serialized in the same format120121### Performance Issues122**Check:**1231. **Enable logging** — set log level to Debug to see connection selection124 ```json125 { "Logging": { "LogLevel": { "StackExchange.Redis.Extensions.Core": "Debug" } } }126 ```1272. **Check outstanding commands** — pool info shows outstanding count per connection1283. **Use compression** for large objects — LZ4 adds ~1ms latency but reduces network 5-10x1294. **Use AddAllAsync** for bulk writes instead of loop of AddAsync1305. **Use GetAllAsync** for bulk reads (note: requires `HashSet<string>` for keys, not arrays)131132## Logging Reference133134| EventId | Level | Meaning |135|---------|-------|---------|136| 1001 | Info | Pool initialized successfully |137| 1003 | Warning | All connections disconnected — degraded mode |138| 1006 | Error | Pool initialization failed |139| 2001 | Error | Connection failed |140| 2002 | Warning | Connection restored |141| 4001 | Error | Pub/Sub handler threw exception |142143Enable with:144```json145{ "Logging": { "LogLevel": { "StackExchange.Redis.Extensions.Core": "Information" } } }146```