Point-in-time reads

Lock-free watermark snapshots and time-travel reads — generated get_at/all_at, REST ?as_of and /snapshot — with the honest note that the token is a row-count watermark, not a wall-clock instant.

ForgeDB gives you consistent point-in-time reads without per-row version metadata. A snapshot is a row-count watermark — a single cutoff into the append-only log — and reading "as of" that watermark returns the state as it was when the watermark was taken.

Watermark snapshots in generated Rust#

Database::snapshot() captures a DatabaseSnapshot — one watermark per model and junction, taken atomically on the single writer. Reads then resolve against that watermark:

let snap = db.snapshot();
 
// Live reads see the newest versions.
db.insert_post(Post { /* … */ })?;
 
// Snapshot reads still see the state at capture time.
let post = db.get_post_at(&snap, id);       // Option<Post> as of the snapshot
let all_posts = db.all_post_at(&snap);      // Vec<Post> as of the snapshot

Index probes have _at variants too — see Indexes.

Point-in-time reads over REST#

The same watermark is exposed over the generated REST API so clients can time-travel:

# List a model as of a watermark:
curl "http://localhost:8080/api/post?as_of=1200"
 
# Get one record as of a watermark:
curl "http://localhost:8080/api/post/<id>?as_of=1200"
 
# Discover the current per-model watermarks:
curl "http://localhost:8080/snapshot"
# → { "watermarks": { "Post": 1450, "User": 320 } }

A client reads /snapshot, then passes a model's watermark back as ?as_of=. A non-numeric as_of is a 400.

The token is a row-count watermark, not a wall-clock instant

as_of is an opaque usize row-count watermark, never a timestamp. There is no wall-clock-to-watermark index, so "as of 3pm yesterday" is not answerable — that is a separate, heavier feature. For offset-based recovery over the replication log, see Backup & restore.

Limits#

  • Row-count watermark, not wall-clock. No time-based ("as of timestamp T") reads.
  • Epoch-scoped. Watermarks are valid only within a compaction epoch — an in-process compact() renumbers rows, so a client must discard pinned tokens when it detects a reopen.
  • REST list ?as_of still reads full rows server-side to filter and sort; only the wire is not shrunk.
  • Single-process/single-writer capture. The snapshot is atomic because capture routes through the one writer.

Search documentation

Find pages across the ForgeDB docs