SochDB gRPC Server Architecture
The SochDB gRPC server (sochdb-grpc-server) is the thick-server / thin-client
network surface for SochDB v2. It is built on tonic
(Rust gRPC over HTTP/2) and exposes the full v2 feature set — vector search, graph,
collections, namespaces, semantic cache, tracing, checkpoints, MCP, key-value, and
change-data-capture subscriptions — as 12 gRPC services plus a standard gRPC
health service.
In addition to the gRPC port, the same binary can serve a Prometheus metrics endpoint, a WebSocket gateway, and a PostgreSQL wire-protocol port. See Deploying to Production for Docker, Helm, and Kubernetes operations.
Quick start
# Build the server
cargo build --release --package sochdb-grpc --bin sochdb-grpc-server
# Run on the default loopback port (50051), no auth
sochdb-grpc-server
# Bind all interfaces with authentication enabled
sochdb-grpc-server --host 0.0.0.0 --port 50051 --auth
The published image runs the same binary:
docker run -p 50051:50051 sochdb/sochdb-grpc:latest. The image's ENTRYPOINT
is sochdb-grpc-server with default CMD --host 0.0.0.0 --port 50051.
CLI flags and default ports
All flags are defined on the clap Args struct in sochdb-grpc/src/main.rs.
| Flag | Default | Env var | Purpose |
|---|---|---|---|
--host | 127.0.0.1 | — | Bind address |
-p, --port | 50051 | — | gRPC port |
-d, --debug | off | — | Debug logging |
--metrics-port | 9090 | — | Prometheus HTTP /metrics port (0 disables) |
--ws-port | 8080 | — | WebSocket gateway port (0 disables) |
--pg-port | 5433 | — | PostgreSQL wire-protocol port (0 disables) |
--auth | off | — | Enable gRPC authentication |
--api-key | none | SOCHDB_API_KEY | Register an API key (requires --auth) |
--tls-cert | none | SOCHDB_TLS_CERT | TLS cert PEM path (enables TLS) |
--tls-key | none | SOCHDB_TLS_KEY | TLS private-key PEM path |
--tls-ca | none | SOCHDB_TLS_CA | CA cert for mTLS client verification |
--secrets-path | none | SOCHDB_SECRETS_PATH | Kubernetes Secrets mount path |
--pg-data-dir | none | SOCHDB_PG_DATA_DIR | Persistent dir enabling real SQL via the PG wire port |
Additional environment variables are read directly (they are not clap flags):
SOCHDB_API_KEY_PEPPER, SOCHDB_JWT_SECRET, SOCHDB_ENCRYPTION_KEY, and
SOCHDB_API_KEYS (comma-separated).
--config flagThe server is configured entirely through the flags and environment variables
above. There is no --config flag — the legacy sochdb-server-config.toml
and the make server-run target that references it are stale and do not work
against the current binary.
Port summary
| Service | Default port | Notes |
|---|---|---|
| gRPC | 50051 | All 12 services + gRPC health |
| Prometheus metrics | 9090 | GET /metrics, GET /health; 0 disables |
| WebSocket gateway | 8080 | JSON message protocol; 0 disables |
| PostgreSQL wire | 5433 | Simple-query only; 0 disables |
The 12 gRPC services
When the server starts it registers the following services on the gRPC port
(main.rs). Every service except VectorIndexService is wrapped with the auth
interceptor. A standard tonic_health service is also mounted and is not
behind auth so that Kubernetes and load-balancer probes can reach it.
| # | Service | Auth interceptor | Notable RPCs |
|---|---|---|---|
| 1 | VectorIndexService | No (64 MB messages) | CreateIndex, InsertBatch, InsertStream, Search, SearchBatch, GetStats |
| 2 | GraphService | Yes | AddNode, AddEdge, Traverse, ShortestPath, AddTemporalEdge, QueryTemporalGraph |
| 3 | PolicyService | Yes | RegisterPolicy, Evaluate, ListPolicies, DeletePolicy |
| 4 | ContextService | Yes | Query, WriteEpisode, EstimateTokens, FormatContext |
| 5 | CollectionService | Yes | CreateCollection, AddDocuments, SearchCollection, GetDocument |
| 6 | NamespaceService | Yes | CreateNamespace, ListNamespaces, SetQuota |
| 7 | SemanticCacheService | Yes | Get, Put, Invalidate, GetStats |
| 8 | TraceService | Yes | StartTrace, StartSpan, EndSpan, AddEvent, GetTrace |
| 9 | CheckpointService | Yes | CreateCheckpoint, RestoreCheckpoint, ExportCheckpoint, ImportCheckpoint |
| 10 | McpService | Yes | RegisterTool, ExecuteTool, ListTools, GetToolSchema |
| 11 | KvService | Yes | Get, Put, Delete, Scan (server-stream), BatchGet, BatchPut |
| 12 | SubscriptionService | Yes | Subscribe (stream), WatchKey (stream), ListSubscriptions, CancelSubscription |
VectorIndexService is the only service registered without the auth
interceptor, and it is the only one configured with a 64 MB max encode/decode
message size (max_encoding_message_size / max_decoding_message_size). All
other services use the tonic default 4 MB decode limit. Plan large vector
batches accordingly, and put VectorIndexService behind a trusted network
boundary if you need to restrict access to it.
Authentication and authorization
Authentication is off by default. Pass --auth to turn it on. The auth
behaviour is implemented in sochdb-grpc/src/auth_interceptor.rs and
sochdb-grpc/src/security.rs. For the full security model — JWT validation, API
key hashing, TLS/mTLS, and Kubernetes secrets — see the
Security guide.
Presenting credentials
Clients pass credentials as gRPC metadata headers:
authorization: Bearer <token>— preferred. With--auth, the token is validated as a JWT (HS256). JWTs are validation-only: SochDB does not mint tokens — they must be issued by your IdP or caller.x-api-key: <key>— fallback. Internally rewritten to aBearerheader.
The interceptor pipeline is: authenticate → check rate limit → inject the
resolved Principal into the request extensions for downstream handlers.
import grpc
# Authenticated channel using a JWT bearer token
channel = grpc.insecure_channel("localhost:50051")
metadata = [("authorization", "Bearer <your-jwt>")]
# ... or, with an API key:
metadata = [("x-api-key", "<your-api-key>")]
When --auth is enabled, both JWT and API-key auth are turned on, and all
Bearer tokens are routed to JWT validation. A bare --api-key is only
reachable through the x-api-key header path. Keep JWT and API-key clients on
the header that matches your intent.
RBAC roles
Authorization is capability-based. The built-in roles and their capabilities are:
| Role | Capabilities |
|---|---|
Owner | Admin, Read, Write, ManageCollections, ManageIndexes, ViewMetrics, ManageBackups, ManageUsers |
Editor | Read, Write, ManageCollections, ManageIndexes |
Viewer | Read, ViewMetrics |
A Custom { name, capabilities } role is also supported. The Admin capability
acts as a wildcard. Roles can be carried in the JWT role/capabilities claims
or bound server-side via per-namespace role bindings (scoped Global,
Namespace, or Collection).
When --auth is not passed, the interceptor runs in pass-through mode and
every request resolves to an anonymous principal with Read, Write, and
ManageCollections capabilities.
Rate limiting
The default rate limit is 1000 requests/second with a burst of 100 per tenant, enforced inside the auth interceptor. Audit logging is on by default.
TLS and mTLS
TLS is enabled when both --tls-cert and --tls-key are set. Adding --tls-ca
additionally enables mTLS client-certificate verification. Certificates can be
hot-reloaded (mtime-based). See the Security guide for
details and the grpcs:// client scheme.
Change-data-capture subscriptions
SubscriptionService exposes SochDB's WAL-derived CDC stream over gRPC
(sochdb-grpc/src/subscription_server.rs).
service SubscriptionService {
rpc Subscribe(SubscribeRequest) returns (stream SubscribeEvent);
rpc WatchKey(WatchKeyRequest) returns (stream WatchKeyEvent);
rpc ListSubscriptions(ListSubscriptionsRequest) returns (ListSubscriptionsResponse);
rpc CancelSubscription(CancelSubscriptionRequest) returns (CancelSubscriptionResponse);
}
A SubscribeRequest carries a namespace, a list of tables, a list of
operations (insert/update/delete/schema-change), a start_sequence
(0 means "from latest", a value greater than 0 resumes from that CDC
sequence), an optional where_predicate, and a batch_size (default 64).
where_predicate is accepted but not yet enforcedThe streaming subscription handler applies table filtering and
operation-type filtering, but it does not currently read or apply the
where_predicate field. SQL-WHERE predicate filtering is parsed into the request
but not enforced in the stream loop yet — treat it as accepted-but-not-yet-active.
For the full CDC model (event shape, sequence semantics, resume, and overrun handling), see CDC Subscriptions.
VectorIndexService reference
VectorIndexService is optimized for high-throughput embedding ingestion and
k-NN search. It wraps the sochdb-index crate's HNSW implementation.
HNSW configuration
When creating an index you can tune the HNSW (Hierarchical Navigable Small World)
parameters. The values below are the server-side defaults; the CreateIndex
path falls back to them when a proto field is left at 0.
| Parameter | Default | Impact |
|---|---|---|
dimension | required | Vector size (e.g. 1536 for OpenAI, 768 for BERT) |
max_connections (M) | 32 | Max edges per node. Higher = better recall, slower build/search |
ef_construction | 256 | Candidate-list size during build. Higher = better index quality, slower build |
ef_search | 500 | Candidate-list size during search. Higher = better recall, slower query |
metric | Cosine | Distance metric: L2, Cosine, DotProduct |
ef_search splitThe core engine uses a single ef_search default of 500 regardless of
dimension. The dimension-aware logic that does exist is the brute-force
flat-scan threshold (≤128D: 10000 vectors, ≤384D: 4000, otherwise 1000), below
which a linear scan is used instead of the HNSW graph.
Index management
CreateIndex — initializes a new HNSW graph.
message CreateIndexRequest {
string name = 1;
uint32 dimension = 2;
HnswConfig config = 3;
DistanceMetric metric = 4;
}
DropIndex — removes an index from memory.
Data ingestion
InsertBatch (recommended) — atomic batch insertion using a flat layout for
zero-copy deserialization in Rust. The most efficient way to load data.
message InsertBatchRequest {
string index_name = 1;
repeated uint64 ids = 2;
repeated float vectors = 3; // Flat array: [v1_0, v1_1... v2_0...]
}
InsertStream — long-running client-streaming RPC for continuous ingestion.
message InsertStreamRequest {
string index_name = 1; // Only needed in the first message
uint64 id = 2;
repeated float vector = 3;
}
Search
Search — standard k-nearest-neighbors search.
message SearchRequest {
string index_name = 1;
repeated float query = 2;
uint32 k = 3;
uint32 ef = 4; // Optional per-query override; defaults to ef_search from config
}
SearchBatch — execute multiple queries in parallel.
Operations
GetStats — returns internal graph statistics useful for debugging:
num_vectors (total count), max_layer (height of the HNSW graph), and
avg_connections (graph connectivity density).
HealthCheck — service-level health check for load balancers (distinct from
the global tonic_health service).
Client usage example (Python)
import grpc
from sochdb_pb2 import CreateIndexRequest, SearchRequest, HnswConfig
from sochdb_pb2_grpc import VectorIndexServiceStub
# 1. Connect (VectorIndexService has no auth interceptor)
channel = grpc.insecure_channel("localhost:50051")
stub = VectorIndexServiceStub(channel)
# 2. Create an index
stub.CreateIndex(CreateIndexRequest(
name="prod_vectors",
dimension=768,
metric=2, # DISTANCE_METRIC_COSINE (1 = L2, 3 = DOT_PRODUCT)
config=HnswConfig(max_connections=32, ef_construction=256),
))
# 3. Search
response = stub.Search(SearchRequest(
index_name="prod_vectors",
query=[0.1, 0.2], # 768 floats
k=5,
))
for match in response.results:
print(f"Doc {match.id}: Score {match.distance}")
The example above targets VectorIndexService, which is unauthenticated. For the
other 11 services, attach an authorization: Bearer <token> or x-api-key
header to every call when the server runs with --auth.
Related pages
- Security guide — JWT, API keys, RBAC, TLS/mTLS, secrets.
- CDC Subscriptions — the change-data-capture model.
- Deploying to Production — Docker, Helm, Kubernetes.
- IPC Server — the local Unix-socket interface.