layercache – biblioteca de caché multicapa para Node.js
(github.com/flyingsquirrel0419)¿Qué es layercache?
Es una biblioteca de caché multicapa que unifica Memory → Redis → Disk bajo una sola API en Node.js.
Cuando hay un acierto de caché, obtiene el valor desde la capa más rápida y rellena automáticamente las capas superiores. Cuando hay un fallo, aunque entren 100 solicitudes simultáneas, el fetcher se ejecuta exactamente una sola vez.
¿Por qué se creó?
Al operar servicios en Node.js, la forma de ir apilando capas de caché suele seguir más o menos los mismos pasos: se empieza con una caché en memoria, cuando aumentan las instancias se añade Redis, luego aparece el problema de stampede, surgen inconsistencias de caché entre instancias... Cada problema se puede resolver por separado, pero enlazar todo esto de una sola vez a nivel de producción terminó requiriendo mucho más trabajo de lo esperado.
Por eso se creó con la idea de hacer bien ese trabajo una sola vez.
¿Cuáles son sus funciones principales?
Funcionamiento central
- Lectura por capas +
backfillautomático (fallo en L1 → consulta en L2 → relleno de L1) - Prevención de stampede: 100 solicitudes simultáneas → 1 ejecución del
fetcher single-flightdistribuido: elimina ejecuciones duplicadas entre instancias con bloqueo distribuido de Redis- Bus de invalidación L1 basado en Redis pub/sub (sincronización de caché en memoria entre instancias)
Invalidación / frescura
- Invalidación basada en etiquetas, invalidación por comodines/prefijos
stale-while-revalidate,stale-if-errorSliding TTL,Adaptive TTL,Refresh-ahead
Resiliencia
- Degradación elegante: si Redis falla, omite la capa y se recupera automáticamente
Circuit breaker- Políticas de escritura
strict/best-effort
Observabilidad
- Exportador para Prometheus, hooks de OpenTelemetry
- Medición de latencia por capa, hooks de eventos
- CLI de administración (
npx layercache stats|keys|invalidate)
Integración con frameworks
Express, Fastify, Hono, tRPC, GraphQL, Next.js
Quiero ver cifras de benchmark
Se basan en una VM de un solo núcleo + Redis real en Docker.
| Escenario | Latencia promedio |
| L1 en memoria, acierto en caliente | 0.005 ms |
| L2 Redis, acierto en caliente (1 KiB) | 0.193 ms |
| Sin caché (simulación de DB) | 5.030 ms |
- Throughput HTTP:
/layered16,211 req/s vs/nocache158 req/s - Stampede: 75 solicitudes simultáneas → 5
origin fetch(sin caché serían 375) Distributed single-flight: 60 solicitudes simultáneas → 1origin fetch
La metodología completa del benchmark y los resultados en bruto están resumidos en docs/benchmarking.md.
¿En qué se diferencia de las bibliotecas existentes?
node-cache-manager, keyv y cacheable son todas buenas opciones. Resumiendo brevemente las diferencias:
- Prevención de stampede /
Distributed single-flight: ninguna de las tres bibliotecas lo ofrece por defecto. layercache fue diseñada con estos dos puntos como núcleo. - Invalidación L1 entre instancias: sincroniza automáticamente la caché en memoria entre instancias con Redis pub/sub. Permite usar caché en memoria con tranquilidad en entornos multiinstancia.
Auto backfill: cuando una capa inferior acierta, rellena automáticamente las capas superiores.- Degradación elegante +
Circuit breaker: el servicio sigue vivo incluso si Redis cae.
Instalación y enlaces
npm install layercache
- GitHub: https://github.com/flyingsquirrel0419/layercache
- npm: https://www.npmjs.com/package/layercache
Si tienen preguntas sobre decisiones de diseño, especialmente sobre cómo se coordina single-flight o cómo funciona la degradación elegante, no duden en preguntar.
4 comentarios
¡Qué buena biblioteca!
¿Hay alguna razón por la que Redis esté incluido en el diseño? ¿Se asume un escenario en el que varias instancias de solo lectura se levantan al mismo tiempo? Si es así, ¿no debería el disco (local) estar ubicado en una capa anterior a Redis?
La razón por la que se incluye Redis es porque se asume un entorno con varios servidores. Como la memoria de cada servidor puede tener valores distintos entre sí, Redis cumple el papel de una "fuente de verdad compartida".
Que Disk vaya después de Redis se debe a que, bajo la suposición de que Redis está en la misma red local, es más rápido. Según los benchmarks, Disk tarda ~2ms y Redis ~0.02ms. Pero si Redis está lejos o la red es deficiente, el Disk local puede ser más rápido, y en ese caso lo correcto es cambiar el orden. La librería tampoco impone el orden; está diseñada para que el usuario lo defina directamente.
Sin importar dónde esté Disk, su propósito principal no es competir en velocidad, sino servir como el último seguro que sobrevive cuando tanto Memory como Redis dejan de funcionar.
Gracias por explicar la intención del diseño. jaja
Entonces, ¿quieres decir que todas las llamadas remotas se guardan como escrituras en disco local y, cuando falla una llamada remota, se hace una lectura desde el disco? Creo que también valdría la pena considerar si realmente es indispensable tener
Disken la capa de caché.DiskLayer no sigue ese patrón, sino que simplemente funciona como una capa de caché normal: lee y escribe, y si falla en una capa superior, accede en orden a las siguientes. Eso pudo haber causado confusión.
El patrón que mencionas de "guardar en disco el resultado de una llamada remota y leerlo si falla" en realidad se parece más a la opción
stale-if-error, pero eso se mantiene en memoria, así que se pierde cuando el proceso se reinicia.Y sobre la observación de si DiskLayer es realmente necesario, pues... en la mayoría de los entornos con múltiples instancias, Memory → Redis suele ser suficiente, y en el momento en que Disk entra como capa, vienen también el costo de serialización y la complejidad de manejar archivos.