Skip to main content
Version: Next

SochDB IPC Server Architecture

The SochDB IPC Server (sochdb-server) provides a high-performance, multi-process interface to the SochDB storage engine. It allows multiple applications to access a single embedded database instance simultaneously using Unix domain sockets.

Architecture

The server uses a thread-per-client model optimized for low-latency local communication.

┌────────────────────────────────────────────────────────────────┐
│ IPC Server Process │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Database Kernel (Arc<Database>) │ │
│ └────────────────────────────────────────────────────────┘ │
│ ▲ ▲ ▲ │
│ │ │ │ │
│ ┌────────┴────────┐ ┌────────┴────────┐ ┌────────┴────────┐ │
│ │ ClientHandler 1 │ │ ClientHandler 2 │ │ ClientHandler N │ │
│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │ │
│ ┌────────┴────────────────────┴────────────────────┴────────┐│
│ │ Unix Domain Socket Listener ││
│ │ /tmp/sochdb-<id>.sock ││
│ └────────────────────────────────────────────────────────────────┘

Key Components

  1. Listener Thread: Accepts incoming Unix socket connections and spawns handler threads.
  2. Client Handlers: Dedicated thread per client that maintains transaction state and cursor isolation.
  3. Shared Kernel: Thread-safe Arc<Database> instance shared across all handlers.
  4. Transaction Map: Each handler maintains its own active_txns map (client_txn_idTxnHandle), ensuring process isolation.

Wire Protocol

The core communication uses a lightweight binary protocol designed for minimal overhead.

Frame Format:

┌─────────────────┬─────────────────────┬──────────────────────────┐
│ OpCode (1 byte) │ Length (4 bytes LE) │ Payload (N bytes) │
└─────────────────┴─────────────────────┴──────────────────────────┘

Protocol Operations (OpCodes)

OpCodeHexNameTypeDescription
10x01PUTCmdAuto-commit key-value write
20x02GETCmdRead value
30x03DELETECmdDelete key
40x04BEGIN_TXNTxnStart a new explicit transaction
50x05COMMIT_TXNTxnCommit an active transaction
60x06ABORT_TXNTxnRollback/Abort transaction
70x07QUERYDataExecute SQL-like query (returns TOON)
80x08CREATE_TABLEDDLDefine table schema
90x09PUT_PATHPathWrite to hierarchical path
100x0AGET_PATHPathRead from hierarchical path
110x0BSCANDataRange scan (prefix)
120x0CCHECKPOINTSysForce durability flush to disk
130x0DSTATSSysdetailed server Runtime metrics
140x0EPINGSysHealth check

Response Codes

HexNameDescription
0x80OKOperation succeeded (no data)
0x81ERROROperation failed (payload = error msg)
0x82VALUEData return
0x83TXN_IDTransaction ID return
0x86STATS_RESPJSON Statistics
0x87PONGHealth check response

Server Statistics

The server maintains atomic counters for real-time monitoring. These are accessible via the STATS opcode.

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,
"active_transactions": 3
}

Developer Guide: Implementing a Client

If you are building a client driver for a new language (e.g., Go, Ruby, Node.js), follow these steps:

  1. Connect: Open a Unix domain socket connection to the path (default: ./sochdb_data/sochdb.sock).
  2. Handshake: (Currently none, connection implies readiness).
  3. Sending Requests:
    • Write 1 byte OpCode.
    • Write 4 bytes Little-Endian Length.
    • Write Length bytes of Payload.
  4. Reading Responses:
    • Read 1 byte OpCode.
    • Read 4 bytes Little-Endian Length.
    • Read Length bytes of Payload.
  5. Clean Up: Close the socket when done. The server will automatically abort any open transactions.

Example: Raw Python Client Implementation

import socket
import struct

def simple_get(key_bytes):
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect("./sochdb_data/sochdb.sock")

# Send GET (0x02)
payload = key_bytes
header = struct.pack("<BI", 0x02, len(payload))
s.sendall(header + payload)

# Read Header
resp_header = s.recv(5)
code, length = struct.unpack("<BI", resp_header)

# Read Body
data = s.recv(length)

if code == 0x82: # VALUE
return data
elif code == 0x81: # ERROR
raise Exception(data.decode())

s.close()