genuinebasil.dev/Projects/Cairn
DepthServicesCommsLayersSurfacesDecisions
LAYER 2 / 7
Rust/distributed query engine/GCP

Watch a query
think.

A SQL engine split across services, built so every stage is legible while it runs — the parse tree, the join-order DP table, the plan fragments fanning out to workers, and inside each worker the B+Tree descent, the buffer-pool frames, the lock waits and the WAL records.

Go to cairn Write-ups
Language  Rust Shape  7 layers, MVP-first Transport  gRPC (tonic) Infra  Cloud Run / GKE · Terraform Surfaces  18 planned
ONE QUERY, TRACED7 STAGES
SELECT … JOIN … WHERE lexer → parser → AST join-order DPbest[S] over subsets plan fragments → dispatch workerb+tree · poollocks · wal workerb+tree · poollocks · wal workerb+tree · poollocks · wal exchange / shuffle · raft metadata
ENGINEERING DEPTH

Four algorithms carry this project. Each one is drawn on screen as it runs, which is only possible because span emission was built into layer 1 rather than retrofitted after.

JOIN-ORDER DP best[S]

Exhaustive DP over relation subsets with a real cost model from catalog statistics — then a GEQO-style genetic fallback once N makes the table stop fitting. The cliff is shown, not hidden.

B+TREE & LATCH CRABBING

A reader releases the parent latch the moment the child is latched, so descents overlap instead of serialising. The hardest part of a B+Tree to get right, stepped one frame at a time.

RAFT & THE WAIT-FOR GRAPH

Hand-rolled Raft for coordinator HA and catalog replication, plus a lock wait-for graph with cycle detection — a deadlock is a cycle you can literally watch close.

ARIES RECOVERY analysis redo undo crash

Write-ahead logging with the three ARIES passes, and a crash you can inject yourself at any point on the timeline to watch analysis, redo and undo do their work.

BUFFER POOL LRU-K ARC evicthit

LRU-K, ARC and Clock run against the same access trace side by side, so the difference between replacement policies is visible rather than asserted.

EXCHANGE & SHUFFLE

The shuffle boundary is where a distributed engine stops resembling a single-node one, so exchange gets first-class treatment in the trace — including backpressure in both directions.

SERVICES — BOUNDED CONTEXTS proposed — layer 6 decomposition
gateway
owns: session, SQL intake, trace fan-in
REST edgestreaming resultsauth/authz

The single public entry. Accepts a statement, opens a trace, and streams span events back to the browser while the query is still running — so the UI renders progress rather than a final result blob.

coordinator
owns: parse, plan, fragment, dispatch
join-order DPpushdown rewriteslocality-aware scheduling

Lexer, recursive-descent parser, logical plan, then the physical plan and its fragment placement. The DP table is emitted as trace data, which is what makes the optimiser view exact rather than illustrative.

worker
owns: storage, execution, txn state
B+TreeLRU-K / ARCARIES WALMVCC

Where the database-internals work lives: heap and columnar page layout, a buffer pool with a real replacement policy, B+Tree indexes, the lock manager, and a Volcano iterator that later goes vectorised.

catalog
owns: schema, statistics, FDs · Cloud SQL
histogramscardinality est.read-mostly cache

Its own gRPC service backed by Cloud SQL. Bad statistics are the usual reason a plan is bad, so the UI shows estimate against actual side by side for every operator.

metadata (raft)
owns: cluster state, leases, shard ownership
Raftconsistent hashing2PC coordinator

The only strongly-consistent store in the system. Everything else may be stale; membership and shard ownership may not. Elections and log replication are drawn live in the distributed view.

specialised workers
owns: vector & graph query paths — cross-cutting
HNSW / IVFproperty graphBFS / PageRank

Vector and graph workers federated behind the coordinator through the same table interface, so one query can span row, column, vector and graph storage without the planner special-casing any of them.

SERVICE COMMUNICATION
FROM → TO TRANSPORT SHAPE WHY THIS AND NOT THE OTHER
browser → gatewaySSEserver streamspans must arrive while the query still runs; polling would miss the thing being visualised
gateway → coordinatorgRPCunaryone statement, one plan — nothing worth streaming
coordinator → cataloggRPCunary + cacheread-mostly; stale stats are survivable, a planning stall is not
coordinator → workergRPCserver streamresult batches flow back as produced, with backpressure on the channel
worker ↔ workergRPCbidi streamexchange/shuffle is symmetric and needs backpressure in both directions
any → metadataRaft RPCquorumthe one place that must be linearizable — kept off the query hot path deliberately
THE SEVEN LAYERS

Two motions, not one sequence. Every layer gets a thin working MVP first; once all seven exist they wire into a walking skeleton on GCP, and only then does each layer get its own expansion track — picked in any order, re-integrated as it lands. There is no stage six where depth finally happens.

