Skip to content

Commit 8a029bc

Browse files
laksamanakerisfarhan-syah
authored andcommitted
docs: cover v0.4.0 — atomic cross-shard transactions, CRDT collections, sparse vectors
- architecture/query-language: interactive cross-shard BEGIN...COMMIT is atomic via the Calvin vote/verdict barrier; OCC read validation aborts stale reads with 40001; TransactionRedo WAL durability; single-node Calvin default-on; overlay lease reaping and backpressure SQLSTATE - documents/query-language: CRDT document collections (WITH (crdt=true)), per-field LWW UPDATE semantics, rejected DML shapes, RETURNING, movable-list SDK ops - vectors/query-language: SPARSEVECTOR column type, '{dim: weight}' literals, sparse_score ORDER BY surface - real-time: graph edge and node-label writes emit WriteEvents (__graph_node_labels__ stream, stable edge row_id) - offline-sync: one-document-per-delta contract rejection - bitemporal: AS OF read parity (predicates, ORDER BY, window functions) - databases: pgwire listener idle watchdog vs per-database IDLE_TIMEOUT - query-language: timestamp string-format coercion in comparisons
1 parent 48a77a4 commit 8a029bc

8 files changed

Lines changed: 110 additions & 6 deletions

File tree

docs/architecture.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,17 +142,29 @@ Each kind has independent leader election. A sequencer leader failure does not a
142142

143143
Write transactions that touch multiple vShards go through the **Calvin sequencer** rather than two-phase commit. The sequencer Raft group produces a globally-ordered log of transaction batches (epochs, default 20 ms). Within each epoch, the scheduler derives a deterministic lock order and the executor runs all writes concurrently without cross-shard coordination.
144144

145+
**Interactive transactions are atomic across shards.** A `BEGIN ... COMMIT` block whose statements span multiple vShards (or nodes) flushes at COMMIT as one Calvin transaction bound by a durable **Vote/Verdict barrier**: each participant replicates a commit/abort vote through the sequencer log; when the tally completes, a replicated verdict commits or drops every participant's staged writes together. Expected-participant counts are seeded from the replicated epoch batch, so the barrier survives sequencer failover. Read-only participants count too — a transaction that wrote shard X but read shard Y validates and votes on both.
146+
147+
**Serializable OCC.** Reads recorded during the transaction (point reads, predicate scans, index equality/range probes, both sides of gathered cross-node JOINs) are validated at COMMIT against per-key, per-collection, and per-index-value write LSNs. A stale read aborts the transaction with SQLSTATE `40001` (retryable) instead of committing silently.
148+
149+
**Durability.** A committed transaction's writes are persisted as a single atomically-replayable `TransactionRedo` WAL record appended before the commit is acknowledged. WAL-only recovery replays it in LSN order, rebuilding even in-memory secondary indexes (vector HNSW, FTS postings) that base storage cannot reconstruct alone.
150+
145151
For value-dependent predicates (e.g., `WHERE balance > 0`), the executor uses **OLLP** (Optimistic Lock Location Prediction): optimistically proceed, then re-validate and retry on mismatch. A circuit breaker opens when the retry ratio exceeds 50% for a predicate class.
146152

147153
```sql
148154
-- Require atomic cross-shard writes (default)
149155
SET cross_shard_txn = 'strict';
150156

151-
-- Opt out of atomicity for bulk loads (each shard commits independently)
157+
-- Opt out of atomicity for bulk loads: writes are grouped per vShard and each
158+
-- group commits as an independent single-vShard transaction; a failure does NOT
159+
-- roll back vShards that already committed
152160
SET cross_shard_txn = 'best_effort_non_atomic';
153161
```
154162

155-
Single-shard writes bypass the sequencer entirely and go directly through the relevant data-group Raft.
163+
(Bare `best_effort` is deliberately rejected; invalid values return SQLSTATE `22023`.)
164+
165+
**Single-node deployments run Calvin by default** (`[server] single_node_calvin = true`): a standalone node synthesizes a one-node sequencer group so transactions spanning multiple cores (vShards) commit atomically instead of being rejected. Set it `false` to force the legacy fast path. Uncontended single-shard point writes bypass the sequencer entirely and go directly through the relevant data-group Raft; contended or predicate/bulk writes route through the deterministic scheduler.
166+
167+
**Overlay hygiene.** Per-transaction staging overlays are kept alive by every staged write/read; overlays orphaned by vanished clients are reaped after a 6-hour lease. The `nodedb_active_txn_overlays` Prometheus gauge tracks live overlays. Data-Plane resource rejection surfaces as SQLSTATE `53200` (backpressure — retry when pressure subsides).
156168

157169
## Distributed Query Execution
158170

docs/bitemporal.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ ORDER BY _ts_system ASC;
164164

