Skip to content

Wire Protocol Versions

DistributedReferenceWire Protocol
Beta Works · surface still evolving · a peer on a different version refuses the frame at the header; every layout below is pinned by const assertions and a layout test in the wire crate

A new discriminant, a re-carved field, or a reshaped payload under an unchanged version is the exact shape of the bug that once booted a database empty and reported success: an older peer reads the bytes plausibly and wrong. So the version is bumped for every such change, and the layout test asserts the current number with the reason in its message. Peers compare versions in the header before anything else.

The header (64 bytes, #[repr(C, align(8))])

Section titled “The header (64 bytes, #[repr(C, align(8))])”
OffsetFieldNotes
0magic: [u8; 4]AEWR
4version: u1617
6message_type: u16raw discriminant; decoded, never transmuted
8target_cluster_hash: u64cluster_hash(name) — a format-stable hash both processes compute with the same code
16instruction_count: u3264-bit words following the header
20constants_len: u32payload bytes following the instructions
24header_checksum: u32FNV-1a over the header; setters re-seal
28_pad: u32zero
32manifest_epoch: u64fencing token; a worker refuses an epoch older than it has seen
40txn_id: u64transaction frames only (v10)
48container_id: u64which container a result chunk indexes (v12)
56reserved: [u8; 8]zero

The frame’s total length is 64 + instruction_count × 8 + constants_len. No byte is compiler padding, so the header is cast to and from bytes directly.

CodeNameDirectionSince
0x01EXEC_BYTECODE_REQorchestrator → worker
0x02RESULT_STREAM_CHUNKworker → orchestrator
0x04EXEC_ABORTworker → orchestrator
0x05FETCH_PROJECTION_REQorchestrator → worker
0x06FETCH_STREAM_CHUNKworker → orchestrator
0x07 / 0x08HEARTBEAT_PING / HEARTBEAT_PONGbothv3
0x09–0x0C, 0x10MIGRATE_*orchestrator → workerv3
0x0C–0x0FVALIDATE_TXN_*, COMMIT_TXN, ABORT_TXNboth
0x11WORKER_READYworker → orchestratorv6
0x12 / 0x13CONTAINER_RETIRED / TOPOLOGY_DELTAorchestrator
0x14EXEC_SUCCESSworker → orchestratorv11
0x15RESULT_AGGREGATE_CHUNK (terminal)worker → orchestratorv13
0x16RESULT_MATERIALIZE_CHUNKworker → orchestratorv13
0x17JOIN_STREAM_CHUNKorchestrator → next stagev13
0x18HASH_TABLE_LOADorchestrator → workerv14 (payload reshaped v15, v16)
0x19RESULT_GROUPED_AGG_CHUNKworker → orchestratorv14
0x1ASEGMENT_FETCH_REQorchestrator → primaryv17
0x1BSEGMENT_STREAM_CHUNKprimary → orchestrator → shadowv17
0x1CSNAPSHOT_REQorchestrator → workerv19
0x1DEXEC_DELETE_REQorchestrator → workerv20
0x1EEXEC_UPDATE_READ_REQorchestrator → workerv21
0x1FWRITE_CONFLICTworker → orchestratorv24
0x20 / 0x21SYNC_TXID_REQ / SYNC_TXID_RESPbothv25
0x22INDEX_BUILD_REQorchestrator → workerv26
0x23STRING_ARENA_CHUNKorchestrator → workerv27
0x24 / 0x25SHUFFLE_DATA_REQ / SHUFFLE_EOFworker → worker (via the orchestrator’s router)v29
0x26JOIN_BROADCAST_REQorchestrator → workerv31

All records are #[repr(C)] with 8-byte fields and no padding, so a payload is a slice cast, checked for alignment and exact length, never a parse.

FramePayload
EXEC_BYTECODE_REQinstruction_count 64-bit instructions, then the constants pool; the first 8 bytes of the pool are the target table id.
RESULT_STREAM_CHUNKKeyOffset { sort_key: u64, row_offset: u64 } × n, at most 4,092 (fills the 64 KiB ring).
RESULT_MATERIALIZE_CHUNKslot_count: u32, row_count: u32; slot-major values (row_count × 8 B per slot); per-slot validity bitmaps (LSB-first, padded to 8 B); optionally row_count row offsets.
RESULT_AGGREGATE_CHUNK{ value: i64, valid: u64 } × accumulators, program order. Terminal: no EXEC_SUCCESS follows.
RESULT_GROUPED_AGG_CHUNKGroupSlot × n (64 B each: key, used, acc[4], valid[4], pad), at most 1,023.
HASH_TABLE_LOADdir_slots: u64, record_count: u64, stride: u64; directory of KeyOffset { composite_key, start << 32 | count } (power of two, row_offset == u64::MAX free); records of stride words: [row_offset, valid_mask, cols…].
JOIN_STREAM_CHUNKMatchedPair { offset_a: u64, offset_b: u64 } × n; constants_len = 16 × n.
HEARTBEAT_PING/PONGHeartbeat { timestamp_us, worker_id, data_end_offset } (24 B). A worker’s PONG carries its first container’s committed end and id.
SEGMENT_FETCH_REQfrom: u64, len: u64; the container in the header (0 = first).
SEGMENT_STREAM_CHUNKfrom: u64, then the bytes; container in the header.
VersionChangeWhy a bump
v10txn_id carved from reserved at offset 40transaction frames used to overload the routing hash
v11EXEC_SUCCESSnew discriminant
v12container_id at offset 48; reserved 16 → 8a worker walks every container; an offset needs its container
v13aggregate, materialize and join-stream chunksthree new discriminants
v14HASH_TABLE_LOAD, RESULT_GROUPED_AGG_CHUNKtwo new discriminants
v15join table becomes 1:N (directory + matches)a v14 peer would read the preamble as two slots and every probe would miss
v16join records become fat ([offset, valid_mask, cols…], stride in the preamble); materialize chunks may carry a trailing offset columnpayload reshaped
v17SEGMENT_FETCH_REQ, SEGMENT_STREAM_CHUNKthe shadow tail stream
v18tenant_id at header offset 56every frame names its tenant; 0 is the system tenant, which is what an older peer writes there
v19LOAD_NULL; SNAPSHOT_REQ; a snapshot word in exec and segment framesvirtual NULL columns; tenant snapshots and restore routing
v20EXEC_DELETE_REQnew discriminant
v21EXEC_UPDATE_READ_REQnew discriminant
v22transaction window words in the exec constantsa worker decides row visibility from the frame alone
v23abort_mask worda reaped transaction stays invisible after the window moves past it
v24WRITE_CONFLICTnew discriminant
v25SYNC_TXID_REQ / SYNC_TXID_RESPorchestrator boot recovery
v26INDEX_BUILD_REQnew discriminant
v27STRING_ARENA_CHUNK; a string-literal pool after the fixed constantsVARCHAR: an older worker would ignore the pool and answer every string compare false, so this is a refusal instead
v28unassigned
v29SHUFFLE_DATA_REQ / SHUFFLE_EOFradix hash-shuffle joins between workers
v30the heartbeat reply echoes the ping; container id in the headerbefore this a healthy worker was scored dead and every query refused — found by the first real two-process boot
v31JOIN_BROADCAST_REQa flat join arena as an alternative broadcast build side

splitmix64, mix and composite_key live in the wire crate so that the orchestrator building a join table and the worker probing it link the same code — the same reason cluster_hash lives there. A per-process seeded hasher would make every probe miss and read as a join bug rather than a hashing one.