Skip to main content

SochDB Architecture

Deep technical reference for SochDB's internals.

Core engine: 2.0.3 ย |ย  License: core engine AGPL-3.0-or-later (commercial licensing available); language SDKs Apache-2.0.

Component versions

The core Rust engine and the language SDKs version independently: core engine 2.0.3, Python SDK 0.5.9, Node.js SDK 0.5.3, Go SDK 0.4.5. See the Architecture overview for the conceptual picture.

This page is the deeper companion to the conceptual Architecture overview. Some diagrams below are illustrative of the design intent; the prose and parameter tables are grounded in the v2.0.3 source.


Table of Contentsโ€‹

  1. Design Philosophy
  2. Crate Map
  3. Data Model
  4. TOON Format Internals
  5. Storage Engine
  6. Transaction System
  7. Index Structures
  8. SQL Engine
  9. Query Processing
  10. Memory Management
  11. Concurrency Model
  12. Recovery & Durability
  13. Server Components
  14. Security
  15. Change Data Capture
  16. SDK Architecture

Design Philosophyโ€‹

SochDB is built around four core principles:

1. Token Efficiency Firstโ€‹

Every design decision prioritizes minimizing tokens when data is consumed by LLMs:

Traditional approach:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ LLM โ† JSON โ† SQL Result โ† Query Optimizer โ† B-Tree โ”‚
โ”‚ ~150 tokens for 3 rows โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

SochDB approach:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ LLM โ† TOON โ† Columnar Scan โ† Path Resolution โ”‚
โ”‚ ~50 tokens for 3 rows โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
Token reduction

TOON typically uses 40-66% fewer tokens than equivalent JSON for the same result set. The exact savings depend on shape and cardinality; the format makes no fixed numeric guarantee in code. See TOON Format.

2. Path-Based Accessโ€‹

O(|path|) resolution instead of O(log N) tree traversal:

Path: "users/42/profile/avatar"

TCH Resolution (Trie-Columnar Hybrid):
โ”œโ”€ users โ†’ Table lookup (O(1) hash)
โ”‚ โ””โ”€ 42 โ†’ Row index (O(1) direct)
โ”‚ โ””โ”€ profile โ†’ Column group (O(1))
โ”‚ โ””โ”€ avatar โ†’ Column offset (O(1))

Total: O(4) = O(|path|), regardless of table size

3. Columnar by Defaultโ€‹

Read only what you need:

SELECT name, email FROM users WHERE id = 42;

Row Store (read all columns):
โ”Œโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ id โ”‚ name โ”‚ email โ”‚ age โ”‚ preferences โ”‚ avatar โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ†‘ Read 6 columns ร— row size

Columnar Store (read only needed):
โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ id โ”‚ โ”‚ name โ”‚ โ”‚ email โ”‚
โ””โ”€โ”€โ†‘โ”€โ”€โ”˜ โ””โ”€โ”€โ†‘โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ†‘โ”€โ”€โ”€โ”˜
Read 3 columns only = 50% I/O reduction

4. Embeddable & Extensibleโ€‹

Single-file deployment with plugin architecture:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Your Application โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ SochDB (embedded) โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ Core โ”‚ โ”‚ Kernel โ”‚ โ”‚ Plugins โ”‚ โ”‚
โ”‚ โ”‚ ~500 KB โ”‚ โ”‚ ~1 MB โ”‚ โ”‚ (optional) โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Crate Mapโ€‹

SochDB is a Rust workspace (edition 2024, MSRV 1.85). The published client crate is sochdb (its source lives in sochdb-client/). All workspace crates inherit version = "2.0.3" and license = "AGPL-3.0-or-later" from [workspace.package].

CrateRole
sochdb-coreCore types, SochValue, and the TOON data format (soch.rs, soch_ql.rs).
sochdb-storageLog-structured columnar store, WAL, MVCC, CDC, encryption, IPC server.
sochdb-indexHNSW vector index (hnsw.rs, hnsw_staged.rs), quantization, SIMD distance.
sochdb-vectorBM25, RRF fusion, sharding/compaction primitives.
sochdb-querySQL/SochQL engine: volcano executor, SqlBridge, trigram + grep lanes, fusion.
sochdb-fusionFused compositional query execution (ART + HNSW + CSR in one pipeline).
sochdb-clientPublished client crate (sochdb).
sochdb-grpc"Thick server" binary sochdb-grpc-server: gRPC, WebSocket, pg-wire, metrics, MCP.
sochdb-mcpStandalone stdio Model Context Protocol server (sochdb-mcp binary).
sochdb-memoryAgent-memory schema (episodes / entities / events).
sochdb-kernel, sochdb-tools, sochdb-simulation, sochdb-bench, sochdb-wasmKernel, tooling, deterministic simulation, benchmarks, WASM target.

The Python native extension (sochdb-python, built with maturin) is excluded from the workspace and built separately.


Data Modelโ€‹

Trie-Columnar Hybrid (TCH)โ€‹

TCH combines trie-based path resolution with columnar storage:

                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Path Trie โ”‚
โ”‚ โ”‚
โ”‚ [root] โ”‚
โ”‚ / \ โ”‚
โ”‚ users orders โ”‚
โ”‚ / \ \ โ”‚
โ”‚ [id] [*] [id] โ”‚
โ”‚ / | \ | โ”‚
โ”‚ name email age amount โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Column Store โ”‚
โ”‚ โ”‚
โ”‚ users.id [1, 2, 3, 4, 5, ...] โ”‚
โ”‚ users.name [Alice, Bob, ...] โ”‚
โ”‚ users.email [a@e.com, b@e.com, ...] โ”‚
โ”‚ users.age [25, 30, 28, ...] โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Path Resolution Algorithmโ€‹

fn resolve(&self, path: &str) -> PathResolution {
let parts: Vec<&str> = path.split('/').collect();

// Navigate trie
let mut node = &self.root;
for part in &parts[..parts.len()-1] {
node = match node.children.get(*part) {
Some(child) => child,
None => return PathResolution::NotFound,
};
}

// Final part determines resolution type
let final_part = parts.last().unwrap();
match node.node_type {
NodeType::Table => PathResolution::Array { ... },
NodeType::Row => PathResolution::Value { ... },
NodeType::Column => {
// Direct column access
let col_idx = self.column_index(node, final_part);
PathResolution::Column { idx: col_idx }
}
}
}

