Skip to main content

SochDB Complete Architecture & API Reference

Core engine version: 2.0.3 Β |Β  Python SDK: 0.5.9 Β |Β  Node.js SDK: 0.5.3 Β |Β  Go SDK: 0.4.5

License: The core engine (Rust workspace, the sochdb crate, gRPC server, MCP server) is AGPL-3.0-or-later with commercial licensing available. The language SDKs (Python, Node.js, Go) are Apache-2.0.

Repository: https://github.com/sochdb/sochdb

License split

This is a genuine per-component split, not a full Apache relicense. The Rust core remains AGPL-3.0-or-later on disk; only the language SDKs are Apache-2.0.


Table of Contents​

  1. Design Philosophy
  2. Executive Summary
  3. Deployment Model: Thick Server / Thin Client
  4. System Architecture
  5. gRPC Service Set
  6. Module Structure
  7. SQL Execution Engine
  8. Storage Engine
  9. Auth & Security Layer
  10. Client SDK API
  11. TOON Format Internals
  12. MCP Protocol & Tools API
  13. Vector Search & HNSW Subsystem
  14. Transaction & MVCC API
  15. Context Query API
  16. Query Processing Pipeline
  17. Memory Management
  18. Concurrency Model
  19. Python SDK Architecture
  20. Performance Characteristics
  21. Configuration Reference

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: LLM ← JSON ← SQL Result ← Query Optimizer ← B-Tree
~150 tokens for 3 rows

SochDB: LLM ← TOON ← Columnar Scan ← Path Resolution
~50 tokens for 3 rows (66% reduction)

2. Path-Based Access​

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

Path: "users/42/profile/avatar"

TCH Resolution:
β”œβ”€ 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 - 50% I/O reduction for typical queries.

4. Embeddable & Extensible​

Single-file deployment (~1.5MB) with optional plugin architecture.


Executive Summary​

SochDB is an AI-native database designed from the ground up for LLM applications and autonomous agents. As of v2.0 it can run either embedded (in-process, SQLite-like) or as a thick gRPC server with thin language clients. Key differentiators:

FeatureDescriptionBenefit
Trie-Columnar Hybrid (TCH)O(|path|) lookups via radix-compressed trieConstant-time path resolution
HNSW Vector IndexSIMD/flat-scan crossover + approximate HNSW graph searchSub-millisecond k-NN at small scale, scalable at large scale
Hybrid SearchVector + BM25 + grep lanes fused via RRF (k = 60.0)One ranked result set across modalities
TOON FormatTypically 40-66% fewer tokens than equivalent JSONSignificant LLM cost savings
MCP ProtocolNative LLM tool integration over stdioSeamless agent orchestration
Dual DeploymentEmbedded in-process OR thick gRPC serverSame data model from laptop to cluster

Deployment Model: Thick Server / Thin Client​

v2.0 introduces a "Thick Server / Thin Client" architecture (sochdb-grpc/src/main.rs). The server binary sochdb-grpc-server holds the storage engine, SQL executor, vector indexes, and security layer; language SDKs are thin clients that speak gRPC (plus optional gateways). The same engine still runs embedded in-process for SQLite-like single-file deployments.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ sochdb-grpc-server (thick) β”‚
β”‚ β”‚
β”‚ gRPC :50051 metrics :9090 WebSocket :8080 pg-wire :5433 β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Auth / Security interceptor (optional --auth) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ 12 gRPC services + SQL executor + HNSW index + storage β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β–² β–² β–² β–²
thin Python SDK thin Node SDK thin Go SDK psql / grpcurl

The unified connect() API (documented in the SDKs) selects a transport from a URI scheme:

file://./data                    β†’ embedded on-disk database (in-process)
ipc:///tmp/sochdb.sock β†’ local IPC over a Unix domain socket
grpc://localhost:50051 β†’ gRPC to a SochDB server (plaintext)
grpcs://prod.example.com:443 β†’ gRPC over TLS

Default ports (main.rs; 0 disables a gateway):

ServiceFlagDefault port
gRPC-p, --port50051
Prometheus metrics (GET /metrics)--metrics-port9090
WebSocket gateway--ws-port8080
PostgreSQL wire protocol--pg-port5433
The make server-run / --config path is stale

The top-level make server-run target passes --config sochdb-server-config.toml, but the server's clap Args has no --config flag (it configures via flags + env vars only). The bundled sochdb-server-config.toml (port 9600) is not consumed by the current binary. Configure with flags/env, not the TOML file.


System Architecture​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ SochDB Architecture β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” Gateways β”‚
β”‚ β”‚ MCP Server β”‚ β”‚ gRPC API β”‚ β”‚ WebSocketβ”‚ β”‚ pg-wire β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Auth / Security interceptor (optional --auth) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Query Processing Engine β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚ SochQL/ β”‚ β”‚ Context β”‚ β”‚ Volcano SQL β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ SQL β”‚ β”‚ Builder β”‚ β”‚ Executor (iterator)β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ Parser β”‚ β”‚ β”‚ β”‚ + sochdb-fusion β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Unified Storage Layer β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚ TCH β”‚ β”‚ HNSW β”‚ β”‚ MVCC + SSI β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ Storage β”‚ β”‚ Index β”‚ β”‚ Transactions β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Durability Layer (WAL + CDC) β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚ WAL β”‚ β”‚ Group β”‚ β”‚ Crash β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ Writer β”‚ β”‚ Commit β”‚ β”‚ Recovery β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Design Principles​

  1. Sync-First Storage: Core storage uses synchronous I/O (like SQLite) for predictable latency and simpler embedding
  2. Optional Async: Tokio runtime only required for server mode (MCP, gRPC, gateways)
  3. Lock-Free Reads: Hazard pointer protection for true lock-free read paths
  4. AI-Native Formatting: TOON format as first-class output for token efficiency
  5. Thick Server / Thin Client: heavy compute lives in the server; SDKs stay thin

