Skip to main content

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;