Advanced (Aktualisiert: 1.6.2026)

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.

Claude Code Redis-Caching: TTL, Invalidierung und Stampede-Schutz

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.

DatenBeispiel-KeyTTLInvalidierungRisiko
Öffentliche Produktlisteclaudecodelab:v1:products:list:de5 MinutenNach Produktupdate Listen-Key löschenPreisänderungen nicht nur per TTL lösen
Artikel-Detailseiteclaudecodelab:v1:posts:item:{slug}10 MinutenNach Publish, Edit oder UnpublishEntwürfe nie cachen
Admin-Statistikclaudecodelab:v1:analytics:daily:{date}30 SekundenMeist nur TTLNicht als Buchhaltungswahrheit nutzen
Externe APIclaudecodelab:v1:exchange-rate:usd-eur1 bis 15 MinutenTTL oder manueller RefreshAnbieterbedingungen prüfen
Login-NutzerdatenStandardmäßig nicht cachen0 SekundenKeinePersö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 KEYS in 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.

#Claude Code #Redis #Caching #Node.js #Performance
Kostenlos

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.

Masa

Über den Autor

Masa

Engineer für praktische Claude-Code-Workflows und Team-Einführung.