gRPC Service Set​

The server registers 12 gRPC services (main.rs), plus a separate tonic health service for Kubernetes probes. Every service except VectorIndexService runs behind the auth interceptor when --auth is enabled.

ServicePurposeNotes
VectorIndexServiceCreate/search HNSW indexesNo auth interceptor; 64 MB max gRPC message (encode + decode)
GraphServiceGraph / relationship operationsauth
PolicyServicePolicy managementauth
ContextServiceToken-budgeted context queriesauth
CollectionServiceCollection CRUDauth
NamespaceServiceNamespace managementauth
SemanticCacheServiceSemantic cacheauth
TraceServiceTracingauth
CheckpointServiceCheckpoint operationsauth
McpServiceMCP surface inside the serverauth
KvServiceKey/value accessauth
SubscriptionServiceCDC streaming subscriptionsauth

Plus a tonic_health service (empty service name set to Serving) mounted at the gRPC port, not behind auth, so probes can reach it.

Message size

Only VectorIndexService raises the gRPC message limit to 64 MB (encode and decode). All other services use the tonic default (4 MB decode).

CDC & Subscriptions​

SubscriptionService streams change events derived from the WAL via a log-structured ring buffer (sochdb-storage/src/cdc.rs, default capacity 65,536, after-image only). A Subscribe request can filter by tables and by operations, and resume from a start_sequence.

where_predicate is accepted but not yet enforced

SubscribeRequest includes a where_predicate (SQL WHERE) field, but the streaming handler does not read or apply it. Table filtering and operation-type filtering ARE enforced; SQL predicate filtering is not yet implemented.

The WebSocket gateway also advertises a subscribe message type, but in the default binary its CDC log is wired as None, so WS subscriptions are not connected to CDC.

Gateways​

  • Prometheus metrics (--metrics-port, default 9090): GET /metrics (Prometheus text format) and GET /health, served on a dedicated OS thread. Metric families include sochdb_grpc_requests_total, sochdb_grpc_request_duration_seconds, sochdb_sql_queries_total, sochdb_transactions_total, sochdb_wal_writes_total, and sochdb_build_info.
  • WebSocket gateway (--ws-port, default 8080): JSON message protocol at path / with message types sql, kv_get, kv_put, kv_delete, subscribe, ping.
  • PostgreSQL wire protocol (--pg-port, default 5433): connect with psql -h 127.0.0.1 -p 5433 -d sochdb.
pg-wire is simple-query only, with no auth

The PostgreSQL wire endpoint implements only the Simple Query Protocol (no extended/prepared statements), has no SSL/TLS (cleartext) and no password (trust auth). It is intended for loopback use only. Without --pg-data-dir it uses a placeholder echo executor; pass --pg-data-dir to run real SQL (SELECT/INSERT/UPDATE/DELETE/DDL including JOINs) against an on-disk database. The server logs a loud warning if --host is non-loopback, because the writable SQL endpoint would then be exposed unauthenticated.


Module Structure​

Crate Dependency Graph​

sochdb-studio (GUI, Tauri desktop)
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Application Layer β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ sochdb-mcp β”‚ β”‚sochdb-grpc β”‚ β”‚sochdb-wasm β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β–Ό β–Ό β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Client Layer β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ sochdb (client) β”‚ β”‚ sochdb-python β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β–Ό β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Query Layer β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚sochdb-queryβ”‚ β”‚sochdb-fusion β”‚ β”‚sochdb-memoryβ”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚sochdb-toolsβ”‚ β”‚ sochdb-simulationβ”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β–Ό β–Ό β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Execution Layer β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚sochdb-indexβ”‚ β”‚sochdb-vectorβ”‚ β”‚sochdb-kernelβ”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β–Ό β–Ό β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Storage Layer β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ sochdb-storage β”‚ β”‚
β”‚ β”‚ (WAL + LSCS + MVCC + CDC + β”‚ β”‚
β”‚ β”‚ compression + encryption) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Core Layer β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ sochdb-core β”‚ β”‚
β”‚ β”‚ (Types, Codec, Trie)β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Crate Responsibilities​

CratePurposeKey Types
sochdb-coreFoundational types, codecs, trie, TOONSochValue, SochSchema, SochTable
sochdb-storageWAL, LSCS, MVCC, GC, CDC, compression, encryptionDatabase, WalManager, CdcLog
sochdb-kernelQuery execution, table operationsKernel, TableHandle, ScanIterator
sochdb-indexHNSW vector index and index utilitiesHnswIndex, HnswConfig
sochdb-vectorCPU-first ANN vector search engine, BM25, RRFBM25Config, RRFConfig
sochdb-querySQL/SochQL parser, volcano executor, fusionSqlBridge, PlanNode, FusionConfig
sochdb-fusionFused compositional query execution (ART + HNSW + CSR in one pipeline)fused query pipeline
sochdb-memoryBi-temporal agent memory with write-time lexical recallmemory layer
sochdb-simulationStandalone & distributed topology modeling with score validationsimulation harness
sochdb (client crate)High-level SDK, context queriesDurableConnection, ContextQueryBuilder
sochdb-mcpMCP protocol server (stdio)McpServer, ToolExecutor
sochdb-grpcThick gRPC server + gatewayssochdb-grpc-server binary
sochdb-pythonPython native bindings (PyO3)HnswIndex, BM25Index, TableDatabase
sochdb-wasmBrowser WASM buildWasmVectorIndex
The published Rust crate is named sochdb

