Open Source Bitemporal Database

One database,
endless history

SirixDB is a bitemporal document store that tracks both when data changed and when you knew about it. Full history, efficient diffs, time-travel queries — with minimal storage overhead.

SirixDB evolution of state — how revisions are stored efficiently

See it in action

Create revisions, modify data, then travel back in time — all from the interactive JSONiq shell.

sirix-shell
(: Rev 1 — store initial data :)
sirix> jn:store('shop','products','[
          {"name":"Laptop", "price":999},
          {"name":"Phone",  "price":699}
        ]')

(: Rev 2 — price drop on the laptop :)
sirix> let $doc := jn:doc('shop','products')
       return replace json value of $doc[0].price with 899

(: Rev 3 — add a new product :)
sirix> let $doc := jn:doc('shop','products')
       return append json {"name":"Tablet","price":449} into $doc

(: Rev 4 — discontinue the phone :)
sirix> let $doc := jn:doc('shop','products')
       return delete json $doc[1]

(: Time travel — what was the original catalog? :)
sirix> jn:open('shop','products', 1)
=> [{"name":"Laptop","price":999}, {"name":"Phone","price":699}]

(: Track how the laptop price evolved across revisions :)
sirix> let $item := sdb:select-item(jn:doc('shop','products'), 6)
       for $v in sdb:item-history($item)
       return {"rev": sdb:revision($v), "price": $v}
=> {"rev":1, "price":999}
   {"rev":2, "price":899}

(: How did the catalog evolve across all revisions? :)
sirix> for $v in jn:all-times(jn:doc('shop','products'))
       return {"rev": sdb:revision($v), "products": count($v[])}
=> {"rev":1, "products":2}
   {"rev":2, "products":2}
   {"rev":3, "products":3}
   {"rev":4, "products":2}

(: What changed between rev 1 and the latest? :)
sirix> jn:diff('shop','products', 1, 4)
=> {"diffs": [
     {"update": {"path":"/[0]/price", "value":899}},
     {"insert": {"path":"/[1]", "data":"{\"name\":\"Tablet\"}"}},
     {"delete": {"path":"/[1]"}}
   ]}

Get running in seconds

Pull the Docker image and start querying your data with full revision history.

Bash
docker run -t -i -p 9443:9443 sirixdb/sirix

Why SirixDB?

Most databases remember only the present. SirixDB remembers everything — what changed, when it changed, and when you knew about it.

01

Bitemporal Versioning

Every revision preserved, every correction tracked. Two time axes — system time and valid time — without duplicating data.

02

Zero-Overhead History

A novel sliding snapshot algorithm eliminates write peaks and storage bloat. Only changed page-fragments are stored per revision.

03

JSON & XML Native

First-class document store for semi-structured data with temporal XPath axes and JSONiq for navigating in both space and time.

04

No Write-Ahead Log

An UberPage is swapped atomically as the last action of a commit. No WAL, no background compaction — just efficient, append-only evolution.

05

Efficient Diffing

Compute diffs between any two revisions using stable node IDs and hashes. Import changes via a fast matching edit-script algorithm.

06

Flash-Drive Optimized

Designed from scratch for modern SSDs. Log-structured, append-only architecture exploits sequential write patterns and zero seek times.

Query across time

Navigate revision history, find corrections, and diff any two snapshots — all with a single query.

JSONiq
(: Open a resource at a specific point in time :)
let $doc := jn:open('database', 'resource',
                    xs:dateTime('2024-01-15T10:30:00Z'))
for $user in $doc.users[]
where $user.age > 25
return $user

Open any resource at an exact timestamp and query it as it existed at that moment. System time is tracked automatically on every commit.

JSONiq
(: Find items added in this revision :)
for $status in jn:open('db','feed').statuses
where not(exists(jn:previous($status)))
return {
  "revision": sdb:revision($status),
  $status{text, created_at}
}

Use jn:previous() to detect new items — nodes that didn't exist in the prior revision. Combine with jn:next(), jn:future(), and jn:past() to navigate freely.

