Claude Fable 5.1 & GPT-6 Astra packages are live

Redis

Free

Using Redis as a cache, lock and queue without losing data or correctness — eviction, persistence, atomicity, and the locking pattern that actually…

234 lines7.6 KB Claude Database
targetModels
Claude Fable 5.1Claude Opus 5Claude Sonnet 5Claude 5 FamilyFuture Claude Models
name
redis
category
Database
description
Using Redis as a cache, lock and queue without losing data or correctness — eviction, persistence, atomicity, and the locking pattern that actually holds.
license
MIT
author
Agent.md maintainers
last-verified
reviewed-by
unreviewed
<!-- Generated from models/_canonical by scripts/build-model-variants.js. Edit the canonical source, not this file. Behavioural profile for Claude: scripts/model-profiles.json -->

<critical_constraints> FORBIDDEN: Truncating code or writing placeholders such as "// ... existing code ..." or "# rest unchanged". Every edit is complete and applies as written. FORBIDDEN: Reporting a check as passed without showing the command and its output. REQUIRED: Reason through the rules below before the first edit; when two rules conflict, the one stated first wins.

  • Never run a queue or session store on allkeys-lru. Redis will evict a job or a live session under memory pressure and report nothing.
  • Never run KEYS * against production. It is O(N) and blocks the single command thread for the duration. Use SCAN with a cursor.
  • Never release a lock with a bare DEL. If your work outran the TTL, the lock is now held by someone else and you have just released theirs. </critical_constraints>

#Purpose

Rules for Redis. Redis is fast because it is in memory and single-threaded for command execution. Both facts drive every rule here: memory is finite and must be bounded, and one slow command blocks every other client.

Decide first what role Redis plays. A cache may lose data. A queue or session store may not. Configure differently for each, and never mix roles in one instance.


#Keys and memory

Every key needs a TTL unless you can state why it must live forever.

arduino
namespace:entity:id:field        →  session:user:8fd2:data

A flat, prefixed namespace makes it possible to reason about, measure, and expire whole classes of key. Untracked keys with no TTL are how an instance reaches maxmemory at 3am.

bash
maxmemory 4gb
maxmemory-policy allkeys-lru        # cache role
maxmemory-policy noeviction         # queue / session role — fail writes, don't drop data
PolicyUse for
allkeys-lruPure cache — anything may be dropped
volatile-lruMixed, where only TTL'd keys are droppable
noevictionQueues, locks, sessions — losing a key is a correctness bug

Never run a queue or session store on allkeys-lru. Redis will evict a job or a live session under memory pressure and report nothing.

Never run KEYS * against production. It is O(N) and blocks the single command thread for the duration. Use SCAN with a cursor.


#Atomicity

Redis executes each command atomically, but a read-then-write in application code is not atomic.

js
// Race — two clients both read 5, both write 6
const n = await redis.get(k);
await redis.set(k, Number(n) + 1);

// Atomic
await redis.incr(k);

For anything multi-step, use a Lua script — it runs as a single atomic unit:

lua
-- Release a lock only if we still own it. Compare-and-delete must be atomic;
-- a GET-then-DEL can delete a lock that expired and was re-acquired by another
-- client in between.
if redis.call("GET", KEYS[1]) == ARGV[1] then
  return redis.call("DEL", KEYS[1])
end
return 0

MULTI/EXEC batches commands but permits no logic between them. Use WATCH for optimistic concurrency, or Lua when you need branching.


#Distributed locks

js
// Acquire: atomic set-if-absent with an expiry and a unique owner token
const token = crypto.randomUUID();
const ok = await redis.set(key, token, { NX: true, PX: 30_000 });

Three requirements, all mandatory:

  1. NX — set only if absent, in one command. A separate EXISTS check races.
  2. PX — always an expiry. A lock without one survives a crashed holder forever.
  3. A unique token — released via the compare-and-delete Lua script above.

Never release a lock with a bare DEL. If your work outran the TTL, the lock is now held by someone else and you have just released theirs.

A single-instance Redis lock is not safe across failover — the lock lives only in memory on the primary and is lost on election. For correctness-critical mutual exclusion, use the database (SELECT … FOR UPDATE, or a unique constraint), not Redis. Redis locks are appropriate for reducing duplicate work, not for preventing double-spend.


#Caching patterns

Cache-aside is the default: read cache, miss → read source → write cache with TTL.

js
const hit = await redis.get(k);
if (hit) return JSON.parse(hit);
const fresh = await loadFromDb();
await redis.set(k, JSON.stringify(fresh), { EX: 300 });
return fresh;

Two failure modes worth designing against:

  • Stampede. A hot key expires and a thousand requests hit the database at once. Fix with a short lock around the recompute, or jittered TTLs (EX: 300 + random(60)).
  • Stale after write. Invalidate on write; do not rely on TTL alone for data the user just changed and expects to see.

Every cache needs a stated invalidation mechanism before it is added. → Performance/caching


#Persistence

ModeGuarantee
NoneEverything lost on restart
RDB snapshotLose everything since the last snapshot
AOF everysecLose at most one second
AOF alwaysNo loss, significant throughput cost

A cache needs no persistence. A queue needs AOF at minimum — and if losing a job is genuinely unacceptable, it belongs in a durable broker or a database table, not in Redis. Be explicit about which you have chosen.


#Anti-patterns

Anti-patternWhy it failsFix
KEYS * in productionO(N), blocks the single threadSCAN
No TTL on cache keysMemory grows to maxmemory, then evicts randomlyTTL on every cache key
allkeys-lru on a queueJobs silently evictednoeviction for durable roles
Cache and queue in one instanceOne eviction policy cannot serve bothSeparate instances
GET-then-SET counterLost updates under concurrencyINCR
Lock without PXCrashed holder deadlocks the systemAlways set an expiry
Lock released with DELReleases someone else's lock after TTL expiryCompare-and-delete in Lua
Redis lock for financial correctnessLost on failoverDatabase lock or constraint
Uniform TTLs on hot keysSynchronised stampedeJitter the TTL
Large values (multi-MB)Blocks the thread on transferKeep values small; paginate
Big MGET/pipeline of 100k keysOne slow command stalls all clientsChunk the batch

#Checklist

  • The role of each instance (cache / queue / session) is explicit
  • maxmemory and an appropriate maxmemory-policy are set for that role
  • Durable roles run noeviction, never allkeys-lru
  • Every cache key carries a TTL, with jitter on hot keys
  • Keys follow a documented namespace convention
  • KEYS is not used in application or operational code
  • Read-modify-write sequences use atomic commands or Lua
  • Locks use SET NX PX with a unique token
  • Lock release is a compare-and-delete Lua script
  • Correctness-critical exclusion uses the database, not Redis
  • Persistence mode matches the durability the role requires
  • Cache invalidation on write is implemented, not just TTL expiry