The high-level Rust SDK crate lives in sochdb-client/ but is published under the name sochdb (cargo add sochdb, currently 2.0.3). This name collides with the PyPI distribution sochdb.


SQL Execution Engine​

SochDB executes SQL through a Volcano (iterator) executor (sochdb-query/src/executor/). Each operator implements a PlanNode trait with schema(), next() -> Result<Option<Row>>, and reset(), pulling rows one at a time from its children.

Volcano operators​

SeqScan / IndexSeek  β†’  Filter (WHERE)  β†’  HashAggregate (GROUP BY)
β†’ Filter (HAVING) β†’ Project (SELECT) β†’ Sort (ORDER BY) β†’ Limit (LIMIT/OFFSET)

Available nodes: SeqScan, IndexSeek, Filter, Project, Sort, Limit, HashJoin, NestedLoopJoin, MergeJoin, HashAggregate, Explain, Values, Empty.

  • Joins: the executor implements HashJoin (INNER/LEFT/RIGHT/FULL equi-joins), NestedLoopJoin (theta + CROSS), and MergeJoin (sorted inputs). ON a=b plans a hash join; non-equi ON plans a nested-loop join; multiple FROM tables plan an implicit CROSS join.
  • Aggregates: the volcano path supports COUNT, COUNT DISTINCT, SUM, AVG, MIN, MAX.
  • EXPLAIN is served by the volcano path (the ExplainNode emits a textual QUERY PLAN).

For the full statement coverage path, the SqlBridge (sochdb-query/src/sql/bridge.rs) dispatches CREATE/DROP TABLE, CREATE/DROP INDEX, ALTER TABLE, INSERT/UPDATE/DELETE, transactions, and scope/permission DDL against storage.

Multi-engine quirks to be aware of
  • There are three SQL code paths (the production SqlBridge, the volcano executor, and an in-memory reference SqlExecutor that rejects multi-table FROM). The bridge is the full path.
  • MEDIAN and STDDEV exist only in the separate sql/aggregate.rs engine (sample stddev via Welford), not in the volcano aggregate operator.
  • DISTINCT (as a SELECT modifier), window functions, CTEs, and subqueries in WHERE/SELECT are not yet implemented in the planner.
  • CAST currently passes the inner value through without real type coercion.

sochdb-fusion provides fused compositional query execution (ART + HNSW + CSR in a single pipeline) for combined relational / vector / graph workloads.


Storage Engine​

Log-Structured Column Store (LSCS)​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Storage Engine β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”‚
β”‚ Write-Ahead Log (WAL) β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Active β”‚ β”‚ Sealed β”‚ β”‚ Sealed β”‚ β”‚ Archived β”‚ β”‚
β”‚ β”‚ Segment β”‚ β”‚ Segment β”‚ β”‚ Segment β”‚ β”‚ Segments β”‚ β”‚
β”‚ β”‚ (writes) β”‚ β”‚ (full) β”‚ β”‚ (full) β”‚ β”‚ (backup) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β–Ό β–Ό β–Ό β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Group Commit Buffer β”‚ β”‚
β”‚ β”‚ Batches transactions for efficient fsync β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚
β”‚ Log-Structured Column Store (LSCS) β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ MemTable β”‚ β”‚ MemTable β”‚ β”‚ MemTable β”‚ β”‚
β”‚ β”‚ (Active) β”‚ β”‚ (Immutable) β”‚ β”‚ (Flushing) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β–Ό β–Ό β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Sorted Runs β”‚ β”‚
β”‚ β”‚ L0: β–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆ (recently flushed) β”‚ β”‚
β”‚ β”‚ L1: β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ (merged) β”‚ β”‚
β”‚ β”‚ L2: β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ (compacted) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚
β”‚ Background Workers β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Compactorβ”‚ β”‚ GC β”‚ β”‚Checkpointβ”‚ β”‚
β”‚ β”‚ Thread β”‚ β”‚ Thread β”‚ β”‚ Thread β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Lock-Free MemTable​

The MemTable uses hazard pointers for true lock-free reads:

pub struct LockFreeMemTable {
data: DashMap<Vec<u8>, LockFreeVersionChain>,
hazard_domain: HazardDomain,
size_bytes: AtomicUsize,
}

impl LockFreeMemTable {
/// Read with zero-copy callback (optimal path)
pub fn read_with<F, R>(
&self,
key: &[u8],
snapshot_ts: u64,
txn_id: Option<u64>,
f: F,
) -> Option<R>
where
F: FnOnce(&[u8]) -> R;

/// Write a value (creates uncommitted version)
pub fn write(&self, key: Vec<u8>, value: Option<Vec<u8>>, txn_id: u64) -> Result<()>;

/// Commit a transaction's writes
pub fn commit(&self, txn_id: u64, commit_ts: u64, keys: &[Vec<u8>]);
}

Scalability: Lock-free design achieves 23% better scaling vs RwLock at 8 threads.

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 β”‚ β”‚β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Compaction Strategy​

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 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

