layercache – Multi-Layer-Cache-Bibliothek für Node.js
(github.com/flyingsquirrel0419)Was ist layercache?
layercache ist eine Multi-Layer-Cache-Bibliothek für Node.js, die Memory → Redis → Disk unter einer einzigen API zusammenfasst.
Bei einem Cache-Hit wird der Wert aus der schnellsten verfügbaren Schicht geholt und die oberen Schichten werden automatisch aufgefüllt. Bei einem Miss wird der fetcher selbst dann nur genau einmal ausgeführt, wenn 100 gleichzeitige Anfragen eingehen.
Warum wurde es entwickelt?
Wenn man Node.js-Services betreibt, folgt der Aufbau von Caching-Layern meist einem ähnlichen Muster. Man beginnt mit einem In-Memory-Cache, bindet Redis an, sobald die Anzahl der Instanzen steigt, stößt dann auf das Stampede-Problem, bekommt Cache-Inkonsistenzen zwischen Instanzen und so weiter. Jedes dieser Probleme lässt sich für sich lösen, aber all das auf Production-Niveau in einem Schritt sauber zusammenzuführen, war aufwendiger als gedacht.
Deshalb wurde es mit dem Ziel entwickelt, diese Arbeit ein einziges Mal richtig zu erledigen.
Was sind die Hauptfunktionen?
Kernverhalten
- Geschichtetes Lesen + automatisches Backfill (L1-Miss → L2-Abfrage → L1 auffüllen)
- Stampede prevention: 100 gleichzeitige Anfragen →
fetcherwird 1-mal ausgeführt - Distributed single-flight: Redis-Distributed-Lock verhindert doppelte Ausführung zwischen Instanzen
- Redis-pub/sub-basiertes L1-Invalidation-Bus (Synchronisierung des In-Memory-Caches zwischen Instanzen)
Invalidierung / Aktualität
- Tag-basierte Invalidierung, Wildcard-/Präfix-Invalidierung
- Stale-while-revalidate, Stale-if-error
- Sliding TTL, Adaptive TTL, Refresh-ahead
Resilienz
- Graceful degradation: Bei Redis-Ausfällen wird die Schicht übersprungen und später automatisch wiederhergestellt
- Circuit breaker
- Strict- / Best-Effort-Schreibrichtlinien
Observability
- Prometheus-Exporter, OpenTelemetry-Hooks
- Messung der Latenz pro Layer, Event-Hooks
- Admin-CLI (
npx layercache stats|keys|invalidate)
Framework-Integration
Express, Fastify, Hono, tRPC, GraphQL, Next.js
Benchmark-Zahlen
Die Werte basieren auf einer Single-Core-VM + echtem Docker-Redis.
| Szenario | Durchschnittliche Latenz |
| L1 Memory Warm Hit | 0.005 ms |
| L2 Redis Warm Hit (1 KiB) | 0.193 ms |
| Kein Cache (DB-Simulation) | 5.030 ms |
- HTTP-Durchsatz:
/layered16,211 req/s vs/nocache158 req/s - Stampede: 75 gleichzeitige Anfragen → 5 Origin-Fetches (ohne Cache wären es 375)
- Distributed single-flight: 60 gleichzeitige Anfragen → 1 Origin-Fetch
Die vollständige Benchmark-Methodik und die Rohdaten sind in docs/benchmarking.md zusammengefasst.
Worin unterscheidet es sich von bestehenden Bibliotheken?
node-cache-manager, keyv und cacheable sind alles gute Optionen. Die Unterschiede kurz zusammengefasst:
- Stampede prevention / Distributed single-flight: Keine der drei Bibliotheken bringt das standardmäßig mit. layercache wurde mit genau diesen beiden Punkten als Kern des Designs entwickelt.
- Cross-instance L1 invalidation: Synchronisiert den In-Memory-Cache zwischen Instanzen automatisch per Redis pub/sub. Dadurch lässt sich In-Memory-Caching auch in Multi-Instanz-Umgebungen zuverlässig einsetzen.
- Auto backfill: Füllt obere Layer automatisch auf, wenn ein unterer Layer einen Hit liefert.
- Graceful degradation + Circuit breaker: Der Service bleibt auch dann verfügbar, wenn Redis ausfällt.
Installation und Links
npm install layercache
- GitHub: https://github.com/flyingsquirrel0419/layercache
- npm: https://www.npmjs.com/package/layercache
Wenn Sie Fragen zu Designentscheidungen haben, insbesondere zur Abstimmung von single-flight oder zum Verhalten von graceful degradation, können Sie diese gerne stellen.
4 Kommentare
Eine gute Bibliothek!
Gibt es einen bestimmten Grund, warum Redis ins Design aufgenommen wurde? Ist dabei eine Situation angenommen, in der mehrere Read-only-Instanzen gleichzeitig hochfahren? Wenn ja, sollte dann nicht die (lokale) Festplatte vor Redis als vordere Ebene platziert werden?
Dass Redis enthalten ist, geht von mehreren Servern aus. Da der Speicher jedes Servers unterschiedliche Werte haben kann, übernimmt Redis die Rolle einer „gemeinsamen Wahrheit“.
Dass Disk hinter Redis kommt, liegt daran, dass Redis unter der Annahme eines gemeinsamen lokalen Netzwerks schneller ist. Laut Benchmark liegt Disk bei ~2 ms, Redis bei ~0,02 ms. Wenn Redis aber weit entfernt ist oder das Netzwerk schlecht ist, kann die lokale Disk schneller sein – dann ist es richtig, die Reihenfolge zu ändern. Die Bibliothek erzwingt diese Reihenfolge auch nicht, sondern ist so aufgebaut, dass der Benutzer sie selbst festlegt.
Der Hauptzweck von Disk ist, unabhängig davon, wo sie sich befindet, weniger der Geschwindigkeitswettbewerb als vielmehr die Rolle der letzten Absicherung, die überlebt, wenn Memory und Redis beide ausfallen.
Danke für die Erläuterung der Designabsicht. :)
Wenn ich Sie richtig verstehe, werden alle Remote-Aufrufe als Schreibvorgänge auf der lokalen Festplatte gespeichert, und wenn ein Remote-Aufruf fehlschlägt, wird stattdessen von der Festplatte gelesen, richtig? Es könnte sich lohnen, auch zu überlegen, ob eine Disk im Cache-Layer wirklich zwingend nötig ist.
DiskLayer folgt nicht diesem Muster, sondern arbeitet einfach als normale Cache-Schicht — sie liest und schreibt, und bei einem Miss in der darüberliegenden Schicht wird der Reihe nach auf die Schichten zugegriffen. Da habe ich für Verwirrung gesorgt.
Das von Ihnen erwähnte Muster, „Ergebnisse von Remote-Aufrufen auf der Festplatte speichern und sie bei einem Fehler lesen“, entspricht tatsächlich eher der Option
stale-if-error, aber die hält die Daten im Speicher, sodass sie nach einem Prozessneustart verloren gehen.Und zu dem Einwand, ob DiskLayer wirklich notwendig ist: Nun ja. In den meisten Multi-Instance-Umgebungen reicht in der Praxis Memory → Redis völlig aus, und sobald Disk als Schicht dazukommt, kommen auch Serialisierungskosten und die Komplexität der Dateiverwaltung dazu.