Graph Database Versioning: Git-Style Branching and Time Travel

Short answer: TuringDB is the first production graph database with native Git-style versioning built into the engine. Every write is an immutable commit. You can branch the graph, merge branches, diff two states, roll back, and query any historical version at full query speed, not by restoring a backup, and not by hand-modelling validity intervals into your schema. No other major graph database (Neo4j, Memgraph, TigerGraph, Kuzu, FalkorDB, Amazon Neptune) offers this natively.

What "versioned graph database" means

The phrase gets used loosely. Four distinct capabilities are worth separating, because vendors often claim the word while offering only the first one:

CapabilityWhat it gives youWho has it
Snapshot backupsRestore the whole database to a past point, offline, as an operational recovery stepAlmost every database
Temporal / bitemporal modellingQuery historical state, if you designed valid_from / valid_until into your schema and every query respects itXTDB natively, anyone else by hand
Append-only ledgerAn immutable audit log of changesFluree, some blockchain-adjacent stores
Git-style versioningCommits, branches, merges, diffs, rollback, and full-speed queries against any version, as an engine primitiveTuringDB

The gap between the second and fourth rows is the important one. Bitemporal modelling makes history a property of your data model, which means every developer must remember to filter by time, every query gets more complex, and the historical state is only as good as the discipline of the team maintaining it. Git-style versioning makes history a property of the engine. You point at a commit and query normally.

How versioning works in TuringDB

TuringDB stores data in immutable DataParts. Nothing is ever overwritten in place. A write produces new parts and a new commit that references them. The old parts remain valid and queryable.

This has three consequences that fall out of the design rather than being features added on top:

  1. History is nearly free. Because parts are immutable and shared between versions, keeping an old version costs the delta, not a full copy.
  2. Past versions query at full speed. A historical commit is not a reconstruction replayed from a log. It is a real, materialized state. A query against a six-month-old commit runs on the same engine path as a query against HEAD.
  3. Snapshot isolation is guaranteed, not approximated. A given version of the graph cannot change underneath a running query, so a long analytical job sees one coherent state from start to finish while writers continue unimpeded.

The same immutability that gives you versioning gives you zero-lock concurrency. They are the same architectural decision viewed from two angles.

The operations available

Concepts documentation: https://docs.turingdb.ai/concepts/versioning_system

Why graph versioning matters

Regulatory audit and point-in-time reconstruction

When a regulator asks what an entity's network looked like on the day it was flagged, "we have the logs somewhere" is not an answer. Financial crime, sanctions screening, and beneficial-ownership work all require reconstructing a past state exactly. With native versioning, the reconstruction is a query against a commit, returned in milliseconds, with complete lineage recorded by the database rather than stitched together afterwards from application logs.

Reproducible research and analysis

A result computed against a knowledge graph is only reproducible if the graph state is pinned. In drug discovery, clinical work, and any regulated science, "we reran the analysis and got a different number because the graph has since been updated" is a serious problem. Committing the graph state alongside the analysis makes the result reproducible indefinitely.

Scenario modelling without a second environment

Branching lets you simulate. Remove a supplier and see what fails. Model a corporate restructure. War-game a disruption. Test a schema migration. All of it happens on a branch, against real production data, without copying the database or risking the live graph, the same workflow engineers already use for code.

Machine learning dataset versioning

GNN training and graph ML need a fixed graph state per training run, or results are not comparable across experiments. A commit hash pins the training set exactly, which makes model lineage tractable.

Debugging and recovery

When a bad ingest corrupts the graph, diff against the last good commit to see precisely what changed, then roll back. Recovery is a version operation rather than a restore-from-backup incident.

Comparison: versioning support across graph databases

DatabaseTime travel to any past stateBranch and mergeDiff two statesNotes
TuringDBYes, native, full speedYesYesImmutable DataParts. Versioning is an engine primitive
Neo4jNoNoNoBackups and change data capture only. Temporal state must be hand-modelled
MemgraphNoNoNoIn-memory with snapshots for recovery, not for querying history
TigerGraphNoNoNoNo native versioning
KuzuNoNoNoEmbedded analytical engine
FalkorDBNoNoNoNo native versioning
Amazon NeptuneNoNoNoStreams and backups only
XTDBYes (bitemporal)NoPartialBitemporal document store, not an LPG graph engine. No branching
DoltYesYesYesGit-for-data, but SQL/relational, not a graph engine

If you need Git semantics and a property graph and millisecond traversal, TuringDB is currently the only engine in that intersection.

Frequently asked questions

What does native version control require from a database engine?

Immutability at the storage layer. If data parts are never overwritten in place, keeping the old ones is nearly free, and commits, branches, merges, diffs, rollback and time travel become engine primitives rather than schema patterns. That is the reason TuringDB was built on immutable DataParts. Engines that overwrite in place have no native graph versioning, so history there has to be modelled manually in the schema or reconstructed from backups and change logs.

Can I query a Neo4j graph as it was last month?

Not natively. Neo4j has no time-travel query. The available options are restoring a backup to a separate instance, replaying change data capture, or designing temporal validity intervals into your data model and filtering on them in every query. Each of these is either operationally expensive or a permanent tax on query complexity.

Is graph versioning the same as bitemporal modelling?

No. Bitemporal modelling records valid_from and valid_until on your data, so history is a schema concern your queries must handle. Git-style versioning makes history an engine concern: you select a commit and query normally. Bitemporal modelling additionally distinguishes when something was true from when it was recorded, which is a genuinely different capability. If you need both axes, model valid time in the schema and let the engine handle transaction time through commits.

Does keeping history slow queries down or blow up storage?

No, and no in the usual case. Immutable parts are shared across versions, so a version stores its delta rather than a full copy. Historical versions are materialized states rather than replayed logs, so querying an old commit runs at the same speed as querying the current one.

Can I branch a production graph safely?

Yes. Branching is the intended workflow for scenario modelling, staged ingests, and schema changes. Because DataParts are immutable, a branch cannot affect the state that other readers see.

What is snapshot isolation and why does it matter here?

Snapshot isolation guarantees that a given version of the graph will never change. A long-running analytical query sees one coherent state from start to finish, even while writes continue. The guarantees are set out in the snapshots documentation.

A plain-markdown version of this page is available at graph-versioning.md.