Zum Inhalt springen
Deutsch

Cache im Überblick

Das Cache-Interface ist der opportunistische Key/Value-Cache des Frameworks. Drei Operationen decken ~95 % der realen Fälle ab: get, set, atomares Inkrement, set-if-absent, delete.

Genutzt von:

interface Cache {
get<V>(key: string): Promise<Option<V>>;
set<V>(key: string, value: V, ttlMs?: number): Promise<void>;
incr(key: string, ttlMs?: number): Promise<number>;
setIfAbsent<V>(key: string, value: V, ttlMs?: number): Promise<boolean>;
delete(...keys: string[]): Promise<void>;
mget<V>(keys: string[]): Promise<Map<string, V>>;
mset(entries: Array<[string, unknown, number?]>): Promise<void>;
}

Kleine Oberfläche — keine Pattern-Scans, kein Pub/Sub (der Cluster hat sein eigenes Pub/Sub).

BackendEinsatz
InMemoryCacheSingle-Pod / Tests. In-Process-Map.
RedisCacheMulti-Pod-Produktion. Umschließt ioredis.
MemcachedCacheMulti-Pod, wo Memcached passt. Umschließt memjs.

Wähle nach Deployment-Form:

  • Single-Pod — InMemoryCache. Schnell, keine extra Peer-Deps.
  • Multi-Pod mit bereits vorhandenem Redis — RedisCache.
  • Multi-Pod mit bereits vorhandenem Memcached — MemcachedCache.
  • Multi-Pod ohne Präferenz — RedisCache. Mehr Features (Pub/Sub, Persistenz usw.) und das größere Ökosystem.

Caches sind per Design verlustbehaftet. Ein get, das None zurückgibt, heißt “nicht gecacht” — Aufgabe des Aufrufers ist, auf die Quelle der Wahrheit zurückzufallen:

const cached = await cache.get<User>(`user:${id}`);
if (cached.isSome()) return cached.value;
const user = await db.users.findById(id); // ← Quelle der Wahrheit
await cache.set(`user:${id}`, user, 60_000);
return user;

Wenn set fehlschlägt (Redis down, Netzwerk-Aussetzer), bleibt der Cache leer — aber der Aufruf liefert trotzdem die richtige Antwort (die Quelle der Wahrheit wurde konsultiert).

Cache-Implementierungen liefern bei transienten Fehlern Defaults zurück, statt zu werfen. Missbrauch (schlechte TTL, malformed Value) wirft.

await cache.set('key', value, 60_000); // läuft in 60s ab
await cache.set('key', value); // kein Ablauf
  • Mit ttlMs — Eintrag läuft nach dem Fenster ab.
  • Ohne — Eintrag bleibt, bis er evicted wird (LRU bei InMemoryCache; Backend-Policy bei Redis / Memcached).

Die meisten Einsätze: immer eine TTL setzen. Einträge ohne TTL wachsen bis zur Eviction; explizite TTLs sind vorhersehbar.

const count = await cache.incr(`requests:${userId}`, 60_000);
if (count > 100) throw new Error('rate limit');

incr liefert den neuen Zählerstand nach dem Inkrement. Wenn ttlMs gesetzt ist UND der Zähler gerade erst erzeugt wurde (count === 1), wird die TTL gesetzt.

Wird von der Rate-Limit-Middleware für Fixed-Window-Counter genutzt.

const got = await cache.setIfAbsent('lock:key', 'me', 5_000);
if (got) {
// Ich habe das Rennen gewonnen; mach die Arbeit
} else {
// Jemand anderes hat es
}

Atomarer Schreibvorgang im CAS-Stil. Wird von der Idempotency- Key-Middleware genutzt, um “ich bin der erste Request mit diesem Key” zu erkennen.

const users = await cache.mget<User>(['user:1', 'user:2', 'user:3']);
// → Map<string, User> — fehlende Keys nicht enthalten

Round-Trip-Optimierung. Entscheidend für Hydratation geteilter Entitäten nach einem Sharding-Rebalance — den State jeder aktiven Entität in einem Redis-Call statt N holen.

mset ist das Gegenstück:

await cache.mset([
['user:1', user1, 60_000],
['user:2', user2, 60_000],
]);

Die Cache-API-Referenz deckt das volle Interface ab.