MCPcopy Create free account
hub / github.com/NodeDB-Lab/nodedb / run_catchup_cycle

Function run_catchup_cycle

nodedb/src/control/wal_catchup.rs:86–182  ·  view source on GitHub ↗

Run one catch-up cycle: read new WAL records, dispatch timeseries batches. Uses paginated mmap replay to bound memory. Passes WAL LSNs to the Data Plane for deduplication (records already ingested are skipped).

(shared: &SharedState)

Source from the content-addressed store, hash-verified

84/// Uses paginated mmap replay to bound memory. Passes WAL LSNs to the
85/// Data Plane for deduplication (records already ingested are skipped).
86async fn run_catchup_cycle(shared: &SharedState) -> CatchupResult {
87 // Backpressure gate: don't compete with live ingest for SPSC slots.
88 if shared.max_spsc_utilization() > 50 {
89 return CatchupResult::Backpressured;
90 }
91
92 let catchup_lsn = shared.wal_catchup_lsn.load(Ordering::Acquire);
93
94 // Read at most PAGE_SIZE WAL records via sequential I/O (bounded memory).
95 // Uses sequential I/O (not mmap) to safely read the active segment
96 // even when written via O_DIRECT which bypasses the page cache.
97 let (records, has_more) = match shared
98 .wal
99 .replay_from_limit(Lsn::new(catchup_lsn + 1), PAGE_SIZE)
100 {
101 Ok(r) => r,
102 Err(e) => {
103 debug!(error = %e, lsn = catchup_lsn, "WAL catch-up replay failed");
104 return CatchupResult::Idle;
105 }
106 };
107
108 if records.is_empty() {
109 return CatchupResult::Idle;
110 }
111
112 let mut dispatched = 0usize;
113 let mut max_lsn = catchup_lsn;
114
115 for record in &records {
116 // Only process TimeseriesBatch records.
117 let record_type = nodedb_wal::record::RecordType::from_raw(record.logical_record_type());
118 if record_type != Some(nodedb_wal::record::RecordType::TimeseriesBatch) {
119 max_lsn = max_lsn.max(record.header.lsn);
120 continue;
121 }
122
123 // Deserialize WAL payload: (collection, raw_ilp_bytes).
124 let Ok((collection, payload)): Result<(String, Vec<u8>), _> =
125 zerompk::from_msgpack(&record.payload)
126 else {
127 max_lsn = max_lsn.max(record.header.lsn);
128 continue;
129 };
130
131 let tenant_id = TenantId::new(record.header.tenant_id);
132 let vshard_id = VShardId::new(record.header.vshard_id);
133
134 let plan = PhysicalPlan::Timeseries(TimeseriesOp::Ingest {
135 collection,
136 payload,
137 format: "ilp".to_string(),
138 wal_lsn: Some(record.header.lsn),
139 // Re-derived on the engine side during apply (record carries
140 // raw ILP — row identities are reconstructed from the wire).
141 surrogates: Vec::new(),
142 });
143

Callers

nothing calls this directly

Calls 7

max_spsc_utilizationMethod · 0.80
to_stringMethod · 0.80
dispatch_to_data_planeFunction · 0.50
loadMethod · 0.45
replay_from_limitMethod · 0.45
is_emptyMethod · 0.45
logical_record_typeMethod · 0.45

Tested by

no test coverage detected