Recipe — Split-storage engine (fast-storage flavor)¶
A deployment flavor that places the epistemic-graph engine on the fleet's
fastest-disk node (NVMe/SSD) and connects graph-os + the host daemon to it over TCP,
instead of co-locating everything on one host. Config-only — no code fork.
When to use it¶
The durable write path is fsync-bound. In the recorded benchmark, HDD fsync p99 near 460 ms built a writer backlog while NVMe was near 3.2 ms, a roughly 143× tail-latency cut. If the operator's fastest disk and the operator's most cores/RAM are on different hosts (common in a heterogeneous homelab/cluster), this flavor lets the engine sit on the fast disk while compute stays where the cores are. It is also the unit that later composes into a sharded cluster — each shard-engine on its own fast disk, clients routing by tenant.
How it works¶
- The engine binds a TCP listener (
--tcp-addr, published:9100) in addition to its local UDS. - graph-os/host resolve the engine via
GRAPH_SERVICE_ENDPOINTS=tls://<engine-host>:9100. A configured endpoint list is connect-only; empty selects the packaged local lifecycle. - The persist dir lives on the fast-storage node's NVMe (
ENGINE_PERSIST), the only thing that must be on the fast disk. The source tree / compute can stay elsewhere.
Config surface¶
All knobs live in services/epistemic-graph/flavors/split-storage.env (parameterized into both
composes; defaults reproduce the original single-host config, so the flavor is reversible —
unset and redeploy):
| Var | Side | Meaning |
|---|---|---|
SERVER |
engine | placement node label (the fast-storage host) |
ENGINE_PERSIST |
engine | persist dir on the NVMe/SSD |
ENGINE_BIN |
engine (dev) | host-built binary path on that node (prod bakes it into the image) |
ENGINE_TCP_ADDR |
engine | 0.0.0.0:9100 — enable the TCP listener |
GRAPH_SERVICE_ENDPOINTS |
clients | tls://<engine-host>:9100 for the host daemon + MCP dial |
Dev vs prod¶
- Dev (source-mounted,
compose.dev.yml): the engine binary is bind-mounted from a host path (ENGINE_BIN) on the fast-storage node —cargo build+ redeploy is live. - Prod (pre-built image): the binary is baked into the registry image
(
${CONTAINER_REGISTRY}/agent-utilities), soENGINE_BINis unused — drop the binary bind-mount, set the image tag, keepSERVER/ENGINE_PERSIST/ENGINE_TCP_ADDR/GRAPH_SERVICE_ENDPOINTS. Same flavor, image-based. (Genesis can offer this as a placement option insingle-node-prod/enterpriseprofiles: "put the engine on the fastest-disk node.")
Cutover (one-time, ~1–2 min downtime, reversible)¶
- Stage the engine binary on the fast node (
ENGINE_BIN); createENGINE_PERSISTon its NVMe. - Stop the engine (
docker service scale epistemic-graph_epistemic-graph=0). rsyncthe*.redb+.annidxfrom the old persist dir → the NVMeENGINE_PERSIST.source split-storage.env;docker stack deployepistemic-graph (now on the fast node, TCP).docker stack deploygraph-os (clients pick upGRAPH_SERVICE_ENDPOINTS).- Verify: engine healthy on the fast node,
graph_searchreturns, ingestion drains. Revert = unset the vars + redeploy (old data is untouched on the original node).
Security¶
The engine and clients share GRAPH_SERVICE_AUTH_SECRET; every request carries
the current verified authority envelope. Routable TCP must use a configured
TLS/mTLS profile and must not be exposed as plaintext, even on a trusted LAN.