Skip to main content
The data model page covered why there are three schemas. This page covers how the runtime keeps them isolated and fast.

Lazy, pooled, per-mode clients

Database exposes core and data(mode). The data clients are instantiated lazily — a process that only ever serves test traffic never opens a connection to the live schema — and each runs with a small connection_limit so a fleet of PM2 workers doesn’t exhaust Postgres. The mode-to-schema mapping is the only place the words “sandbox” and “live” appear. Everything above it speaks in Mode ('test' | 'live'); the client translates that to a search_path.

Caching without the footguns

Reads are cached in Redis through a CacheStore with three properties that matter:

Single-flight

remember(key, ttl, fn) collapses a stampede: if a thousand requests miss the same key at once, the loader runs once and the rest await it. No cache-miss storm.

Null-cacheable

Values are wrapped in an envelope { v } so a legitimately-null result is cacheable and distinguishable from a miss. No re-querying for things that don’t exist.

Version-bump invalidation

A resource’s list cache lives under a namespace whose version is bumped on write. Invalidation is a single counter increment — never a SCAN over Redis keyspace.

Why version-bump beats key-deletion

The naive approach to “invalidate all the list pages for this tenant’s customers” is to SCAN for matching keys and delete them. SCAN is O(keyspace) and blocks. Instead, every list key embeds a version: Bumping the version makes every old key instantly unreachable. The stale entries aren’t deleted; they simply age out on their TTL while no one reads them. Invalidation is O(1) and lock-free.

The cache is mode-aware too

Cache keys are namespaced by mode:tenantId:resource. Test-mode writes can never invalidate live-mode reads, and vice versa — the same isolation guarantee as the schemas, carried into Redis. Next: the Billing Engine — the subscription state machine that everything orbits.