tidaldb/tidal-net/proto/wal_shipping.proto
jx12n 95461d3cf8 feat(m11): Raft leader election over WAL stream (m11p4)
Kind-3 term markers in the WAL stream, STREAM-relative vote frontiers,
heartbeat-only divergence detection + quarantine, and fenced promote.
Elections converge in 0.6–1.0s; zero acked-write loss across all kill points.
Closes G5 (leaderless recovery) from the v0.9 wave.
2026-06-11 23:30:24 -06:00

183 lines
7.0 KiB
Protocol Buffer

syntax = "proto3";
package tidal.replication.v1;
// Globally unique identifier for a WAL segment.
message WalSegmentId {
uint32 region_id = 1;
uint32 shard_id = 2;
uint64 seqno = 3;
}
// A WAL segment ready for shipping to a peer shard.
message ShipSegmentRequest {
WalSegmentId id = 1;
bytes payload = 2;
uint64 event_count = 3;
// The segment's authoritative last WAL sequence number, computed by the
// leader from the ORIGINAL (pre-community-overlay-filter) bytes. Lets the
// receiver advance its replication-lag leader high-water-mark even for an
// all-local segment that filters to an empty payload (obs-REPL-1). A 0 value
// (e.g. from an older sender that omits this field) means "unknown" and the
// receiver falls back to the per-batch boundaries it decodes.
uint64 leader_last_seq = 4;
// The shipping stream's baseline (m11p2): the WAL seqno at which this
// leader's stream STARTED. Non-zero only on catch-up stream chunks from a
// promoted leader; seqnos at or below it are pre-stream history the
// receiver jumps its frontier past instead of treating as a gap. 0 (the
// default, and what every live unary ship carries) means "stream from the
// beginning".
uint64 stream_baseline = 5;
// The sender's leadership term (m11p4 fencing). 0 = the topology era or a
// pre-m11p4 sender; a receiver at term >= 1 rejects term-0 traffic, and any
// receiver rejects a term below its own (FAILED_PRECONDITION) — a deposed
// leader's ships can never apply.
uint64 term = 6;
// The region claiming leadership of `term` (m11p4).
uint32 leader_region = 7;
}
// Response to a segment shipment.
message ShipSegmentResponse {
bool accepted = 1;
// The receiver's contiguous applied seqno for the request's source shard at
// acceptance time (m11p2): a monotonic hint the sender folds into its acked
// frontier so retries of already-applied data prune and heal needs no
// separate status fetch. 0 = unknown (older peer / no applied source wired).
uint64 applied_seqno = 2;
// The receiver's current term (m11p4): a value above the sender's term is
// the sender's step-down signal.
uint64 term = 3;
}
// Request to stream segments from a given sequence number.
message StreamRequest {
uint32 shard_id = 1;
uint64 from_seqno = 2;
// The puller's current term (m11p4). The source serves only when the terms
// match: a stale puller must rejoin first, and a stale SOURCE must step
// down rather than serve its possibly-divergent tail.
uint64 term = 3;
}
// Heartbeat request matching ControlPlane's ShardStats.
message HeartbeatRequest {
uint32 shard_id = 1;
uint32 region_id = 2;
uint64 entity_count = 3;
double signal_throughput_eps = 4;
uint64 disk_bytes = 5;
// Replication lag per peer region (region_id -> lag in events).
map<uint32, uint64> replication_lag = 6;
uint64 last_heartbeat_ns = 7;
// The sender's leadership term (m11p4): a leader heartbeat is the lease
// assertion + failure-detector input. 0 = the topology era / a non-leader
// health probe.
uint64 term = 8;
// The region asserting leadership of `term` (m11p4).
uint32 leader_region = 9;
// The asserted term's activation stream baseline (m11p4): immutable for
// the term; a joining follower jumps its applied frontier for the leader's
// stream to it (seqnos at or below are pre-stream history).
uint64 stream_baseline = 10;
// The leader's ELECTION-TIME log position (m11p4): the term and frontier
// of its log in the PREVIOUS stream's numbering — the same pair the vote
// restriction compares. A joining node is DIVERGENT iff its own position
// exceeds this lexicographically (it holds entries the new leadership's
// history does not subsume).
uint64 prev_log_term = 11;
uint64 prev_log_seq = 12;
}
// Heartbeat acknowledgement.
message HeartbeatResponse {
bool acknowledged = 1;
// The responder's current term (m11p4): above the sender's term = the
// sender's step-down signal.
uint64 term = 2;
// Whether the responder accepted the sender's leadership assertion
// (false = the sender's term is stale).
bool accepted = 3;
}
// A follower's self-report of its durable frontier (m11p3).
//
// Pushed by the receiver once per apply round — fully decoupled from ship
// acks, so the leader's quorum commit index stays fresh even when its
// outbound ships stall (gap-parked follower, quiet leader, pull catch-up).
message AppliedReport {
// The reporting node's shard id.
uint32 reporter_shard = 1;
// The stream's source shard (the leader being reported to).
uint32 source_shard = 2;
// The reporter's contiguous durably-applied seqno for that stream.
uint64 applied_seqno = 3;
// The reporter's current term (m11p4): the leader folds a report into its
// quorum commit index ONLY when this matches the index's activation term —
// a stale or cross-leadership report can never advance commitment.
uint64 reporter_term = 4;
}
// Applied-report acknowledgement.
message AppliedReportAck {
bool acknowledged = 1;
}
// A pre-vote or vote request (m11p4 leader election).
message VoteRequest {
// The term votes are requested for. For a pre-vote this is the PROPOSED
// term (candidate's current + 1) — nothing has been bumped.
uint64 term = 1;
uint32 candidate_region = 2;
// The candidate's log position for the up-to-date restriction,
// compared lexicographically: (last_log_term, last_log_seq).
uint64 last_log_term = 3;
uint64 last_log_seq = 4;
// Pre-vote probe: changes no voter state, never inflates terms.
bool prevote = 5;
// Leadership-transfer election (`TimeoutNow`): voters skip the
// leader-freshness refusal — the current leader sanctioned this.
bool transfer = 6;
}
// A vote (or pre-vote) reply.
message VoteResponse {
// The voter's current term (above the candidate's = step-down signal).
uint64 term = 1;
bool granted = 2;
}
// The current leader tells `target` to start an immediate transfer election
// (m11p4 fenced promote).
message TimeoutNowRequest {
// The sanctioning leader's current term.
uint64 term = 1;
uint32 leader_region = 2;
}
message TimeoutNowResponse {
// Whether the target started an election.
bool accepted = 1;
}
// WAL segment shipping service between tidalDB shards.
service WalShipping {
// Ship a single WAL segment to a peer shard (unary).
rpc ShipSegment(ShipSegmentRequest) returns (ShipSegmentResponse);
// Stream WAL segments from a given sequence number (server-streaming).
rpc StreamSegments(StreamRequest) returns (stream ShipSegmentRequest);
// Periodic health check for the ControlPlane; with m11p4, the leader's
// lease assertion and the failure detector's input.
rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse);
// Follower -> leader durable-frontier report (m11p3 quorum acks).
rpc ReportApplied(AppliedReport) returns (AppliedReportAck);
// Pre-vote / vote (m11p4 leader election).
rpc RequestVote(VoteRequest) returns (VoteResponse);
// Fenced leadership transfer: start an immediate election (m11p4).
rpc TimeoutNow(TimeoutNowRequest) returns (TimeoutNowResponse);
}