A collection of demos showing how an app can adopt the pseudotime pattern in its data layer.
Pseudotime is a lightweight approach to temporal data that lets you query "what did the data look like at point N in time?" without any wallclock machinery or event-sourcing infrastructure.
Instead of real timestamps, each row carries a monotonically incrementing integer — the pseudotime — that advances once per write transaction. Writes never mutate existing rows in place; instead they expire the old revision and insert a new one. This makes full history retention and point-in-time reads a natural consequence of the schema rather than a bolt-on.
Wall-clock timestamps carry hidden problems: clock drift, skew across machines, timezone ambiguity, and the inability to express "strictly before this event" rather than "before this millisecond." A pseudotime counter sidesteps all of that. It advances exactly once per transaction, giving you a clean causal ordering with no clock machinery involved. Two events that coincide in wall-clock time are still strictly ordered; two events that happen milliseconds apart are indistinguishable if no write separates them — which is exactly the right semantics for data history.
This makes pseudotime a genuinely different thing from SQL:2011 temporal tables or most SCD Type 2 implementations, which tie history to real timestamps and inherit their problems. The closest real-world analogues are Datomic's transaction t value (each write increments a logical counter; db.asOf(t) queries at that point) and Git (a repository is a "world," each commit is a pseudotime tick, git checkout <sha> is a point-in-time read).
A world is the unit of causal isolation: the scope within which the pseudotime counter is authoritative and consistent. All writes to the same world share a single counter; writes to different worlds are causally independent and carry no ordering relationship to each other.
Where you draw the world boundary is a domain decision, not a framework decision. A multi-tenant app might give each tenant its own world. A simulation might fork worlds to explore "what if" scenarios. A workflow engine might isolate each job. No database feature or ORM can infer this boundary for you, because it reflects the semantics of your application.
Core columns every pseudotime table carries:
| Column | Meaning |
|---|---|
eid |
Stable entity ID (same across all revisions of one logical record) |
created_at_pseudotime |
The pseudotime at which this revision was born |
valid_before_pseudotime |
The pseudotime at which this revision was superseded (nil = still current) |
A point-in-time query for pseudotime T filters for rows where created_at_pseudotime <= T and (valid_before_pseudotime >= T or valid_before_pseudotime is nil).
Properties:
- Immutable writes — updates expire the old revision (set
valid_before_pseudotime) and insert a new one - Deletes without a successor — deletions only expire the current row; no new revision is inserted
- Full history — the entire audit trail lives in the same table, no separate log needed
- World isolation — multiple independent timelines ("worlds") coexist in the same tables
| Demo | Stack | Description |
|---|---|---|
| ruby-sqlite | Ruby + SQLite via Sequel | A simple todo list showing inserts, updates, and deletes — then time-travel queries back through the history |
-
David P. Reed — Naming and Synchronization in a Decentralized Computer System (MIT PhD thesis, 1978) The foundational treatment of treating mutable objects as sequences of immutable versions named by an integer counter, with synchronization as the mechanism for ordering those versions. The pseudotime schema is a direct descendant of this model.
-
Rich Hickey — Are We There Yet? (JVM Language Summit keynote, 2009) Argues that time should be understood as a derivative of observing a succession of immutable values — not as a property of mutable objects. Introduces the epochal time model that underlies Datomic and motivates designs like pseudotime.