WAL Record 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> {
// 1. Find latest checkpoint
let checkpoint = self.find_latest_checkpoint()?;

// 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) {
self.replay_put(&record)?;
}
}
RecordType::CommitTxn => {
active_txns.remove(&record.txn_id);
}
RecordType::AbortTxn => {
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)?;
}

Ok(stats)
}

Storage feature summary (v2.0)​

FeatureStateNotes
MVCCShippedLock-free version chains; snapshot isolation
WAL + group commitShippedCRC-checked records, crash recovery via checkpoint replay
SSI (serializable isolation)Shippedrw-antidependency cycle detection
CompressionShippedLZ4 / Zstd (workspace deps); columnar blocks
CDC (change data capture)ShippedWAL-derived ring buffer (CdcLog), after-image only
At-rest encryptionLibrary API onlySee caution below
At-rest encryption is a library API, not yet a server flag

EncryptionEngine (sochdb-storage/src/encryption.rs) implements AES-256-GCM-SIV (nonce-misuse-resistant AEAD) over data blocks, WAL entries, and checkpoint files, with a 32-byte key. It is unit-tested and reachable as an API, but there is no CLI flag on sochdb-grpc-server to enable it and main.rs does not construct an EncryptionEngine. Treat it as an available API with planned server wiring, not a runtime toggle.


Auth & Security Layer​

When --auth is passed, the server installs an auth interceptor on every service except VectorIndexService (sochdb-grpc/src/auth_interceptor.rs, security.rs).

Credentials over gRPC​

  • authorization: Bearer <token> β€” preferred. Interpreted as a JWT when JWT is enabled, otherwise as an API key.
  • x-api-key: <key> β€” fallback; rewritten internally to Bearer <key>.

The interceptor pipeline is: authenticate β†’ rate-limit β†’ inject a Principal into request extensions. When --auth is not passed, requests resolve to an anonymous principal with Read + Write + ManageCollections capabilities. Default rate limit is 1000 req/s with a burst of 100 per tenant; audit logging is on by default.

RBAC roles​

The actual roles are Owner, Editor, Viewer (plus a Custom { name, capabilities } variant):

RoleCapabilities
OwnerAdmin, Read, Write, ManageCollections, ManageIndexes, ViewMetrics, ManageBackups, ManageUsers (all)
EditorRead, Write, ManageCollections, ManageIndexes
ViewerRead, ViewMetrics

Roles can be scoped Global, Namespace(name), or Collection { namespace, collection } via bind_role; effective_capabilities(principal, namespace) unions all applicable bindings.

JWT (validation only)​

JWT is validation-only; there is no token-issuance API

The server validates HS256 JWTs (jsonwebtoken::decode), checking exp and optionally iss/aud, and derives a Principal from the role/capabilities claims. There is no token-minting API in the server β€” tokens must be issued by an external IdP or the calling application. The verification secret comes from the jwt-secret Kubernetes secret or the SOCHDB_JWT_SECRET env var.

API keys and passwords​

Hashing

API keys are stored as SHA-256(key) by default, or HMAC-SHA256(pepper, key) when SOCHDB_API_KEY_PEPPER is set β€” never plaintext, and not argon2. argon2 is used only for user passwords (register_user / verify_password).

TLS / mTLS and secrets​

  • TLS is enabled when both --tls-cert and --tls-key are set; adding --tls-ca enables mTLS client-certificate verification. Certs support mtime-based hot reload.
  • A SecretsProvider reads Kubernetes secret mounts (--secrets-path) or env vars: jwt-secret, api-keys, encryption-key (base64, 32 bytes), tls-cert, tls-key, tls-ca.

Client SDK API​

SochClient (In-Memory)​

For testing and development without durability requirements:

use sochdb::prelude::*;

// Open database
let client = SochClient::open("./mydb")?;

// Configure token budget for LLM responses
let client = client.with_token_budget(4096);

// Path-based queries (O(|path|) resolution)
let result = client.query("/users/123").execute()?;

// Execute SochQL
let rows = client.execute("SELECT * FROM users WHERE active = true")?;

// Begin transaction
let txn = client.begin()?;

// Vector operations
let vectors = client.vectors("embeddings")?;
vectors.add(&["doc1", "doc2"], &[vec1, vec2])?;
let results = vectors.search(&query_embedding, 10)?;

DurableSochClient (Production)​

Full WAL/MVCC support for production workloads:

use sochdb::prelude::*;

// Open with durability
let client = DurableSochClient::open("./mydb")?;

// Path-based CRUD
client.put("/users/123", b"{\"name\": \"Alice\"}")?;
let data = client.get("/users/123")?;
client.delete("/users/123")?;

// Scan with prefix
let results = client.scan("/users/")?;

// Transaction support
client.begin()?;
client.put("/users/1", value1)?;
client.put("/users/2", value2)?;
let commit_ts = client.commit()?;

// Force durability
client.fsync()?;

PathQuery Builder​

Leverages TCH's O(|path|) resolution:

use sochdb::path_query::{PathQuery, CompareOp};

// Fluent query builder
let results = client.query("/users")
.filter("score", CompareOp::Gt, SochValue::Int(80))
.filter("active", CompareOp::Eq, SochValue::Bool(true))
.select(&["name", "email", "score"])
.order_by("score", SortDirection::Desc)
.limit(10)
.execute()?;

Comparison Operators​

pub enum CompareOp {
Eq, // =
Ne, // !=
Lt, // <
Le, // <=
Gt, // >
Ge, // >=
Like, // LIKE pattern matching
In, // IN (array)
IsNull, // IS NULL
IsNotNull, // IS NOT NULL
}

