SochDB IPC Server Architecture
The SochDB IPC server provides a high-performance, multi-process interface to a single embedded SochDB database over a Unix domain socket. It lets several local processes share one on-disk database instance with low-latency, length-prefixed binary framing — no network stack, no serialization overhead of a full RPC layer.
The IPC server is a Rust library type, IpcServer (with a matching IpcClient),
defined in sochdb-storage/src/ipc_server.rs (part of the core engine, v2.0.3). It
is not shipped as a separate sochdb-server executable — you embed it in your own
process, or reach it through the unified connect() client API (see below). The
networked, multi-tenant surface is the separate
gRPC server (sochdb-grpc-server).
The core engine (including this IPC server) is licensed AGPL-3.0-or-later with commercial licensing available; the language SDKs are Apache-2.0.
Connecting via connect() (ipc://)
The IPC server is the transport behind the ipc:// scheme in SochDB's unified
connect() client API. The URI is the socket path:
ipc:///tmp/sochdb.sock
The unified URI → transport mapping is:
| URI scheme | Transport |
|---|---|
file://./data | Embedded on-disk database (in-process) |
ipc:///tmp/sochdb.sock | Local IPC over a Unix domain socket (this page) |
grpc://localhost:50051 | gRPC to a SochDB server (plaintext) |
grpcs://prod.example.com:443 | gRPC over TLS |
The ipc:// path is a filesystem socket path, not a host:port. There is no TCP
port for the IPC server — it is local-only by design. The unified connect() surface
is documented in the engine CHANGELOG; the in-repo language SDKs currently expose
transport-specific clients, so the exact ipc:// parsing may vary by SDK version.
Confirm support in your SDK before relying on it.
Architecture
The server uses a thread-per-client model optimized for low-latency local
communication. A shared Arc<Database> kernel is accessed concurrently by one handler
thread per connected client.
┌────────────────────────────────────────────────────────────────┐
│ IPC Server Process │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Database Kernel (Arc<Database>) │ │
│ └────────────────────────────────────────────────────────┘ │
│ ▲ ▲ ▲ │
│ │ │ │ │
│ ┌────────┴────────┐ ┌────────┴────────┐ ┌────────┴────────┐ │
│ │ ClientHandler 1 │ │ ClientHandler 2 │ │ ClientHandler N │ │
│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │ │
│ ┌────────┴────────────────────┴────────────────────┴────────┐│
│ │ Unix Domain Socket Listener ││
│ │ /tmp/sochdb.sock ││
│ └──────────────────────────────────────────────────────────┘│
└────────────────────────────────────────────────────────────────┘
Key Components
- Listener Thread: Accepts incoming Unix socket connections and spawns a handler thread per client. Honors a
max_connectionslimit (default 100), rejecting connections beyond it. - Client Handlers: One dedicated thread per client that maintains per-connection transaction state and cursor isolation.
- Shared Kernel: A thread-safe
Arc<Database>instance shared across all handlers. - Transaction Map: Each handler maintains its own
active_txnsmap (client_txn_id→TxnHandle), ensuring per-connection isolation. On disconnect, the handler aborts any still-open transactions.
Configuration defaults
IpcServerConfig defaults (ipc_server.rs):
| Field | Default | Meaning |
|---|---|---|
socket_path | /tmp/sochdb.sock | Path to the Unix socket file |
max_connections | 100 | Maximum concurrent connections |
thread_pool_size | 4 | Handler thread-pool hint |
connection_timeout_secs | 300 | Connection timeout (5 minutes) |
Security model
The IPC protocol has no handshake and no authentication. Access control is the
filesystem permission on the socket file. On bind, the server best-effort chmods
the socket to 0600 (owner read/write only), so other local users cannot connect
to the unauthenticated endpoint. This is defense-in-depth for a local-only transport —
do not expose the socket file to untrusted local users, and use the
gRPC server with --auth/TLS for any networked or
multi-tenant deployment.
Wire Protocol
Every message uses a lightweight length-prefixed binary frame designed for minimal overhead. Lengths are little-endian. The maximum payload size is 16 MB; larger frames are rejected.
Frame Format:
┌─────────────────┬─────────────────────┬──────────────────────────┐
│ OpCode (1 byte) │ Length (4 bytes LE) │ Payload (N bytes) │
└─────────────────┴─────────────────────┴──────────────────────────┘
Request OpCodes (Client → Server)
| OpCode | Hex | Name | Description |
|---|---|---|---|
| 1 | 0x01 | PUT | Auto-commit key-value write |
| 2 | 0x02 | GET | Read value by key |
| 3 | 0x03 | DELETE | Delete key (auto-commit) |
| 4 | 0x04 | BEGIN_TXN | Start a new explicit transaction |
| 5 | 0x05 | COMMIT_TXN | Commit an active transaction |
| 6 | 0x06 | ABORT_TXN | Roll back / abort a transaction |
| 7 | 0x07 | QUERY | Execute a path query (returns TOON) |
| 8 | 0x08 | CREATE_TABLE | Define a table schema |
| 9 | 0x09 | PUT_PATH | Write to a hierarchical path |
| 10 | 0x0A | GET_PATH | Read from a hierarchical path |
| 11 | 0x0B | SCAN | Prefix range scan |
| 12 | 0x0C | CHECKPOINT | Force a durability flush to disk |
| 13 | 0x0D | STATS | Server runtime metrics |
| 14 | 0x0E | PING | Health check |
| 15 | 0x0F | EXECUTE_SQL | Submit a SQL string (see note) |
EXECUTE_SQL is a stubEXECUTE_SQL (0x0F) is accepted but does not run SQL server-side. It returns a
VALUE response containing a JSON error directing callers to perform SQL-to-KV mapping
client-side (as the Python SDK does). For full SQL over the wire use the
PostgreSQL wire protocol on the gRPC server.
Response Codes (Server → Client)
| Hex | Name | Description |
|---|---|---|
| 0x80 | OK | Operation succeeded (no data) |
| 0x81 | ERROR | Operation failed (payload = UTF-8 error message) |
| 0x82 | VALUE | Data return (empty payload = key/path not found) |
| 0x83 | TXN_ID | Transaction ID / commit timestamp (u64 LE) |
| 0x86 | STATS_RESP | JSON statistics |
| 0x87 | PONG | Health-check response |
The ROW (0x84) and END_STREAM (0x85) opcodes are reserved in the protocol
constants but are not emitted by the current server — QUERY returns a single
VALUE frame containing the full TOON result rather than a streamed row sequence.
Server Statistics
The server maintains atomic counters for real-time monitoring, returned by the STATS
opcode as a STATS_RESP (0x86) JSON payload.
Returned JSON Structure:
{
"connections_total": 150,
"connections_active": 12,
"requests_total": 45000,
"requests_success": 44995,
"requests_error": 5,
"bytes_received": 1024000,
"bytes_sent": 2048000,
"uptime_secs": 3600,
"memtable_size_bytes": 0,
"wal_size_bytes": 0,
"active_transactions": 3
}
memtable_size_bytes and wal_size_bytes are currently always reported as 0 — they
are placeholders not yet wired to live storage metrics. active_transactions reflects
the open transactions for the connection that issued the STATS request.
Developer Guide: Implementing a Client
If you are building a client driver in a new language (e.g. Go, Ruby, Node.js), follow these steps:
- Connect: Open a Unix domain socket to the configured path (default
/tmp/sochdb.sock). - Handshake: None — a successful connection implies readiness. (Access is gated by the socket file's
0600permissions.) - Send requests:
- Write 1 byte OpCode.
- Write 4 bytes little-endian Length.
- Write
Lengthbytes of Payload.
- Read responses:
- Read 1 byte response OpCode.
- Read 4 bytes little-endian Length.
- Read
Lengthbytes of Payload.
- Clean up: Close the socket when done. The server automatically aborts any open transactions for the connection.
Payload encodings
A few opcodes use sub-framing inside the payload (all integers little-endian):
- PUT (0x01):
key_len (u32)+key+value. - GET / DELETE (0x02 / 0x03): the raw key bytes.
- PUT_PATH / GET_PATH (0x09 / 0x0A):
segment_count (u16)+ repeated [seg_len (u16)+segment] + trailing value (empty for GET). - COMMIT_TXN / ABORT_TXN (0x05 / 0x06): the 8-byte
client_txn_id(u64). - SCAN (0x0B): the prefix string; the
VALUEreply iscount (u32)+ repeated [key_len (u16)+key+val_len (u32)+value].
Example: Raw Python Client
This is a minimal raw-socket example for illustration. For production use, prefer the
Python SDK (pip install sochdb, v0.5.9), which wraps this
protocol for you.
- Python (raw socket)
- Rust (IpcClient)
import socket
import struct
SOCKET_PATH = "/tmp/sochdb.sock"
GET = 0x02
VALUE = 0x82
ERROR = 0x81
def simple_get(key_bytes: bytes) -> bytes | None:
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect(SOCKET_PATH)
try:
# Send GET: opcode (1) + length (4 LE) + payload
header = struct.pack("<BI", GET, len(key_bytes))
s.sendall(header + key_bytes)
# Read response header (opcode + length)
resp_header = s.recv(5)
code, length = struct.unpack("<BI", resp_header)
# Read response body
data = b""
while len(data) < length:
data += s.recv(length - len(data))
if code == VALUE:
return data or None # empty payload means "not found"
if code == ERROR:
raise RuntimeError(data.decode())
raise RuntimeError(f"unexpected opcode: {code:#x}")
finally:
s.close()
use sochdb_storage::ipc_server::IpcClient;
// Connect to the server's Unix socket.
let mut client = IpcClient::connect("/tmp/sochdb.sock")?;
// Health check.
let latency = client.ping()?;
println!("ping: {latency:?}");
// Auto-commit put / get.
client.put(b"key1", b"value1")?;
let value = client.get(b"key1")?; // Option<Vec<u8>>
assert_eq!(value, Some(b"value1".to_vec()));
See also
- gRPC Server — the networked, authenticated, multi-tenant surface.
- Python SDK — the high-level embedded/server client (v0.5.9).