Skip to content

Commit 427c5bf

Browse files
committed
fix(control): stamp replicated writes with a follower-local write-version LSN
Followers previously built replicated-write requests with wal_lsn always None, which starved OCC read-set validation of a write-version LSN on replica apply. Derive it from the Raft log entry index instead, since WAL LSN allocation is leader-local and not carried on ReplicatedWrite, while the log index is monotonic per core and already consistent with the local note_write_lsn feed used for read_lsn.
1 parent f66051c commit 427c5bf

1 file changed

Lines changed: 42 additions & 2 deletions

File tree

nodedb/src/control/distributed_applier/apply_loop.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::control::array_sync::raft_apply::{
1717
use crate::control::cluster::calvin::ReadResultEvent;
1818
use crate::control::state::SharedState;
1919
use crate::control::wal_replication::{ReplicatedEntry, ReplicatedWrite, from_replicated_entry};
20-
use crate::types::{DatabaseId, ReadConsistency, TenantId, TraceId, VShardId};
20+
use crate::types::{DatabaseId, Lsn, ReadConsistency, TenantId, TraceId, VShardId};
2121
use nodedb_physical::physical_plan::ArrayOp;
2222

2323
use super::applier::ApplyBatch;
@@ -49,6 +49,7 @@ fn build_request(
4949
plan: PhysicalPlan,
5050
resolved_now_ms: Option<u64>,
5151
event_source: crate::event::EventSource,
52+
record_lsn: Option<Lsn>,
5253
) -> Request {
5354
Request {
5455
request_id: state.next_request_id(),
@@ -66,14 +67,27 @@ fn build_request(
6667
user_id: None,
6768
statement_digest: None,
6869
txn_id: None,
69-
wal_lsn: None,
70+
wal_lsn: record_lsn,
7071
resolved_now_ms,
7172
admission: crate::bridge::envelope::Admission::Exempt(
7273
crate::bridge::envelope::ExemptReason::AlreadyOrdered,
7374
),
7475
}
7576
}
7677

78+
/// The write-version LSN a follower stamps for a committed entry: the Raft log
79+
/// index (1-based, monotonic per core, identical across replicas). The leader's
80+
/// WAL LSN is intentionally not carried on the wire (`ReplicatedWrite` omits
81+
/// it — followers allocate their own WAL LSN at apply time); OCC validation is
82+
/// shard-local (both `read_lsn` and the recorded write-version LSN come from
83+
/// the same `note_write_lsn` feed on a given replica), so a consistent local
84+
/// monotonic LSN is the correct source, and `entry.index` is monotonic per
85+
/// core (one vShard homes to one Raft group). `Lsn::ZERO` stays the
86+
/// "never-written" sentinel.
87+
fn record_lsn_for(entry: &nodedb_raft::message::LogEntry) -> Option<Lsn> {
88+
(entry.index != 0).then(|| Lsn::new(entry.index))
89+
}
90+
7791
/// Dispatch a request through the SPSC bridge and await its response.
7892
///
7993
/// Returns:
@@ -323,6 +337,7 @@ pub async fn run_apply_loop(
323337
plan,
324338
resolved_now_ms,
325339
crate::event::EventSource::User,
340+
record_lsn_for(entry),
326341
);
327342

328343
let result = match dispatch_and_await(&state, request).await {
@@ -386,3 +401,28 @@ fn maybe_compact_log(state: &Arc<SharedState>, group_id: u64, applied_index: u64
386401
}
387402
}
388403
}
404+
405+
#[cfg(test)]
406+
mod tests {
407+
use super::*;
408+
409+
fn log_entry(index: u64) -> nodedb_raft::message::LogEntry {
410+
nodedb_raft::message::LogEntry {
411+
term: 1,
412+
index,
413+
data: Vec::new(),
414+
}
415+
}
416+
417+
#[test]
418+
fn record_lsn_for_uses_entry_index() {
419+
let entry = log_entry(7);
420+
assert_eq!(record_lsn_for(&entry), Some(Lsn::new(7)));
421+
}
422+
423+
#[test]
424+
fn record_lsn_for_none_when_index_zero() {
425+
let entry = log_entry(0);
426+
assert_eq!(record_lsn_for(&entry), None);
427+
}
428+
}

0 commit comments

Comments
 (0)