Schema Representationโ€‹

struct TableInfo {
schema: ArraySchema,
columns: Vec<ColumnRef>,
next_row_id: u64,
indexes: HashMap<String, IndexRef>,
}

struct ColumnRef {
id: u32,
name: String,
field_type: FieldType,
compression: Compression,
encoding: Encoding,
}

enum FieldType {
Int64,
UInt64,
Float64,
Text,
Bytes,
Bool,
Vector(usize),
}

TOON Format Internalsโ€‹

Text Format Grammarโ€‹

document     ::= table_header newline row*
table_header ::= name "[" count "]" "{" fields "}" ":"
name ::= identifier
count ::= integer
fields ::= field ("," field)*
field ::= identifier
row ::= value ("," value)* newline
value ::= null | bool | number | string | array | ref

null ::= "โˆ…"
bool ::= "T" | "F"
number ::= integer | float
integer ::= "-"? digit+
float ::= "-"? digit+ "." digit+
string ::= raw_string | quoted_string
raw_string ::= [^,;\n"]+
quoted_string::= '"' ([^"\\] | escape)* '"'
array ::= "[" value ("," value)* "]"
ref ::= "ref(" identifier "," integer ")"

Binary Formatโ€‹

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ TOON Binary Format โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Header (16 bytes) โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ Magic โ”‚ Version โ”‚ Flags โ”‚ Row Countโ”‚ โ”‚
โ”‚ โ”‚ (4 bytes)โ”‚ (2 bytes)โ”‚ (2 bytes)โ”‚ (8 bytes)โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Schema Section โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ Name Len โ”‚ Table Name (UTF-8) โ”‚ โ”‚
โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚
โ”‚ โ”‚ Col Countโ”‚ [Column Definitions...] โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Data Section (columnar) โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ Column 0: [type_tag][values...] โ”‚ โ”‚
โ”‚ โ”‚ Column 1: [type_tag][values...] โ”‚ โ”‚
โ”‚ โ”‚ ... โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Type Tagsโ€‹

#[repr(u8)]
pub enum SochTypeTag {
Null = 0x00,
False = 0x01,
True = 0x02,
PosFixint = 0x10, // 0-15 in lower nibble
NegFixint = 0x20, // -16 to -1 in lower nibble
Int8 = 0x30,
Int16 = 0x31,
Int32 = 0x32,
Int64 = 0x33,
Float32 = 0x40,
Float64 = 0x41,
FixStr = 0x50, // 0-15 char length in lower nibble
Str8 = 0x60,
Str16 = 0x61,
Str32 = 0x62,
Array = 0x70,
Ref = 0x80,
}

Varint Encodingโ€‹

fn encode_varint(mut value: u64, buf: &mut Vec<u8>) {
while value >= 0x80 {
buf.push((value as u8) | 0x80);
value >>= 7;
}
buf.push(value as u8);
}

fn decode_varint(buf: &[u8]) -> (u64, usize) {
let mut result = 0u64;
let mut shift = 0;
let mut bytes_read = 0;

for &byte in buf {
bytes_read += 1;
result |= ((byte & 0x7F) as u64) << shift;
if byte & 0x80 == 0 {
break;
}
shift += 7;
}

(result, bytes_read)
}

Storage Engineโ€‹

Log-Structured Columnar Store (LSCS)โ€‹

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ LSCS Architecture โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ โ”‚
โ”‚ Write Path: โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ Write โ”‚ โ†’ โ”‚ WAL โ”‚ โ†’ โ”‚Memtable โ”‚ โ†’ โ”‚ SST โ”‚ โ”‚
โ”‚ โ”‚ Request โ”‚ โ”‚ (Append)โ”‚ โ”‚(In-mem) โ”‚ โ”‚ (Disk) โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ”‚
โ”‚ Read Path: โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ Read โ”‚ โ†’ โ”‚ Memtable โ†’ L0 SSTs โ†’ L1 โ†’ L2 โ†’ ... โ”‚ โ”‚
โ”‚ โ”‚ Request โ”‚ โ”‚ (Bloom filter + block cache) โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

SST File Formatโ€‹

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ SST File Structure โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Data Blocks โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ Block 0: [key1:val1][key2:val2]...[keyN:valN][trailer] โ”‚ โ”‚
โ”‚ โ”‚ Block 1: [key1:val1][key2:val2]...[keyN:valN][trailer] โ”‚ โ”‚
โ”‚ โ”‚ ... โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Meta Blocks โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ Bloom Filter Block โ”‚ โ”‚
โ”‚ โ”‚ Column Stats Block โ”‚ โ”‚
โ”‚ โ”‚ Compression Dict Block (optional) โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Index Block โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ [first_key_0, offset_0, size_0] โ”‚ โ”‚
โ”‚ โ”‚ [first_key_1, offset_1, size_1] โ”‚ โ”‚
โ”‚ โ”‚ ... โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Footer (48 bytes) โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚
โ”‚ โ”‚ Meta Index โ”‚ Index Handle โ”‚ Magic + Version โ”‚โ”‚
โ”‚ โ”‚ BlockHandle โ”‚ BlockHandle โ”‚ โ”‚โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Block Checksumsโ€‹

/// CRC32C with hardware acceleration (SSE4.2)
pub fn crc32c(data: &[u8]) -> u32 {
let mut crc: u32 = !0;

// Process 8 bytes at a time using CRC32Q
let chunks = data.chunks_exact(8);
let remainder = chunks.remainder();

for chunk in chunks {
let val = u64::from_le_bytes(chunk.try_into().unwrap());
crc = unsafe { _mm_crc32_u64(crc as u64, val) as u32 };
}

// Handle remaining bytes
for &byte in remainder {
crc = unsafe { _mm_crc32_u8(crc, byte) };
}

!crc
}

/// Mask CRC to avoid all-zeros problem
pub fn mask_crc(crc: u32) -> u32 {
((crc >> 15) | (crc << 17)).wrapping_add(0xa282ead8)
}

Compactionโ€‹

Level 0 (L0): Recent flushes, may overlap
โ”Œโ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”
โ”‚SST1โ”‚ โ”‚SST2โ”‚ โ”‚SST3โ”‚ โ”‚SST4โ”‚ โ† 4 files, overlapping key ranges
โ””โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”˜
โ”‚
โ–ผ Compaction (merge sort)
Level 1 (L1): Non-overlapping, sorted
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ SST (merged) โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚
โ–ผ Size-triggered compaction
Level 2 (L2): 10x larger budget
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ SST files โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Transaction Systemโ€‹

MVCC Implementationโ€‹

struct VersionedValue {
value: Option<Vec<u8>>, // None = tombstone
txn_id: u64, // Transaction that wrote this
timestamp: u64, // Commit timestamp
next: Option<Box<VersionedValue>>, // Older versions
}

struct MVCCStore {
current: HashMap<Key, VersionedValue>,
gc_watermark: AtomicU64,
}

impl MVCCStore {
fn get(&self, key: &Key, snapshot_ts: u64) -> Option<&[u8]> {
let mut version = self.current.get(key)?;

// Find visible version
while version.timestamp > snapshot_ts {
version = version.next.as_ref()?;
}

version.value.as_deref()
}

fn put(&self, key: Key, value: Vec<u8>, txn: &Transaction) {
let new_version = VersionedValue {
value: Some(value),
txn_id: txn.id,
timestamp: txn.commit_ts,
next: self.current.remove(&key).map(Box::new),
};
self.current.insert(key, new_version);
}
}

Transaction Lifecycleโ€‹

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Transaction Lifecycle โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ โ”‚
โ”‚ BEGIN โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ 1. Allocate txn_id (atomic increment) โ”‚ โ”‚
โ”‚ โ”‚ 2. Take snapshot_ts = current_ts โ”‚ โ”‚
โ”‚ โ”‚ 3. Add to active_transactions set โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ”‚ โ”‚
โ”‚ โ–ผ โ”‚
โ”‚ OPERATIONS (read/write) โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ Reads: Use snapshot_ts for visibility โ”‚ โ”‚
โ”‚ โ”‚ Writes: Buffer in transaction-local write set โ”‚ โ”‚
โ”‚ โ”‚ Check for write-write conflicts โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ”‚ โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ–ผ โ–ผ โ”‚
โ”‚ COMMIT ROLLBACK โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ 1. Validate โ”‚ โ”‚ 1. Discard write โ”‚ โ”‚
โ”‚ โ”‚ 2. Write to WAL โ”‚ โ”‚ set โ”‚ โ”‚
โ”‚ โ”‚ 3. Apply writes โ”‚ โ”‚ 2. Remove from โ”‚ โ”‚
โ”‚ โ”‚ 4. Advance ts โ”‚ โ”‚ active set โ”‚ โ”‚
โ”‚ โ”‚ 5. Remove from โ”‚ โ”‚ 3. Release locks โ”‚ โ”‚
โ”‚ โ”‚ active set โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Group Commitโ€‹

/// Optimal batch size: N* = โˆš(2 ร— L_fsync ร— ฮป / C_wait)
struct GroupCommitBuffer {
pending: VecDeque<PendingCommit>,
batch_id: u64,
config: GroupCommitConfig,
}

impl GroupCommitBuffer {
fn optimal_batch_size(&self, arrival_rate: f64, wait_cost: f64) -> usize {
let l_fsync = self.config.fsync_latency_us as f64 / 1_000_000.0;
let n_star = (2.0 * l_fsync * arrival_rate / wait_cost).sqrt();
(n_star as usize).clamp(1, self.config.max_batch_size)
}

fn submit_and_wait(&self, op_id: u64) -> Result<u64> {
let mut inner = self.inner.lock();

inner.pending.push_back(PendingCommit {
id: op_id,
batch_id: inner.batch_id,
committed: false,
});

// Flush if batch is full
if inner.pending.len() >= self.config.target_batch_size {
self.flush_batch(&mut inner)?;
}

// Wait for commit (with timeout)
self.condvar.wait_for(&mut inner, self.config.max_wait);

Ok(inner.batch_id)
}
}

Index Structuresโ€‹

HNSW (Vector Index)โ€‹

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ HNSW Graph Structure โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ โ”‚
โ”‚ Layer 2: โ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ— โ”‚
โ”‚ โ”‚ โ”‚ โ”‚
โ”‚ Layer 1: โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ— โ”‚
โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚
โ”‚ Layer 0: โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ— โ”‚
โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚
โ”‚ v0 v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 โ”‚
โ”‚ โ”‚
โ”‚ Search: Start at top layer, greedily descend โ”‚
โ”‚ Insert: Random level, connect at each layer โ”‚
โ”‚ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

SochDB treats HNSW as the default, correctnessโ€‘first navigator. It is trainingโ€‘free, robust under updates, and provides predictable tail behavior. A learned navigator (CHN) is optional and only enabled behind a feature gate with strict acceptance tests:

  • Routing accuracy: must meet recall@k vs. a fixed probe budget.
  • Worstโ€‘case fallback: every CHN proposal must degrade to HNSW/IVF probing if confidence is low.
  • Drift detection: monitored accuracy triggers retraining or fallback to HNSW.

This keeps production behavior stable while allowing controlled experimentation with learned routing.

HNSW Parametersโ€‹

HnswConfig (sochdb-index/src/hnsw.rs) with the v2.0.3 defaults:

struct HnswConfig {
/// M: max connections per node per layer (except layer 0).
/// Higher M = better recall, more memory.
max_connections: usize, // default: 32

/// M0: max connections at layer 0 (standard m0 = 2*M).
max_connections_layer0: usize, // default: 64

/// Level multiplier mL = 1/ln(M).
level_multiplier: f32, // default: 1/ln(32) โ‰ˆ 0.288

/// ef_construction: search width during build.
/// Higher = better graph quality, slower build.
ef_construction: usize, // default: 256

/// ef_search: search width during query.
/// Higher = better recall, slower query.
ef_search: usize, // default: 500

/// Distance metric: Cosine | Euclidean | DotProduct.
metric: DistanceMetric, // default: Cosine

/// Storage precision: F32 | F16 | BF16.
quantization_precision: Option<Precision>, // default: Some(F32)
}

These defaults are recall-tuned: M=32 clears recall@10 โ‰ˆ 95% out of the box (deep-1M recall@10 rises from 0.967 at M=16 to 0.988 at M=32), and ef_construction=256 helps hard high-dimensional embeddings.

No dimension-aware ef_search

ef_search is a single fixed default of 500 โ€” there is no dimension-keyed ef_search branching (e.g. a 500/1500 split) anywhere in the core. The dimension-aware logic that does exist is the brute-force flat-scan threshold (see below), keyed on the flat-scan crossover, not on ef_search.

When the corpus is small, search bypasses the graph and runs an exact parallel SIMD flat scan. The crossover is dimension-aware (hnsw.rs):

let flat_scan_threshold = if dimension <= 128 { 10_000 }
else if dimension <= 384 { 4_000 }
else { 1_000 }; // 768D and above

For node_count <= flat_scan_threshold a parallel flat scan is exact and faster than HNSW. AdaptiveSearchConfig (target recall 0.95, min_ef=10, max_ef=500) can binary-search the minimum ef that hits a target recall.

Level Generationโ€‹

/// Per HNSW paper: level = floor(-ln(uniform(0,1)) * mL)
fn random_level(&self) -> usize {
let uniform: f32 = thread_rng().gen();
let level = if uniform > 0.0 {
(-uniform.ln() * self.level_multiplier).floor() as usize
} else {
0
};
level.min(MAX_LEVEL)
}

// Distribution for M=32, mL โ‰ˆ 0.288:
// most nodes live only at layer 0; higher layers thin out
// geometrically, giving the logarithmic search structure.

Staged Parallel Constructionโ€‹

Bulk index builds use a three-phase deferred-backedge construction (hnsw_staged.rs) to parallelize while keeping the graph correct:

Phase 1 โ€” Sequential Scaffold
Insert S seed nodes single-threaded to establish the upper layers.

Phase 2 โ€” Parallel Waves
Insert the remaining nodes in waves of B nodes across rayon threads.
Backedges are NOT applied inline; each thread buffers them locally
(insert_with_deferred_backedges).

Phase 3 โ€” Backedge Consolidation
Apply all deferred backedges in parallel, partitioned by target node,
then run refine_graph_additive() (2 passes, ef = ef_construction.max(200))
to repair neighbor quality.

rebuild_layer0_exact() is a small-index recall booster (brute-force exact layer-0 rebuild, O(NยฒยทD)). It is a no-op above a size cap โ€” a 1M-vector exact rebuild OOM'd a 55 GB box, whereas skipping it built in ~195s at recall@10 โ‰ˆ 0.968.

MultiShardHnswIndex is Python-only

MultiShardHnswIndex is a threaded scatter-gather wrapper that exists only in the Python native package (v2.0.3 PyO3 extension). It is not a core-engine or server type โ€” there is no HNSW shard/scatter-gather struct in the Rust engine.

Search Algorithmโ€‹

fn search(&self, query: &[f32], k: usize) -> Vec<(u64, f32)> {
let nodes = self.nodes.read();
let entry = self.entry_point.load(Ordering::Acquire);

if nodes.is_empty() {
return vec![];
}

let mut current = entry;
let mut current_dist = self.distance(query, &nodes[current].vector);

// Traverse from top to layer 1
for layer in (1..=self.max_level).rev() {
loop {
let mut changed = false;
for &neighbor in &nodes[current].layers[layer] {
let dist = self.distance(query, &nodes[neighbor].vector);
if dist < current_dist {
current = neighbor;
current_dist = dist;
changed = true;
}
}
if !changed { break; }
}
}

// Search at layer 0 with ef candidates
self.search_layer(&nodes, query, current, 0, self.ef_search)
.into_iter()
.take(k)
.map(|idx| (nodes[idx].edge_id, self.distance(query, &nodes[idx].vector)))
.collect()
}

SQL Engineโ€‹

SochDB does not have a single SQL engine. Three distinct code paths exist in sochdb-query, with different statement coverage:

PathSourceRole
Volcano executorsochdb-query/src/executor/The real operator engine. Row-at-a-time iterator model (PlanNode::next() -> Result<Option<Row>>). Drives EXPLAIN.
SqlBridgesochdb-query/src/sql/bridge.rsStorage-backed dispatcher with the fullest statement coverage (CREATE/DROP INDEX, UPDATE, DELETE, ALTER TABLE, scopes/permissions). This is the production write path and what pg-wire uses with --pg-data-dir.
SqlExecutorsochdb-query/src/sql/mod.rsAn in-memory reference implementation (tables: HashMap). Handles only SELECT/INSERT/UPDATE/DELETE/CREATE TABLE/DROP TABLE/BEGIN/COMMIT/ROLLBACK, and rejects multi-table FROM. Not the production path.

Volcano Operatorsโ€‹

The planner pipeline is FROM โ†’ WHERE (Filter) โ†’ GROUP BY/agg (HashAggregate) โ†’ HAVING (Filter) โ†’ SELECT (Project) โ†’ ORDER BY (Sort) โ†’ LIMIT/OFFSET (Limit). Operators (all implement PlanNode): SeqScan, IndexSeek, Filter, Project, Sort, Limit, HashJoin / NestedLoopJoin / MergeJoin, HashAggregate, Explain, Values, Empty.

JOINs: the executor and bridge implement INNER, LEFT, RIGHT, FULL, and CROSS. ON a=b plans a HashJoin; non-equi ON plans a NestedLoopJoin; USING(col) plans a HashJoin; multiple FROM tables become an implicit CROSS via NestedLoopJoin.

Stale compatibility matrix

The in-repo compatibility.rs matrix marks LEFT/RIGHT/CROSS joins as Partial/Planned, but the executor and bridge actually implement all five join types. The matrix lags the code. NATURAL JOIN does fall back to CROSS (it is not a true natural join).

Aggregatesโ€‹

There are two aggregate implementations with different coverage:

  • Volcano executor/aggregate.rs: Count, CountDistinct, Sum, Avg, Min, Max. No MEDIAN or STDDEV.
  • sql/aggregate.rs: adds Median (accumulated values) and Stddev (sample, n-1, via Welford online variance โ€” matching R's sd() / DuckDB's stddev). AVG/MEAN alias to Avg; STDDEV/STDDEV_SAMP/STDEV/SD alias to Stddev. Parallel grouped accumulation via rayon above a row-count threshold.

MEDIAN/STDDEV are therefore available only via the sql/aggregate.rs path.

Vector Search in SQLโ€‹

In SQL proper, vector search is the VECTOR_SEARCH(column, query_vector, k, metric) function, with metric keywords COSINE, EUCLIDEAN, DOT_PRODUCT. (SIMILAR_TO is a SochQL comparison operator โ€” column SIMILAR TO 'query text' โ€” routed to vector search by the optimizer, with a LIKE-style row-level fallback. It is not a raw-SQL token.)

Data Typesโ€‹

Beyond the standard SQL types (integers, Float/Double/Decimal, Char/Varchar/Text, Binary/Blob, date/time, Boolean, Json/Jsonb), SochDB adds VECTOR(dims) and EMBEDDING(dims).

Not Supportedโ€‹

DISTINCT (planner stub), window functions, CTEs/WITH, stored procedures, subqueries in WHERE/SELECT (planned), INTERSECT/EXCEPT (planned), graph traversal operators in scalar eval, and real CAST coercion (currently a pass-through). EXPLAIN works only via the volcano path โ€” the bridge returns NotImplemented for it.

Grep Laneโ€‹

A trigram inverted index (trigram_index.rs, Cox / Google Code Search design) backs a regex grep lane (grep_executor.rs): regex โ†’ required-literal extraction โ†’ trigram conjunction โ†’ posting intersection โ†’ regex verification (via the linear-time regex crate, no catastrophic backtracking). It has a no-false-negatives safety property. GrepMode::Rank plugs into RRF fusion as a third ranked lane alongside Vector and BM25; GrepMode::Gate returns an AllowedSet filter.


Query Processingโ€‹

Query Pipelineโ€‹

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Query Pipeline โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ โ”‚
โ”‚ 1. PARSE โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ Input: conn.query("users").where_eq("status", "active") โ”‚ โ”‚
โ”‚ โ”‚ Output: QueryAST { table, predicates, projections, ... } โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ”‚ โ”‚
โ”‚ 2. PLAN โ–ผ โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ โ€ข Choose access method (scan vs index) โ”‚ โ”‚
โ”‚ โ”‚ โ€ข Push down predicates โ”‚ โ”‚
โ”‚ โ”‚ โ€ข Plan column projections โ”‚ โ”‚
โ”‚ โ”‚ Output: LogicalPlan โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ”‚ โ”‚
โ”‚ 3. OPTIMIZE โ–ผ โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ โ€ข Cost-based index selection โ”‚ โ”‚
โ”‚ โ”‚ โ€ข Predicate ordering (selectivity) โ”‚ โ”‚
โ”‚ โ”‚ โ€ข Join ordering (if applicable) โ”‚ โ”‚
โ”‚ โ”‚ Output: PhysicalPlan โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ”‚ โ”‚
โ”‚ 4. EXECUTE โ–ผ โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ โ€ข Open column readers โ”‚ โ”‚
โ”‚ โ”‚ โ€ข Apply predicates (vectorized) โ”‚ โ”‚
โ”‚ โ”‚ โ€ข Materialize results โ”‚ โ”‚
โ”‚ โ”‚ Output: QueryResult โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ”‚ โ”‚
โ”‚ 5. FORMAT โ–ผ โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ โ€ข TOON: users[N]{cols}:row1;row2;... โ”‚ โ”‚
โ”‚ โ”‚ โ€ข JSON: [{"col": val}, ...] โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Predicate Pushdownโ€‹

fn push_predicates(plan: &mut ScanPlan, predicates: &[Predicate]) {
for pred in predicates {
match pred {
// Can push to storage layer
Predicate::Eq(col, val) if is_indexed(col) => {
plan.index_lookup = Some(IndexLookup {
column: col.clone(),
value: val.clone(),
});
}

// Push to block-level filtering
Predicate::Range(col, min, max) => {
plan.block_filters.push(BlockFilter {
column: col.clone(),
min: min.clone(),
max: max.clone(),
});
}

// Late filter (after scan)
_ => plan.late_filters.push(pred.clone()),
}
}
}

Memory Managementโ€‹

Buddy Allocatorโ€‹

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Buddy Allocator โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ โ”‚
โ”‚ Order 10 (1KB): [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ] โ”‚
โ”‚ โ”‚ โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ Order 9 (512B): [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ] [________________] โ”‚
โ”‚ โ”‚ โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ Order 8 (256B): [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ] [โ–ˆโ–ˆโ–ˆโ–ˆ] ... โ”‚
โ”‚ โ”‚
โ”‚ Allocation: Find smallest power-of-2 block, split if needed โ”‚
โ”‚ Deallocation: Coalesce with buddy if both free โ”‚
โ”‚ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Arena Allocatorโ€‹

struct BuddyArena {
buddy: BuddyAllocator,
current_block: Mutex<Option<ArenaBlock>>,
block_size: usize,
}

impl BuddyArena {
fn allocate(&self, size: usize, align: usize) -> Result<usize> {
let mut current = self.current_block.lock();

// Try current block
if let Some(ref mut block) = *current {
let aligned = (block.offset + align - 1) & !(align - 1);
if aligned + size <= block.size {
block.offset = aligned + size;
return Ok(block.base + aligned);
}
}

// Allocate new block
let new_size = size.max(self.block_size).next_power_of_two();
let base = self.buddy.allocate(new_size)?;

*current = Some(ArenaBlock {
base,
offset: size,
size: new_size,
});

Ok(base)
}

fn reset(&self) {
// Free all blocks at once
self.current_block.lock().take();
for block in self.blocks.drain(..) {
self.buddy.deallocate(block);
}
}
}

Concurrency Modelโ€‹

Lock Hierarchyโ€‹

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Lock Acquisition Order โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ โ”‚
โ”‚ 1. Catalog Lock (RwLock) โ”‚
โ”‚ โ””โ”€โ”€ Table schema changes โ”‚
โ”‚ โ”‚
โ”‚ 2. Table Lock (per-table RwLock) โ”‚
โ”‚ โ””โ”€โ”€ DDL operations on specific table โ”‚
โ”‚ โ”‚
โ”‚ 3. Transaction Manager Lock (Mutex) โ”‚
โ”‚ โ””โ”€โ”€ Begin/commit/abort transactions โ”‚
โ”‚ โ”‚
โ”‚ 4. WAL Lock (Mutex) โ”‚
โ”‚ โ””โ”€โ”€ Append to write-ahead log โ”‚
โ”‚ โ”‚
โ”‚ 5. Memtable Lock (RwLock) โ”‚
โ”‚ โ””โ”€โ”€ In-memory writes โ”‚
โ”‚ โ”‚
โ”‚ 6. Index Lock (per-index RwLock) โ”‚
โ”‚ โ””โ”€โ”€ Index modifications โ”‚
โ”‚ โ”‚
โ”‚ ALWAYS acquire in this order to prevent deadlocks โ”‚
โ”‚ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Lock-Free Readsโ€‹

// MVCC enables lock-free reads via snapshots
impl Database {
fn read(&self, key: &[u8], snapshot: Snapshot) -> Option<Value> {
// No locks needed - snapshot isolation
let version = self.mvcc.get_visible(key, snapshot.timestamp);
version.map(|v| v.value.clone())
}
}

// Snapshot is just a timestamp
struct Snapshot {
timestamp: u64,
txn_id: u64,
}

Recovery & Durabilityโ€‹

WAL Formatโ€‹

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ WAL Record Format โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ CRC32 โ”‚ Length โ”‚ Type โ”‚ TxnID โ”‚ Data โ”‚ โ”‚
โ”‚ โ”‚ (4 bytes)โ”‚ (4 bytes)โ”‚ (1 byte) โ”‚ (8 bytes)โ”‚ (variable) โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ”‚
โ”‚ Record Types: โ”‚
โ”‚ โ€ข 0x01: PUT (key, value) โ”‚
โ”‚ โ€ข 0x02: DELETE (key) โ”‚
โ”‚ โ€ข 0x03: BEGIN_TXN (txn_id) โ”‚
โ”‚ โ€ข 0x04: COMMIT_TXN (txn_id, commit_ts) โ”‚
โ”‚ โ€ข 0x05: ABORT_TXN (txn_id) โ”‚
โ”‚ โ€ข 0x06: CHECKPOINT (LSN, active_txns) โ”‚
โ”‚ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Recovery Processโ€‹

fn recover(&self) -> Result<RecoveryStats> {
let mut stats = RecoveryStats::default();

// 1. Find latest checkpoint
let checkpoint = self.find_latest_checkpoint()?;
stats.checkpoint_lsn = checkpoint.lsn;

// 2. Replay WAL from checkpoint
let mut wal_reader = WalReader::open_from(checkpoint.lsn)?;
let mut active_txns: HashSet<u64> = checkpoint.active_txns;

while let Some(record) = wal_reader.next()? {
match record.record_type {
RecordType::BeginTxn => {
active_txns.insert(record.txn_id);
}
RecordType::Put => {
if !active_txns.contains(&record.txn_id) {
// Skip aborted transaction
continue;
}
self.replay_put(&record)?;
stats.records_replayed += 1;
}
RecordType::CommitTxn => {
active_txns.remove(&record.txn_id);
stats.transactions_recovered += 1;
}
RecordType::AbortTxn => {
// Roll back buffered writes
self.rollback_txn(record.txn_id)?;
active_txns.remove(&record.txn_id);
}
_ => {}
}
}

// 3. Abort incomplete transactions
for txn_id in active_txns {
self.rollback_txn(txn_id)?;
stats.transactions_aborted += 1;
}

Ok(stats)
}

Checkpointingโ€‹

fn checkpoint(&self) -> Result<u64> {
// 1. Acquire checkpoint lock
let _guard = self.checkpoint_lock.lock();

// 2. Get current LSN
let checkpoint_lsn = self.wal.current_lsn();

// 3. Flush memtable to SST
let immutable = self.memtable.freeze();
self.flush_memtable_to_sst(&immutable)?;

// 4. Write checkpoint record
let active_txns = self.txn_manager.active_transactions();
self.wal.append(WalRecord::Checkpoint {
lsn: checkpoint_lsn,
active_txns,
})?;

// 5. Sync WAL
self.wal.sync()?;

// 6. Remove old WAL segments
self.wal.truncate_before(checkpoint_lsn)?;

Ok(checkpoint_lsn)
}

Server Componentsโ€‹

The production server is a single "thick server" binary, sochdb-grpc-server (crate sochdb-grpc). It hosts several protocol surfaces over one embedded engine. All ports below are configured by CLI flags; passing 0 disables that surface.

SurfaceFlagDefault portNotes
gRPC-p, --port5005112 services. VectorIndexService allows 64 MB messages and is the only service registered without the auth interceptor; all others run behind it.
Prometheus metrics--metrics-port9090GET /metrics (Prometheus text), GET /health. Runs on a dedicated OS thread, binds 0.0.0.0.
WebSocket gateway--ws-port8080JSON message protocol: sql, kv_get, kv_put, kv_delete, subscribe, ping. In the default binary it uses a fresh in-memory KvStore and is not wired to CDC.
PostgreSQL wire--pg-port5433See warning below.

The gRPC tonic_health service is mounted at the gRPC port and is not behind auth, so Kubernetes probes can reach it. Default rate limit is 1000 req/s with a burst of 100 per tenant; audit logging is on by default.

Bind address is --host (default 127.0.0.1). There is no --config flag โ€” the legacy sochdb-server-config.toml and the make server-run target reference a --config flag and port 9600 that no current binary consumes; configure via flags and environment variables instead.

PostgreSQL Wire Protocolโ€‹

psql -h 127.0.0.1 -p 5433 -d sochdb

Scope is intentionally narrow: Simple Query Protocol only (no extended/prepared statements), no SSL/TLS (cleartext), and trust auth (no password). Two executors are selected at startup:

  • With --pg-data-dir โ†’ real SQL (SELECT/INSERT/UPDATE/DELETE/DDL incl. JOINs) via SqlBridge over a persistent Database.
  • Without it โ†’ an echo placeholder that only echoes queries back.
pg-wire has no auth

The PostgreSQL wire surface has no authentication layer. Because the --pg-data-dir executor is a writable SQL database, exposing it on a non-loopback --host exposes it unauthenticated; the server logs a loud warning in that case. Keep it loopback-only unless you accept the risk, and supply --pg-data-dir for real SQL.


Securityโ€‹

Authentication is opt-in via --auth. When disabled, requests resolve to an anonymous principal with Read + Write + ManageCollections. When enabled, both API-key and JWT verification are configured.

Credential headers (gRPC): authorization: Bearer <token> (preferred) or x-api-key: <key> (rewritten internally to a Bearer token). The interceptor runs authenticate โ†’ rate-limit โ†’ inject Principal.

RBAC roles are Owner, Editor, Viewer, and Custom { name, capabilities }:

RoleCapabilities
OwnerAdmin (wildcard) + all others
EditorRead, Write, ManageCollections, ManageIndexes
ViewerRead, ViewMetrics

Roles are bound per scope via RoleBinding with RoleScope::{ Global, Namespace, Collection }; effective_capabilities(principal, namespace) unions all applicable bindings.

JWT is validation-only (HS256). verify_jwt decodes claims (sub, exp, iat, iss, aud, tenant_id, role, capabilities) and validates exp plus optional issuer/audience. There is no token-issuance API in the server โ€” JWTs must be minted by an external IdP or the caller. The verification key comes from the jwt-secret / SOCHDB_JWT_SECRET secret.

API keys are never stored plaintext: they are hashed with SHA-256(key) by default, or HMAC-SHA256(pepper, key) when SOCHDB_API_KEY_PEPPER is set. argon2 is used only for user passwords, not API keys.

TLS / mTLS: set --tls-cert + --tls-key to enable TLS; add --tls-ca to require client certificates (mTLS). Certs hot-reload on file mtime change.

Secrets: --secrets-path (or env) loads jwt-secret, api-keys, encryption-key (base64, 32 bytes), and tls-* from a mount (Kubernetes Secrets), auto-refreshing on an interval.

At-Rest Encryptionโ€‹

[1 byte version=1][12 byte random nonce][ciphertext + 16-byte auth tag]

The storage layer ships an EncryptionEngine using AES-256-GCM-SIV (nonce-misuse-resistant AEAD) with a 32-byte key (zeroized on drop), designed to encrypt data blocks, WAL entries, and checkpoint files with per-block random nonces.

Library API, not yet wired to a server flag

At-rest encryption is a tested library capability. There is currently no CLI flag that enables it in sochdb-grpc-server, and the server's main path does not construct an EncryptionEngine. Treat it as an available API / planned wiring, not a runtime toggle.


Change Data Capture (CDC)โ€‹

CDC is a WAL-derived, log-structured ring buffer (sochdb-storage/src/cdc.rs). Default capacity is 65,536 events; sequence numbers are monotonic and start at 1, independent of WAL LSNs. On overflow the oldest events are evicted, and reading an evicted position returns an Overrun error (slow-subscriber WAL replay is not yet implemented).

Events carry { sequence, timestamp_us, txn_id, table, key, operation }. Operations are Insert, Update, Delete, and SchemaChange. Note the current implementation is after-image only โ€” the before value is None.

Subscriptions are exposed over gRPC SubscriptionService (Subscribe, WatchKey, ListSubscriptions, CancelSubscription). A SubscribeRequest carries namespace, tables, operations, start_sequence (0 = from latest, > 0 = resume), where_predicate, and batch_size (default 64).

where_predicate is accepted but not enforced

The server applies table filtering and operation-type filtering, both enforced. The where_predicate (SQL WHERE) field exists in the proto and is accepted, but the streaming handler does not yet read or apply it โ€” SQL-predicate CDC filtering is not implemented.


SDK Architectureโ€‹

SochDB provides official SDKs for Python, Node.js, Go, and Rust. Each can talk to a SochDB server or run an embedded/in-process engine; the Go SDK is remote-first by default (embedded behind a build tag), while Python and Node.js support both out of the box. The unified connect() URI maps to a transport:

URI schemeTransport
file://./dataEmbedded on-disk database (in-process)
ipc:///tmp/sochdb.sockLocal IPC over a Unix domain socket
grpc://localhost:50051gRPC (plaintext)
grpcs://prod.example.com:443gRPC over TLS
Per-SDK entry points

The unified connect() URI scheme is the documented deployment surface (see the CHANGELOG). The concrete entry points currently differ per SDK: Python uses Database.open(path) for embedded and sochdb.connect("grpc://host:port") for remote; Go uses sochdb.NewGrpcClient(...) for remote and a Unix-socket sochdb.Connect(socketPath) for local IPC. Use the per-language examples below for runnable code.

Language-specific notes:

  • Python has two importable packages both named sochdb: the pure-Python ctypes SDK (v0.5.9, the broad embedded + server SDK) and the PyO3 native engine extension (v2.0.3, exposing HnswIndex/BM25Index/TableDatabase/MultiShardHnswIndex and friends). Prefer the 0.5.9 SDK for general usage.
  • Node.js (@sochdb/sochdb, v0.5.3): EmbeddedDatabase.open() is synchronous and commit() returns Promise<void>.
  • Go (github.com/sochdb/sochdb-go, v0.4.5) is remote-first by default; the embedded FFI engine is behind the sochdb_embedded build tag.

Client-Server Architectureโ€‹

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Client Applications โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ Python โ”‚ โ”‚ Node.js โ”‚ โ”‚ Go โ”‚ โ”‚ Rust โ”‚ โ”‚
โ”‚ โ”‚ sochdb โ”‚ โ”‚ @sochdb/sochdb โ”‚ โ”‚ sochdb-go โ”‚ โ”‚ sochdb โ”‚ โ”‚
โ”‚ โ”‚ v0.5.9 โ”‚ โ”‚ v0.5.3 โ”‚ โ”‚ v0.4.5 โ”‚ โ”‚ v2.0.3 โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚ โ”‚ โ”‚ โ”‚
โ”‚ connect() URI โ†’ embedded | ipc | grpc | grpcs โ”‚
โ–ผ โ–ผ โ–ผ โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ sochdb-grpc-server (thick) โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ gRPC ยท WebSocket ยท pg-wire ยท MCP โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ Query Engine โ”‚ โ”‚
โ”‚ โ”‚ (sochdb-query crate) โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ Storage Engine โ”‚ โ”‚
โ”‚ โ”‚ (LSCS + WAL + Memtable) โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The standalone sochdb-mcp binary is a separate, thin stdio adapter for LLM clients that makes direct embedded calls into the engine โ€” it is not the transport used by the language SDKs.

SDK Comparisonโ€‹

CapabilityPython (0.5.9)Node.js (0.5.3)Go (0.4.5)Rust (2.0.3)
Default modeEmbedded + serverEmbedded + serverRemote-firstDirect (in-process)
Embedded engineYesYes (EmbeddedDatabase.open() is sync)Behind sochdb_embedded build tagYes
gRPC / remote clientYesYesYesYes
Vector searchYesYesYesYes
TransactionsYesYes (commit() โ†’ Promise<void>)YesYes
Native engine extrasPyO3 module (HNSW/BM25/RRF, MultiShardHnswIndex)โ€”โ€”full crate
SDK higher-level modules

In Python, context-builder / policy-hooks / tool-routing / graph-overlay are example patterns (shipped in sochdb-python-examples), not importable SDK classes. In the Rust and Node.js SDKs, several of these correspond to real modules. Node has no routing module.

Embedded vs Remoteโ€‹

The Python and Node.js SDKs open an embedded engine in-process from a local path โ€” there is no separate server process to spawn or manage:

# Python โ€” embedded engine (0.5.9 SDK)
from sochdb import Database
db = Database.open("./my_database")
# ... use db ...
db.close()
// Node.js โ€” embedded engine (0.5.3 SDK); open() and close() are synchronous
import { EmbeddedDatabase } from '@sochdb/sochdb';
const db = EmbeddedDatabase.open('./my_database');
const txn = db.transaction();
// ... txn.put(...) etc ...
await txn.commit(); // Promise<void>
db.close();

The Go SDK is remote-first: by default it connects to a running sochdb-grpc-server over gRPC. The embedded FFI engine is opt-in behind the sochdb_embedded build tag.

// Go โ€” remote-first by default (thin gRPC client; all logic runs on the server)
client, err := sochdb.NewGrpcClient(sochdb.GrpcClientOptions{
Address: "localhost:50051",
})

To run a server for remote clients:

sochdb-grpc-server --host 0.0.0.0 --port 50051

Python SDK Architectureโ€‹

The Python SDK provides multiple access patterns to SochDB:

Access Modesโ€‹

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Python Application โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ sochdb (PyPI) โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ Embedded โ”‚ โ”‚ IPC โ”‚ โ”‚ Bulk API โ”‚ โ”‚
โ”‚ โ”‚ FFI โ”‚ โ”‚ Client โ”‚ โ”‚ (subprocess โ†’ sochdb-bulk) โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ”‚ โ”‚ โ”‚ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚ โ”‚ โ”‚
โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Rust โ”‚ โ”‚ IPC โ”‚ โ”‚ sochdb-bulk โ”‚
โ”‚ FFI โ”‚ โ”‚ Server โ”‚ โ”‚ binary โ”‚
โ”‚ (.so) โ”‚ โ”‚ โ”‚ โ”‚ โ”‚
โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚ โ”‚ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”
โ”‚ SochDB โ”‚
โ”‚ Core โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Distribution Model (uv-style)โ€‹

Wheels contain pre-built Rust binaries, eliminating compilation requirements:

sochdb-0.5.9-py3-none-manylinux_2_17_x86_64.whl
โ”œโ”€โ”€ sochdb/
โ”‚ โ”œโ”€โ”€ __init__.py
โ”‚ โ”œโ”€โ”€ database.py # Embedded FFI
โ”‚ โ”œโ”€โ”€ ipc.py # IPC client
โ”‚ โ”œโ”€โ”€ bulk.py # Bulk operations
โ”‚ โ””โ”€โ”€ _bin/
โ”‚ โ””โ”€โ”€ linux-x86_64/
โ”‚ โ””โ”€โ”€ sochdb-bulk # Pre-built binary
โ””โ”€โ”€ METADATA

Platform matrix:

  • manylinux_2_17_x86_64 - Linux glibc โ‰ฅ 2.17
  • manylinux_2_17_aarch64 - Linux ARM64
  • macosx_11_0_universal2 - macOS Intel + Apple Silicon
  • win_amd64 - Windows x64

Bulk API FFI Bypassโ€‹

For vector-heavy workloads, the Bulk API avoids FFI overhead:

Python FFI path (130 vec/s):
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” memcpy โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ numpy โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ†’โ”‚ Rust โ”‚ โ†’ repeated N times
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ per batch โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Bulk API path (1,600 vec/s):
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” mmap โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” fork โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ numpy โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ†’ โ”‚ temp file โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ†’ โ”‚ sochdb-bulk โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ 1 write โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ 1 proc โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

This document describes SochDB core engine v2.0.3 internals. Implementation details may change.