Core concepts.
OriginChain is a single managed engine with a Plan tree on top. SQL, vector, full-text, and graph are not separate engines - they are different query modes and different Plan operators over the same store. Understand the engine, the schemas, and the Plan and the rest of the surface follows.
A single managed engine.
Every write is made durable before it is acknowledged, then applied. Reads go through a process-wide cache. There is no row-store / column-store / vector-engine split - every query mode is a different way to read the same row.
Each tenant gets a single, region-isolated managed instance. No shared compute, no noisy neighbour. Writes go to one primary; a sync replica stays in lockstep on paid configurations for RPO=0. A new replica bootstraps from a snapshot of the store and then keeps up with the live stream.
Declared in TOML.
A schema manifest declares the table's namespace + name, primary key, columns, secondary indexes, graph relations to other tables, and derived JSON extractions. The catalog is itself stored as rows - adding a column is a write, not a downtime migration.
# schemas/orders.toml
namespace = "shop"
table = "orders"
primary_key = ["id"]
[[columns]]
name = "id"
ty = "str" # ULIDs / UUIDs travel as text
required = true
[[columns]]
name = "customer"
ty = "str"
[[columns]]
name = "amount_cents"
ty = "i64" # money in minor units - never f64
[[columns]]
name = "status"
ty = "str"
[[columns]]
name = "placed_ms"
ty = "u64" # epoch milliseconds
[[indexes]]
name = "by_status"
columns = ["status"]
[[indexes]]
name = "by_customer_placed"
columns = ["customer", "placed_ms"]
[[relations]]
name = "by_customer"
from_col = "customer"
bidirectional = true
[relations.target]
namespace = "shop"
table = "customers"
pk = "id"
Six core scalars - str, i64, u64, f64, bool, bytes - plus purpose-built types (timestamp, uuid, decimal, json, and more). Vector and full-text indexes are NOT declared here; they live on their own runtime endpoints (see vector, fts) and link back to rows by primary key. Indexes and relations are honoured at write time - no separate "build index" step. See schemas reference for the full grammar.
How data shapes work.
A single row is reachable through several query modes - SQL, secondary index, relation walk, full-text, and vector - and the engine keeps all of them in lockstep on every write. Each mode is a different way to read the same row, not a different store.
| Mode | Purpose |
|---|---|
| Rows | The primary user-facing record. PK is one or more columns (ULIDs / UUIDs travel as str). Read via the typed /rows API or SQL. |
| Secondary indexes | Speed up equality filters and left-prefix range scans on declared columns. Maintained automatically on every write. |
| Relations | Graph edges between rows. Forward and reverse traversal are both O(degree) - declare a relation in the manifest and walk it with neighbors / BFS / Dijkstra. |
| Full-text | BM25 inverted index. Stored on a separate runtime endpoint - index text under (table, field, doc_id), then search by query string. Boolean, BM25, phrase, fuzzy modes. |
| Vector | Embeddings indexed with HNSW (default), IVF, or IVF-PQ. Stored on a separate runtime endpoint - put one vector per row by primary key, then top-k by similarity. Optional metadata filter. |
| Plan cache | Compiled Plan tree for a /ask question template. Skips the rule-grammar and LLM compile on cache hit; replays the tree through the executor. |
Eleven operators, one tree.
Both /sql and /ask compile to the same Plan tree. The tree is JSON-serialisable, cached by question hash, and replayable. Every shipped query shape is one of these operators or a composition.
Scan ColumnScan IndexScan Filter Project Limit Sort Aggregate HashJoin OuterJoin RelationHop -- SELECT c.name, SUM(o.amount_cents) AS total
-- FROM shop.orders o
-- JOIN shop.customers c ON c.id = o.customer
-- WHERE o.status = 'paid'
-- GROUP BY c.name
-- LIMIT 100;
Limit { 100 }
└── Aggregate { group: [c.name], agg: SUM(o.amount_cents) AS total }
└── Project { c.name, o.amount_cents }
└── HashJoin { o.customer = c.id }
├── IndexScan { shop.orders, status = "paid" }
└── Scan { shop.customers } Active-passive, sync.
One primary, one optional sync replica. The commit replicates before the primary returns 200. A replica joining a running cluster bootstraps via a snapshot transfer - a chunked transfer of the full store - then keeps up with the live stream from where the snapshot left off.
| Mode | Configurations | RPO | RTO | Notes |
|---|---|---|---|---|
| Primary only | Single-zone | ~0.5s (durable commit) | ~5-10 min (archive restore) | Single instance, no replica. Restore replays the continuous backup archive. |
| Sync replica | HA, HA+, Enterprise | 0 | ~25s (drilled) | Automatic failover. Verified end-to-end with snapshot bootstrap. HA has 1 replica; HA+ has 2. |
On paid configurations, active-passive sync replication is the production path. Commits durably acknowledge only after the replica also holds the commit - RPO=0, RTO ~25 s. See ops → failover for the promotion procedure. Need more write throughput than one node delivers? Multi-node configurations scale writes across nodes behind the same single endpoint - with the same per-node standby options.
Single-row optimistic CAS.
Every row carries an internal _oc_row_version field. The API exposes put_row_cas, get_row_versioned, and delete_row_cas for optimistic concurrency. A CAS that loses the race fails the entire batch with a deterministic error - no partial application. Idempotency keys make retries safe; the same key plus the same body returns the original response, a different body with the same key returns 409.
Continuous backup archive.
Two streams flow to the archive in parallel: periodic durable snapshots, and a continuous backup stream that ships recent changes every few hundred milliseconds. Restore-to-timestamp resolves to sub-second precision on the paid configuration.
Restore-to-timestamp runs from the console (or the API): pick a wall-clock target and OriginChain rebuilds a fresh instance to exactly that point in time. The result opens cleanly through the same validating loaders that serve live traffic.
Continuous backups (segment-boundary granularity, ~5–10 min restore window) are included on every configuration. Sub-second precise PITR (~0.5–1.5 s data-loss window) is a paid add-on - see pricing.