Redis Storage
Redis storage is suitable for production environments and distributed applications, providing high performance and automatic expiration capabilities.
Features
- Redis-based persistence for sessions, events, and state
- Supports Redis Standalone / Sentinel / Cluster deployment modes
- Independent TTL control for Session, AppState, and UserState
- Optional async persistence to reduce write latency
- Optional OpenTelemetry tracing
- Async session summary generation
- AppendEvent / GetSession hook extension points
Configuration Options
Connection:
| Option | Type | Default | Description |
|---|---|---|---|
WithRedisClientURL(url string) |
string |
- | Create Redis client via URL, format: redis://[username:password@]host:port[/database] |
WithRedisInstance(instanceName string) |
string |
- | Use a pre-configured Redis instance (lower priority than URL) |
WithExtraOptions(extraOptions ...any) |
[]any |
nil |
Extra options for the Redis client, passed to the underlying client builder |
Session:
| Option | Type | Default | Description |
|---|---|---|---|
WithSessionEventLimit(limit int) |
int |
1000 |
Maximum events per session |
WithSessionTTL(ttl time.Duration) |
time.Duration |
0 (no expiry) |
TTL for session state and events; negative values are treated as 0 |
WithAppStateTTL(ttl time.Duration) |
time.Duration |
0 (no expiry) |
TTL for app-level state |
WithUserStateTTL(ttl time.Duration) |
time.Duration |
0 (no expiry) |
TTL for user-level state |
WithKeyPrefix(prefix string) |
string |
"" |
Redis key prefix; all keys will start with prefix:. Useful when multiple apps share one Redis instance |
WithCompatMode(mode CompatMode) |
CompatMode |
CompatModeLegacy |
Storage compatibility mode. Options: CompatModeNone, CompatModeLegacy, CompatModeTransition. See Storage Compatibility Mode (CompatMode) |
WithEnableUserSessionIndex(enable bool) |
bool |
false |
Enable the per-user session index for HashIdx. See User Session Index |
Async Persistence:
| Option | Type | Default | Description |
|---|---|---|---|
WithEnableAsyncPersist(enable bool) |
bool |
false |
Enable async persistence. When enabled, AppendEvent and AppendTrackEvent write events to an internal channel and background workers flush them to Redis asynchronously |
WithAsyncPersisterNum(num int) |
int |
10 |
Number of async persistence workers. Each worker handles one Event channel and one TrackEvent channel with a buffer size of 100 |
Summary:
| Option | Type | Default | Description |
|---|---|---|---|
WithSummarizer(s summary.SessionSummarizer) |
summary.SessionSummarizer |
nil |
Inject session summarizer. When not set, summary operations are no-ops |
WithAsyncSummaryNum(num int) |
int |
3 |
Number of summary processing workers |
WithSummaryQueueSize(size int) |
int |
100 |
Summary task queue size |
WithSummaryJobTimeout(timeout time.Duration) |
time.Duration |
60s |
Timeout for a single summary job |
Tracing:
| Option | Type | Default | Description |
|---|---|---|---|
WithEnableTracing(enable bool) |
bool |
false |
Enable OpenTelemetry tracing. When enabled, operations such as CreateSession, GetSession, AppendEvent, DeleteSession, AppendTrackEvent, CreateSessionSummary, and GetSessionSummaryText automatically create spans |
About Root Span
Session operations are executed by the Runner before and after the Agent's
Run()call. The Agent's root span is created insideagent.Run(), so Session spans are not automatically attached under it. To see the full Session span chain in observability platforms like Langfuse, manually create a root span before callingrunner.Run():
Hooks:
| Option | Type | Default | Description |
|---|---|---|---|
WithAppendEventHook(hooks ...session.AppendEventHook) |
[]session.AppendEventHook |
nil |
Add event write hooks |
WithGetSessionHook(hooks ...session.GetSessionHook) |
[]session.GetSessionHook |
nil |
Add session read hooks |
Basic Configuration
Instance Reuse
If multiple components need to use the same Redis instance, register and reuse it:
With Summary
Async Persistence
When async persistence is enabled, AppendEvent and AppendTrackEvent no longer write to Redis synchronously. Instead, events are dispatched to internal channels and consumed by background worker goroutines. This significantly reduces request latency for write-sensitive scenarios.
How it works:
- Each worker goroutine owns one Event channel and one TrackEvent channel (buffer size 100).
AppendEventselects a channel viasession.Hash % workerNum, ensuring ordered writes for the same session.- If the channel is full and the context is cancelled,
context.Canceledis returned. - Async write timeout is 2 seconds (
defaultAsyncPersistTimeout). - Calling
Close()closes all channels and waits for workers to drain remaining tasks.
Caution
In async persistence mode, events still in the channel may be lost if the process crashes unexpectedly. Evaluate whether to enable this based on your data consistency requirements.
User Session Index
WithEnableUserSessionIndex(true) is an optional capability for HashIdx storage only. It maintains a user-scoped index that maps userID to the session IDs created by that user.
The main purpose of this index is to avoid the SCAN operation currently used by ListSessions.
This option is intended for fresh HashIdx writes. If you enable it on an environment that already contains historical HashIdx sessions created before the index was introduced, those older sessions will not automatically appear in the index unless you migrate or rebuild the index separately.
Storage Compatibility Mode (CompatMode)
The new version of Redis Session uses a new storage engine (HashIdx) that distributes data across different Redis Cluster slots by user, eliminating the hotspot issue where all data was concentrated in a single slot in the old version. If you have legacy data to migrate, use WithCompatMode to configure compatibility mode for a smooth transition.
In most cases, you don't need to worry about compatibility mode
The default
CompatModeLegacymode automatically handles read/write compatibility between old and new data — just upgrade and it works. You only need to pay attention to compatibility mode configuration in these two cases:
- Heavy use of UserState: The old and new engines use different Redis keys for UserState.
CreateSession/GetSessiononly read the new key when merging UserState internally, so legacy UserState data won't automatically carry over to new sessions. If your application relies heavily on UserState, choose the appropriate compatibility mode as described below.- Large-scale canary deployment: When old and new instances run simultaneously, use
CompatModeTransitionto ensure mixed-deployment compatibility.
New Deployments (No Legacy Data)
Use CompatModeNone directly to skip all compatibility logic and get the best performance:
Single Node or Small-Scale Upgrade
For single-node deployments, or when you have few enough nodes to upgrade all at once, just upgrade directly using the default CompatModeLegacy (no explicit configuration needed):
In CompatModeLegacy mode, newly created sessions use the new storage engine while legacy data remains accessible via fallback reads. Once legacy data expires by TTL, you can switch to CompatModeNone.
Large-Scale Canary Upgrade
For large-scale deployments requiring canary releases where old and new instances run simultaneously, follow these steps:
Step 1: Canary Phase — Set CompatModeTransition
In Transition mode, new instances behave identically to old instances (session creation uses legacy storage, UserState dual-writes to both old and new keys), ensuring full data compatibility during mixed deployment.
Step 2: Full Upgrade Complete — Switch to CompatModeLegacy
After all instances are upgraded, remove the WithCompatMode configuration (or explicitly set it to CompatModeLegacy). New sessions will use the new storage engine while legacy data remains accessible via fallback reads.
Step 3: Legacy Data Expired — Switch to CompatModeNone
Once legacy data has expired by TTL (or manually cleaned up if no TTL was set), switch to CompatModeNone to remove the compatibility layer.
Compatibility Mode Reference
| Mode | Session Read | Session Write | UserState Read | UserState Write | Use Case |
|---|---|---|---|---|---|
CompatModeNone |
New engine only | New engine only | New key only | New key only | New deployments, or all legacy data has expired |
CompatModeLegacy (default) |
Legacy first, fallback to new engine | New engine only | New key first, fallback to old key | New key only | Single/small-scale direct upgrade |
CompatModeTransition |
Legacy first, fallback to new engine | Legacy engine only | New key first, fallback to old key | Dual-write both keys | Large-scale canary, mixed old/new instances |
UserState Migration Notes
The old and new storage engines use different Redis keys for UserState (old: userstate:{appName}:{userID}, new: hashidx:userstate:appName:{userID}).
- In
CompatModeTransitionmode,UpdateUserStatewrites to both old and new keys. It is recommended to re-write UserState viaUpdateUserStateduring the canary phase to sync data to the new key. - The
ListUserStatesAPI in Transition and Legacy modes tries the new key first and falls back to the old key if empty. However,CreateSession/GetSessiononly read the new key internally when merging UserState, without fallback. - AppState is not affected —
appstate:{appName}uses the same format in both engines, zero migration cost.
Use Cases
| Scenario | Recommended Configuration |
|---|---|
| New deployment | CompatModeNone |
| Single/small-scale upgrade | Default CompatModeLegacy, upgrade directly |
| Large-scale canary upgrade | CompatModeTransition → CompatModeLegacy → CompatModeNone |
| Production | Configure TTL, enable async persistence |
| Distributed deployment | Use Redis Cluster |
| High concurrency | Increase AsyncPersisterNum |
Notes
- Connection: Ensure Redis service is accessible; use connection pooling
- TTL management: Redis natively supports TTL; no additional cleanup tasks needed
- Memory management: Monitor Redis memory usage; configure reasonable maxmemory
- High availability: Use Redis Sentinel or Cluster for production
- Priority:
WithRedisClientURLhas higher priority thanWithRedisInstance - Async persistence risk: Events in the channel may be lost on unexpected process exit; evaluate your tolerance