JSONiq
(: Track a node's evolution across all revisions :)
let $node := sdb:select-item(jn:doc('database','resource'), 1)
for $version in sdb:item-history($node)
return {
  "revision":  sdb:revision($version),
  "timestamp": sdb:timestamp($version),
  "value":     $version
}

Track how a specific node evolves over time. sdb:select-item() finds a node by its stable key; sdb:item-history() returns it from every revision in which it changed.

JSONiq
(: Compute differences between any two revisions :)
sdb:diff('database', 'resource', 1, 5)

(: Returns an XQuery Update statement:
   insert nodes {"name":"Ada"} into sdb:select-item($doc, 23)
   replace value of node sdb:select-item($doc, 7) with "updated"
   delete node sdb:select-item($doc, 14)  :)

Get a precise edit script between any two revisions. SirixDB uses stable node IDs and hashes to compute structural diffs efficiently.

Bitemporality

What did you know, and when?

A customer address was entered on March 5. On April 10, you discover it was wrong since February 1. SirixDB lets you correct it while preserving what you believed on March 5.

Two independent time axes make this possible:

System time — when SirixDB recorded it
Valid time — when it actually happened
System Time (when recorded) Valid Time (when true) Mar 5 Apr 10 Feb 1 Mar 5 Address v1 "123 Old St" Address v2 "456 New Ave" — corrected

Explore your data visually

The SirixDB Web GUI lets you browse databases, run queries, visualize document structure, and travel through revision history — all from your browser.

SirixDB Web GUI Dashboard
SirixDB Web GUI Database Explorer
SirixDB Web GUI Sunburst Visualization
SirixDB Web GUI Treemap
SirixDB Web GUI Diff View comparing revisions

Dashboard

Overview of your databases, resources, and tracked revisions. Create new databases or jump straight into the query editor.

Database Explorer

Navigate JSON and XML document trees with expandable nodes. Each node shows its stable ID, type, and value at a glance.

Sunburst Visualization

Hierarchical sunburst chart for exploring document structure. Click to zoom into subtrees, adjust depth and minimum arc size.

Treemap View

Space-filling treemap visualization shows document structure at a glance. Hover for details, click to drill into subtrees.

Revision Diff

Compare any two revisions side by side. See inserts, updates, and deletes at a glance with Explorer, Tree, and List diff views.

How it works

SirixDB stores data in an append-only, persistent tree structure. Revisions share unchanged pages via copy-on-write — keeping storage minimal.

01. Persistent Data Structure

Lightweight revisions that share unchanged data

Every commit creates an immutable snapshot. New revisions reference unchanged page-fragments from prior versions via copy-on-write pointers — only modified data is written. This eliminates the need for full copies or expensive compaction.

New page Modified page Shared (copy-on-write pointer) Time Rev 1 Rev 2 Rev 3 root A B a1 b1 root B' b1' root A' a2
02. Persistent Tree

Variable-sized page-fragments in a durable tree

Data, indexes, and revision metadata are stored in a persistent tree structure. Variable-sized page-fragments minimize storage while enabling reconstruction of any revision in linear time. The tree is checksummed for integrity, inspired by ZFS.

Time Travel
High Performance
ACID Compliant
XML & JSON

Time-travel queries with JSONiq

Open a database at a specific point in time and query across revisions. Find data that was added, modified, or corrected — all in a single query.

JSONiq
let $doc := jn:open('database','resource', xs:dateTime('2019-04-13T16:24:27Z'))
let $statuses := $doc.statuses
let $foundStatus := for $status in $statuses
  let $dateTimeCreated := xs:dateTime($status.created_at)
  where $dateTimeCreated > xs:dateTime("2018-02-01T00:00:00")
        and not(exists(jn:previous($status)))
  order by $dateTimeCreated
  return $status
return {"revision": sdb:revision($foundStatus), $foundStatus{text}}

This query opens a resource at a specific revision timestamp and finds statuses created after Feb 1, 2018 that did not exist in the previous revision. The jn:previous() function navigates the revision history; sdb:revision() returns the revision number.

Supported by

Your data has a story.
SirixDB remembers every chapter.

Get started with the documentation or contribute on GitHub.