Output Formats​

pub enum OutputFormat {
Soch, // Default: typically 40-66% fewer tokens than JSON
Json, // Standard JSON for compatibility
Columnar, // Raw columnar for analytics
}

TOON Format Internals​

Text Format Grammar​

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

null ::= "βˆ…"
bool ::= "T" | "F"
number ::= integer | float
string ::= raw_string | quoted_string
array ::= "[" value ("," value)* "]"
ref ::= "ref(" identifier "," integer ")"

Binary Format Structure​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 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;
for (i, &byte) in buf.iter().enumerate() {
result |= ((byte & 0x7F) as u64) << shift;
if byte & 0x80 == 0 {
return (result, i + 1);
}
shift += 7;
}
(result, buf.len())
}

MCP Protocol & Tools API​

Server Lifecycle​

Client                           Server
β”‚ β”‚
│──── initialize ──────────────►│
β”‚ β”‚ Create SochDB connection
│◄─── capabilities ─────────────│
β”‚ β”‚
│──── initialized ─────────────►│
β”‚ β”‚
│──── tools/list ──────────────►│
β”‚ β”‚ Return built-in tools
│◄─── tool definitions ─────────│
β”‚ β”‚
│──── tools/call ──────────────►│
β”‚ { "name": "sochdb_query", β”‚
β”‚ "arguments": {...} } β”‚
β”‚ β”‚ Execute query
│◄─── result (TOON format) ─────│

MCP Server Implementation​

impl McpServer {
pub fn new(conn: Arc<EmbeddedConnection>) -> Self;

/// Dispatch JSON-RPC request
pub fn dispatch(&self, req: &RpcRequest) -> RpcResponse;

/// Get database statistics
pub fn db_stats(&self) -> DatabaseStats;
}

Built-in Tools Reference​

Core Database Tools​

ToolDescriptionRequired Args
sochdb_context_queryAI-optimized context with token budgetingsections
sochdb_queryExecute SochQL queryquery
sochdb_getGet value at pathpath
sochdb_putSet value at pathpath, value
sochdb_deleteDelete at pathpath
sochdb_list_tablesList tables with metadata-
sochdb_describeGet table schematable

Memory Tools (Episode/Entity Schema)​

ToolDescriptionRequired Args
memory_search_episodesSemantic episode searchquery
memory_get_episode_timelineEvent timeline for episodeepisode_id
memory_search_entitiesEntity searchquery
memory_get_entity_factsEntity detailsentity_id
memory_build_contextOne-shot context packinggoal, token_budget

Log Tools​

ToolDescriptionRequired Args
logs_tailGet last N rowstable
logs_timelineEvents in a time range for an entityentity_id

Agentic Search Tools​

ToolDescriptionRequired Args
sochdb_grepIndexed grep over an in-memory corpus (trigram-index lane)pattern
sochdb_peekRead a line range from a documentdoc_id, start_line, end_line
sochdb_expandExpand Β±N lines around a hitdoc_id, line
MCP tool names use underscores

The authoritative tool list is the Rust source (tools.rs::get_built_in_tools()), which uses underscore names (sochdb_query, sochdb_get, memory_search_episodes, sochdb_grep, ...). The dot-named catalog in mcp.json and the MCP README (sochdb.query, memory.search_episodes) is stale/illustrative β€” use the underscore names.

Tool Schemas​

sochdb_context_query​

{
"name": "sochdb_context_query",
"description": "Fetch AI-optimized context from SochDB with token budgeting",
"inputSchema": {
"type": "object",
"properties": {
"sections": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"kind": { "type": "string", "enum": ["literal", "get", "last", "search"] },
"text": { "type": "string", "description": "For kind=literal" },
"path": { "type": "string", "description": "For kind=get" },
"table": { "type": "string", "description": "For kind=last/search" },
"query": { "type": "string", "description": "For kind=search" },
"top_k": { "type": "integer", "default": 10 }
},
"required": ["name", "kind"]
}
},
"token_budget": { "type": "integer", "default": 4096 },
"format": { "type": "string", "enum": ["toon", "json", "markdown"], "default": "toon" },
"truncation": { "type": "string", "enum": ["tail_drop", "head_drop", "proportional"], "default": "tail_drop" }
},
"required": ["sections"]
}
}

sochdb_query​

{
"name": "sochdb_query",
"description": "Execute a SochQL query. Returns results in TOON format.",
"inputSchema": {
"type": "object",
"properties": {
"query": { "type": "string", "description": "SochQL query" },
"format": { "type": "string", "enum": ["toon", "json"], "default": "toon" },
"limit": { "type": "integer", "default": 100 }
},
"required": ["query"]
}
}

memory_search_episodes​

{
"name": "memory_search_episodes",
"description": "Search for similar past episodes by semantic similarity",
"inputSchema": {
"type": "object",
"properties": {
"query": { "type": "string", "description": "Natural language query" },
"k": { "type": "integer", "default": 5 },
"episode_type": {
"type": "string",
"enum": ["conversation", "task", "workflow", "debug", "agent_interaction"]
},
"entity_id": { "type": "string", "description": "Filter by entity" }
},
"required": ["query"]
}
}

memory_build_context​

