1use {
6 super::{
7 Engine, KraftConfig, KraftControlState, KraftController, METADATA_LOG_CLEAN_INTERVAL,
8 PendingDowngradeSnapshot,
9 activation::{Activation, check_bootstrap_records},
10 applied_image::AppliedImage,
11 checkpoint::{BOOTSTRAP_SNAPSHOT_ID, latest_checkpoint_id, load_latest_checkpoint},
12 quorum_state_file::load_quorum_state,
13 records::features_epoch_after_snapshot,
14 recovery::{
15 CommittedReplay, control_state_at, replay_committed_with_timestamp,
16 replay_control_records, with_uncommitted_feature_levels,
17 },
18 timing::initial_election_at,
19 },
20 crate::{
21 config::{
22 ControllerFetchMissLimit, MetadataLogConfig, MetadataRaftCommandQueueCapacity,
23 MetadataRaftFetchMax,
24 },
25 error::RaftError,
26 event_queue::{EventQueueMeter, EventQueueRecorder},
27 kraft::{
28 core::{QuorumStateMachine, QuorumTimeouts},
29 log::KraftLog,
30 snapshot_fetch::MetadataSnapshotFetchMax,
31 transport::{CommandSender, PeerSender},
32 types::{NodeId, QuorumState},
33 },
34 snapshot::SnapshotReader,
35 },
36 krabka_ids::Offset,
37 krabka_metadata::{MetadataImage, MetadataRecord, VotersRecord, voters::VoterSet},
38 krabka_units::{
39 fmt::Human as _,
40 prelude::{ByteSize, Time},
41 },
42 std::{collections::BTreeMap, fs, path::PathBuf, sync::Arc, time::SystemTime},
43 tokio::{
44 sync::{mpsc, watch},
45 time::{Duration, Instant},
46 },
47 uuid::Uuid,
48};
49
50struct RecoveredMetadata {
52 image: MetadataImage,
53 last_snapshot_end_offset: Offset,
54 last_snapshot_timestamp_ms: i64,
55 last_applied_record_timestamp_ms: i64,
56 downgrade_snapshot_pending: Option<PendingDowngradeSnapshot>,
57 features_epoch: i64,
60}
61
62impl KraftController {
63 pub fn spawn(config: KraftConfig, log: KraftLog, data_dir: PathBuf) -> Result<Self, RaftError> {
78 let cluster_id = config.cluster_id;
79 let image = MetadataImage::new(cluster_id);
80 Self::spawn_with_image(config, log, data_dir, RecoveredMetadata {
81 image,
82 last_snapshot_end_offset: Offset(0),
83 last_snapshot_timestamp_ms: 0,
84 last_applied_record_timestamp_ms: 0,
85 downgrade_snapshot_pending: None,
86 features_epoch: -1,
87 })
88 }
89
90 fn spawn_with_image(
94 config: KraftConfig,
95 log: KraftLog,
96 data_dir: PathBuf,
97 recovered: RecoveredMetadata,
98 ) -> Result<Self, RaftError> {
99 let RecoveredMetadata {
100 image,
101 last_snapshot_end_offset,
102 last_snapshot_timestamp_ms,
103 last_applied_record_timestamp_ms,
104 downgrade_snapshot_pending,
105 features_epoch,
106 } = recovered;
107 let KraftConfig {
108 me,
109 directory_id,
110 mut initial_state,
111 timeouts,
112 heartbeat_interval,
113 controller_fetch_miss_limit,
114 metadata_raft_command_queue_capacity,
115 metadata_raft_fetch_max,
116 peers,
117 snapshot_interval_records,
118 max_bytes_between_snapshots,
119 max_snapshot_interval,
120 metadata_snapshot_fetch_max,
121 metadata_log,
122 activation,
123 event_queue_recorder,
124 ..
125 } = config;
126
127 replay_control_records(&log, &mut initial_state, metadata_raft_fetch_max)?;
131 let controls =
132 KraftControlState::new(initial_state.voters.clone(), initial_state.kraft_version);
133 let core = QuorumStateMachine::new(me, initial_state, timeouts);
134 let initial_leader = core.quorum_state().leader_id;
135 let initial_was_leader = core.role().is_leader();
136 let initial_epoch = core.quorum_state().leader_epoch;
137
138 let mut image = image;
145 image.apply(&MetadataRecord::V1KRaftVersion(
146 krabka_metadata::KRaftVersionRecord {
147 kraft_version: core.quorum_state().kraft_version,
148 },
149 ));
150 image.apply(&MetadataRecord::V1Voters(VotersRecord {
151 voters: core.quorum_state().voters.clone(),
152 }));
153
154 let (image, image_rx) = AppliedImage::new(image);
155 let (features_epoch_tx, features_epoch_rx) = watch::channel(features_epoch);
156 let (leader_tx, leader_rx) = watch::channel(initial_leader);
157 let initial_hwm = log.hwm().0;
161 let initial_snapshot = super::queries::initial_quorum_snapshot(
162 &core,
163 &log,
164 initial_hwm,
165 last_snapshot_timestamp_ms,
166 );
167 let (quorum_tx, quorum_rx) = watch::channel(initial_snapshot);
168 let (fault_tx, fault_rx) = watch::channel(None);
169 let (cmd_tx, cmd_rx) = CommandSender::channel(metadata_raft_command_queue_capacity.get());
170
171 let clock_base = Instant::now();
172 let election_at = initial_election_at(
175 &core,
176 initial_leader,
177 clock_base,
178 me,
179 initial_epoch,
180 timeouts.election,
181 );
182
183 let engine_peers = Arc::clone(&peers);
184 let mut engine = Engine {
185 me,
186 core,
187 log,
188 image,
189 peers,
190 features_epoch,
191 features_epoch_tx,
192 leader_tx,
193 quorum_tx,
194 cmd_tx: cmd_tx.clone(),
195 event_queue: EventQueueMeter::new(event_queue_recorder),
196 turn: None,
197 fault_tx,
198 data_dir,
199 clock_base,
200 timeouts,
201 heartbeat_interval,
202 controller_fetch_miss_limit,
203 metadata_raft_fetch_max,
204 election_at,
205 fetch_at: None,
206 check_quorum_at: None,
207 fetch_misses: 0,
208 discovery_attempts: 0,
209 commit_waiters: Vec::new(),
210 was_leader: initial_was_leader,
211 held_epoch: initial_epoch,
212 registration_writes: BTreeMap::new(),
213 snapshot_interval_records,
214 max_bytes_between_snapshots,
215 max_snapshot_interval,
216 metadata_snapshot_fetch_max,
217 metadata_log,
218 noop_at: None,
219 clean_at: clock_base + METADATA_LOG_CLEAN_INTERVAL,
220 last_snapshot_end_offset,
221 last_snapshot_timestamp_ms,
222 last_applied_record_timestamp_ms,
223 pending_record_processing_time_us_total: 0,
224 pending_record_processing_time_samples: 0,
225 pending_record_processing_time_us_max: 0,
226 last_snapshot_at_ms: 0,
227 bytes_since_snapshot: 0,
228 snapshot_job: None,
229 snapshot_write_delay: Duration::ZERO,
230 downgrade_snapshot_pending,
231 #[cfg(test)]
232 downgrade_snapshot_failures_remaining: 0,
233 snapshot_fetch: None,
234 installed_snapshot_epoch: None,
235 controls,
236 directory_id,
237 observers: BTreeMap::new(),
238 fetch_purgatory: Vec::new(),
239 wall_clock_base: SystemTime::now(),
240 leader_reported_hwm: initial_hwm,
241 pending_reconfig: None,
242 activation,
243 activation_fault: None,
244 replay_fault: None,
245 };
246
247 while engine.downgrade_snapshot_pending.is_some() {
251 engine.write_downgrade_snapshot_and_prune()?;
252 }
253
254 tokio::spawn(engine.run(cmd_rx));
255
256 Ok(Self {
257 cmd_tx,
258 image_rx,
259 features_epoch_rx,
260 fault_rx,
261 leader_rx,
262 quorum_rx,
263 peers: engine_peers,
264 me,
265 registration_admission: Arc::default(),
266 })
267 }
268
269 #[tracing::instrument(
288 level = "info",
289 skip_all,
290 fields(node = me.0, %cluster_id, election_timeout = %timeouts.election.human(), fetch_timeout = %timeouts.fetch.human()),
291 err
292 )]
293 #[allow(
294 clippy::too_many_arguments,
295 reason = "recovery inputs are independent and explicit at this low-level boundary"
296 )]
297 pub fn open(
298 data_dir: PathBuf,
299 me: NodeId,
300 cluster_id: Uuid,
301 directory_id: Uuid,
302 bootstrap_voters: VoterSet,
303 timeouts: QuorumTimeouts,
304 heartbeat_interval: Option<Time>,
305 controller_fetch_miss_limit: ControllerFetchMissLimit,
306 metadata_raft_command_queue_capacity: MetadataRaftCommandQueueCapacity,
307 metadata_raft_fetch_max: MetadataRaftFetchMax,
308 peers: Arc<dyn PeerSender>,
309 snapshot_interval_records: u64,
310 max_bytes_between_snapshots: ByteSize,
311 max_snapshot_interval: Time,
312 metadata_snapshot_fetch_max: MetadataSnapshotFetchMax,
313 metadata_log: MetadataLogConfig,
314 mut activation: Activation,
315 event_queue_recorder: Arc<dyn EventQueueRecorder>,
316 validate_image: impl Fn(&MetadataImage) -> Result<(), RaftError>,
317 ) -> Result<Self, RaftError> {
318 fs::create_dir_all(&data_dir).map_err(krabka_log::LogError::Io)?;
319 let log = KraftLog::open(&data_dir, &metadata_log)?;
320
321 let mut image = MetadataImage::new(cluster_id);
325 let mut snapshot_control = None;
326 let mut last_snapshot_end_offset = Offset(0);
327 let mut last_snapshot_timestamp_ms = 0;
328 let mut features_epoch = -1;
329 let mut bootstrap_source = "the controller configuration";
330 if let Some(bytes) = load_latest_checkpoint(&data_dir)? {
331 let contents = SnapshotReader::read(&bytes)?;
332 last_snapshot_timestamp_ms = contents.last_contained_log_timestamp;
336 if latest_checkpoint_id(&data_dir) == Some(BOOTSTRAP_SNAPSHOT_ID) {
337 if !contents.metadata_records.is_empty() {
343 tracing::info!(
344 records = contents.metadata_records.len(),
345 "loaded the bootstrap records of the bootstrap checkpoint"
346 );
347 activation.bootstrap_records = contents.metadata_records;
348 bootstrap_source = "the bootstrap checkpoint";
349 }
350 } else {
351 image = MetadataImage::from_records(cluster_id, &contents.metadata_records);
352 if let Some((end_offset, _)) = latest_checkpoint_id(&data_dir) {
353 features_epoch = features_epoch_after_snapshot(
354 &contents.metadata_records,
355 end_offset,
356 features_epoch,
357 );
358 }
359 }
360 if let Some(control) = contents.control_state {
361 image.apply(&MetadataRecord::V1KRaftVersion(
362 krabka_metadata::KRaftVersionRecord {
363 kraft_version: control.kraft_version,
364 },
365 ));
366 image.apply(&MetadataRecord::V1Voters(VotersRecord {
367 voters: control.voters.clone(),
368 }));
369 snapshot_control = Some(control);
370 }
371 if let Some((off, _ep)) = latest_checkpoint_id(&data_dir) {
372 last_snapshot_end_offset = Offset(off);
374 }
375 }
379 check_bootstrap_records(&activation.bootstrap_records, bootstrap_source)?;
380 let CommittedReplay {
381 pending: mut downgrade_snapshot_pending,
382 last_applied_timestamp_ms: replayed_timestamp_ms,
383 features_epoch: replayed_features_epoch,
384 } = replay_committed_with_timestamp(
385 &log,
386 &mut image,
387 log.log_start_offset(),
388 metadata_raft_fetch_max,
389 )?;
390 let last_applied_record_timestamp_ms =
391 replayed_timestamp_ms.unwrap_or(last_snapshot_timestamp_ms);
392 let features_epoch =
396 replayed_features_epoch.map_or(features_epoch, |replayed| replayed.max(features_epoch));
397
398 let mut boundary_state = QuorumState::bootstrap(cluster_id, bootstrap_voters.clone());
399 if let Some(control) = &snapshot_control {
400 boundary_state.kraft_version = control.kraft_version;
401 boundary_state.voters = control.voters.clone();
402 }
403
404 let mut initial_state = load_quorum_state(&data_dir, cluster_id, &bootstrap_voters)?
407 .unwrap_or_else(|| QuorumState::bootstrap(cluster_id, bootstrap_voters));
408 if let Some(control) = &snapshot_control {
409 initial_state.kraft_version = control.kraft_version;
410 initial_state.voters = control.voters.clone();
411 }
412 if let Some(pending) = &mut downgrade_snapshot_pending {
413 let boundary_state = control_state_at(
414 &log,
415 &boundary_state,
416 pending.end_offset,
417 metadata_raft_fetch_max,
418 )?;
419 pending.image.apply(&MetadataRecord::V1KRaftVersion(
420 krabka_metadata::KRaftVersionRecord {
421 kraft_version: boundary_state.kraft_version,
422 },
423 ));
424 pending.image.apply(&MetadataRecord::V1Voters(VotersRecord {
425 voters: boundary_state.voters,
426 }));
427 }
428 replay_control_records(&log, &mut initial_state, metadata_raft_fetch_max)?;
429 image.apply(&MetadataRecord::V1KRaftVersion(
430 krabka_metadata::KRaftVersionRecord {
431 kraft_version: initial_state.kraft_version,
432 },
433 ));
434 image.apply(&MetadataRecord::V1Voters(VotersRecord {
435 voters: initial_state.voters.clone(),
436 }));
437
438 validate_image(&image)?;
446 validate_image(&with_uncommitted_feature_levels(
447 &log,
448 &image,
449 metadata_raft_fetch_max,
450 )?)?;
451
452 Self::spawn_with_image(
453 KraftConfig {
454 me,
455 cluster_id,
456 directory_id,
457 initial_state,
458 timeouts,
459 heartbeat_interval,
460 controller_fetch_miss_limit,
461 metadata_raft_command_queue_capacity,
462 metadata_raft_fetch_max,
463 peers,
464 snapshot_interval_records,
465 max_bytes_between_snapshots,
466 max_snapshot_interval,
467 metadata_snapshot_fetch_max,
468 metadata_log,
469 activation,
470 event_queue_recorder,
471 },
472 log,
473 data_dir,
474 RecoveredMetadata {
475 image,
476 last_snapshot_end_offset,
477 last_snapshot_timestamp_ms,
478 last_applied_record_timestamp_ms,
479 downgrade_snapshot_pending,
480 features_epoch,
481 },
482 )
483 }
484}