165165
`AS OF SYSTEM TIME NULL` returns every system-time version of each matching row, ordered ascending. Each version row carries the user columns plus three synthetic temporal columns: `_ts_system`, `_ts_valid_from`, and `_ts_valid_until`. Unbounded valid-time ends surface as the raw `i64::MIN` / `i64::MAX` sentinels (matching the Columnar and Timeseries engines). It is supported on the Document (strict and schemaless), Columnar, and Timeseries engines. It is not supported on the Graph or Array engines, nor when reading through a database clone — those return a typed error rather than collapsing to a single version.
166166

167+
`AS OF` reads run at parity with non-temporal reads: `WHERE` predicates (including on strict Binary-Tuple collections), `ORDER BY`, computed columns, and window functions all apply to temporal queries.
168+
167169
**Reserved columns never leak.** Bitemporal collections store their temporal envelope in reserved columns (`__system_from_ms`, `__valid_from_ms`, `__valid_until_ms`). These are hidden from user projections: `SELECT *` on a bitemporal collection returns exactly the declared user columns. Temporal data is only exposed through the synthetic `_ts_*` columns of audit queries.
168170

169171
## Index Engines and Temporal Composition

docs/databases.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,8 @@ ALTER DATABASE analytics SET IDLE_TIMEOUT 0; -- disabled
337337

338338
Sessions idle longer than the configured time are automatically closed with `SESSION_IDLE_TIMEOUT`. Tracks are monitored per-database; timeout is checked at request boundaries.
339339

340+
Separately, a process-global pgwire listener watchdog force-closes idle connections after `auth.idle_timeout_secs` (default 3600s; `auth.session_absolute_timeout_secs` caps total session age, default disabled). The watchdog never kills a connection mid-statement — the idle window starts when a statement completes — and its teardown reclaims any idle-in-transaction staging overlay. The per-database `SET IDLE_TIMEOUT` and the global watchdog are independent mechanisms; the stricter one wins.
341+
340342
### View and Kill Sessions
341343

342344
```sql

docs/documents.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,43 @@ FROM orders
137137
GROUP BY status;
138138
```
139139

140+
## CRDT Document Collections
141+
142+
Declare a document collection CRDT-backed at creation time and plain SQL DML converges via last-writer-wins instead of overwriting:
143+
144+
```sql
145+
CREATE COLLECTION crdt_notes (
146+
id TEXT PRIMARY KEY,
147+
title TEXT,
148+
body TEXT
149+
) WITH (crdt=true);
150+
151+
-- Full-replace write (untouched keys pruned)
152+
INSERT INTO crdt_notes (id, title, body) VALUES ('a', 'v1', 'text');
153+
UPSERT INTO crdt_notes (id, title) VALUES ('a', 't2');
154+
155+
-- PK-targeted UPDATE is a per-field LWW merge: only the provided
156+
-- fields are written; untouched fields survive concurrent writers
157+
UPDATE crdt_notes SET title = 't3' WHERE id = 'a';
158+
159+
-- PK-targeted DELETE writes a tombstone
160+
DELETE FROM crdt_notes WHERE id = 'a';
161+
162+
-- RETURNING works on CRDT UPDATE/DELETE
163+
UPDATE crdt_notes SET title = 't4' WHERE id = 'a' RETURNING id, title;
164+
DELETE FROM crdt_notes WHERE id = 'a' RETURNING id;
165+
```
166+
167+
`crdt=true` is only valid on document collections. DML on a CRDT collection routes through the CRDT engine — there is no silent fallthrough to the plain document path (that would bypass convergence). Statement shapes that cannot be expressed as a CRDT operation are rejected with a typed error rather than downgraded:
168+
169+
- Predicate (non-primary-key) `UPDATE` / `DELETE`
170+
- `UPDATE` with a non-literal right-hand side (`SET count = count + 1`)
171+
- `INSERT ... ON CONFLICT DO UPDATE`
172+
173+
The `crdt` flag is part of the collection descriptor: it replicates across the cluster and travels in sync `CollectionSchema` announcements, so Lite/WASM peers see the same convergence semantics.
174+
175+
**Movable lists (SDK).** The client SDK exposes CRDT movable-list operations on documents: `list_insert(collection, doc_id, list_path, index, fields)`, `list_delete(...)`, and `list_move(collection, doc_id, list_path, from_index, to_index)` — dispatched as native opcodes and merged conflict-free across devices.
176+
140177
## Choosing Between Modes
141178

142179
| | Schemaless | Strict |

docs/offline-sync-patterns.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ Columnar, Vector, FTS, and Spatial collections now participate in **inbound sync
117117

