Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Time Travel

Historical reads use recorded snapshots and forward changes, not guessed undo against the current graph. For a selective node-property filter followed by a plain WITH node and one typed relationship hop, reconstruction can retain only the candidate nodes and their needed neighbors (#1261). The original query still evaluates all filters and OPTIONAL MATCH behavior. Broad or unsupported shapes fall back to the existing reconstruction path; this is not a universal cold-read latency guarantee. See limitations for qualification status.

Because every change is recorded as an engram, patinaDB can answer read queries against the graph as it was at any past engram. The state is reconstructed (nearest snapshot + forward delta replay) into a temporary view, and your query runs against that view. The live graph is never modified.

USE … AS OF

Prefix a read with USE <db> AS OF '<engram-id>' to travel back in a specific database:

USE sales AS OF '<engram-id>'
MATCH (o:Order) RETURN count(o)

Over REST you can equivalently pass an at field in the request body; over Bolt the USE … AS OF prefix is parsed per query.

Semantics & constraints

  • Reads only. Time travel reconstructs a read-only past view. You cannot write to the past or “restore” the database to an old state through time travel (that’s a different operation — full snapshot import/export).
  • Consistent point-in-time. A time-travel query sees the entire graph as it was at that engram — a coherent snapshot, not a mix of old and new.
  • Cost. Reconstruction is bounded by the distance from the nearest snapshot to the target engram. Frequent snapshots (see Engrams) keep this cheap; querying a point far from any snapshot replays more deltas. See Performance & tuning below.

An unknown engram id is an ERROR, not the empty graph (changed in issue #590). AS OF an id that is not in the database’s history — a typo’d or stale id, or one squashed away by retention (see Engrams) — fails with an actionable unknown engram … error, so your application can always tell “that point in history is gone” apart from “the query matched nothing”. (Only the low-level Dataset::reconstruct API keeps its empty-on-unknown behaviour; the user-facing AS OF / restore entry points validate the id first.)

Even so, if your application time-travels to ids it stores, prefer a tag — it is pinned against squash, so the point it names stays reachable and SHOW TAGS tells you whether it is still there.

Damaged history is an ERROR too — never a smaller graph (issue #1145). The engram history is a chain: each point is reconstructed from the nearest stored snapshot plus the deltas above it. If any of those pieces is missing — a lost chain link, a lost delta, a snapshot file that went away while its metadata still claims it — the read fails, naming the engram it could not resolve.

It did not used to. Each missing piece read as “nothing here”: a lost chain link looked like “no snapshot in this history” and replay restarted from an empty graph, a lost delta replayed as “that commit did nothing”. The answer was a graph that never existed, returned with no error at all — measured in one real case at 5 nodes where 40 were correct. Nothing distinguished it from a genuinely small result.

You should never see this: it means storage damage or partial data loss, not a normal condition. What matters is that you now see it as an error instead of as data. Take a backup restore seriously if it appears — and note that a live (non-AS OF) query is unaffected, since it reads the current graph directly rather than replaying history.

Performance & tuning

An AS OF read is served by one of several paths. All of them return the byte-identical result — the choice affects only speed, and every acceleration falls back to a full reconstruct when it can’t prove it applies. That is why these knobs are safe to flip in either direction on a live system.

  • Full reconstruct (the floor). Materialises the whole graph as of the target engram. Always correct, cost grows with graph size.
  • Warm-window reconstruct (always on). The nearest snapshot’s base state is cached and shared by every read in that window; only the in-window deltas are replayed on top. A second read in the same window is dramatically cheaper than the first.
  • Label-scoped partial reconstruct (PATINADB_PARTIAL_TIMETRAVEL, default on). When a query’s labels and relationship types are statically bounded, only that subgraph is rebuilt instead of the whole graph. An unbounded shape — a bare MATCH (n), a variable-length hop, a procedure call — falls back automatically. Read-only, so a write-only workload pays nothing for it.
  • Point-index lookup (PATINADB_INDEXED_SNAPSHOTS, opt-in). For a cold single-vertex lookup anchored on id(n) = '<uuid>', a per-snapshot .snapidx sidecar turns a full-graph rebuild into a point read.

Practical guidance. Leave PATINADB_PARTIAL_TIMETRAVEL on; if you ever suspect it, =0 restores the full reconstruct for every read — slower, identical results. Reach for PATINADB_INDEXED_SNAPSHOTS only when a known set of historical points is read repeatedly: the sidecar is built lazily and in the background, so the read that triggers a build is itself served by the scan path and only later reads benefit. Bound its disk with PATINADB_MAX_SNAPSHOT_INDEXES (default 16 sidecars). Neither knob touches the write path, so neither can slow a commit or a cluster’s apply loop.

A tag requests a snapshot at its engram. Once that snapshot is available, reconstruction can start there without replaying earlier deltas. Raft-server materialization is asynchronous: acknowledgement is not snapshot readiness, and a snapshot does not guarantee fast queries. See the unresolved 1M historical workload findings in limitations.

Tags — named, snapshotted points in history

A tag is a stable name for an engram (like a git tag), so you can time-travel to a meaningful point without tracking raw engram ids. Creating a tag also pins its engram and requests a snapshot:

  • Pinned — a tagged engram is protected from squash: compaction never collapses it, so the point-in-time it names stays reachable.
  • Snapshot preparation — on a Raft server, a background job builds the full snapshot. Until it completes, reads use existing checked reconstruction; incomplete history is reported rather than replaced by the current state.
CREATE TAG v1                    -- tag the current HEAD
CREATE TAG release AS OF '<engram-uuid>'   -- tag a specific engram
SHOW TAGS                        -- list name → engram
DROP TAG v1                      -- remove (unpins if no other tag references it)

Read a database as it was at a tag:

USE default AS OF TAG 'v1' MATCH (n) RETURN n

On a cluster, CREATE TAG / DROP TAG replicate through Raft — every node names and pins the same engram. Snapshot preparation finishes independently on each node. AS OF TAG works where the required history is available, including followers. SHOW TAGS is a local read; CREATE/DROP TAG need admin and go through the leader. The returned bookmark covers tag creation, not snapshot completion. Use admin-only SHOW JOBS on the target node to inspect preparation. Embedded Dataset::create_tag remains synchronous.