Historical Queries¶
Quine records every change to every node. Instead of overwriting a node's properties and edges in place, each change is appended to that node's own journal along with the moment it happened, a technique known as event sourcing. Replaying that journal up to a chosen moment rebuilds the node exactly as it stood then.
A historical query is a read answered from the graph as it was at a moment you choose. The history comes free with ordinary ingest: there is nothing to configure when the data is written, and nothing to declare in advance about which moments you might want back.
To run one, pin a read to a moment with the atTime request parameter.
What history requires¶
Historical queries read the journal, so keeping the journal is what keeps them available.
| Setting | Default | Effect on historical queries |
|---|---|---|
quine.persistence.journal-enabled |
true |
Keep this enabled. The journal is the per-change record a historical query replays; with journal-enabled = false, a historical query can report only what a snapshot happened to capture. |
quine.persistence.snapshot-schedule |
on-node-sleep |
Snapshots make replay faster. A node is rebuilt from its most recent snapshot plus the journal since, rather than from the beginning of its history. |
quine.persistence.snapshot-singleton |
false |
Keep the default. Snapshots are keyed by the time they were taken, so a historical read replays from the newest snapshot at or before the moment asked for. With true, a single row holds only current state, which a historical read cannot start from. |
See Persistors for the full persistence configuration.
Two more things to know before you build on historical results:
- A historical moment is read-only. A query pinned to the past that tries to write returns an error, so a write can never land silently against a moment that has already passed.
purgeNoderemoves a node's history. Once purged, moments that node participated in can no longer be reconstructed. AvoidpurgeNodein a deployment that relies on historical queries.
Querying at a past moment¶
Every graph-scoped read endpoint that can be pinned to a moment accepts an atTime query parameter, an RFC 3339 timestamp such as 2026-04-27T15:30:00Z. It must not be in the future.
curl -X "POST" "http://127.0.0.1:8080/api/v2/graph/quine/cypher:query?atTime=2026-04-27T15:30:00Z" \
-H 'Content-Type: text/plain' \
-d "MATCH (n:Account {id: 'A1'}) RETURN n.balance AS balance"
The query runs exactly as it would against the present graph. Every node it touches is rebuilt at that moment first, so MATCH traverses the edges that existed then and returns the property values that were set then.
These endpoints accept atTime:
| Endpoint | What it returns at that moment |
|---|---|
POST /api/v2/graph/quine/cypher:query |
Arbitrary Cypher results |
POST /api/v2/graph/quine/cypher:queryNodes |
Nodes only |
POST /api/v2/graph/quine/cypher:queryEdges |
Edges only |
GET /api/v2/graph/quine/hashCode |
A hash of the graph's state, useful for confirming two moments agree |
POST /api/v2/graph/quine/algorithms/randomWalk/nodes/{nodeId}:generateRandomWalk |
A random walk over the graph as it was |
POST /api/v2/graph/quine/algorithms/randomWalk:saveWalks |
A saved set of walks over the graph as it was |
Moving from API v1
API v1 spelled this parameter at-time and took epoch milliseconds. API v2 spells it atTime and takes an RFC 3339 timestamp. See Migrating from API v1.
From the Exploration UI¶
The Exploration UI exposes the same capability through its History button: choose a moment, and every query issued from then on runs against the graph as it stood at that moment. Choosing a moment starts a new exploration session and clears the canvas, because the results already rendered describe a different moment.
Choosing a moment¶
A historical query answers about one instant, so picking the right instant is the work. Two approaches help:
- Reify time into the graph. The
reify.timeprocedure builds nodes representing periods — years, months, days, minutes — and attaches your data to them. You can then find the period you care about by querying the graph normally and read its timestamp off the time node. - Record the moment on the data itself. If your ingest writes a timestamp property, an ordinary query finds the events of interest and hands you the moments to query at.
Next steps¶
- Core Concepts: Historical Versioning: how the graph comes to be versioned.
- Persistors: the configuration that decides what history is kept.
- Random Walk: graph algorithms over a past moment.
reify.time: build time nodes so moments are discoverable by query.