Redis caching and pubsub
Skill hamzabellouch/agent-skills/Databases and Caching/redis-caching-and-pubsub
Comprehensive collection of 380+ production-ready Agent Skills (26 domains) conforming to the Agent Skills Standard, featuring native auto-discovery for Antigravity, Gemini CLI, Claude Code, Cursor, and Codex.
npx -y skills add hamzabellouch/agent-skills --skill redis-caching-and-pubsubAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 18 days oldThe repository was created 18 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Architecting high-performance caching strategies, Cache-Aside/Write-Through/Write-Behind patterns, Redis Pub/Sub, Streams, eviction policies, distributed locking (Redlock), and memory optimization. Use when implementing Redis caching, real-time messaging, rate limiting, or session management.
SKILL.md
8.2 KB, as published. Nobody here has run it
Redis Caching & Pub/Sub Architecture Guide
Production-grade guide for designing, implementing, and scaling Redis as a high-performance cache, session store, real-time pub/sub message broker, and distributed coordinator.
1. Caching Strategies & Design Patterns
1.1 Cache-Aside (Lazy Loading)
The application reads from the cache first. On a cache miss, it reads from the primary database, updates the cache, and returns the result.
+-------------+ 1. Read Key +-------+
| Application | --------------------> | Cache |
| | <-------------------- | |
+-------------+ 2. Cache Hit +-------+
|
| 3. Cache Miss
v
+---------------+
| Primary DB |
+---------------+
- Best For: Read-heavy workloads with unpredictable access patterns.
- Trade-off: Stale data risk if DB writes don't invalidate/update cache; higher latency on cache miss.
1.2 Write-Through Caching
The application writes data directly to the cache, and the cache synchronously writes to the database.
- Best For: Mission-critical reads where cache staleness cannot be tolerated.
- Trade-off: Higher write latency; potential cache pollution with infrequently read data.
1.3 Write-Behind (Write-Back) Caching
The application writes to the cache immediately, and an asynchronous process flushes cache updates to the underlying DB in batches.
- Best For: Write-heavy systems (e.g., analytics, metrics aggregators, counter updates).
- Trade-off: Risk of data loss if Redis crashes before flushing to primary DB.
1.4 Cache Stampede (Thundering Herd) Mitigation
When a popular cache key expires, thousands of concurrent requests hit the backend database simultaneously.
Solution A: Distributed Mutex / Lock
Acquire a lock to recompute the cache value; force other concurrent requests to wait or return stale data.
Solution B: Probabilistic Early Expiration (XFetch Algorithm)
As a key nears expiration, probabilistically refresh it in the background before it expires.
$$\Delta - \beta \cdot \ln(\text{rand}()) > \text{TTL}$$
2. Redis Data Structures & Scalable Usage Patterns
| Data Structure | Primary Use Cases | Recommended Max Size / Rule |
|---|---|---|
| Strings | Simple key-value, HTML fragments, JSON payloads, counters (INCRBY) | Limit single string value to $< 100\text{ KB}$ |
| Hashes | User objects, entity fields, session attributes | Use fields instead of JSON strings for partial updates |
| Sets | Unique tags, IP blacklists, mutual friends, deduplication | Max set size $< 10,000$ members for fast bulk operations |
| Sorted Sets (ZSET) | Leaderboards, rate limit sliding windows, priority queues | High efficiency ($O(\log N)$ inserts/searches) |
| HyperLogLog | Unique visitor counting (Cardinality estimation) | Constant $12\text{ KB}$ footprint with $0.81%$ standard error |
| Bitmaps | User daily active status, feature toggles | $512\text{ MB}$ max space = $4.29$ billion flags |
| Streams | Append-only event logs, persistent pub/sub, message processing queues | Use XADD with explicit trimming (MAXLEN ~ 10000) |
3. Real-Time Messaging: Pub/Sub vs. Streams
Pub/Sub (At-Most-Once)
- Behavior: Fire-and-forget message delivery. Messages are not persisted. If a subscriber is disconnected during publication, the message is lost.
- Use Cases: Live UI updates, websocket notifications, ephemeral chat messages.
Redis Streams (At-Least-Once)
- Behavior: Append-only log with Consumer Groups, message ACK, pending entry lists (PEL), and replay capability.
- Use Cases: Async task processing, distributed event sourcing, reliable messaging workflows.
// Redis Streams Consumer Group Pattern (TypeScript Example)
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);
const STREAM_KEY = 'events:orders';
const GROUP_NAME = 'order-processing-group';
const CONSUMER_NAME = `worker-${process.pid}`;
async function initializeStreamGroup() {
try {
await redis.xgroup('CREATE', STREAM_KEY, GROUP_NAME, '$', 'MKSTREAM');
} catch (err: any) {
if (!err.message.includes('BUSYGROUP')) throw err;
}
}
async function processOrderEvents() {
await initializeStreamGroup();
while (true) {
// Read up to 10 new unconsumed messages for this group
const results = await redis.xreadgroup(
'GROUP', GROUP_NAME, CONSUMER_NAME,
'BLOCK', 2000,
'COUNT', 10,
'STREAMS', STREAM_KEY, '>'
) as any;
if (!results) continue;
for (const [stream, messages] of results) {
for (const [id, fields] of messages) {
try {
const payload = JSON.parse(fields[1]);
await handleOrderEvent(payload);
// Acknowledge successful processing
await redis.xack(STREAM_KEY, GROUP_NAME, id);
} catch (error) {
console.error(`Failed to process message ${id}:`, error);
// Failed messages remain in PEL for retry/dead-letter inspection
}
}
}
}
}
4. Distributed Locking (Redlock & Atomic Lua)
Single Instance Atomic Locking with Renewal & Release
To safely acquire and release locks, use a cryptographically random token and release via Lua script to avoid releasing another client's lock.
import Redis from 'ioredis';
import { randomBytes } from 'crypto';
export class RedisDistributedLock {
private redis: Redis;
constructor(redisClient: Redis) {
this.redis = redisClient;
}
/**
* Acquire a distributed lock.
*/
async acquire(key: string, ttlMs: number): Promise<string | null> {
const lockToken = randomBytes(16).toString('hex');
const acquired = await this.redis.set(
`lock:${key}`,
lockToken,
'PX',
ttlMs,
'NX'
);
return acquired === 'OK' ? lockToken : null;
}
/**
* Release lock safely using an atomic Lua script.
*/
async release(key: string, lockToken: string): Promise<boolean> {
const luaScript = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
`;
const result = await this.redis.eval(luaScript, 1, `lock:${key}`, lockToken);
return result === 1;
}
}
5. Eviction Policies & Memory Tuning
Redis Eviction Policies (maxmemory-policy)
volatile-lru: Evict keys with an explicit expiration using Least Recently Used algorithm.allkeys-lru: Recommended for general caching. Evicts any key based on LRU regardless of TTL.volatile-lfu/allkeys-lfu: Evicts based on Least Frequently Used frequency counters.noeviction: Returns Out-Of-Memory errors on writes when limit is reached. Required when Redis acts as a primary database.
Memory Optimization Rules
- Set
maxmemoryexplicitly (e.g., $75%$ of total instance RAM) to leave overhead for replication buffers and BGSAVE copy-on-write fork allocation. - Enable Listpack / Ziplist encoding for small Hashes and Sets:
hash-max-listpack-entries 512 hash-max-listpack-value 64
6. Anti-Patterns & Production Pitfalls
- ❌ Running
KEYS *in Production: Blocks single-threaded Redis execution loop. UseSCANwith cursor iteration instead. - ❌ Hot Key Bottlenecks: Single key accessed by thousands of app instances concurrently causing network NIC saturation. Fix: Implement local in-memory L1 cache (e.g., LRU cache) alongside Redis L2.
- ❌ Big Keys ($> 10\text{ MB}$ collections/strings): Unlinking/deleting large collections causes latency spikes. Use
UNLINKinstead ofDELfor async garbage collection. - ❌ Missing Key Namespaces: Keys without structured prefixes leads to collisions and unsafe bulk flush actions. Pattern:
app:domain:entity:id(e.g.,shop:user:profile:10948). - ❌ Unbounded Collection Growth: Sets, Lists, or Hashes left to grow indefinitely without TTLs or trimming logic.