# LAYER MVP SCOPE EXPANSION TRACKS STATE
01SQL frontendHand-written lexer + recursive-descent parser for SELECT … WHERE → ASTJoins, subqueries, CTEs; semantic analysis against the catalog; type checking & coercion; parse-error recoveryMVP DONE
02Query plannerAST → logical plan, one fixed rule-based physical planDP join-order optimiser over best[S] with a cost model; predicate/projection pushdown; GEQO for large NBUILDING
03Execution engineVolcano iterator model: scan, filter, projectHash & sort-merge join, aggregate, sort; vectorised batch execution; the exchange operator for cross-worker shuffleQUEUED
04Storage enginePlain heap file on local disk, read-through, no buffer poolBuffer pool with LRU-K/ARC; B+Tree indexes; WAL + ARIES recovery; MVCC & lock manager; hand-rolled columnar pages; an LSM backend alternativeQUEUED
05Distributed storageTwo workers, static hash partitioning, no replicationConsistent hashing; real exchange/shuffle for distributed joins; hand-rolled Raft for coordinator HA; 2PC across shards; locality-aware schedulingQUEUED
06Service layerGateway → coordinator → workers over gRPC; catalog as its own service on Cloud SQLStreaming result sets; coordinator↔worker backpressure; API versioning; auth/authz at the gatewayQUEUED
07Cloud & infraContainers on Cloud Run or GKE, Terraform for GCP resources, one Cloud Build pipelineFull CI/CD on merge; VPC + private networking; autoscaling; Cloud Monitoring/Logging/Trace on the trace contract; chaos testing — kill a worker mid-queryQUEUED
GCP SURFACE·Cloud Run / GKE·Cloud Build·Artifact Registry·Terraform·Cloud SQL·Cloud Storage·VPC·Load Balancing·IAM·Secret Manager·Pub/Sub·Cloud Trace·
GCP SURFACE·Cloud Run / GKE·Cloud Build·Artifact Registry·Terraform·Cloud SQL·Cloud Storage·VPC·Load Balancing·IAM·Secret Manager·Pub/Sub·Cloud Trace·
THE SURFACES — 18 PLANNED

Every screen has an owning layer and a status. Live means it renders real data from a running service; mockup means it is a design reference for a layer that does not exist yet. If a screen cannot be placed in this table, either the layer list is short or the screen is wrong.

waypointsFLAGSHIP
One query traced end to end across every service
layers 1–6
console
SQL REPL, results grid, EXPLAIN jumps into waypoints
layers 1, 6
distributed
2PC handshake, Raft election, hashing ring, shuffle
layer 5
trace
gRPC span waterfall, service topology map
layers 6, 7
buffer-pool lab
LRU-K vs ARC vs Clock on one access trace
layer 4
btree lab
Split/merge stepped one op at a time; latch crabbing
layer 4
optimizer lab
DP table at N tables; exhaustive vs greedy vs GEQO
layer 2
concurrency lab
2PL/MVCC/T-O simulator, wait-for graph, deadlock
layer 4
recovery lab
WAL timeline, crash injection, ARIES three passes
layer 4
columnar lab
Row vs column layout, vectorised batches, SIMD lanes
layers 3, 4
storage engines
B-Tree vs LSM — memtable, levels, compaction
layer 4
vector lab
HNSW multi-layer graph, greedy beam search
cross-cutting
graphdb lab
Property graph, BFS wavefront, PageRank convergence
cross-cutting
federation
Routing graph across row/column/vector/graph workers
cross-cutting
catalog
Tables, columns, indexes, stats, FD dependency graph
layer 6
admin
Hit ratio, eviction rate, active txns, per-worker usage
layers 4, 7
progress
The seven layers as a literal cairn, stone by stone
all layers
home
The pitch — seven layers, skeleton, per-layer depth
all layers
DECISIONS WORTH DISCUSSING
sequencing

MVP every layer thin, then combine, then deepen — rather than finishing layer 1 before starting layer 2. A linear sequence would mean the distributed and cloud work never gets touched until the single-node engine is “done”, which is to say never. The seams get proven while they are still cheap to move.

tracing

Span emission was built in layer 1, not retrofitted. Every later layer inherits the visualisation for free instead of needing a bespoke debug view, and the trace contract in ARCHITECTURE.md is what Cloud Trace later plugs into unchanged.

optimiser

Exhaustive join-order DP is O(3ⁿ) and stops being viable around a dozen relations. Rather than hide the cliff, the optimiser lab lets you raise N until the DP table visibly stops fitting and the genetic fallback takes over. The failure mode is the lesson.

consensus

Raft holds cluster metadata only, never query data. Putting shard ownership behind consensus and leaving everything else eventually consistent keeps the quorum path off the hot path — a query never waits on an election unless membership actually changed.

non-goals

No Postgres wire-protocol compatibility, no WASM, no multi-tenant SaaS framing. Cairn speaks its own SQL surface over its own API, and nothing runs client-side — the whole point is real server processes with real network hops between them.

design

Colour encodes engine state and nothing else — amber is running, teal is a hit or a commit, red is a real failure, violet is blocked on a lock or a quorum. A reader who learns that amber also means “the storage layer” has learned that amber means nothing.

live →write-ups© 2026 genuinebasil.dev│
← marginalblog →