SirixDB Documentation

repository·main·Indexed 22 days ago

https://github.com/sirixdb/sirix

A bitemporal database system that treats every revision as a first-class citizen, enabling high-performance querying of historical data without event replaying. It supports transaction time and valid time modeling, offering various page versioning strategies (FULL, INCREMENTAL, DIFFERENTIAL, SLIDING_SNAPSHOT). The system provides a Java/Kotlin embedded library (sirix-core), a REST API, a CLI (sirix-cli), an interactive shell (sirix-shell), and an MCP server for AI agents.

Tokens
169K
Snippets
207
Records
674
Agent score
75%

What's inside SirixDB

  1. Supported workloads and constraints

    main

    When designing your application with Sirix, note the following workload characteristics:

    DimensionSupportedNotes
    Document modelJSON, XMLOne or the other per resource; no mixing
    Document sizeup to 64 KiB per LZ77 blockDocuments > 64 KiB fall back to literal-only (no compression)
    Page size256 KiB ceilingPractical max for in-memory page buffers
    ConcurrencyMany readers, 1 writerExactly one writer per resource (via Semaphore(1))
    BitemporalitySystem & Valid timeQueryable via jn:all-times, jn:open-bitemporal, sdb:timestamp, sdb:valid-from
    VersioningMultiple strategiesFULL, INCREMENTAL, DIFFERENTIAL, SLIDING_SNAPSHOT (default)
    IndexesVariousname, path, CAS, and HOT (height-optimized trie)
    Query languageJSONiq & XQueryVia Brackit; JSONiq includes a cost-based optimizer
  2. Implementation status of the Projection Index Storage Redesign

    main

    The Projection Index Storage Redesign is being implemented in phases (P0 through P8). As of 2026-07-20, the following status applies:

    • P0/P1 (Page Layer): Done. Includes ProjectionSegmentPage (PageKind 18), HOT side-map commit chains, and split-time ref routing.
    • P2 (Descriptor + Segment Codec): Done. Includes PIXD descriptor, PIXS segments, and XXH3 integrity hashes.
    • P3 (Storage Rewrite): Done. Includes ProjectionIndexHOTStorage with putLeaf/putEncodedLeaf, tombstone support, and resetTree migration.
    • P4 (Builder + Maintenance): Done. Includes streaming builds and per-segment maintenance writes.
    • P5 (Query Integration): Parity done. Serving/gate/differential suites are green; columnar Handle/kernel restructure is deferred.
    • P6 (Numeric Double + ALP): Done. Supports NUMERIC_DOUBLE via sortable-bits transform and ALP compression.
    • P7 (FSST Dictionaries): Done. Uses mode-byte DICT segments with deterministic training.
    • P8 (Migration + Docs): Done. ProjectionIndexMetadata.VERSION = 0 and rebuild-on-open logic implemented.
  3. Explore SirixDB usage examples

    main

    The sirix-examples bundle provides small, self-contained Java programs demonstrating the embedded SirixDB API. These examples automatically write data to ~/sirix-data/ and clean up previous runs.

    Key examples include:

    • ResourceTransactionUsage: Demonstrates creating an XML database and resource, shredding an XML document into the database, moving subtrees, committing transactions, and serializing results.
    • QueryUsage: Demonstrates building sample documents and executing JSONiq/XQuery queries via Brackit, including the use of secondary indexes.
  4. Identify SirixDB Module Structures

    main

    The SirixDB project is organized into several functional bundles:

    • sirix-core: Core storage engine, transactions, and pages.
    • sirix-query: Brackit XQuery engine integration.
    • sirix-rest-api: Vert.x REST server (Kotlin).
    • sirix-kotlin-cli: Command-line interface.
    • sirix-kotlin-api: Kotlin extensions.
    • sirix-mcp: Model Context Protocol server for AI agents.
    • sirix-distributed: Distributed features (experimental).
    • sirix-examples: Usage examples.
  5. Explore the Cost-Based Optimizer source code structure

    main

    The optimizer implementation is located in bundles/sirix-query/src/main/java/io/sirix/query/. Key components include:

    • Entry Points: SirixCompileChain.java (creates optimizer + translator) and SirixQueryContext.java (handles statistics invalidation).
    • Optimizer Pipeline: SirixOptimizer.java orchestrates a 10-stage pipeline (including JQGM rewrites, cost analysis, join reordering via DPhyp/GOO, and SIMD/Vectorized detection) with a 50ms circuit breaker.
    • Join Ordering: AdaptiveJoinOrderOptimizer.java uses DPhyp for $\le 20$ relations and GOO for $> 20$ relations.
    • Search Space: Mesh.java and EquivalenceClass.java manage alternative plan groupings.
    • Statistics & Cost Model: Includes SelectivityEstimator.java, CardinalityEstimator.java, and Histogram.java (supporting equi-width, equi-depth, and MCV).
    • Physical Operators: IndexExpr.java (reads from CAS/PATH/NAME indexes) and VectorizedPipelineExpr.java (SIMD batch processing).
    • Query Inspection: sdb:explain() function and QueryPlan.java for programmatic plan inspection.
    bundles/sirix-query/src/main/java/io/sirix/query/
    ├── SirixCompileChain.java              ← Entry point: creates optimizer + translator
    ├── SirixQueryContext.java              ← Post-commit statistics invalidation
    ├── compiler/
    │   ├── XQExt.java                      ← Extension AST node types (IndexExpr, VectorizedPipelineExpr)
    │   ├── translator/
    │   │   ├── SirixTranslator.java        ← Converts optimized AST → physical operators
    │   │   ├── SirixPipelineStrategy.java  ← Intersection join: forces hash mode on TableJoin
    │   │   └── ...
    │   ├── expression/
    │   │   ├── IndexExpr.java              ← Physical operator: reads from CAS/PATH/NAME index
    │   │   └── VectorizedPipelineExpr.java ← Physical operator: SIMD batch scan-filter-project
    │   ├── optimizer/
    │   │   ├── SirixOptimizer.java         ← Orchestrates 10-stage pipeline with 50ms circuit breaker
    │   │   ├── PlanCache.java              ← LRU cache: queryText+schemaVersion → optimized AST
    │   │   ├── CardinalityTracker.java     ← Detects estimate-vs-actual drift, invalidates stale plans
    │   │   └── ...
    │   └── ...
    └── function/sdb/explain/
        ├── Explain.java                    ← sdb:explain() XQuery function
        ├── QueryPlan.java                  ← Programmatic plan inspection API
        └── QueryPlanSerializer.java        ← Converts AST → human-readable JSON
  6. What is SirixDB and how does it handle history?

    main

    SirixDB is a bitemporal database system where every revision is a first-class citizen. Unlike traditional databases that overwrite data or require event sourcing to reconstruct history, SirixDB allows you to query any past revision as fast as the latest. It achieves this through structural sharing and sub-page versioning, meaning unchanged data is shared between revisions via copy-on-write, and only changes are appended to an append-only log.

    Key benefits include:

    • Direct Access: No event replay or log scanning required to see past states.
    • Efficient Storage: Storage scales with the number of changed records, not the total size multiplied by the number of revisions.
    • Fast Reads: Reading an old revision is a direct page lookup, making the cost of accessing history nearly identical to accessing the current state.
  7. What is Vectorized Execution in SirixDB

    main

    For simple scan-filter-project pipelines (queries without joins, grouping, or subqueries), SirixDB uses vectorized execution to improve performance.

    Instead of processing one row at a time, the VectorizedRoutingStage replaces eligible subtrees with VectorizedPipelineExpr nodes. These nodes process rows in batches (e.g., 1024 at a time) using CPU SIMD instructions and columnar batch processing. This reduces function call overhead, improves CPU cache utilization, and leverages SIMD for faster comparisons.

  8. What is a Projection Index

    main

    A projection index is a persistent, incrementally-maintained columnar projection of selected fields from a JSON record set. It allows SirixDB to serve OLAP-style analytical queries (such as sum, avg, count, group-by, and count-distinct) at columnar-engine speeds without scanning the full JSON document tree.

    Key properties:

    • Versioned: It lives inside SirixDB's copy-on-write page tree, meaning every commit produces a new queryable snapshot. Time-travel analytics can run on the same kernels as current-state queries.
    • Automatically Maintained: A change listener patches the columns at every commit (rebuilding leaves, appending tails, or dropping rows). No manual refresh is required.
    • Fail-Closed: If the projection cannot prove it can answer a query exactly (due to type provenance, presence, or staleness), the query silently falls back to the generic document-scan pipeline to ensure correctness.
  9. How GOO-DP (Greedy + DP) optimizes large queries

    main

    For queries too large for exhaustive DP, the GOO-DP strategy is used. It works in two distinct phases:

    Phase 1: Greedy Operator Ordering

    The optimizer builds an initial bushy plan by iteratively selecting the pair of available relations or sub-plans that produces the minimum join result cardinality. If the join graph is disconnected, it forces a Cartesian product between the two smallest remaining components to minimize cross-product costs.

    Phase 2: DP Refinement

    The optimizer identifies 'boundary subtrees'—subtrees that are small enough to be re-optimized (size $\le$ maxSubproblemSize) but are part of a larger, more expensive structure.

    Refinement follows these steps:

    1. Collect Candidates: Traverse the join tree to find subtrees within the size limit.
    2. Prioritize: Sort candidates by descending estimated cost so the most expensive subtrees are addressed first.
    3. Re-optimize: Replace the greedy subtree with a plan generated by exact DP (dpHyp).
    4. Budget Management: Each refinement consumes a portion of the CC_BUDGET, estimated by the number of DP table entries (up to $3^n$ for $n$ relations).
  10. Avoid Caching Record Shells in PathSummaryReader

    main

    The PathSummaryReader implementation contains a specific hazard regarding its pathNodeMapping[] cache. The constructor performs a walk that stores pathNode references in an array.

    The Hazard: Storing record shells in a long-lived array like pathNodeMapping[] is incompatible with a pooling mechanism. If the pool reuses a shell that is currently stored in the cache, the cache entry will silently point to a new, unrelated record.

    Recommended Fix: To ensure safety, the PathSummaryReader should be refactored to store nodeKey values (the long identifiers) in the pathNodeMapping[] cache instead of the record shells themselves. Alternatively, the PathNode kind must be excluded from the pooling mechanism.

  11. Understand how Projection Index maintenance works

    main

    Projection indexes use a ProjectionIndexChangeListener to hook into every commit of a resource. The maintenance process follows a specific lifecycle to handle modified nodes:

    1. Attribute dirty nodes: Identify which records under the projection root were modified.
    2. Read fence chunks: Reassemble the zone map by reading fence chunks.
    3. Merge dirty keys: Perform a two-pointer merge of dirty keys against per-leaf fence ranges.
    4. Re-extract and Re-encode: Re-extract touched leaves from the document, re-encode them, and use hash-comparison to write only changed segments.
    5. Rewrite: Update slot 0 (the shape) and write only the changed fence chunks and segments.

    Key Behaviors:

    • Appends: Because record keys are monotone, new records always land at the tail and classify cleanly.
    • Non-blocking: The maintenance ladder never blocks a commit. If maintenance fails (e.g., due to maxIncrementalRecords being exceeded), the system falls back to a rebuildFully() which may result in a stale tombstone over slot 0, degrading queries until the next manual rebuild or first-use rebuild.
    • Sharing: The rebuild process uses hash-compare no-op writes to share unchanged leaf segments, softening the performance impact of large updates.
  12. Identify the logical slot layout for Projection Indexes

    main

    Within a single IndexDef sub-tree, data is organized into logical slots:

    leafIndexContent
    0ProjectionIndexMetadata payload (PIXM magic, version, stale flag, leafCount, buildRevision, per-leaf record-key fences, root path, column shapes)
    1..None compacted ProjectionIndexLeafPage payload per logical leaf (≤ 1024 rows each)

    Important constraints:

    • The leafCount in the metadata bounds every read. If a rebuild shrinks the projection, stale payloads at higher slots are ignored.
    • parse() returns null (triggering a rebuild) if the magic number is missing or the version is unknown. It only throws on structural corruption.