I wanted to ask a database a question it isn't supposed to answer:
What if this had happened instead — without re-running history from scratch?
Every system I build has the same blind spot. To test a different past you copy the data, replay everything, and pray it's deterministic. So I tried the opposite premise: don't store the world after computing it somewhere else — make the database itself BE the world.
The result is Loom: a deterministic simulation whose authoritative reality lives entirely inside Aurora DSQL. Three properties fall out of that one decision:
- Each tick is one SQL transaction.
- Time-travel is a query (just read a smaller tick).
- Forking the universe is O(1) — one inserted row. Pre-fork history is shared, never copied.
🔗 Live demo: https://loom-ivory.vercel.app
The data model: copy-on-write, versioned state
Three tables. That's the whole engine.
-
branches— the multiverse tree. Forking =INSERTone row. -
world_meta— the strongly-consistent "now" pointer per branch. -
entity_state— copy-on-write, versioned world state. The table.
A branch only ever writes rows for ticks after its fork point. Everything before is read through its ancestors. So forking copies zero history.
CREATE TABLE entity_state (
branch_id TEXT NOT NULL,
entity_id TEXT NOT NULL DEFAULT gen_random_uuid()::text,
tick BIGINT NOT NULL,
kind TEXT NOT NULL, -- 'AGENT' | 'WORLD'
state JSONB NOT NULL,
PRIMARY KEY (branch_id, entity_id, tick)
);
A tick reads the current frame, runs a pure nextState(prev) (no RNG, no wall-clock, no iteration-order surprises), and writes back only the entities that changed. At equilibrium a calm world writes ~1 row per tick instead of thousands — divergence becomes the only thing that costs storage.
The crown jewel: one query that is both a time machine and a multiverse
Reconstructing any frame of any timeline is a single recursive query. Vary the tick → you time-travel. Vary the branch → you cross into a parallel timeline.
WITH RECURSIVE lineage AS (
SELECT branch_id, parent_branch_id, fork_tick, $2::bigint AS cap, 0 AS depth
FROM branches WHERE branch_id = $1
UNION ALL
SELECT p.branch_id, p.parent_branch_id, p.fork_tick,
LEAST(l.cap, l.fork_tick) AS cap, l.depth + 1
FROM branches p JOIN lineage l ON p.branch_id = l.parent_branch_id
)
SELECT DISTINCT ON (es.entity_id) es.entity_id, es.kind, es.state
FROM entity_state es
JOIN lineage ln ON es.branch_id = ln.branch_id AND es.tick <= ln.cap
ORDER BY es.entity_id, es.tick DESC, ln.depth ASC;
LEAST(l.cap, l.fork_tick) caps each ancestor's contribution at the child's fork point, so a branch sees its ancestors' history only up to where it split off. DISTINCT ON … ORDER BY tick DESC, depth ASC takes the newest visible version of each entity, preferring the closest branch in the lineage. That one query is the engine.
The part I checked instead of claimed
A forked branch stores zero rows for everything before its fork point — I didn't want to assume it, so the proof endpoint measures it:
{ "branch": "drought", "isFork": true, "forkTick": 15,
"rowsBeforeFork": 0, "sharedIdentical": true }
rowsBeforeFork: 0, and readWorld('drought', t) === readWorld('root', t) for every t < 15. Forking reality in two cost one branch row + one meta row.
And it holds at scale: 10,000 agents, a stable world writing about one row per tick, any moment rebuilt in roughly 8 ms, and two full 10k-agent universes stored in about 91% less space than materializing each branch in full.
Two regions, one world
This isn't a laptop Postgres. It runs on a real multi-region Aurora DSQL cluster, active-active across Tokyo (ap-northeast-1) and Seoul (ap-northeast-2). Reading the same (branch, tick) from each regional endpoint returns byte-identical frames, and a fork written through one region shows up immediately in the other — strong global consistency, no replication lag. A region-proof script fingerprints both continents and asserts the hashes match.
DSQL is Postgres-wire-compatible but not Postgres, and a few things cost me time:
-
No
DESCin index keys ("specifying sort order not supported") — use plain ascending and let the planner scan backward for theORDER BY ... tick DESC. -
Optimistic concurrency — a commit conflict comes back as
SQLSTATE 40001, not a blocking lock. The per-branch ticker just retries. - ~10k changed-row transaction cap — which is exactly why copy-on-write (write only what changed) isn't just an optimization, it's load-bearing.
Hosting it: Vercel serverless → Aurora DSQL, with no static secrets
The frontend is Next.js on Vercel. The interesting part is how the deployed app talks to DSQL without me pasting an AWS key anywhere.
The Vercel AWS integration uses OIDC role assumption: each serverless function gets a short-lived OIDC token, assumes an IAM role, and those temporary credentials mint a DSQL auth token. The whole connection layer is a few lines:
import pg from 'pg';
import { DsqlSigner } from '@aws-sdk/dsql-signer';
import { awsCredentialsProvider } from '@vercel/functions/oidc';
const signer = new DsqlSigner({
hostname: process.env.DDB_PGHOST,
region: process.env.DDB_AWS_REGION,
credentials: awsCredentialsProvider({ roleArn: process.env.DDB_AWS_ROLE_ARN }),
});
const pool = new pg.Pool({
host: process.env.DDB_PGHOST, port: 5432, user: 'admin', database: 'postgres',
ssl: { rejectUnauthorized: true },
password: () => signer.getDbConnectAdminAuthToken(), // pg re-mints per connection
});
DSQL IAM tokens are short-lived (~15 min), and pg calls the password function per new connection — so it transparently re-mints. The reconstruction query for the live timeline, the divergence chart, the "show me the SQL" receipts, the zero-rows proof — every panel on the hosted site is a real DSQL read, not an animation.
One gotcha worth knowing: the per-tick series chart did N sequential reconstructions, which is fine next to the cluster but brutal across an ocean from a function in another region. Running those reconstructions in parallel (Promise.all) collapses the wall-clock to roughly one round-trip.
Making it something you can feel
Numbers don't land in a demo, so the headline scenario is a concrete one: a small city of people who can all reach a hospital. I rewind, fork that exact moment, and inject one event — a flood that cuts the city in half. The south is stranded, resource access falls from 100% toward collapse… while the original timeline keeps flowing, untouched. Same seed, one changed event, two diverging futures — and history was never re-run.
That's the whole pitch: what-if stops being a thought experiment and becomes a row you insert. A substrate for digital twins, scenario modeling, and agent backtesting — run a thousand alternate histories of one decision on a single consistent engine and compare them.
The database isn't storing the world. It is the world — and you can rewind it, and fork it.

Top comments (0)