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.
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โ
- Design Philosophy
- Crate Map
- Data Model
- TOON Format Internals
- Storage Engine
- Transaction System
- Index Structures
- SQL Engine
- Query Processing
- Memory Management
- Concurrency Model
- Recovery & Durability
- Server Components
- Security
- Change Data Capture
- 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 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
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].
| Crate | Role |
|---|---|
sochdb-core | Core types, SochValue, and the TOON data format (soch.rs, soch_ql.rs). |
sochdb-storage | Log-structured columnar store, WAL, MVCC, CDC, encryption, IPC server. |
sochdb-index | HNSW vector index (hnsw.rs, hnsw_staged.rs), quantization, SIMD distance. |
sochdb-vector | BM25, RRF fusion, sharding/compaction primitives. |
sochdb-query | SQL/SochQL engine: volcano executor, SqlBridge, trigram + grep lanes, fusion. |
sochdb-fusion | Fused compositional query execution (ART + HNSW + CSR in one pipeline). |
sochdb-client | Published client crate (sochdb). |
sochdb-grpc | "Thick server" binary sochdb-grpc-server: gRPC, WebSocket, pg-wire, metrics, MCP. |
sochdb-mcp | Standalone stdio Model Context Protocol server (sochdb-mcp binary). |
sochdb-memory | Agent-memory schema (episodes / entities / events). |
sochdb-kernel, sochdb-tools, sochdb-simulation, sochdb-bench, sochdb-wasm | Kernel, 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 โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Navigator Strategy (HNSW Baseline, Optional CHN)โ
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.
ef_searchef_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-onlyMultiShardHnswIndex 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:
| Path | Source | Role |
|---|---|---|
| Volcano executor | sochdb-query/src/executor/ | The real operator engine. Row-at-a-time iterator model (PlanNode::next() -> Result<Option<Row>>). Drives EXPLAIN. |
SqlBridge | sochdb-query/src/sql/bridge.rs | Storage-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. |
SqlExecutor | sochdb-query/src/sql/mod.rs | An 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.
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. NoMEDIANorSTDDEV. sql/aggregate.rs: addsMedian(accumulated values) andStddev(sample, n-1, via Welford online variance โ matching R'ssd()/ DuckDB'sstddev).AVG/MEANalias to Avg;STDDEV/STDDEV_SAMP/STDEV/SDalias 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.
| Surface | Flag | Default port | Notes |
|---|---|---|---|
| gRPC | -p, --port | 50051 | 12 services. VectorIndexService allows 64 MB messages and is the only service registered without the auth interceptor; all others run behind it. |
| Prometheus metrics | --metrics-port | 9090 | GET /metrics (Prometheus text), GET /health. Runs on a dedicated OS thread, binds 0.0.0.0. |
| WebSocket gateway | --ws-port | 8080 | JSON 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-port | 5433 | See 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) viaSqlBridgeover a persistentDatabase. - Without it โ an echo placeholder that only echoes queries back.
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 }:
| Role | Capabilities |
|---|---|
Owner | Admin (wildcard) + all others |
Editor | Read, Write, ManageCollections, ManageIndexes |
Viewer | Read, 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.
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 enforcedThe 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 scheme | Transport |
|---|---|
file://./data | Embedded on-disk database (in-process) |
ipc:///tmp/sochdb.sock | Local IPC over a Unix domain socket |
grpc://localhost:50051 | gRPC (plaintext) |
grpcs://prod.example.com:443 | gRPC over TLS |
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, exposingHnswIndex/BM25Index/TableDatabase/MultiShardHnswIndexand friends). Prefer the 0.5.9 SDK for general usage. - Node.js (
@sochdb/sochdb, v0.5.3):EmbeddedDatabase.open()is synchronous andcommit()returnsPromise<void>. - Go (
github.com/sochdb/sochdb-go, v0.4.5) is remote-first by default; the embedded FFI engine is behind thesochdb_embeddedbuild 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โ
| Capability | Python (0.5.9) | Node.js (0.5.3) | Go (0.4.5) | Rust (2.0.3) |
|---|---|---|---|---|
| Default mode | Embedded + server | Embedded + server | Remote-first | Direct (in-process) |
| Embedded engine | Yes | Yes (EmbeddedDatabase.open() is sync) | Behind sochdb_embedded build tag | Yes |
| gRPC / remote client | Yes | Yes | Yes | Yes |
| Vector search | Yes | Yes | Yes | Yes |
| Transactions | Yes | Yes (commit() โ Promise<void>) | Yes | Yes |
| Native engine extras | PyO3 module (HNSW/BM25/RRF, MultiShardHnswIndex) | โ | โ | full crate |
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.17manylinux_2_17_aarch64- Linux ARM64macosx_11_0_universal2- macOS Intel + Apple Siliconwin_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.