Skip to content

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.
  • purgeNode removes a node's history. Once purged, moments that node participated in can no longer be reconstructed. Avoid purgeNode in 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.time procedure 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