Claude Code Redis-Caching: TTL, Invalidierung und Stampede-Schutz
Praxisguide für Redis-Caching mit Claude Code: Key-Design, TTLs, Invalidierung, Node.js-Code, Tests und Review-Checkliste.
Redis ist ein schneller Key-Value-Store im Arbeitsspeicher. Daten werden also im RAM gehalten und über einen Schlüssel sehr schnell gelesen. Das passt gut zu wiederholten Datenbankaggregaten, externen API-Antworten, Rankings, kurzen Sessions und Rate Limits. Ohne klare Regeln kann Redis aber alte Preise, falsche Bestände oder private Nutzerdaten ausliefern.
Dieser Leitfaden beschreibt einen Redis-Workflow, den Sie Claude Code direkt geben können: Cache-Policy, Key-Design, TTL, Invalidierung, Stampede-Schutz, Node.js-Implementierung, Tests und Review. Für die Gesamtstrategie lesen Sie auch Claude Code Caching-Strategien; wenn Redis auch Queue-Infrastruktur ist, hilft Claude Code Queue System.
flowchart LR
Request["HTTP request"] --> Cache["Redis cache"]
Cache -->|hit| Response["Fast response"]
Cache -->|miss| Lock["Short lock"]
Lock --> Loader["DB or external API"]
Loader --> Cache
Loader --> Response
Admin["Update event"] --> Invalidate["Delete known keys"]
Invalidate --> Cache
Cache-Policy zuerst
Schreiben Sie vor der Implementierung eine Policy in CLAUDE.md oder in die Aufgabe. Claude Code kann Datenflüsse erkennen, aber nicht wissen, wie alt ein fachlicher Wert sein darf.
| Daten | Beispiel-Key | TTL | Invalidierung | Risiko |
|---|---|---|---|---|
| Öffentliche Produktliste | claudecodelab:v1:products:list:de | 5 Minuten | Nach Produktupdate Listen-Key löschen | Preisänderungen nicht nur per TTL lösen |
| Artikel-Detailseite | claudecodelab:v1:posts:item:{slug} | 10 Minuten | Nach Publish, Edit oder Unpublish | Entwürfe nie cachen |
| Admin-Statistik | claudecodelab:v1:analytics:daily:{date} | 30 Sekunden | Meist nur TTL | Nicht als Buchhaltungswahrheit nutzen |
| Externe API | claudecodelab:v1:exchange-rate:usd-eur | 1 bis 15 Minuten | TTL oder manueller Refresh | Anbieterbedingungen prüfen |
| Login-Nutzerdaten | Standardmäßig nicht cachen | 0 Sekunden | Keine | Persönliche Daten nicht in Shared Cache |
Öffentliche, reproduzierbare Daten sind gute Kandidaten. Berechtigungen, Rechnungen, Authentifizierung und personenbezogene Daten bleiben draußen, bis es dafür ein eigenes Sicherheitsdesign gibt.
Aufgabe für Claude Code
Füge dieser Node.js-App eine Redis-Cache-Aside-Schicht hinzu.
Anforderungen:
1. Nutze das offizielle node-redis-Paket redis
2. Baue Keys als claudecodelab:v1:{domain}:{resource}:{id}
3. Wähle TTLs aus der Cache-Policy und füge bis zu 10% Jitter hinzu
4. Lösche bekannte verwandte Keys erst nach erfolgreichem DB-Write
5. Verwende KEYS nicht in Production-Code; nutze bekannte Keys, SCAN oder Related-Key-Sets
6. Verhindere Cache Stampede bei beliebten Keys mit einem kurzen Lock
7. Ergänze node:test für Key-Generierung, TTL-Bereich, getOrSet und parallele Misses
Gib zurück:
- Geänderte Dateien
- Ausgeführte Tests
- Daten, die bewusst nicht gecacht wurden, mit Begründung
Diese Regeln passen gut in die Claude Code Code-Review-Checkliste.
Node.js-Implementierung
Der offizielle Node.js-Client ist das Paket redis; die Verbindungsmuster stehen im node-redis guide.
mkdir redis-cache-demo
cd redis-cache-demo
npm init -y
npm install redis
docker run --name redis-cache-demo -p 6379:6379 -d redis:7-alpine
Zuerst kommen Key- und TTL-Regeln in eine eigene Datei.
// cache-policy.js
const CACHE_PREFIX = "claudecodelab";
const CACHE_VERSION = "v1";
const CACHE_POLICY = {
productList: { ttl: 300, jitter: 30 },
productItem: { ttl: 600, jitter: 60 },
dailyStats: { ttl: 30, jitter: 5 },
};
function normalizePart(value) {
const part = String(value).trim().toLowerCase();
if (part.length === 0) {
throw new Error("cache key part must not be empty");
}
return encodeURIComponent(part);
}
function cacheKey(parts) {
if (!Array.isArray(parts) || parts.length === 0) {
throw new Error("cacheKey requires a non-empty parts array");
}
return [CACHE_PREFIX, CACHE_VERSION, ...parts.map(normalizePart)].join(":");
}
function ttlWithJitter(baseSeconds, maxJitterSeconds = 30) {
if (!Number.isInteger(baseSeconds) || baseSeconds <= 0) {
throw new Error("base TTL must be a positive integer");
}
const jitter = Math.max(0, Math.floor(maxJitterSeconds));
return baseSeconds + Math.floor(Math.random() * (jitter + 1));
}
module.exports = { CACHE_POLICY, cacheKey, ttlWithJitter };
Die Redis-Verbindung wird geteilt und nicht pro Request neu geöffnet.
// redis-client.js
const { createClient } = require("redis");
const redis = createClient({
url: process.env.REDIS_URL || "redis://localhost:6379",
});
redis.on("error", (error) => {
console.error("Redis Client Error", error);
});
let connecting;
async function getRedis() {
if (redis.isOpen) return redis;
if (!connecting) {
connecting = redis.connect();
}
await connecting;
return redis;
}
async function closeRedis() {
if (redis.isOpen) {
await redis.quit();
}
connecting = undefined;
}
module.exports = { getRedis, closeRedis };
Der Helper implementiert Cache-Aside mit JSON, TTL und einem kurzen Lock gegen Cache Stampede.
// redis-cache.js
const { randomUUID } = require("node:crypto");
const UNLOCK_SCRIPT = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
end
return 0
`;
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
class RedisJsonCache {
constructor(redis, options = {}) {
this.redis = redis;
this.defaultTtl = options.defaultTtl || 300;
this.lockMs = options.lockMs || 5000;
this.waitMs = options.waitMs || 50;
this.waitRetries = options.waitRetries || 10;
}
async get(key) {
const raw = await this.redis.get(key);
if (raw === null) return null;
try {
return JSON.parse(raw);
} catch {
await this.redis.del(key);
return null;
}
}
async set(key, value, ttlSeconds = this.defaultTtl) {
if (!Number.isInteger(ttlSeconds) || ttlSeconds <= 0) {
throw new Error("ttlSeconds must be a positive integer");
}
await this.redis.set(key, JSON.stringify(value), { EX: ttlSeconds });
}
async invalidate(keys) {
const list = Array.isArray(keys) ? keys : [keys];
if (list.length === 0) return 0;
return this.redis.del(list);
}
async getOrSet(key, ttlSeconds, loader) {
const cached = await this.get(key);
if (cached !== null) {
return { value: cached, cacheStatus: "hit" };
}
const lockKey = `${key}:lock`;
const token = randomUUID();
const acquired = await this.redis.set(lockKey, token, { NX: true, PX: this.lockMs });
if (acquired === "OK") {
try {
const fresh = await loader();
await this.set(key, fresh, ttlSeconds);
return { value: fresh, cacheStatus: "miss" };
} finally {
await this.redis.eval(UNLOCK_SCRIPT, { keys: [lockKey], arguments: [token] });
}
}
for (let attempt = 0; attempt < this.waitRetries; attempt += 1) {
await sleep(this.waitMs);
const afterWait = await this.get(key);
if (afterWait !== null) {
return { value: afterWait, cacheStatus: "hit-after-wait" };
}
}
const fallback = await loader();
await this.set(key, fallback, Math.max(5, Math.floor(ttlSeconds / 3)));
return { value: fallback, cacheStatus: "miss-after-timeout" };
}
}
module.exports = { RedisJsonCache };
Die Demo nutzt eine kleine In-Memory-Datenquelle. In echten Projekten ersetzen Sie loadProductsFromDb() durch Prisma, Supabase oder einen API-Client. Siehe auch Prisma ORM mit Claude Code und Supabase Integration mit Claude Code.
// demo-products.js
const { CACHE_POLICY, cacheKey, ttlWithJitter } = require("./cache-policy");
const { getRedis, closeRedis } = require("./redis-client");
const { RedisJsonCache } = require("./redis-cache");
const db = {
products: [
{ id: "p1", locale: "de", name: "CLAUDE.md Vorlage", price: 9, published: true },
{ id: "p2", locale: "de", name: "Claude Code Training", price: 199, published: true },
],
};
async function loadProductsFromDb(locale) {
await new Promise((resolve) => setTimeout(resolve, 80));
return db.products.filter((product) => product.locale === locale && product.published);
}
async function listPublishedProducts(cache, locale) {
const key = cacheKey(["products", "list", locale]);
const ttl = ttlWithJitter(CACHE_POLICY.productList.ttl, CACHE_POLICY.productList.jitter);
return cache.getOrSet(key, ttl, () => loadProductsFromDb(locale));
}
async function main() {
const redis = await getRedis();
const cache = new RedisJsonCache(redis);
const first = await listPublishedProducts(cache, "de");
const second = await listPublishedProducts(cache, "de");
console.log({ firstStatus: first.cacheStatus, secondStatus: second.cacheStatus, products: second.value });
await cache.invalidate([cacheKey(["products", "list", "de"])]);
await closeRedis();
}
main().catch(async (error) => {
console.error(error);
await closeRedis();
process.exitCode = 1;
});
node demo-products.js
Tests
Die wichtigsten Cache-Fehler lassen sich ohne Redis-Server testen.
// redis-cache.test.js
const test = require("node:test");
const assert = require("node:assert/strict");
const { cacheKey, ttlWithJitter } = require("./cache-policy");
const { RedisJsonCache } = require("./redis-cache");
class FakeRedis {
constructor() {
this.store = new Map();
}
async get(key) {
const entry = this.store.get(key);
if (!entry) return null;
if (entry.expiresAt && entry.expiresAt <= Date.now()) {
this.store.delete(key);
return null;
}
return entry.value;
}
async set(key, value, options = {}) {
if (options.NX && (await this.get(key)) !== null) return null;
const ttlMs = options.PX || (options.EX ? options.EX * 1000 : 0);
this.store.set(key, { value, expiresAt: ttlMs ? Date.now() + ttlMs : 0 });
return "OK";
}
async del(keys) {
const list = Array.isArray(keys) ? keys : [keys];
let deleted = 0;
for (const key of list) {
if (this.store.delete(key)) deleted += 1;
}
return deleted;
}
async eval(_script, options) {
const [key] = options.keys;
const [token] = options.arguments;
if ((await this.get(key)) === token) return this.del(key);
return 0;
}
}
test("cacheKey encodes dynamic parts", () => {
assert.equal(cacheKey(["Products", "List", "de/DE"]), "claudecodelab:v1:products:list:de%2Fde");
});
test("ttlWithJitter stays inside the expected range", () => {
for (let i = 0; i < 50; i += 1) {
const ttl = ttlWithJitter(300, 30);
assert.ok(ttl >= 300);
assert.ok(ttl <= 330);
}
});
test("getOrSet caches the first loader result", async () => {
const cache = new RedisJsonCache(new FakeRedis());
let loads = 0;
const first = await cache.getOrSet("products:list", 60, async () => {
loads += 1;
return [{ id: "p1" }];
});
const second = await cache.getOrSet("products:list", 60, async () => {
loads += 1;
return [{ id: "p2" }];
});
assert.equal(first.cacheStatus, "miss");
assert.equal(second.cacheStatus, "hit");
assert.equal(loads, 1);
assert.deepEqual(second.value, [{ id: "p1" }]);
});
test("getOrSet waits instead of running duplicate loaders", async () => {
const cache = new RedisJsonCache(new FakeRedis(), { waitMs: 5, waitRetries: 20 });
let loads = 0;
const loader = async () => {
loads += 1;
await new Promise((resolve) => setTimeout(resolve, 20));
return { total: 42 };
};
const results = await Promise.all([
cache.getOrSet("analytics:daily", 30, loader),
cache.getOrSet("analytics:daily", 30, loader),
]);
assert.equal(loads, 1);
assert.deepEqual(results[0].value, { total: 42 });
assert.deepEqual(results[1].value, { total: 42 });
});
node --test redis-cache.test.js
Praxisfälle
Ein Produktkatalog oder eine Artikelliste ist der beste Einstieg: viele Nutzer sehen dieselbe Antwort, und nach einem Update löschen Sie bekannte Keys.
Admin-Dashboards sind ein zweiter Fall. Page Views, Conversion Rate und Umsatzvorschau dürfen oft 30 Sekunden bis 5 Minuten alt sein; Berechtigungen, Rechnungen und Sicherheitsentscheidungen nicht.
Externe APIs sind ein dritter Fall. Wechselkurse, Wetter, SaaS-Pläne und öffentliche GitHub-Metadaten können kurz gecacht werden, solange die Anbieterbedingungen es erlauben.
Rate Limits sind ein vierter Fall. Redis INCR plus EXPIRE eignet sich für kurze Zeitfenster; Authentifizierungsdaten brauchen eine eigene Sicherheitsprüfung.
Häufige Fehler
Unvollständige Keys sind der Klassiker. Sprache, Währung, Tenant, Rolle, Query und Version gehören in den Key, wenn sie das Ergebnis ändern.
Cache darf nicht vor dem DB-Write gelöscht werden. Die Reihenfolge ist: DB-Write erfolgreich, Redis-Keys löschen, optional CDN purgen.
KEYS user:* gehört nicht in Production-Code. Nutzen Sie bekannte Keys, Related-Key-Sets oder SCAN in Wartungsbefehlen.
Wenn Sie negative Ergebnisse cachen wollen, speichern Sie { found: false }; die Implementierung oben behandelt null als Miss.
Review-Checkliste
- Persönliche, rollenbasierte und abrechnungsrelevante Daten sind ausgeschlossen
- Keys enthalten alle ergebnisrelevanten Bedingungen
- TTL ist fachlich begründet
- Updates löschen Keys erst nach erfolgreichem Write
- Kein
KEYSin Production-Code - Lock, Jitter oder Fallback verhindert Stampede
- Tests decken Key, TTL, Hit und parallelen Miss ab
- Hit Rate und Fallback sind beobachtbar
Offizielle Quellen und nächster Schritt
Verlinken Sie in Claude-Code-Aufgaben die Redis documentation, den node-redis guide, Redis patterns und Redis optimization.
Für wiederverwendbare Prompts, CLAUDE.md-Vorlagen und Review-Regeln starten Sie mit den ClaudeCodeLab Produkten und Templates. Wenn Redis, CDN, DB-Writes und Monitoring auf ein echtes Repository gemappt werden sollen, hilft Claude Code Training und Beratung.
Im kleinen Node.js-Test von Masa rief die erste Anfrage den Loader auf, die zweite wurde ein Redis-Hit, und parallele Misses führten nur zu einem Loader-Aufruf. Der eigentliche Gewinn lag darin, Key, TTL, Invalidierung und Review-Regeln vor der Implementierung festzulegen.
Kostenloses PDF: Claude-Code-Cheatsheet
E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.
Wir schützen Ihre Daten und senden keinen Spam.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Claude-Code-Permission-Receipt: Scope, Beweis und Rollback festhalten
Permission-Receipt für Claude Code: erlaubte Aktionen, Freigabegrenzen, Prüfbefehle, Rollback und Umsatz-CTA-Prüfung.
Sicheres Agent Harness fur Claude Code und Codex: Rechte, Prufung und Rollback
Ein praktisches Agent Harness fur Claude Code und Codex mit Policy, Plan, Verifikation und Recovery.
Claude Code Subagents: Praxisleitfaden für sichere Agent-Delegation
Claude Code Subagents praktisch nutzen: Artikel- und Codearbeit sicher aufteilen, Prompts einsetzen, Fehler vermeiden.