118118
**Schema announce.** A sync peer announces each collection's schema (`CollectionSchema` message) before sending any shape or delta data. Collections unknown to the receiver are materialized into its catalog and propagate cluster-wide via Raft — create-only, so an existing collection is never clobbered. A Lite client can therefore bootstrap collections on Origin simply by announcing their schema.
119119

120-
**Constraint validation and rejects.** UNIQUE, FK, required-field, and CHECK constraints are validated when deltas apply on Origin. Because CRDT deltas are commutative and already merged on import, enforcement is a post-hoc pass: a violating row is quarantined and the client receives a `DeltaReject` carrying a typed `CompensationHint` (e.g., `UniqueViolation { field, conflicting_value }`, `RetryWithDifferentValue`, `ManualIntervention`) telling it precisely what to fix. A rejected delta does not wedge the stream — later deltas continue to apply.
120+
**Constraint validation and rejects.** UNIQUE, FK, required-field, and CHECK constraints are validated when deltas apply on Origin. Because CRDT deltas are commutative and already merged on import, enforcement is a post-hoc pass: a violating row is quarantined and the client receives a `DeltaReject` carrying a typed `CompensationHint` (e.g., `UniqueViolation { field, conflicting_value }`, `RetryWithDifferentValue`, `ManualIntervention`) telling it precisely what to fix. A rejected delta does not wedge the stream — later deltas continue to apply. A delta must also carry exactly **one document** — cross-engine identity binds one surrogate per delta — so a delta writing rows outside its frame target is rejected with `RejectedConstraint { constraint: "crdt_single_document_delta" }`.
121121

122122
**Durability.** Acknowledged sync writes are quorum-durable: the delta commits through the data group's Raft log before the ack, so it survives leader failover. Producers carry `(producer_id, epoch, seq)` provenance; replays are acknowledged as duplicates rather than double-applied.
123123

docs/query-language.md

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ SELECT * FROM users WHERE role IN ('admin', 'editor');
4949
SELECT * FROM users WHERE deleted_at IS NULL;
5050
```
5151

52+
Timestamp-valued strings compare by instant, not bytes: a stored ISO-8601 value (`'2026-07-02T13:00:00.000000Z'`) matches a SQL-style literal (`'2026-07-02 13:00:00'`) in `=` and range predicates when both sides parse as datetimes. Non-datetime strings keep lexicographic comparison.
53+
5254
### Aggregates
5355

5456
```sql
@@ -722,6 +724,21 @@ WHERE category = 'machine-learning'
722724
);
723725
```
724726

727+
### Sparse Vector Search
728+
729+
```sql
730+
-- Dimensionless sparse-vector column; values are '{dimension: weight}' literals
731+
CREATE TABLE sparse_docs (id TEXT PRIMARY KEY, terms SPARSEVECTOR);
732+
INSERT INTO sparse_docs (id, terms) VALUES ('a', '{3: 1.0, 7: 1.0}');
733+
734+
-- Dot-product top-k ranking; LIMIT sets k
735+
SELECT id FROM sparse_docs
736+
ORDER BY sparse_score(terms, '{3: 1.0, 7: 0.5}') DESC
737+
LIMIT 3;
738+
```
739+
740+
The inverted index is maintained automatically on document writes (insert, update, delete). Documents sharing no dimension with the query are never scanned. `sparse_score` is a standalone surface — it is not fused into `rrf_score` hybrid ranking.
741+
725742
### Full-Text Search
726743

727744
```sql
@@ -856,13 +873,18 @@ SELECT * FROM events WHERE doc_array_contains(payload, '$.tags', 'important');
856873
### CRDT
857874

858875
```sql
859-
-- Read CRDT state
860-
SELECT crdt_state('collab_docs', 'doc123');
876+
-- Declare a document collection CRDT-backed: plain DML converges via LWW
877+
CREATE COLLECTION notes (id TEXT PRIMARY KEY, title TEXT, body TEXT) WITH (crdt=true);
878+
UPDATE notes SET title = 't2' WHERE id = 'a'; -- per-field LWW merge
879+
DELETE FROM notes WHERE id = 'a' RETURNING id; -- tombstone
861880

862-
-- Apply delta
881+
-- Low-level delta path
882+
SELECT crdt_state('collab_docs', 'doc123');
863883
SELECT crdt_apply('collab_docs', 'doc123', '<delta_bytes>');
864884
```
865885

