Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Architecture

This chapter documents the internal architecture. Dr Strange is built in distinct layers, each with a narrow seam to the next, so the engine can be embedded, served, or swapped at the storage level without disturbing the layers above.

The layers

LayerResponsibility
Storagedurable key/value with MVCC snapshots, behind a backend trait
Cachedecoded-object memoization, version-stamped by commit sequence
Computationplan execution, graph algorithms, hybrid-retrieval fusion
APIthe Database / PlaneHandle surface: queries, writes, indexes, time-travel, the change feed, snapshots
Planesthe cross-cutting namespace that scopes all data

Above the core sit the wrapper layers: the JSON-RPC web backend and dashboard, the language SDKs, the CLI, the MCP server, and the LLM layer that powers natural-language query and ingestion.

The commit sequence

One primitive threads through every layer: the commit sequence — a monotonic number assigned to each committed write. It is the storage engine’s MVCC version, the cache’s coherence stamp, the freshness stamp on the index sidecars, the address for a time-travel read, and the sequence carried by every change event. A single, engine-wide clock keeps the layers consistent without separate coordination.

Storage

The graph layer is written against a backend trait — begin_read yields a stable MVCC snapshot, begin_write a serialized single-writer transaction — over a fixed set of logical tables (nodes, edges, forward/reverse adjacency, label and property indexes, external keys, plane metadata). Keys are big-endian, so byte order is scan order and every per-plane or per-node range is a contiguous prefix.

The default backend is a hand-rolled LSM engine:

  • A write is appended to a write-ahead log — a length-prefixed, CRC32-checked batch record, fsynced before the change is published — and inserted into a versioned in-memory memtable stamped with the commit sequence.
  • When the memtable exceeds a threshold it is flushed to an immutable, sorted SST file (with a per-SST Bloom filter), and the WAL is rotated.
  • A read merges the memtable over the SSTs, newest run first, returning the first version at or below its snapshot.
  • Compaction merges accumulated runs into one, reclaiming versions no reader can still observe.

Because each version carries its commit sequence and SSTs are immutable, a reader holds no locks across its lifetime, and an old snapshot’s data remains reachable until compaction reclaims it.

Time-travel and retention

Time-travel falls out of the MVCC design almost for free. begin_read_at(seq) pins a read to any past commit sequence, and the read path — which already returns versions at or below a snapshot — serves it unchanged. The only requirement is that the historical versions survive compaction, which a retention window guarantees by flooring the compaction GC (unbounded by default; a bounded window trades history for disk). A timestamp address resolves to a commit by binary-searching a wall-clock time recorded on each commit. Only the native backend retains prior versions, so time-travel is native-only.

The cache

The executor never touches storage directly; it reads through a cache with two tiers. A per-query L1 memoizes decoded nodes, edges, and adjacency for the life of one query (bound to its snapshot). A cross-query L2 — a bounded, version-stamped object cache — is shared by every query: an entry is served only when its stamp equals the reader’s commit sequence, so any write silently invalidates prior entries for later snapshots. This exact-sequence rule also makes a historical read cache-safe: it can never be served a newer version.

Computation

The executor runs a logical plan over a stream of rows, each carrying a current node and the path that produced it, so a read can return the connected subgraph rather than bare endpoints. Graph algorithms run over an index-dense frame of the plane’s adjacency, read-only over one snapshot. Hybrid retrieval gathers each channel independently, normalizes within-channel scores, and fuses them by weighted sum, with the graph channel seeded from the strongest hits.

Indexes

The vector (HNSW) and keyword (BM25) indexes are in-memory registries, their declarations recorded in storage. On open, a registry loads from its sidecar (.hnsw / .bm25) when the sidecar’s stamped commit sequence matches the data, and otherwise rebuilds from the key/value store — which is always the source of truth. Coherence is maintained at commit: a write transaction buffers the index changes it implies and applies them to the registries under a write lock once the data is durable. A snapshot serializes the live registries to sidecar bytes, and an id-faithful restore reloads them without a rebuild.

The change feed

A Database accepts one commit-time observer. A write transaction buffers the node and edge mutations it makes; at commit, once the data is durable, they are collapsed per entity, resolved to records at the committed snapshot (with embeddings and internal properties stripped), and handed to the observer. The web layer registers an observer that publishes each change set into a broadcast channel, which every plane.watch WebSocket subscriber drains — delivery is best-effort, so a slow consumer drops overflow rather than stalling writers.

Backends

The storage backend is a compile-time choice: the native LSM engine (default), the legacy redb engine, or an in-memory engine for tests. Capabilities that depend on prior versions — time-travel, and its retention — exist only on the native backend and are feature-gated accordingly; the read seam compiles on every backend regardless.

The wire contract

The wrapper layers speak JSON-RPC 2.0. The drsg serve backend, the MCP server, and the SDKs all use it, and the surface is described by an OpenRPC schema that is the single source of truth: the SDKs are generated from it, the server returns it from rpc.discover, and drift tests fail if the schema, the dispatch table, and the generated clients ever disagree. The change-feed subscription is a WebSocket extension of the same protocol.

Design documents

The detailed, per-layer design notes live under arch/ in the repository — the source of truth this chapter summarizes.