{
"name": "memory_build_context",
"description": "Build optimized LLM context from memory automatically",
"inputSchema": {
"type": "object",
"properties": {
"goal": { "type": "string", "description": "What the context will be used for" },
"token_budget": { "type": "integer", "default": 4096 },
"session_id": { "type": "string" },
"episode_id": { "type": "string" },
"entity_ids": { "type": "array", "items": { "type": "string" } },
"include_schema": { "type": "boolean", "default": false }
},
"required": ["goal", "token_budget"]
}
}

Vector Search & HNSW Subsystem​

The vector subsystem lives in sochdb-index (HnswIndex, HnswConfig) and sochdb-vector (CPU-first ANN, BM25, RRF fusion). Search picks between an exact parallel SIMD flat scan for small datasets and the approximate HNSW graph for larger ones.

Dimension-aware flat-scan crossover​

The dimension-aware logic in the core is the flat-scan (brute-force) threshold β€” when the node count is at or below the threshold, search uses an exact parallel SIMD scan (faster than HNSW for small N):

flat_scan_threshold = if dimension <= 128 { 10_000 }
else if dimension <= 384 { 4_000 }
else { 1_000 } // 768D+

search_smart(query, k, exact_threshold) routes to search_exact when the dataset is at or below the threshold (default 1000), otherwise to adaptive HNSW search.

No dimension-aware ef_search split

There is no dimension-aware 500/1500 ef_search split in the core. The HNSW ef_search default is a single fixed 500. The dimension-aware behavior is the flat-scan crossover above (10000 / 4000 / 1000), not ef_search.

VectorCollection API​

pub struct VectorCollection {
dimension: usize,
backend: VectorBackend,
pq: Option<ProductQuantizer>,
id_map: RwLock<HashMap<String, usize>>,
reverse_map: RwLock<HashMap<usize, String>>,
}

impl VectorCollection {
/// Open or create a vector collection
pub fn open(conn: &SochConnection, name: &str) -> Result<Self>;

/// Add vectors in batch
pub fn add(&mut self, ids: &[&str], vectors: &[Vec<f32>]) -> Result<()>;

/// Add a single vector
pub fn add_one(&mut self, id: &str, vector: Vec<f32>) -> Result<()>;

/// Search for nearest neighbors
pub fn search(&self, query: &[f32], k: usize) -> Result<Vec<SearchResult>>;

/// Get vector by ID
pub fn get(&self, id: &str) -> Option<Vec<f32>>;

/// Delete vector by ID
pub fn delete(&mut self, id: &str) -> Result<bool>;

/// Get collection statistics
pub fn stats(&self) -> VectorStats;

/// Get compression ratio (if PQ trained)
pub fn compression_ratio(&self) -> Option<f32>;

/// Migrate batch during idle time (for hybrid mode)
pub fn migrate_batch(&mut self) -> Result<usize>;
}

pub struct SearchResult {
pub id: String,
pub distance: f32,
pub metadata: Option<Value>,
}

pub struct VectorStats {
pub count: usize,
pub dimension: usize,
pub backend: String,
pub memory_bytes: usize,
pub pq_enabled: bool,
pub migration_progress: Option<f32>,
}

HNSW Configuration & Defaults​

HnswConfig::default() (sochdb-index/src/hnsw.rs):

ParameterFieldDefaultDescription
Mmax_connections32Max connections per node (raised from 16; m=32 clears 95% recall out of the box)
M0max_connections_layer064Max connections at layer 0 (2Β·M)
mLlevel_multiplier1/ln(32)Level-generation multiplier
ef_constructionef_construction256Build-time search width (raised from 200 for hard high-dim embeddings)
ef_searchef_search500Query-time search width (single fixed value)
MetricmetricCosineAlso Euclidean, DotProduct
Precisionquantization_precisionSome(F32)Also F16, BF16 (half the bytes)

Reported recall (from in-source benchmarks): deep-1M recall@10 β‰ˆ 0.967 at m=16/m0=32 β†’ 0.988 at m=32; the header claims roughly 250x speedup over brute force.

AdaptiveSearchConfig can binary-search the minimum ef that meets a target recall (default target_recall = 0.95, min_ef = 10, max_ef = 500).

No MultiShardHnswIndex in the core engine

There is no MultiShardHnswIndex (or HNSW-level scatter-gather shard router) in the Rust core. A MultiShardHnswIndex exists only in the Python native package as a threaded scatter-gather wrapper β€” it is not a server/core feature.

Quantization​

Precision is F32 / F16 / BF16 (4 / 2 / 2 bytes per element, via the half crate). Product Quantization is also available (HnswConfig.enable_product_quantization, default false, intended for very large / high-dimensional collections).

HNSW Graph Structure​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 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 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Level Generation Algorithm​

/// Per HNSW paper: level = floor(-ln(uniform(0,1)) * mL)
fn random_level(&self) -> usize {
let uniform: f32 = rand::random();
let level = (-uniform.ln() * self.level_multiplier).floor() as usize;
level.min(16) // Cap at 16 layers
}

// Distribution for M=32, mL = 1/ln(32) β‰ˆ 0.288:
// most nodes live at level 0; higher levels are exponentially rarer

Search Algorithm​

fn search_internal(&self, query: &[f32], k: usize) -> Vec<(u64, f32)> {
let entry_point = self.entry_point?;

// Navigate from top layer to layer 1
let mut current = entry_point;
for layer in (1..=self.max_layer).rev() {
current = self.search_layer_single(query, current, layer);
}

// Search layer 0 with ef_search candidates
let candidates = self.search_layer(query, current, self.ef_search.max(k), 0);

// Return top-k results
candidates.into_iter()
.take(k)
.map(|(dist, idx)| (self.nodes[idx].id, dist))
.collect()
}

