quorum_set/progress/vec_progress.rs
1use std::collections::BTreeMap;
2use std::collections::BTreeSet;
3use std::error::Error;
4use std::fmt::Debug;
5use std::fmt::Display;
6use std::fmt::Formatter;
7use std::slice::Iter;
8use std::slice::IterMut;
9
10use validit::Validate;
11
12use super::VecProgressEntry;
13use super::VecProgressEntryData;
14use super::display_vec_progress::DisplayVecProgress;
15use super::progress_stats::ProgressStats;
16use crate::quorum::QuorumSet;
17
18/// Tracks per-node progress and the greatest value accepted by a quorum.
19///
20/// `Entry` stores a node ID, an ordered progress value, and optional
21/// application-owned data. `QS` decides which node IDs constitute a quorum. In
22/// Raft terms, this is a compact map from node ID to replicated log ID plus any
23/// follower state the application keeps beside it.
24///
25/// Internally this type uses a vector and keeps only the voter prefix above the
26/// current quorum-accepted value sorted. Normal updates may only keep or
27/// increase progress; explicit resets may move an entry backward without
28/// lowering the recorded quorum-accepted value. This makes the type a good fit
29/// for small consensus memberships.
30#[derive(Clone, Debug)]
31pub struct VecProgress<Entry, QS>
32where
33 Entry: VecProgressEntry,
34 QS: QuorumSet<Id = Entry::Id>,
35{
36 /// Quorum set used to decide whether candidate IDs constitute a quorum.
37 quorum_set: QS,
38
39 /// The greatest value accepted by a quorum.
40 quorum_accepted: Entry::Progress,
41
42 /// Number of voter entries.
43 voter_count: usize,
44
45 /// Progress data.
46 ///
47 /// Elements with values greater than `quorum_accepted` are sorted in descending order.
48 /// Others are unsorted.
49 ///
50 /// The first `voter_count` entries are voters; the rest are learners.
51 /// Learners are not reordered by progress updates.
52 /// Voters may move within the voter range to maintain the sorted prefix.
53 entries: Vec<Entry>,
54
55 /// Statistics of how it runs.
56 stat: ProgressStats,
57}
58
59impl<Entry, QS> Display for VecProgress<Entry, QS>
60where
61 Entry: VecProgressEntry + Display,
62 QS: QuorumSet<Id = Entry::Id>,
63{
64 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
65 write!(f, "{{")?;
66 for (i, item) in self.entries.iter().enumerate() {
67 if i > 0 {
68 write!(f, ", ")?;
69 }
70 write!(f, "{}", item)?
71 }
72 write!(f, "}}")?;
73
74 Ok(())
75 }
76}
77
78impl<Entry, QS> VecProgress<Entry, QS>
79where
80 Entry: VecProgressEntry,
81 Entry::Id: Ord + Clone + Debug,
82 Entry::Progress: Debug,
83 QS: QuorumSet<Id = Entry::Id>,
84{
85 /// Create a progress tracker from a quorum set and learner IDs.
86 ///
87 /// Voters are created from `quorum_set.ids()`. Learners are tracked after
88 /// voters and never contribute to quorum acceptance. Every ID is tracked
89 /// once: a learner ID that `quorum_set.ids()` also yields is a voter, and
90 /// repeated IDs are ignored. `default_entry` builds the initial entry for
91 /// every voter and learner ID; entries may start at any progress value, and
92 /// the initial quorum-accepted value is computed from the initial voter
93 /// progress.
94 pub fn new(
95 quorum_set: QS,
96 learner_ids: impl IntoIterator<Item = Entry::Id>,
97 mut default_entry: impl FnMut(Entry::Id) -> Entry,
98 ) -> Self {
99 let voter_ids = quorum_set.ids().collect::<BTreeSet<_>>();
100 let learner_ids =
101 learner_ids.into_iter().filter(|id| !voter_ids.contains(id)).collect::<BTreeSet<_>>();
102
103 let mut entries = voter_ids.into_iter().map(&mut default_entry).collect::<Vec<_>>();
104
105 let voter_count = entries.len();
106
107 // Initial progress is not necessarily `Progress::default()`: sort voters
108 // in descending progress order and find the greatest accepted value.
109 entries.sort_by(|a, b| b.progress().cmp(a.progress()));
110
111 let mut quorum_accepted = Entry::Progress::default();
112 for i in 0..voter_count {
113 let ids = entries[..=i].iter().map(|entry| entry.id());
114 if quorum_set.is_quorum(ids) {
115 quorum_accepted = entries[i].progress().clone();
116 break;
117 }
118 }
119
120 entries.extend(learner_ids.into_iter().map(default_entry));
121
122 Self {
123 quorum_set,
124 quorum_accepted,
125 voter_count,
126 entries,
127 stat: Default::default(),
128 }
129 }
130
131 /// Find the index of the specified id.
132 #[inline(always)]
133 fn index(&self, target: &Entry::Id) -> Option<usize> {
134 self.entries.iter().position(|item| item.id() == target)
135 }
136
137 /// Move an element at `index` up so that voters stay sorted.
138 #[inline(always)]
139 fn move_up(&mut self, index: usize) -> usize {
140 self.stat.move_count += 1;
141 for i in (0..index).rev() {
142 if self.entries[i].progress() < self.entries[i + 1].progress() {
143 self.entries.swap(i, i + 1);
144 } else {
145 return i + 1;
146 }
147 }
148
149 0
150 }
151
152 /// Move a voter element at `index` down so that voters stay sorted
153 /// after its progress value is lowered.
154 ///
155 /// It is the counterpart of [`Self::move_up`], used by [`Self::reset_entry_with()`].
156 fn move_down(&mut self, index: usize) -> usize {
157 self.stat.move_count += 1;
158 let mut i = index;
159 while i + 1 < self.voter_count
160 && self.entries[i].progress() < self.entries[i + 1].progress()
161 {
162 self.entries.swap(i, i + 1);
163 i += 1;
164 }
165
166 i
167 }
168
169 /// Return mutable entries without maintaining the progress ordering.
170 ///
171 /// Mutating progress values through this iterator can leave the internal
172 /// ordering and quorum-accepted value stale. Normal progress updates must
173 /// use [`Self::update_progress()`] or [`Self::update_entry_with()`] instead.
174 /// Mutating entry IDs can corrupt membership lookup.
175 pub fn iter_mut_without_reorder(&mut self) -> IterMut<'_, Entry> {
176 self.entries.iter_mut()
177 }
178
179 #[cfg(test)]
180 pub(crate) fn stat(&self) -> &ProgressStats {
181 &self.stat
182 }
183
184 /// Return a display adapter that formats entries with a caller-provided formatter.
185 pub fn display_with<Fmt>(&self, f: Fmt) -> DisplayVecProgress<'_, Entry, QS, Fmt>
186 where Fmt: Fn(&mut Formatter<'_>, &Entry) -> std::fmt::Result {
187 DisplayVecProgress { inner: self, f }
188 }
189
190 /// Validates progress-update invariants in debug builds.
191 fn debug_assert_progress_valid(&self) {
192 #[cfg(debug_assertions)]
193 self.validate().expect("VecProgress progress invariant violation");
194 }
195}
196
197impl<Entry, QS> VecProgress<Entry, QS>
198where
199 Entry: VecProgressEntry,
200 Entry::Id: Ord + Clone + Debug,
201 Entry::Progress: Debug,
202 QS: QuorumSet<Id = Entry::Id>,
203{
204 /// Update one progress value monotonically and recalculate the quorum-accepted value.
205 ///
206 /// It returns `None` if the `id` is not found.
207 /// Otherwise, it returns the current quorum-accepted value.
208 /// Updating with the same value leaves the state unchanged.
209 ///
210 /// # Algorithm
211 ///
212 /// Only one case can increase the quorum-accepted value: the **previous value**
213 /// is less than or equal to the current quorum-accepted value, and the **new
214 /// value** is greater than it.
215 ///
216 /// This avoids many unnecessary quorum recalculations and sorts. Progress
217 /// entries above the quorum-accepted value are kept in descending order, and
218 /// entries at or below it do not need to be sorted.
219 ///
220 /// E.g., given 3 ids with values `1,3,5`, as shown in the figure below:
221 ///
222 /// ```text
223 /// a -----------+-------->
224 /// b -------+------------>
225 /// c ---+---------------->
226 /// ------------------------------
227 /// 1 3 5
228 /// ```
229 ///
230 /// the quorum-accepted is `3` and assumes a majority quorum set is used.
231 /// Then:
232 /// - update_progress(a, 6): nothing to do: quorum-accepted is still 3;
233 /// - update_progress(b, 4): re-calc: quorum-accepted becomes 4;
234 /// - update_progress(b, 6): re-calc: quorum-accepted becomes 5;
235 /// - update_progress(c, 2): nothing to do: quorum-accepted is still 3;
236 /// - update_progress(c, 3): nothing to do: quorum-accepted is still 3;
237 /// - update_progress(c, 4): re-calc: quorum-accepted becomes 4;
238 /// - update_progress(c, 6): re-calc: quorum-accepted becomes 5;
239 fn update_progress_with<F>(&mut self, id: &Entry::Id, f: F) -> Option<&Entry::Progress>
240 where F: FnOnce(&mut Entry::Progress) {
241 self.update_entry_with(id, |entry| f(entry.progress_mut()))
242 }
243
244 /// Update an entry and recalculate the quorum-accepted value.
245 ///
246 /// Use this when application-owned fields must change together with the
247 /// progress value. The progress update must not lower progress, and the
248 /// entry ID must not change.
249 ///
250 /// It returns `None` if the `id` is not found.
251 /// Otherwise, it returns the current quorum-accepted value.
252 pub fn update_entry_with<F>(&mut self, id: &Entry::Id, f: F) -> Option<&Entry::Progress>
253 where F: FnOnce(&mut Entry) {
254 self.stat.update_count += 1;
255
256 let index = self.index(id)?;
257
258 let prev_progress = self.entries[index].progress().clone();
259
260 f(&mut self.entries[index]);
261
262 debug_assert!(self.entries[index].id() == id);
263
264 Some(self.update_at(index, prev_progress))
265 }
266
267 /// Update application-owned data without recalculating quorum-accepted progress.
268 ///
269 /// This method only exposes [`VecProgressEntryData::Data`], so it cannot
270 /// change progress or invalidate the ordering maintained by [`VecProgress`].
271 ///
272 /// Returns the updated data when `id` is found, otherwise returns `None`.
273 pub fn update_data_with<F>(&mut self, id: &Entry::Id, f: F) -> Option<&Entry::Data>
274 where
275 Entry: VecProgressEntryData,
276 F: FnOnce(&mut Entry::Data),
277 {
278 let index = self.index(id)?;
279
280 f(self.entries[index].data_mut());
281
282 Some(self.entries[index].data())
283 }
284
285 /// Update an entry whose progress value may move backward, for example when
286 /// replication progress is reset upon log reversion.
287 ///
288 /// If the progress value is lowered, the entry is moved down to keep the
289 /// values greater than `quorum_accepted` sorted. The recorded
290 /// quorum-accepted value is deliberately not recalculated: a value accepted
291 /// by a quorum must never be withdrawn.
292 /// The entry ID must not be changed.
293 ///
294 /// It returns the updated entry if the `id` is found, otherwise returns `None`.
295 pub fn reset_entry_with<F>(&mut self, id: &Entry::Id, f: F) -> Option<&Entry>
296 where F: FnOnce(&mut Entry) {
297 let index = self.index(id)?;
298
299 let prev_progress = self.entries[index].progress().clone();
300
301 f(&mut self.entries[index]);
302
303 debug_assert!(self.entries[index].id() == id);
304 debug_assert!(self.entries[index].progress() <= &prev_progress);
305
306 // Learners are never reordered.
307 let new_index =
308 if index < self.voter_count && self.entries[index].progress() < &prev_progress {
309 self.move_down(index)
310 } else {
311 index
312 };
313
314 self.debug_assert_progress_valid();
315 Some(&self.entries[new_index])
316 }
317
318 fn update_at(&mut self, index: usize, prev_progress: Entry::Progress) -> &Entry::Progress {
319 debug_assert!(self.entries[index].progress() >= &prev_progress,);
320
321 // No change, return early
322 if &prev_progress == self.entries[index].progress() {
323 self.debug_assert_progress_valid();
324 return &self.quorum_accepted;
325 }
326
327 // Learner does not grant a value.
328 // And it won't be moved up to adjust the order.
329 if index >= self.voter_count {
330 self.debug_assert_progress_valid();
331 return &self.quorum_accepted;
332 }
333
334 let prev_le_qa = prev_progress <= self.quorum_accepted;
335 let new_gt_qa = self.entries[index].progress() > &self.quorum_accepted;
336
337 // Sort and find the greatest value accepted by a quorum set.
338
339 if new_gt_qa {
340 let new_index = self.move_up(index);
341
342 if prev_le_qa {
343 // From high to low, find the max value that has constituted a quorum.
344 for i in new_index..self.voter_count {
345 let prog = self.entries[i].progress();
346
347 // No need to recalculate an already quorum-accepted value.
348 if prog <= &self.quorum_accepted {
349 break;
350 }
351
352 // Ids of the target that has value GE `entries[i]`
353 let it = self.entries[0..=i].iter().map(|item| item.id());
354
355 self.stat.is_quorum_count += 1;
356
357 if self.quorum_set.is_quorum(it) {
358 self.quorum_accepted = prog.clone();
359 break;
360 }
361 }
362 }
363 }
364
365 self.debug_assert_progress_valid();
366 &self.quorum_accepted
367 }
368
369 /// Set one node's progress and recalculate the quorum-accepted value.
370 ///
371 /// The new value must be greater than or equal to the current progress. Use
372 /// [`Self::reset_entry_with()`] for an explicit backward move.
373 ///
374 /// It returns `None` if the `id` is not found.
375 /// Otherwise, it returns the current quorum-accepted value.
376 pub fn update_progress(
377 &mut self,
378 id: &Entry::Id,
379 value: Entry::Progress,
380 ) -> Option<&Entry::Progress> {
381 self.update_progress_with(id, |x| *x = value)
382 }
383
384 /// Increase one node's progress if `value` is greater than its current value.
385 ///
386 /// It returns `None` if the `id` is not found.
387 /// Otherwise, it returns the current quorum-accepted value.
388 pub fn increase_to(
389 &mut self,
390 id: &Entry::Id,
391 value: Entry::Progress,
392 ) -> Option<&Entry::Progress> {
393 self.update_progress_with(id, |x| {
394 if value > *x {
395 *x = value;
396 }
397 })
398 }
399
400 /// Return the tracked entry for `id`.
401 pub fn try_get(&self, id: &Entry::Id) -> Option<&Entry> {
402 let index = self.index(id)?;
403 Some(&self.entries[index])
404 }
405
406 /// Return the greatest progress value accepted by the quorum set.
407 ///
408 /// If no value has been accepted by any quorum, it returns
409 /// `Progress::default()`.
410 ///
411 /// In Raft, this is the replication progress reached by enough voters to be
412 /// considered committed once the term-specific commit rule also allows it.
413 pub fn quorum_accepted(&self) -> &Entry::Progress {
414 &self.quorum_accepted
415 }
416
417 /// Return the quorum set that decides which entries constitute a quorum.
418 pub fn quorum_set(&self) -> &QS {
419 &self.quorum_set
420 }
421
422 /// Return the number of voter entries.
423 ///
424 /// [`Self::iter()`] yields these voters first, before the learners.
425 pub fn voter_count(&self) -> usize {
426 self.voter_count
427 }
428
429 /// Iterate over all entries, with voters first and learners after them.
430 pub fn iter(&self) -> Iter<'_, Entry> {
431 self.entries.as_slice().iter()
432 }
433
434 /// Map every entry and collect the mapped values.
435 pub fn collect_mapped<F, T, C>(&self, f: F) -> C
436 where
437 F: Fn(&Entry) -> T,
438 C: FromIterator<T>,
439 {
440 self.iter().map(f).collect()
441 }
442
443 /// Build a tracker for a new quorum set while preserving progress for shared IDs.
444 ///
445 /// Entries whose IDs still exist in the new voter or learner set keep their
446 /// previous progress and application data. New IDs are initialized through
447 /// `default_entry`. The quorum-accepted value is recomputed for the new
448 /// quorum set, so it may be lower than before the upgrade.
449 pub fn upgrade_quorum_set(
450 self,
451 quorum_set: QS,
452 learner_ids: impl IntoIterator<Item = Entry::Id>,
453 mut default_entry: impl FnMut(Entry::Id) -> Entry,
454 ) -> Self {
455 let mut old = self
456 .entries
457 .into_iter()
458 .map(|entry| (entry.id().clone(), entry))
459 .collect::<BTreeMap<_, _>>();
460
461 let mut new_prog = Self::new(quorum_set, learner_ids, |id| {
462 old.remove(&id).unwrap_or_else(|| default_entry(id))
463 });
464
465 new_prog.stat = self.stat;
466 new_prog
467 }
468
469 /// Return whether the given ID is a voter.
470 ///
471 /// A voter is a node in the quorum set that can grant a value.
472 /// A learner's progress is also tracked, but it will never grant a value.
473 ///
474 /// If the given id is not in this [`VecProgress`], it returns `None`.
475 pub fn is_voter(&self, id: &Entry::Id) -> Option<bool> {
476 let index = self.index(id)?;
477 Some(index < self.voter_count)
478 }
479}
480
481impl<Entry, QS> IntoIterator for VecProgress<Entry, QS>
482where
483 Entry: VecProgressEntry,
484 QS: QuorumSet<Id = Entry::Id>,
485{
486 type Item = Entry;
487 type IntoIter = std::vec::IntoIter<Entry>;
488
489 fn into_iter(self) -> Self::IntoIter {
490 self.entries.into_iter()
491 }
492}
493
494impl<Entry, QS> Validate for VecProgress<Entry, QS>
495where
496 Entry: VecProgressEntry,
497 Entry::Id: Ord + Clone + Debug,
498 Entry::Progress: Debug,
499 QS: QuorumSet<Id = Entry::Id>,
500{
501 /// Validates the voter-order invariant maintained after progress updates.
502 fn validate(&self) -> Result<(), Box<dyn Error>> {
503 self.validate_voter_order()
504 }
505}
506
507impl<Entry, QS> VecProgress<Entry, QS>
508where
509 Entry: VecProgressEntry,
510 Entry::Id: Ord + Clone + Debug,
511 Entry::Progress: Debug,
512 QS: QuorumSet<Id = Entry::Id>,
513{
514 /// Validates that voter entries whose progress is greater than
515 /// `quorum_accepted` form a descending prefix, reporting the first
516 /// out-of-order entry with the current voter and learner progress state.
517 fn validate_voter_order(&self) -> Result<(), Box<dyn Error>> {
518 let voters = &self.entries[..self.voter_count];
519 let progress_state = || {
520 let voter_progress = voters
521 .iter()
522 .map(|entry| (entry.id().clone(), entry.progress().clone()))
523 .collect::<Vec<_>>();
524 let learner_progress = self.entries[self.voter_count..]
525 .iter()
526 .map(|entry| (entry.id().clone(), entry.progress().clone()))
527 .collect::<Vec<_>>();
528
529 (voter_progress, learner_progress)
530 };
531
532 let suffix_start = voters
533 .iter()
534 .position(|entry| entry.progress() <= &self.quorum_accepted)
535 .unwrap_or(voters.len());
536
537 for (previous_index, pair) in voters[..suffix_start].windows(2).enumerate() {
538 let previous = &pair[0];
539 let item = &pair[1];
540 if previous.progress() < item.progress() {
541 let (voter_progress, learner_progress) = progress_state();
542 return Err(format!(
543 "voter progress above quorum_accepted is not descending: quorum_accepted={:?}, previous_entry={:?}, out_of_order_entry={:?}, voter_progress={voter_progress:?}, learner_progress={learner_progress:?}",
544 self.quorum_accepted,
545 (previous_index, previous.id(), previous.progress()),
546 (previous_index + 1, item.id(), item.progress())
547 )
548 .into());
549 }
550 }
551
552 for (suffix_offset, item) in voters[suffix_start..].iter().enumerate() {
553 if item.progress() <= &self.quorum_accepted {
554 continue;
555 }
556
557 let index = suffix_start + suffix_offset;
558 let (voter_progress, learner_progress) = progress_state();
559 return Err(format!(
560 "voter progress above quorum_accepted appears after the unsorted suffix: quorum_accepted={:?}, out_of_order_entry={:?}, voter_progress={voter_progress:?}, learner_progress={learner_progress:?}",
561 self.quorum_accepted,
562 (index, item.id(), item.progress())
563 )
564 .into());
565 }
566
567 Ok(())
568 }
569}
570
571#[cfg(test)]
572mod vec_progress_test;