SirixDB 是一个可嵌入、双时段、只能追加的数据库系统和事件存储库,存储不可变的轻量级快照。它保存每个对象的完整历史,并支持事务性的数据修改。
SirixDB 是一个可嵌入、双时段、只能追加的数据库系统和事件存储库,存储不可变的轻量级快照。它保存每个对象的完整历史,并支持事务性的数据修改。
SirixDB - The Bitemporal Database System
Query any revision as fast as the latest
Live Demo · Why SirixDB · Docs · Website · Discord · Forum · Web UI
Status: 1.0.0-beta — usable today and actively developed. The on-disk format and public APIs are stabilizing toward a 1.0 release; feedback from real use is exactly what we're looking for.
You update a row in your database. The old value is gone.
To get history, you bolt on audit tables, change-data-capture, or event sourcing. Now you have two systems: one for current state, one for history. Querying the past means replaying events or scanning logs. Your "simple" audit requirement just became an infrastructure project.
Git solves this for files—but you can't query a Git repository. Event sourcing preserves history—but reconstructing past state means replaying from the beginning.
SirixDB is a database where every revision is a first-class citizen. Not an afterthought. Not a log you replay.
// Query revision 1 - instant, not reconstructed
session.beginNodeReadOnlyTrx(1)
// Query by timestamp - which revision was current at 3am last Tuesday?
session.beginNodeReadOnlyTrx(Instant.parse("2024-01-15T03:00:00Z"))
// Both return the same thing: a readable snapshot, as fast as querying "now"
This works because SirixDB uses structural sharing with sub-page versioning. Unchanged pages are shared between revisions via copy-on-write — and versioning continues below the page: a commit writes page fragments containing only the changed records, and the sliding-snapshot algorithm guarantees any page is reconstructible from at most N fragments. Block-level COW (ZFS-style) copies a whole page when one byte in it changes; delta-based systems make reads replay ever-growing diff chains. SirixDB pays neither cost. Revision 1000 doesn't store 1000 copies—it stores the current state plus pointers to shared history.
The result:
Most databases (if they version at all) track one timeline: when data was written. SirixDB tracks two:
Why does this matter?
January 15: You record "Price = $100, valid from January 1"
January 20: You discover the price was actually $95 on January 1
After correction, you can ask:
"What did we THINK the price was on Jan 16?" → $100 (transaction time)
"What WAS the price on Jan 1?" → $95 (valid time)
Both questions have correct, different answers. Without bitemporal support, the correction destroys the audit trail.
History is not a tax. Reading an old revision is a direct page lookup, not a replay — any revision reads as fast as the latest, and session-open cost is flat regardless of how much history exists (0.18 ms at 10,000 revisions).
A few measured receipts (we benchmark against ourselves and publish the losses, methodology in WHY-SIRIX.md and the linked comparison docs):
BENCHMARKS.md). The aged database now outruns the pre-fix fresh one.COMPARISON_DUCKDB.md, NATIVE_IMAGE.md). The standalone query engine, brackit, beats jq several-fold on its own benchmark suite.COMPARISON_POSTGRES.md, re-run 2026-07) — PostgreSQL with a trigger-maintained history table still wins raw small-document ingest (1,093 vs 169 durable commits/s same-machine, both verified fsync-bound) and total storage (~2×, down from ~3× after recent storage work). SirixDB wins semantic diffs (0.58 ms node-level patch vs a top-level-only compare), now ties history listing, and offers sub-document time travel PostgreSQL doesn't have. Durability settings verified equivalent before measuring; the fsync floor of the box is published alongside the numbers.Every fast path is fail-closed: a kernel only runs when the optimizer can prove the query's shape matches what it emits, and a differential suite requires byte-identical output against the general path. Wrong-but-fast is a bug class, not a setting.
Logical page structure of a resource with 3 revisions — read-only transactions (RTX) can open any revision, while a single write transaction (WTX) appends to the latest.
SirixDB stores data in a persistent tree structure where revisions share unchanged pages and nodes. Traditional databases overwrite data in place and use write-ahead logs for recovery. SirixDB takes a different approach:
All data is written sequentially to an append-only log. Nothing is ever overwritten.
Physical Log (append-only, sequential writes)
┌────────────────────────────────────────────────────────────────────────┐
│ [R1:Root] [R1:P1] [R1:P2] [R2:Root] [R2:P1'] [R3:Root] [R3:P2'] ... │
└────────────────────────────────────────────────────────────────────────┘
t=0 t=1 t=2 t=3 t=4 t=5 t=6 → time
Each revision has a root node in a trie. Unchanged pages are shared via references.
[Rev 1] [Rev 2] [Rev 3]
│ │ │
▼ ▼ ▼
[Root₁] [Root₂] [Root₃]
│ │ │ │ │ │
│ └──────────┐ │ └────────┐ │ └─────────┐
▼ ▼ ▼ ▼ ▼ ▼
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ P1 │ │ P2 │ │ P1' │ │ P2' │
└──────┘ └──────┘ └──────┘ └──────┘
Rev 1 Rev 1+2 Rev 2+3 Rev 3
(shared) (shared)
Rev 2 modified only P1 (writing P1') and still shares P2 with Rev 1; Rev 3 modified only P2 (writing P2') and still shares P1' with Rev 2 — matching the physical log above.
How page versions are stored is configurable per resource:
| Strategy | Page fragments per read | Write cost | Notes |
|---|---|---|---|
| FULL | 1 | full page per change | fastest reads, largest storage |
| INCREMENTAL | up to N−1 diffs | small diffs + periodic full snapshot | write spikes at snapshot points |
| DIFFERENTIAL | 2 | diffs grow between snapshots | bounded reads, uneven writes |
| SLIDING_SNAPSHOT (default) | ≤ N (default 3) | small diffs, out-of-window records rescued into the newest fragment | no full-snapshot spikes, best balance |
Modifying data copies only the affected pages (copy-on-write); unchanged pages are referenced
from the new revision, and the old revision remains intact and queryable. Storage cost is
O(changed records) per revision. Read cost: opening a revision is O(1) by number, O(log R)
by timestamp; each page read combines at most N fragments. The full strategy walkthrough is in
docs/ARCHITECTURE.md.
Platform support: Linux, macOS, and Windows are CI-tested on every pull request. Linux additionally gets native binaries and the Docker images; known limitations are listed in
docs/KNOWN_LIMITATIONS.md.
SirixDB provides two CLI tools, both available as instant-startup native binaries:
| Binary | Module | Description |
|---|---|---|
sirix-cli |
sirix-kotlin-cli | Full-featured CLI for database operations |
sirix-shell |
sirix-query | Interactive JSONiq/XQuery shell |
Build native binaries with GraalVM:
# Build both CLIs as native binaries (requires GraalVM with native-image)
./gradlew :sirix-kotlin-cli:nativeCompile # produces: sirix-cli
./gradlew :sirix-query:nativeCompile # produces: sirix-shell
# Or run via JAR
./gradlew :sirix-kotlin-cli:run --args="-l /tmp/mydb create"
The whole create/update/time-travel loop from the shell (-l names the database path):
sirix-cli -l /tmp/mydb create json -r myresource -d '{"name": "Alice", "role": "admin"}'
sirix-cli -l /tmp/mydb update -r myresource '{"team": "engineering"}' -im as-first-child
sirix-cli -l /tmp/mydb query -r myresource '$$.name' # $$ is the document root
sirix-cli -l /tmp/mydb query -r myresource -rev 1 # query a previous revision
sirix-cli -l /tmp/mydb resource-history myresource
sirix-shell is a JSONiq/XQuery REPL over the same data (jn:store(...), jn:doc(...) —
multi-line queries, empty line executes, Control-D exits).
Start SirixDB and its bundled OAuth2 provider (Keycloak) with Docker:
git clone https://github.com/sirixdb/sirix.git
cd sirix
docker compose up
This starts the REST server on http://localhost:9443 plus a Keycloak instance seeded with
demo users admin/admin and viewer/viewer. All endpoints are OAuth2-protected:
TOKEN=$(curl -s -X POST http://localhost:9443/token \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin","grant_type":"password"}' | jq -r .access_token)
curl -X PUT http://localhost:9443/mydb/myresource \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"name":"Alice","role":"admin"}'
curl http://localhost:9443/mydb/myresource -H "Authorization: Bearer $TOKEN"
For local development, auth.mode=none (docker run -e SIRIX_AUTH_MODE=none ...) skips
Keycloak entirely (loud warning, admin-for-all). → docs/QUICKSTART.md
walks the whole loop — create, query, commit, time-travel read, diff — with verified commands;
the REST API documentation has the full endpoint
reference.
Security note: the bundled Keycloak realm, demo users, client secret, and self-signed TLS certificate are for local development only — see
docs/operations.mdbefore any public deployment.
SirixDB ships a native Model Context Protocol server, so AI agents (Claude, Cursor, Windsurf, or any MCP client) can talk to it directly. Because every revision is copy-on-write, agents get O(1) disposable snapshots, time-travel reads, and structural diffs for free — branch, experime
暂无开放 Issues,或尚未同步最近议题。