WASM Vector Index (Browser)​

import init, { WasmVectorIndex } from 'sochdb-wasm';

async function main() {
await init();

// Create index: dimension=768, M=16, ef_construction=100
const index = new WasmVectorIndex(768, 16, 100);

// Insert vectors
const ids = BigUint64Array.from([1n, 2n, 3n]);
const vectors = new Float32Array(768 * 3);
const inserted = index.insertBatch(ids, vectors);

// Search
const query = new Float32Array(768);
const results = index.search(query, 10);
}

Hybrid Search (Vector + BM25 + Grep via RRF)​

sochdb-vector and sochdb-query provide hybrid retrieval that fuses up to three ranked lanes β€” vector, BM25 lexical, and grep (trigram-indexed regex) β€” using Reciprocal Rank Fusion:

RRF_score(d) = Ξ£ weightα΅’ / (k + rankα΅’(d))      // rank is 1-indexed

Defaults: BM25 k1 = 1.2, b = 0.75; RRF k = 60.0 with vector and lexical weights of 1.0. Presets include semantic_focused (0.7 / 0.3) and keyword_focused (0.3 / 0.7). The fusion config defaults to 100 candidates per modality and a final top-k of 10.

No adaptive RRF-k in the core

The RRF k is a fixed 60.0 in the Rust core (both fusion implementations). An adaptive_rrf_k=True option exists only in the Python HybridSearchIndex β€” adaptivity is a Python-SDK concept, not a core feature.


Transaction & MVCC API​

Transaction Lifecycle​

// Begin transaction
client.begin()?;

// Operations within transaction
client.put("/users/1", value1)?;
client.put("/users/2", value2)?;
let data = client.get("/users/1")?;

// Commit or abort
let commit_ts = client.commit()?; // Returns commit timestamp
// OR
client.abort()?; // Discard all changes

Isolation Levels​

pub enum IsolationLevel {
ReadCommitted, // See committed data at statement start
RepeatableRead, // See committed data at transaction start
Serializable, // SSI - Full serializability
}

// Begin with specific isolation
let txn = client.begin_with_isolation(IsolationLevel::Serializable)?;

MVCC Internals​

Version chain structure:

pub struct LockFreeVersion {
storage: VersionStorage, // Inline (<48B) or heap
txn_id: AtomicU64, // Writing transaction
commit_ts: AtomicU64, // Commit timestamp (0 if uncommitted)
next: AtomicPtr<LockFreeVersion>, // Next older version
}

Visibility rules:

  • Own writes: Visible if txn_id matches current transaction
  • Committed writes: Visible if commit_ts <= snapshot_ts
  • Conflict detection: SSI validation checks for rw-antidependency cycles

MVCC Version Chain​

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
}

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()
}
}

Group Commit Optimization​

/// Optimal batch size: N* = √(2 Γ— L_fsync Γ— Ξ» / C_wait)
struct GroupCommitBuffer {
pending: VecDeque<PendingCommit>,
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)
}
}

Context Query API​

ContextQueryBuilder​

Build AI-optimized context with automatic token budgeting:

pub struct ContextQueryBuilder {
sections: Vec<ContextSection>,
token_budget: usize,
format: ContextFormat,
truncation: TruncationStrategy,
}

pub enum SectionKind {
Literal { text: String },
Get { path: String },
Last { table: String, top_k: usize, filter: Option<Filter> },
Search { query: String, collection: String, top_k: usize },
ToolRegistry { include_schema: bool },
}

pub enum TruncationStrategy {
TailDrop, // Drop lowest priority sections first
HeadDrop, // Drop highest priority sections first
Proportional, // Reduce all sections proportionally
}

Usage Example​

let context = ContextQueryBuilder::new()
.section("system", SectionKind::Literal {
text: "You are a helpful assistant.".to_string()
})
.section("history", SectionKind::Last {
table: "messages".to_string(),
top_k: 10,
filter: None,
})
.section("knowledge", SectionKind::Search {
query: user_message.clone(),
collection: "docs".to_string(),
top_k: 5,
})
.with_budget(4096)
.with_format(ContextFormat::Soch)
.with_truncation(TruncationStrategy::TailDrop)
.execute()?;

TOON Format Token Savings​

JSON (52 chars β‰ˆ 13 tokens):
{"user":{"name":"Alice","age":30,"active":true}}

TOON (47 chars β‰ˆ 12 tokens, 8% savings):
user.name="Alice" user.age=30 user.active=true

TOON Table Format (40%+ savings for arrays):
[users]
name | age | active
"Alice" | 30 | true
"Bob" | 25 | false

Performance Characteristics​

Complexity Analysis​

OperationSochDBB-Tree (SQLite)Notes
Point ReadO(|path|)O(log N)TCH path-based
Point WriteO(|path|)O(log N)+ WAL
Range ScanO(|path| + K)O(log N + K)K = result count
Vector Search (HNSW)O(log N)N/Aef-dependent (approximate)
Vector Search (flat scan)O(N)N/Aexact; used at or below the dimension-aware threshold
Full ScanO(N)O(N)Columnar advantage

Memory Budget​

ComponentDefaultConfiguration
MemTable64MBmemtable_size
Block Cache128MBblock_cache_size
HNSW Index~1.5KB/vecM, ef_construction
PQ Codes (optional)32B/vecsubquantizers, centroids
WAL Buffer16MBwal_buffer_size

