Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<!-- General information -->
<PropertyGroup>
<Authors>Ugo Lattanzi</Authors>
<VersionPrefix>12.0.0</VersionPrefix>
<VersionPrefix>12.1.0</VersionPrefix>
<!--
<VersionSuffix>pre</VersionSuffix>
-->
Expand Down Expand Up @@ -46,6 +46,15 @@ Features:
Serializer packages (pick one): System.Text.Json, Newtonsoft, MemoryPack, MsgPack, Protobuf, ServiceStack, Utf8Json.
</Description>
<PackageReleaseNotes>
v12.1.0:
- Added VectorSet API for AI/ML similarity search (Redis 8.0+): VADD, VSIM, VREM, VCONTAINS, VCARD, VDIM, VGETATTR, VSETATTR, VINFO, VRANDMEMBER, VLINKS
- Added llms.txt for AI coding assistant documentation indexing
- Added Claude Code plugin with configure, scaffold, and diagnose skills
- Added complete API reference tables to all feature documentation
- Added SECURITY.md with GitHub Private Vulnerability Reporting
- Added CodeQL Advanced security analysis workflow
- Added CI workflow for automated testing on push/PR

v12.0.0:
- Added .NET 10 target framework
- Added GeoSpatial API (GEOADD, GEOSEARCH, GEODIST, GEOPOS, GEOHASH)
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
![Tests](https://img.shields.io/badge/tests-970%2B%20passing-brightgreen)
![.NET](https://img.shields.io/badge/.NET-8%20%7C%209%20%7C%2010-blue)

> **AI-Ready:** This library provides an [`llms.txt`](llms.txt) file for AI coding assistants and a Claude Code plugin for configuration, scaffolding, and troubleshooting.
>
> ```bash
> claude plugin add imperugo/StackExchange.Redis.Extensions
> ```
> Then use `/redis-configure`, `/redis-scaffold`, or `/redis-diagnose` in Claude Code.

## Features

- Store and retrieve complex .NET objects with automatic serialization
Expand Down Expand Up @@ -312,12 +319,14 @@ Full documentation is available in the [doc/](doc/) folder:
**Features**
- [Usage Guide](doc/usage/README.md) — Add, Get, Replace, Bulk operations
- [GeoSpatial Indexes](doc/geospatial.md)
- [VectorSet — AI/ML Similarity Search](doc/vectorset.md) (Redis 8.0+)
- [Redis Streams](doc/streams.md)
- [Pub/Sub Messaging](doc/pubsub.md)
- [Hash Field Expiry](doc/hash-field-expiry.md) (Redis 7.4+)
- [Compression](doc/compressors.md) — GZip, Brotli, LZ4, Snappy, Zstandard

**Advanced**
- [Migration Guide: v11 → v12](doc/migration-v11-to-v12.md)
- [Logging & Diagnostics](doc/logging.md)
- [Multiple Redis Servers](doc/multipleServers.md)
- [Azure Managed Identity](doc/azure-managed-identity.md)
Expand Down
12 changes: 12 additions & 0 deletions claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "stackexchange-redis-extensions",
"version": "1.0.0",
"description": "Configure, scaffold, and troubleshoot StackExchange.Redis.Extensions — the high-level .NET Redis client with serialization, pooling, Streams, Geo, VectorSet, compression, and more.",
"author": "imperugo",
"homepage": "https://github.com/imperugo/StackExchange.Redis.Extensions",
"skills": [
"skills/configure",
"skills/scaffold",
"skills/diagnose"
]
}
115 changes: 115 additions & 0 deletions claude-plugin/skills/configure/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
---
name: redis-configure
description: Generate StackExchange.Redis.Extensions configuration and DI registration from natural language requirements
---

# Redis Configure

Generate complete `RedisConfiguration` and ASP.NET Core DI setup for StackExchange.Redis.Extensions based on the user's requirements.

## When to use

When the user asks to:
- Set up Redis in their .NET project
- Configure connection pooling, Sentinel, TLS, Azure Managed Identity
- Choose a serializer or compressor
- Set up multiple Redis instances

## How to respond

1. Ask clarifying questions if needed (single vs. multi-instance, cloud vs. local, auth method)
2. Generate the complete configuration

## Configuration Reference

### NuGet packages required
- `StackExchange.Redis.Extensions.Core` — always required
- `StackExchange.Redis.Extensions.AspNetCore` — for DI registration
- One serializer: `System.Text.Json` (recommended), `Newtonsoft`, `MemoryPack`, `MsgPack`, `Protobuf`, `ServiceStack`, `Utf8Json`
- Optional compressor: `Compression.LZ4` (fastest), `Compression.Snappier`, `Compression.ZstdSharp` (best ratio), `Compression.GZip` (no deps), `Compression.Brotli` (best ratio for text)

### appsettings.json structure
```json
{
"Redis": {
"Password": "",
"AllowAdmin": true,
"Ssl": false,
"ConnectTimeout": 5000,
"SyncTimeout": 5000,
"Database": 0,
"Hosts": [{ "Host": "localhost", "Port": 6379 }],
"PoolSize": 5,
"IsDefault": true
}
}
```

### Program.cs registration
```csharp
var redisConfig = builder.Configuration.GetSection("Redis").Get<RedisConfiguration>();
builder.Services.AddStackExchangeRedisExtensions<SystemTextJsonSerializer>(redisConfig);
// Optional compression:
builder.Services.AddRedisCompression<LZ4Compressor>();
```

### Azure Managed Identity
```csharp
redisConfig.ConfigurationOptionsAsyncHandler = async opts =>
{
await opts.ConfigureForAzureWithTokenCredentialAsync(new DefaultAzureCredential());
return opts;
};
```

### Sentinel configuration
```csharp
var config = new RedisConfiguration
{
ServiceName = "mymaster",
Hosts = new[] {
new RedisHost { Host = "sentinel1", Port = 26379 },
new RedisHost { Host = "sentinel2", Port = 26379 },
},
IsDefault = true,
};
```

### Multiple instances
```csharp
var configs = new[]
{
new RedisConfiguration { Name = "Cache", IsDefault = true, /* ... */ },
new RedisConfiguration { Name = "Session", /* ... */ },
};
builder.Services.AddStackExchangeRedisExtensions<SystemTextJsonSerializer>(configs);

// Resolve: inject IRedisClientFactory, call GetRedisClient("Session")
```

### Key properties
| Property | Default | Description |
|----------|---------|-------------|
| PoolSize | 5 | Connection pool size |
| ConnectionSelectionStrategy | LeastLoaded | LeastLoaded or RoundRobin |
| SyncTimeout | 5000 | Sync timeout (ms) |
| ConnectTimeout | 5000 | Connect timeout (ms) |
| KeyPrefix | "" | Prefix for all keys and channels |
| KeepAlive | -1 | Heartbeat interval (seconds) |
| ClientName | null | Connection client name |
| MaxValueLength | 0 | Max serialized value size (0 = unlimited) |

### Serializer selection guide
| Scenario | Recommended |
|----------|-------------|
| General purpose | System.Text.Json |
| Legacy JSON.NET compatibility | Newtonsoft |
| Maximum performance (binary) | MemoryPack (net8.0+) |
| Cross-language compatibility | Protobuf or MsgPack |

### Compressor selection guide
| Scenario | Recommended |
|----------|-------------|
| Lowest latency (caching) | LZ4 |
| Best compression ratio | ZstdSharp or Brotli |
| No external dependencies | GZip |
106 changes: 106 additions & 0 deletions claude-plugin/skills/diagnose/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
---
name: redis-diagnose
description: Troubleshoot common StackExchange.Redis.Extensions issues — timeouts, connection failures, serialization problems, pool exhaustion
---

# 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
- Performance issues

## Diagnostic Tree

### RedisTimeoutException
**Symptoms:** `StackExchange.Redis.RedisTimeoutException`

**Check in order:**
1. **SyncTimeout too low** — default is 5000ms, increase for slow networks
```csharp
config.SyncTimeout = 10000; // 10 seconds
```
2. **Pool size too small** — default 5, increase for high-throughput
```csharp
config.PoolSize = 10;
```
3. **Connection strategy** — switch to LeastLoaded if using RoundRobin
```csharp
config.ConnectionSelectionStrategy = ConnectionSelectionStrategy.LeastLoaded;
```
4. **Large values** — enable compression to reduce payload size
```csharp
services.AddRedisCompression<LZ4Compressor>();
```
5. **ThreadPool starvation** — check with `ThreadPool.GetAvailableThreads()`, increase min threads

### RedisConnectionException
**Symptoms:** `SocketClosed`, `ConnectionFailed`

**Check:**
1. **Redis server reachable?** — `redis-cli -h <host> -p <port> ping`
2. **Firewall/NSG rules** — port 6379 (or 6380 for TLS) open?
3. **TLS misconfiguration** — if using Ssl=true, check certificate callbacks
4. **Sentinel misconfiguration** — ServiceName must match Redis master name exactly
5. **Azure Cache for Redis** — ensure Managed Identity is configured if not using password

### Serialization Errors
**Symptoms:** `JsonException`, `InvalidOperationException`, corrupt data

**Check:**
1. **Type mismatch** — GetAsync<T> must use same T as AddAsync<T>
2. **String values are JSON-encoded** — "hello" is stored as "\"hello\"", this is by design
3. **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
4. **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:**
1. **KeyPrefix** — channels are automatically prefixed. Don't add prefix manually.
2. **Serializer mismatch** — publisher and subscriber must use the same serializer
3. **Handler exceptions** — check logs for EventId 4001 errors. Handlers that throw don't crash but the message is lost.
4. **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:**
1. **Pool health** — inject `IRedisClient` and call `client.ConnectionPoolManager.GetConnectionInformation()`
2. **Redis server overloaded** — check `INFO clients` on Redis
3. **Network partition** — the pool skips disconnected connections automatically and logs warnings
4. **Dispose pattern** — ensure IRedisConnectionPoolManager is not disposed prematurely

### Performance Issues
**Check:**
1. **Enable logging** — set log level to Debug to see connection selection
```json
{ "Logging": { "LogLevel": { "StackExchange.Redis.Extensions.Core": "Debug" } } }
```
2. **Check outstanding commands** — pool info shows outstanding count per connection
3. **Use compression** for large objects — LZ4 adds ~1ms latency but reduces network 5-10x
4. **Use AddAllAsync** for bulk writes instead of loop of AddAsync
5. **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:
```json
{ "Logging": { "LogLevel": { "StackExchange.Redis.Extensions.Core": "Information" } } }
```
Loading
Loading