886+
`WITH (crdt=true)` is document-collection-only. Non-PK-targeted UPDATE/DELETE, non-literal SET values, and `ON CONFLICT DO UPDATE` are rejected on CRDT collections (no representable CRDT operation). See [Documents — CRDT Document Collections](documents.md#crdt-document-collections).
887+
866888
### Array (NDArray)
867889

868890
```sql
@@ -967,6 +989,10 @@ COMMIT;
967989

968990
Isolation level: **Snapshot Isolation (SI)**. Reads see a consistent snapshot from `BEGIN` time. Write conflicts detected at `COMMIT` and surfaced as SQLSTATE `40001` (`could not serialize access due to concurrent update`).
969991

992+
### Cross-Shard Transactions
993+
994+
An interactive `BEGIN ... COMMIT` block whose statements span multiple vShards or nodes commits **atomically** by default — the whole block flushes through the Calvin sequencer's durable vote/verdict barrier at COMMIT. Reads taken during the transaction (point reads, predicate scans, index probes, both sides of distributed JOINs) are OCC-validated at COMMIT; a stale read aborts with `40001` (retry the transaction). `SET cross_shard_txn = 'best_effort_non_atomic'` opts bulk loads out of cross-shard atomicity. Single-node deployments run the same path by default (`single_node_calvin = true`), so transactions spanning cores commit atomically too. See [Architecture — Cross-Shard Transactions](architecture.md#cross-shard-transactions).
995+
970996
### Read-Your-Own-Writes
971997

972998
Statements inside `BEGIN ... COMMIT` are staged in a per-transaction overlay on the Data Plane. Reads within the same transaction observe staged writes **across every engine**: KV, document (schemaless and strict), columnar, timeseries, graph (single-hop, multi-hop, shortest path), vector search, spatial predicates, full-text search, and hybrid search fusion. Constraint violations (e.g., primary-key uniqueness) surface immediately at statement time, not at COMMIT.

docs/real-time.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ SHOW TRIGGERS;
7171
| `SYNC` | Same transaction (ACID) | Trigger time added | Yes |
7272
| `DEFERRED` | Same transaction, batched | At COMMIT time | Yes |
7373

74+
**Graph mutations emit events.** Graph edge and node-label writes publish `WriteEvent`s like any other mutation, so triggers, change streams, and streaming MVs can react to graph changes:
75+
76+
- **Edges** — published on the edge's collection with a stable `row_id` composed from the `(src, label, dst)` triple. Insert/put carries the edge properties in `new_value`; delete carries no payload.
77+
- **Node labels** — published tenant-wide on the dedicated stream `__graph_node_labels__`. `SET` labels → `Insert` with the added labels in `new_value` (`{ "labels": [...] }`); `REMOVE``Delete` with the removed labels in `old_value`. `row_id` is the node id.
78+
79+
Implicit edges (a document insert carrying `_from`/`_to`) are not double-published — the underlying document write already emits its event. WAL replay reconstructs graph events byte-identically and dedups on LSN.
80+
7481
**UPSERT and `ON CONFLICT` firing semantics.** The `WriteOp` tag emitted to the Event Plane is derived from storage prior-bytes, not from the surface SQL verb. An `UPSERT` or `INSERT ... ON CONFLICT (pk) DO UPDATE` that finds an existing row fires `AFTER UPDATE`; the same statement against a non-existent key fires `AFTER INSERT`. `ON CONFLICT DO NOTHING` on a conflict emits no event at all.
7582

7683
## CDC Change Streams

docs/vectors.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,24 @@ LIMIT 10;
7878

7979
Set `target_recall` and let the planner do the rest unless you need a hard latency ceiling.
8080

81+
## Sparse Vectors
82+
83+
For learned sparse representations (SPLADE, uniCOIL, BM25-style term weights), declare a dimensionless `SPARSEVECTOR` column. An inverted index over the non-zero dimensions is maintained automatically on every document write.
84+
85+
```sql
86+
CREATE TABLE sparse_docs (id TEXT PRIMARY KEY, terms SPARSEVECTOR);
87+
88+
-- Values are '{dimension: weight}' string literals
89+
INSERT INTO sparse_docs (id, terms) VALUES ('a', '{3: 1.0, 7: 1.0}');
90+
91+
-- Dot-product top-k; LIMIT sets k
92+
SELECT id FROM sparse_docs
93+
ORDER BY sparse_score(terms, '{3: 1.0, 7: 0.5}') DESC
94+
LIMIT 3;
95+
```
96+
97+
`SPARSEVECTOR` carries no fixed dimensionality — only non-zero `(dimension, weight)` pairs are stored and indexed. Documents sharing no dimension with the query are excluded by the inverted index rather than scored at zero. `sparse_score` ranks by descending dot product; it is a standalone surface, separate from the dense `vector_distance` path and the RRF hybrid fusion.
98+
8199
## Vector-Primary Collections
82100

83101
By default, vectors are an _index_ attached to a column on a normal collection — the document store is the source of truth. For pure-vector workloads (RAG corpora, recommendation memory, embedding stores) flip a collection into vector-primary mode where the vector index is the primary access path and the document store is a metadata sidecar:

0 commit comments

Comments
 (0)