Expand description
Replication — synchronous input-stream streaming from primary to replica.
The primary’s JournalStage encodes each InputSlot it just durably
journaled into a wire-ready InputBatch frame in the replication ring
(separately from the journal-codec bytes it writes to disk). The
ReplicationSender thread forwards those frames as-is over TCP/DPDK
— no decode + re-encode on the hot path. The replica decodes the
frames straight back into InputSlots, publishes them to its local
input disruptor with the primary’s pre-assigned sequences and
timestamps, and the replica’s JournalStage re-encodes them through
its own writer for byte-exact-on-replay durability.
§Wire Protocol
Length-prefixed frames, little-endian, over a dedicated TCP connection
(or DPDK pipe). The full InputBatch payload layout lives in
melin_transport_core::replication::protocol /
melin_transport_core::replication_wire.
§Auth (before handshake)
- Challenge (Primary → Replica):
[len:u32][0x03][nonce:[u8;32]] - ChallengeResponse (Replica → Primary):
[len:u32][0x04][signature:[u8;64]][pubkey:[u8;32]] - AuthOk (Primary → Replica):
[len:u32][0x05] - AuthFailed (Primary → Replica):
[len:u32][0x06]
§Replica → Primary
- Handshake:
[len:u32][0x01][last_sequence:u64][chain_hash:[u8;32]] - Ack:
[len:u32][0x02][acked_sequence:u64][in_memory_sequence:u64]
§Primary → Replica
- StreamStart:
[len:u32][0x10][start_sequence:u64][segment_start_sequence:u64][anchor_hash:[u8;32]][epoch:u64][genesis_known:u8][genesis_entries:u64]— the segment header identity a fresh replica creates its journal with (lineage origin for full catch-up, the seeded segment’s identity after a snapshot transfer), and the lineage’s genesis length, which a fresh replica records and one with a journal checks against its own - NeedSnapshot:
[len:u32][0x11] - HashMismatch:
[len:u32][0x12]— divergent replica journal; the replica archives its lineage, then the snapshot flow follows - SnapshotBegin:
[len:u32][0x13][snapshot_len:u64][snap_sequence:u64][snap_chain_hash:[u8;32]] - SnapshotChunk:
[len:u32][0x14][data...] - SnapshotEnd:
[len:u32][0x15][crc32c:u32] - Rotate:
[len:u32][0x16][boundary_seq:u64][tail_hash:[u8;32]]— primary-driven rotation, verified + adopted by the replica - ChainCheck:
[len:u32][0x17][sequence:u64][chain_hash:[u8;32]]— periodic live-stream chain validation - SegmentSeedBegin:
[len:u32][0x18][seed_len:u64]— raw byte prefix of the primary’s segment containing the snapshot boundary; body rides SnapshotChunk frames, ends with SnapshotEnd - InputBatch:
[len:u32][0x21][count:u16][slot...]— seetransport-core::replication_wirefor the per-slot layout - Heartbeat:
[len:u32][0x30][sequence:u64]
§Limitations
- Dual replication (up to 2 replicas in parallel)
See docs/replication.md for the full design document and limitation details.
Structs§
- Replica
Control Plane - The handles a replica’s receive loop shares with the rest of the node — the admin endpoint and the control-plane raft driver. Bundled (rather than five positional parameters) because they always travel together through the kernel-TCP and DPDK receiver signatures, and a transposition between same-typed flags is exactly the bug a bundle prevents.
- Replication
Listener - Owned state for the replication sender thread.
A TCP listener guaranteed non-blocking: the only constructor sets
the flag, so the guarantee is carried by the type.
run_sender’s accept loop polls the shutdown flag between accepts — handed a blocking listener it would park insideaccept()and only observe shutdown when the next replica happened to dial in. This newtype makes that state unrepresentable instead of documenting it away. - Replication
Metrics - Per-slot replication metrics exposed via the health endpoint.
- Sender
Functions§
- run_
receiver - run_
sender - Run the replication sender. Listens for replica connections, streams journal data batches, processes acks, and updates the replication cursor.
Type Aliases§
- Receiver
Result - Outcome of
run_receiver:None= clean shutdown,Some= promotion.