How MemoryProxy Handles Redis Session Storage: A Deep Dive into RedisSessionStore
TLDR: MemoryProxy stores transient session data in Redis through its RedisSessionStore class, which serializes session blobs as JSON, applies TTLs via SETEX, mantains atomic per-session turn-sequence counters with INCR, and gracefully degrades to a no-op mode whenever the Redis connection drops.
MemoryProxy, part of the TencentCloud/TencentDB-Agent-Memory repository, uses Redis as the backing store for short-lived session state consumed by the CostGuard router and injection layer. The implementation lives primarily in MemoryProxy/src/redis-session-store.ts and is wired up at startup by src/guard-adapter.ts and src/injection/index.ts. This article walks through the entire Redis session storage path — from configuration to instantiation to the atomic operations that keep session data consistent under concurrent loads.
Redis Configuration via the RedisConfig Interface
The Redis connection details are defined by the RedisConfig interface in src/types.ts. Every field controls a specific aspect of how MemoryProxy talks to Redis:
enabled— toggles Redis usage entirely. Whenfalse, the session store is skipped.url— full Redis connection string (mutually exclusive with discrete host fields).host,port,password,db— individual connection fields used whenurlis empty.keyPrefix— defaults tocg:sess:and isolates keys per tenant.ttlSeconds— defaults to1800seconds (30 minutes), determining how long a session blob survives.injectionTtlSeconds— optional override for injection-layer cache TTLs.
Here is the exact interface as written in the source:
// src/types.ts – Redis configuration
export interface RedisConfig {
enabled: boolean;
url: string;
host: string;
port: number;
password: string;
db: number;
keyPrefix: string;
ttlSeconds: number;
injectionTtlSeconds?: number;
}
The RedisSessionStore Implementation
MemoryProxy's Redis session storage centers on the RedisSessionStore class. This class implements the SessionStore contract used across the proxy layer, translating high-level session operations into Redis commands.
Instantiation from Configuration
When config.redis.enabled is true, MemoryProxy creates the session store during startup. This happens in two places: the injection initializer and the guard adapter.
The injection layer (src/injection/index.ts) creates a higher-level RedisSessionRepo if Redis is detected:
// src/injection/index.ts – creating the session repo
const redis = config.redis?.enabled ? getRedisClient(config.redis) : null;
if (redis) {
setSessionRepo(new RedisSessionRepo(redis, ttl));
}
Meanwhile, src/guard-adapter.ts directly instantiates a RedisSessionStore for the CostGuard router as a fallback path:
// src/guard-adapter.ts – fallback for CostGuard
if (config.redis.enabled) {
redisSessionStore = new RedisSessionStore(config.redis);
sessionStore = redisSessionStore;
}
Connection Handling with ioredis
The store's constructor builds an ioredis client either from a full URL or from the discrete host options. It opens the connection lazily — meaning no network activity until the first command — and tracks the connection state via three events.
// src/redis-session-store.ts – constructor (excerpt)
if (config.url) {
this.client = new Redis(config.url, { lazyConnect: true, ... });
} else {
this.client = new Redis({ host, port, password, db, lazyConnect: true, ... });
}
this.client.on('connect', () => { this.connected = true; });
this.client.on('error', err => { this.connected = false; });
this.client.on('close', () => { this.connected = false; });
The connected flag is the linchpin of the store's graceful degradation strategy described later.
Session Blob API: Get, Set, Delete
The session store exposes three core operations that map directly to Redis primitives. Each key is built by prepending keyPrefix to the session identifier.
get(key)
Reads the JSON-encoded session blob from Redis, parses it, and returns the opaque state object — or null on a miss or any error.
set(key, state)
Serializes the state to JSON, writes it with SETEX so the key expires after the configured TTL, and also refreshes the TTL of the associated turn-sequence counter:
// src/redis-session-store.ts – get / set / delete (excerpt)
async get(key) { ... const raw = await this.client.get(this.buildKey(key)); ... }
async set(key, state) { ... await this.client.setex(this.buildKey(key), this.ttlSeconds, serialized); ... }
async delete(key) { ... await this.client.del(this.buildKey(key)); ... }
delete(key)
Removes both the session blob and the per-session turn-sequence key in one DEL call, so no orphaned Redis keys remain.
Atomic Turn Sequence Counter
A distinguishing feature of MemoryProxy's Redis session storage is the per-session DNA counter. For every session, the store maintains a separate key (<prefix>turnseq:<session>). The incrTurnSeq(key) method uses Redis INCR to atomically increment this counter and refreshes its TTL:
// src/redis-session-store.ts – incrTurnSeq (excerpt)
async incrTurnSeq(key) {
if (!this.connected) return 0;
const turnSeqKey = this.buildTurnSeqKey(key);
const next = await this.client.incr(turnSeqKey);
await this.client.expire(turnSeqKey, this.ttlSeconds);
return next;
}
INCR guarantees a strictly increasing turn identifier even under heavy concurrent requests, preserving the ordering expected by the CostGuard router. If Redis is unavailable, the method returns 0 so the host can fall back to a stateless counter without breaking the pipeline.
Graceful Degradation Without Redis
Every operation in the store first checks the internal this.connected flag:
- Reads return
nullwhen disconnected. - Writes become a permissive no-op.
- Turn-sequence increments return
0.
When the connection drops and a method is called, MemoryProxy logs a warning via log.warn and immediately returns the fallback value. This preserves the "passthrough" behavior expected by the CostGuard router, ensuring requests continue processing even when Redis is entirely offline.
Session Lifecycle Management
The store also exposes two lifecycle hooks for use in health checks and shutdown sequences:
isConnected()— reports the current connection state.close()— callsclient.quit()for a clean shutdown, orclient.disconnect()if an error occurred.
Default TTL is 1800 seconds (30 minutes), after which both the session blob and the turn-sequence counter are automatically evicted by Redis.
Code Example: Full Redis Session Storage Workflow
The following example shows how a host application would drive the RedisSessionStore directly:
import { RedisSessionStore } from './redis-session-store';
import { RedisConfig } from './types';
// 1️⃣ Create a store (using env-provided config)
const cfg: RedisConfig = {
enabled: true,
url: process.env.REDIS_URL ?? '',
host: '127.0.0.1',
port: 6379,
password: '',
db: 0,
keyPrefix: 'cg:sess:',
ttlSeconds: 1800,
};
const store = new RedisSessionStore(cfg);
// 2️⃣ Store a session blob
await store.set('session-123', { userId: 'u42', state: 'init' });
// 3️⃣ Retrieve it later
const sess = await store.get('session-123');
console.log(sess?.userId); // → 'u42'
// 4️⃣ Increment the per-session turn sequence
const turn = await store.incrTurnSeq('session-123');
console.log(`Turn #${turn}`); // → Turn #1 (first call)
// 5️⃣ Delete the session when finished
await store.delete('session-123');
Key Files in the Repository
| File | Purpose |
|---|---|
MemoryProxy/src/redis-session-store.ts |
Core Redis-backed session store implementation |
MemoryProxy/src/types.ts |
RedisConfig definition and related type contracts |
MemoryProxy/src/guard-adapter.ts |
Instantiates RedisSessionStore for CostGuard routing |
MemoryProxy/src/injection/index.ts |
Sets up injection-layer repo using Redis when enabled |
MemoryProxy/src/db/redis-client.js (helper) |
Provides getRedisClient that creates the ioredis instance |
Summary
- MemoryProxy's Redis session storage is implemented by the
RedisSessionStoreclass insrc/redis-session-store.ts, instantiated by both the CostGuard guard adapter and the injection initializer. - Configuration comes from the
RedisConfiginterface, with a default TTL of 1800 seconds andcg:sess:key prefix. - The session blob API uses
GET,SETEX, andDELRedis commands, while the turn-sequence counter uses atomicINCRwith TTL refresh. - Graceful degradation is enforced via the
connectedflag — reads returnnull, writes are no-ops, and turn counters return0when Redis is unreachable. - Lifecycle management includes
isConnected()andclose()for health checks and clean shutdown.
Frequently Asked Questions
What happens when Redis goes down during a MemoryProxy session?
All RedisSessionStore operations first check the this.connected flag. On a connection failure, reads return null, writes become permissive no-ops, and the turn-sequence counter returns 0. This preserves the passthrough behavior the CostGuard router expects, so requests continue processing without blocking on Redis.
How does MemoryProxy guarantee atomic turn identifiers for concurrent requests?
It uses the Redis INCR command against a dedicated <prefix>turnseq:<session> key. Because INCR is atomic, each concurrent caller receives a unique, strictly increasing turn number. The store refreshes that key's TTL with EXPIRE as well so the counter aligns with the session blob's expiration.
What is the default TTL for Redis session storage?
The default TTL is 1800 seconds (30 minutes), configured via the ttlSeconds property in RedisConfig. An optional injectionTtlSeconds can override the TTL specifically for injection-layer caches.
Does MemoryProxy use lazy connections to Redis?
Yes. The RedisSessionStore constructs its ioredis client with lazyConnect: true, meaning no network traffic occurs until the first command is issued. Connection state is tracked by listening to the connect, error, and close events, and any failure updates the internal connected flag that gates all operations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →