Skip to content

RedDB navigation

tiered TTL

Cache

Tiered blob cache built into the engine: L1 memory + L2 durable.

What it is

Cache on RedDB.

A tiered cache module purpose-built for the application caching workload Redis dominates today. L1 is a process-local memory shard with SIEVE / TinyLFU-style admission. L2 is a durable native cache store inside the database file.

Namespace + key + opaque bytes is the surface. TTL is per-entry, prefix and tag invalidation are first-class, and a Bloom filter sits between L1 and L2 so cache misses stay cheap.

If you came in for Redis-style ephemeral caching, this is the matching module — durability and the rest of RedDB's data models come along for free.

Code

Write it. Read it. Same engine.

Insert
CACHE PUT sessions 'abc123' '<bytes>' WITH TTL 3600s;
Query
CACHE GET sessions 'abc123';
CACHE INVALIDATE PREFIX sessions 'org:42:';

Driver examples

Connect from your language.

Real driver code — copy-paste ready, no pseudocode.

TypeScript
import { Client } from '@reddb/client'

const db = new Client({ url: process.env.REDDB_URL })

// Store a value with a 1-hour TTL
await db.query(
  "CACHE PUT 'session:abc123' = $1 TTL 3600",
  [JSON.stringify({ userId: 42, role: 'admin' })],
)

// Read it back — returns null on miss
const { rows } = await db.query("CACHE GET 'session:abc123'")
const session = rows[0]?.value ? JSON.parse(rows[0].value) : null

// Invalidate by tag
await db.query("CACHE INVALIDATE TAGS $1", [['user:42']])

Use cases

Where cache earn their place.

  • Session stores

    User sessions with TTL, fast lookup on every request, durable across restarts.

  • API response cache

    Memoised expensive third-party API responses keyed by request signature.

  • Render caches

    Computed page fragments / personalisation slots invalidated by tag when the source changes.

  • Migration off Redis

    Replace a Redis cluster while keeping the cache miss / invalidation contract Redis users expect.

Build it

End-to-end walkthrough.

Four steps from empty database to a real cache workload — each step shows the exact code and explains what the engine is doing.

  1. Step 1

    1. Create a namespace

    Namespaces let you size and configure each cache workload separately. L1 is process-local memory (SIEVE / TinyLFU admission); L2 is durable in the database file. The DEFAULT TTL applies when a PUT does not specify one.

    Step 1
    CREATE CACHE sessions
      L1 BYTES 256MB
      L2 BYTES 4GB
      DEFAULT TTL 1h;
  2. Step 2

    2. Put + get

    Tags are first-class. A session blob can be tagged by both user:42 and org:7 so it can be invalidated by either dimension later. Bytes are opaque — the engine never parses them.

    Step 2
    CACHE PUT sessions 'abc123' '<session-blob-bytes>'
      WITH TTL 3600s
      TAGS ['user:42', 'org:7'];
    
    CACHE GET sessions 'abc123';
    -- returns the bytes if present + not expired,
    -- otherwise returns null and increments the miss counter.
  3. Step 3

    3. Invalidation by prefix or tag

    Two invalidation surfaces for two patterns. Prefix is great when the key carries the dimension (org:42:abc123); tags are great when the dimension is orthogonal to the key. Both fire in O(matching) time.

    Step 3
    -- Bulk invalidate every session under org 42.
    CACHE INVALIDATE PREFIX sessions 'org:42:';
    
    -- Or invalidate everything tagged 'user:42'.
    CACHE INVALIDATE TAGS ['user:42'] FROM sessions;
  4. Step 4

    4. Read-through helper

    The Node / Python / Rust drivers ship a read-through helper so you write the cache pattern once instead of in every callsite. Cache hit is one round trip; miss is two.

    Step 4
    -- App-side pseudocode
    let cached = await db.cacheGet('sessions', sessionId)
    if (cached) return cached
    let computed = await rebuildSession(sessionId)
    await db.cachePut('sessions', sessionId, computed,
      { ttlMs: 3600_000, tags: [`user:${userId}`] })
    return computed

Engine features

What RedDB adds on top of cache.

The engine knobs you would otherwise wire up yourself — TTL, encryption, indexes, atomic ops, eventual consistency — applied to this data model.

  • Tiered L1 (memory) + L2 (durable)

    L1 is process-local memory with SIEVE/TinyLFU admission. L2 is durable shard inside the database file. Hot keys stay in L1; cold keys live in L2 — restarts do not flush the working set.

  • TTL per entry + namespace default

    CACHE PUT ... WITH TTL <ms> is per-entry. CREATE CACHE ... DEFAULT TTL sets the namespace fallback. Entries without explicit TTL inherit the namespace value.

  • Bloom filter for misses

    A space-efficient membership filter sits between L1 and L2. Negative answers skip the L2 lookup — cache misses stay cheap even at scale.

  • Tag-based invalidation

    CACHE PUT … TAGS [...] attaches arbitrary tags. CACHE INVALIDATE TAGS [...] fans out across L1 + L2 in one call. Lets you invalidate by orthogonal dimensions (user:42, org:7) without prefix gymnastics.

  • Prefix invalidation

    CACHE INVALIDATE PREFIX namespace 'org:42:' drops every key under that prefix. O(matching), not O(total). Pairs with prefixed-key conventions.

  • Read-through driver helper

    Every official driver (TS / Python / Rust / Go) ships a readThrough(namespace, key, builder) helper — single round trip on hit, two on miss. The miss path is in your driver, not in 30 callsites.

  • Same backup story as the rest of the engine

    Cache lives in the .rdb file. Snapshot + WAL archive cover it just like tables. No separate Redis backup pipeline to operate.

  • Per-namespace policies

    Policies attach per cache namespace. A worker role can read+write sessions but only read feature_flags. Tenant isolation works the same way — namespace per tenant + a policy per principal.

  • PITR via WAL archive

    Because cache shares the WAL, point-in-time recovery is free — restore the database to a wall-clock timestamp and the cache state at that timestamp comes back too. Sessions survive a botched deploy rollback.

Showcases

What cache can do once you bring the rest of the engine in.

Cross-model correlation, algorithm flex, multi-model patterns — things that take a stack of services elsewhere.

TTLs and quotas configured at runtime via KV

Cache-namespace defaults read $config.<path> so you tune them without redeploys.

A cache namespace reads its default TTL and capacity hints from KV at runtime. Operations team adjusts a single key during an incident — every CACHE PUT after that picks up the new value. No app rebuild, no Helm chart edit, no deploy.

sql
TTLs and quotas configured at runtime via KV
-- Knobs live in KV (engine config namespace).
SET CONFIG acme.cache.sessions.defaultTtlMs = 3600000;
SET CONFIG acme.cache.sessions.maxBytes     = 268435456;

-- Cache writes pull TTL from config.
CACHE PUT sessions $session_id $blob
  WITH TTL $config.acme.cache.sessions.defaultTtlMs ms
  TAGS [$user_tag];

-- Tune during an incident. No rebuild.
SET CONFIG acme.cache.sessions.defaultTtlMs = 600000;

Cache invalidation tied to source data writes

Update a row in users and drop the matching cache entries in the same transaction.

Bundle the row update and the CACHE INVALIDATE TAGS calls inside a single transaction. They commit together or roll back together — no "I changed the user but the cached session still says old name" window, and no application-tier coordinator to keep in sync.

sql
Cache invalidation tied to source data writes
BEGIN;

UPDATE users
   SET email = $1
 WHERE id = 42;

CACHE INVALIDATE TAGS sessions ['user:42'];
CACHE INVALIDATE TAGS render_fragments ['user:42'];

COMMIT;

Per-tenant cache namespaces with quota

Each customer gets a sized cache, isolated by namespace, billed per-tenant.

A multi-tenant SaaS gives every customer 64MB of L1 cache. Namespacing keeps blast radius scoped: a noisy tenant can't evict another tenant's hot data. The meta->>'org' filter on a periodic stats query produces per-tenant billing.

sql
Per-tenant cache namespaces with quota
-- Provision per tenant.
CREATE CACHE org_42 L1 BYTES 64MB L2 BYTES 1GB DEFAULT TTL 1h;

-- Usage stats per tenant for billing.
SELECT
  namespace,
  hits,
  misses,
  bytes_used_l1,
  bytes_used_l2,
  evictions
FROM CACHE_STATS
WHERE namespace LIKE 'org_%'
ORDER BY bytes_used_l2 DESC;

Migrate from

Bring cache over from your current tool.

From

Redis cache

Most Redis cache patterns map 1:1 (GET / SET / SETEX / DEL / EXPIRE / EXISTS). The migration guide in our docs walks through each.

Migrate from Redis cache
# Side-by-side dual-write during migration.
red migrate cache --from "redis://prod:6379" \
  --to "reds://admin@.../my-db:5050" \
  --namespace sessions \
  --pattern "session:*" \
  --strip-prefix "session:"

From

Memcached

Walk the slabs, replay every entry as a PUT with the original TTL. Keys without TTL inherit the namespace default.

Migrate from Memcached
red migrate cache --from "memcached://prod:11211" \
  --to   "reds://admin@.../my-db:5050" \
  --namespace api_responses

Recipes

Common patterns, real driver code.

  • Read-through helper

    typescript

    Idiomatic cache pattern in 6 lines — the driver hides the miss path.

    typescript
    async function getSession(id: string) {
      return db.cache.readThrough('sessions', id, async () => {
        const session = await rebuildSession(id)
        return { value: session, ttlMs: 3600_000, tags: [`user:${session.userId}`] }
      })
    }
  • Tag-based invalidation

    sql

    When a user changes their password, kill every session tagged with their user id — one statement, fans out across L1 + L2.

    sql
    CACHE INVALIDATE TAGS ['user:42'] FROM sessions;
  • Namespace per workload

    sql

    Size each cache for its access pattern — sessions are large + long-lived, render fragments are small + short-lived.

    sql
    CREATE CACHE sessions       L1 BYTES 256MB L2 BYTES 4GB DEFAULT TTL 1h;
    CREATE CACHE render_fragments L1 BYTES 64MB  L2 BYTES 1GB DEFAULT TTL 5m;
    CREATE CACHE api_responses    L1 BYTES 128MB L2 BYTES 2GB DEFAULT TTL 30s;

Limits

What it does. What it costs.

Operational caps and durability semantics, no hand-waving.

Max value size
~10 MB per entry (configurable per namespace)
L1 hit latency
Single-digit microseconds (in-process)
L2 hit latency
Sub-millisecond (durable shard)
Eviction
TTL + SIEVE/TinyLFU admission, scan-resistant
Invalidation
Key, prefix, tag, dependency — all O(matching)

When NOT to use

Don't pick cache for these.

Honest constraints — when another model fits better.

  • Don't use Cache as the source of truth

    Cache evicts under pressure. State you can't reconstruct (auth tokens, billing state, user data) belongs in tables / KV with explicit durability semantics.

  • Don't cache results that change every request

    A 30-second TTL on a per-request response is hot path overhead, not optimisation. Profile first; cache where the same answer goes to many requests.

  • Avoid pub/sub patterns through cache TTL hacks

    Cache is not a message bus. For cross-service notifications use the queue primitive or KV's WATCH stream.

vs

How RedDB cache compare.

  • Redis

    Same hot-path latency for L1 hits, plus durability and one-engine ops. No sync between cache and the database it caches.

  • Memcached

    Tag and prefix invalidation are first-class. Multi-tenancy via namespace is native.

Pairs with

Cache earns more next to these.

The data models cache compose with — a click away.

  • Tables

    Read-through caches over relational queries — bundle the source UPDATE and the `CACHE INVALIDATE TAGS` call in one transaction so the cache evicts in the same commit.

    relational Tables guide
  • Key-Value

    KV is durable with no eviction; Cache is ephemeral with TTL. Sessions in Cache, idempotency keys in KV.

    fast state Key-Value guide
  • Vectors

    Cache the embedding output for repeat queries — the same question hits the LLM API once, returns from cache the next 99 times.

Questions teams ask before they pick cache.

Explore

Every other data model on RedDB.

One engine, 9 first-class data models. Mix as needed.

  • Tables

    Postgres-wire SQL with indexes, joins, transactions.

    relational Tables guide
  • Documents

    Schemaless JSON when structure is partial — query by path.

  • Graphs

    Native edges and traversals — multi-hop without a side-car.

  • Vectors

    Auto-embed on insert, similarity search, no vector DB to sync.

  • Key-Value

    Configuration, feature flags, hot keys — millisecond reads.

    fast state Key-Value guide
  • Time-series

    Retention, downsampling, native window queries.

  • Queues

    FIFO, priority, consumer groups — durable workflow primitives.

    workflows Queues guide
  • Probabilistic

    HyperLogLog, Count-Min Sketch, Cuckoo Filter — bounded-memory analytics.

Try cache on RedDB.

Free nano database, no credit card. Self-host under BSL 1.1 or managed Cloud — same engine, same file format.