Latency Targets​

OperationTargetP99
Point Read (cached)<100ΞΌs<500ΞΌs
Point Read (disk)<1ms<5ms
Point Write<100ΞΌs<1ms
Transaction Commit<1ms<5ms
Vector Search (10K)<1ms<5ms
Vector Search (1M)<10ms<50ms
Context Query<50ms<200ms

Configuration Reference​

DatabaseConfig​

pub struct DatabaseConfig {
// WAL settings
pub wal_segment_size: usize, // Default: 64MB
pub wal_sync_mode: SyncMode, // Fsync, FsyncDelayed, None
pub group_commit: bool, // Batch commits for throughput
pub group_commit_delay_us: u64, // Max wait time

// LSCS settings
pub memtable_size: usize, // Default: 64MB
pub level_ratio: usize, // Default: 10
pub max_levels: usize, // Default: 7

// GC settings
pub gc_interval_secs: u64, // Default: 60
pub min_versions_to_keep: usize, // Default: 2
}

ClientConfig​

pub struct ClientConfig {
/// Maximum tokens per response (for LLM context management)
pub token_budget: Option<usize>,
/// Enable streaming output
pub streaming: bool,
/// Default output format
pub output_format: OutputFormat,
/// Connection pool size
pub pool_size: usize,
}

SyncMode Options​

pub enum SyncMode {
Fsync, // fsync after every commit (safest)
FsyncDelayed, // fsync after group_commit_delay_us
None, // No fsync (fastest, risk of data loss)
}

Workspace Dependencies​

Key dependencies from Cargo.toml:

[workspace.dependencies]
# Serialization
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
bincode = "1.3"

# Compression
lz4 = "1.24"
zstd = "0.13"

# Data structures
crossbeam-skiplist = "0.1"
parking_lot = "0.12"
dashmap = "5.5"

# Hashing
blake3 = "1.5"
twox-hash = "1.6"

# Vectors
ndarray = "0.15"

# Error handling
thiserror = "1.0"
anyhow = "1.0"

# Async (optional)
tokio = { version = "1.35", optional = true }

Query Processing 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 β”‚
β”‚ Output: LogicalPlan β”‚
β”‚ β”‚
β”‚ 3. OPTIMIZE β”‚
β”‚ β€’ Cost-based index selection β”‚
β”‚ β€’ Predicate ordering by selectivity β”‚
β”‚ Output: PhysicalPlan β”‚
β”‚ β”‚
β”‚ 4. EXECUTE β”‚
β”‚ β€’ Open column readers β”‚
β”‚ β€’ Apply predicates (vectorized) β”‚
β”‚ 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 {
// Push to index lookup
Predicate::Eq(col, val) if is_indexed(col) => {
plan.index_lookup = Some(IndexLookup { column: col, value: val });
}
// Push to block-level filtering
Predicate::Range(col, min, max) => {
plan.block_filters.push(BlockFilter { column: col, min, max });
}
// 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 first
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 from buddy allocator
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 - O(1) reset
self.current_block.lock().take();
}
}

Concurrency Model​

Lock Hierarchy​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Lock Acquisition Order β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 1. Catalog Lock (RwLock) - Table schema changes β”‚
β”‚ 2. Table Lock (per-table RwLock) - DDL operations β”‚
β”‚ 3. Transaction Manager Lock (Mutex) - Begin/commit/abort β”‚
β”‚ 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 via MVCC​

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,
}

Python SDK Architecture​

Access Modes​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Python Application β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ sochdb (PyPI) β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Embedded β”‚ β”‚ IPC β”‚ β”‚ Bulk API β”‚ β”‚
β”‚ β”‚ FFI β”‚ β”‚ Client β”‚ β”‚ (subprocess β†’ sochdb-bulk) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚ β”‚
β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”
β”‚ Rust β”‚ β”‚ IPC β”‚ β”‚ sochdb-bulk β”‚
β”‚ FFI β”‚ β”‚ Server β”‚ β”‚ binary β”‚
β”‚ (.so) β”‚ β”‚ β”‚ β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Distribution Model (uv-style wheels)​

Wheels contain pre-built Rust binaries - no compilation required:

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

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 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Result: 12Γ— throughput improvement for bulk vector operations.


Summary​

SochDB's architecture delivers:

  1. AI-Native Design:

    • TOON format (typically 40-66% token savings vs JSON)
    • Context queries with automatic token budgeting
    • MCP integration for seamless LLM tool use
  2. Dual Deployment:

    • Embedded, SQLite-like in-process database
    • Thick gRPC server with thin language clients
    • Unified connect() API across file://, ipc://, grpc://, grpcs://
  3. Hybrid Retrieval:

    • HNSW approximate search with an exact SIMD flat-scan crossover
    • Vector + BM25 + grep lanes fused via RRF (k = 60.0)
    • Quantization (F32 / F16 / BF16, optional PQ)
  4. Production-Ready:

    • WAL durability with group commit and CDC
    • MVCC transactions with SSI isolation
    • Crash recovery with checkpoint/replay
    • Optional auth (JWT validation, API keys), RBAC (Owner/Editor/Viewer), TLS/mTLS
  5. High Performance:

    • O(|path|) lookups via TCH (Trie-Columnar Hybrid)
    • Lock-free reads via hazard pointers
    • Volcano iterator SQL executor + sochdb-fusion pipeline

This enables SochDB to replace the traditional AI stack (PostgreSQL + Pinecone + Redis + custom RAG) with a single, optimized system.