Bentocache

Bentocache is a robust multi-tier caching solution for Node.js applications

README

image

Bentocache is a robust multi-tier caching solution for Node.js applications

Features


- 🗄️ Multi-tier caching
- 🔄 Synchronization of local cache via Bus
- 🚀 Many drivers (Redis, Upstash, In-memory, Postgres, Sqlite and others)
- 🛡️ Grace period and timeouts. Serve stale data when the store is dead or slow
- 🔄 Early refresh. Refresh cached value before needing to serve it
- 🗂️ Namespaces. Group your keys by categories.
- 🛑 Cache stamped protection.
- 🏷️ Named caches
- 📖 Well documented + handy JSDoc annotations
- 📊 Events. Useful for monitoring and metrics
- 📝 Easy Prometheus integration and ready-to-use Grafana dashboard
- 🧩 Easily extendable with your own driver

See documentation at bentocache.dev

Why Bentocache ?


There are already caching libraries for Node: [keyv](https://keyv.org/), [cache-manager](https://github.com/node-cache-manager/node-cache-manager#readme), or [unstorage](https://unstorage.unjs.io/). However, I think that we could rather consider these libraries as bridges that allow different stores to be used via a unified API, rather than true caching solutions as such.

Not to knock them, on the contrary, they have their use cases and cool. Some are even "marketed" as such and are still very handy for simple caching system.

Bentocache, on the other hand, is a full-featured caching solution. We indeed have this notion of unified access to differents drivers, but in addition to that, we have a ton of features that will allow you to do robust caching.

With that in mind, then I believe there is no serious alternative to Bentocache in the JavaScript ecosystem. Which is regrettable, because all other languages have powerful solutions. This is why Bentocache was created.

Quick presentation


Bentocache is a caching solution aimed at combining performance and flexibility. If you are looking for a caching system that can transition from basic use to advanced multi-level configuration, you are in the right place. Here's what you need to know :

One-level


The one-level mode is a standard caching method. Choose from a variety of drivers such as Redis, In-Memory, Filesystem, DynamoDB, and more, and you're ready to go.

In addition to this, you benefit from many features that allow you to efficiently manage your cache, such as cache stampede protection, grace periods, timeouts, namespaces, etc.

Two-levels

For those looking to go further, you can use the two-levels caching system. Here's basically how it works:

- L1: Local Cache: First level cache. Data is stored in memory with an LRU algorithm for quick access
- L2: Distributed Cache: If the data is not in the in-memory cache, it is searched in the distributed cache (Redis, for example)
- Synchronization via Bus: In a multi-instance context, you can synchronize different local in-memory caches of your instances via a Bus like Redis or RabbitMQ. This method maintains cache integrity across multiple instances

Here is a simplified diagram of the flow :

All of this is managed invisibly for you via Bentocache. The only thing to do is to set up a bus in your infrastructure. But if you need multi-level cache, you're probably already using Redis rather than your database as a distributed cache. So you can leverage it to synchronize your local caches

The major benefit of multi-tier caching, is that it allows for responses between 2,000x and 5,000x faster. While Redis is fast, accessing RAM is REALLY MUCH faster.

In fact, it's a quite common pattern : to quote an example, it's what Stackoverflow does.

Namespaces


The ability to create logical groups for cache keys together, so you can invalidate everything at once later :

  1. ```ts
  2. const users = bento.namespace('users')

  3. users.set('32', { name: 'foo' })
  4. users.set('33', { name: 'bar' })

  5. users.clear()
  6. ```

Events


Events are emitted by Bentocache throughout its execution, allowing you to collect metrics and monitor your cache.

  1. ```ts
  2. bento.on('cache:hit', () => {})
  3. bento.on('cache:miss', () => {})
  4. // ...
  5. ```

See the events documentation for more information.

Friendly TTLs


All TTLs can be passed in a human-readable string format. We use lukeed/ms under the hood. (this is optional, and you can pass anumber in milliseconds if you prefer)

  1. ```ts
  2. bento.getOrSet('foo', () => getFromDb(), {
  3.   ttl: '2.5h'
  4.   gracePeriod: { enabled: true, duration: '6h' }
  5. })
  6. ```

Early refresh


When you cached item will expire soon, you can refresh it in advance, in the background. This way, next time the entry is requested, it will already be computed and thus returned to the user super quickly.

  1. ```ts
  2. bento.getOrSet('foo', () => getFromDb(), {
  3.   earlyExpiration: 0.8
  4. })
  5. ```

In this case, when only 20% or less of the TTL remains and the entry is requested :

- It will returns the cached value to the user.
- Start a background refresh by calling the factory.
- Next time the entry is requested, it will be already computed, and can be returned immediately.

Logging


You can pass a logger to Bentocache, and it will log everything that happens. Can be useful for debugging or monitoring.

  1. ```ts
  2. import { pino } from 'pino'

  3. const bento = new BentoCache({
  4.   logger: pino()
  5. })
  6. ```

See the logging documentation for more information.

Sponsor


If you like this project, please consider supporting it by sponsoring it. It will help a lot to maintain and improve it. Thanks a lot !