# Redis Patterns

> When to activate: Redis, cache, pub/sub, streams, Lua, cluster, persistence, rate limiting, session store, queue

- Skill: `mattakushi432/redis-patterns` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/redis-patterns`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/redis-patterns/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/redis-patterns

---

# Redis Patterns

## Core Data Structures

```bash
# Strings — counters, cache
SET user:1:name "Alice" EX 3600
INCR page:views
SETNX lock:resource 1  # atomic set if not exists

# Hashes — objects
HSET user:1 name "Alice" email "a@b.com" age 30
HGETALL user:1
HINCRBY user:1 login_count 1

# Lists — queues, feeds
LPUSH queue:jobs task1 task2   # push left
BRPOP queue:jobs 30            # blocking pop right
LRANGE feed:user:1 0 49        # paginate

# Sets — unique items, social graphs
SADD user:1:friends 2 3 4
SINTERSTORE common_friends user:1:friends user:2:friends

# Sorted Sets — leaderboards, priority queues
ZADD leaderboard 1500 "user:1"
ZREVRANGE leaderboard 0 9 WITHSCORES  # top 10
ZRANGEBYSCORE events 1700000000 1700086400  # time window
```

## Pub/Sub and Streams

```bash
# Pub/Sub (fire and forget — no persistence)
SUBSCRIBE channel:notifications
PUBLISH channel:notifications '{"type":"alert","msg":"hello"}'

# Streams — persistent, consumer groups
XADD events:stream * type "click" user_id "42"
XGROUP CREATE events:stream workers $ MKSTREAM
XREADGROUP GROUP workers consumer1 COUNT 10 BLOCK 2000 STREAMS events:stream >
XACK events:stream workers <message-id>

# Python stream consumer
import redis
r = redis.Redis()
while True:
    msgs = r.xreadgroup('GROUP', 'workers', 'c1', count=10,
                         block=2000, streams={'events:stream': '>'})
    for stream, messages in (msgs or []):
        for msg_id, data in messages:
            process(data)
            r.xack('events:stream', 'workers', msg_id)
```

## Lua Scripting (Atomic Operations)

```lua
-- Rate limiter (sliding window)
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])

redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local count = redis.call('ZCARD', key)
if count < limit then
  redis.call('ZADD', key, now, now)
  redis.call('EXPIRE', key, window / 1000)
  return 1
end
return 0
```

```python
rate_limit_script = r.register_script(lua_script)
allowed = rate_limit_script(
    keys=[f'rate:{user_id}'],
    args=[int(time.time() * 1000), 60000, 100]  # 100 req/min
)
```

## Caching Patterns

```python
# Cache-aside (most common)
def get_user(user_id):
    cached = r.get(f'user:{user_id}')
    if cached:
        return json.loads(cached)
    user = db.query(User).get(user_id)
    r.setex(f'user:{user_id}', 300, json.dumps(user.to_dict()))
    return user

# Cache stampede prevention (probabilistic early expiration)
def get_with_lock(key, ttl, fetch_fn):
    val = r.get(key)
    if val:
        return json.loads(val)
    lock_key = f'lock:{key}'
    if r.set(lock_key, 1, nx=True, ex=10):
        val = fetch_fn()
        r.setex(key, ttl, json.dumps(val))
        r.delete(lock_key)
        return val
    time.sleep(0.1)
    return get_with_lock(key, ttl, fetch_fn)
```

## Cluster and Persistence

```bash
# Cluster — 16384 hash slots across nodes
redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 \
  127.0.0.1:7002 --cluster-replicas 1

# Hash tags for multi-key atomicity in cluster
SET {user:1}:profile "..."
SET {user:1}:settings "..."  # same slot as profile

# Persistence config
# RDB: snapshot every N seconds if M keys changed
save 900 1
save 300 10
# AOF: append-every-second (balance durability vs perf)
appendonly yes
appendfsync everysec

# INFO persistence
redis-cli INFO persistence
```

## Common Patterns

```python
# Distributed lock (Redlock)
from redis import Redis
from redlock import Redlock

dlm = Redlock([{"host": "redis1"}, {"host": "redis2"}])
lock = dlm.lock("resource", 10000)  # 10s TTL
if lock:
    try:
        do_work()
    finally:
        dlm.unlock(lock)

# Session store
r.setex(f'session:{token}', 86400, json.dumps(session_data))

# Bloom filter (RedisBloom module)
r.execute_command('BF.ADD', 'seen_emails', 'user@example.com')
exists = r.execute_command('BF.EXISTS', 'seen_emails', 'user@example.com')
```

