oximedia_distributed/
compaction.rs1use crate::distributed_enhancements::LogEntry;
7
8#[derive(Debug, Clone)]
10pub struct Snapshot {
11 pub last_included_index: u64,
13 pub last_included_term: u64,
15 pub data: Vec<u8>,
17}
18
19pub struct LogCompactor;
22
23impl LogCompactor {
24 #[must_use]
26 pub fn new() -> Self {
27 Self
28 }
29
30 #[must_use]
47 pub fn compact(log: &mut Vec<LogEntry>, snapshot_idx: u64) -> Snapshot {
48 let last_included_term = log
50 .iter()
51 .rev()
52 .find(|e| e.index <= snapshot_idx)
53 .map(|e| e.term)
54 .unwrap_or(0);
55
56 log.retain(|e| e.index > snapshot_idx);
58
59 Snapshot {
60 last_included_index: snapshot_idx,
61 last_included_term,
62 data: Vec::new(),
63 }
64 }
65
66 pub fn install_snapshot(log: &mut Vec<LogEntry>, snapshot: &Snapshot) -> bool {
72 let log_last_index = log.last().map(|e| e.index).unwrap_or(0);
73 if snapshot.last_included_index >= log_last_index {
74 log.clear();
75 true
76 } else {
77 log.retain(|e| e.index > snapshot.last_included_index);
79 false
80 }
81 }
82}
83
84impl Default for LogCompactor {
85 fn default() -> Self {
86 Self::new()
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 fn make_log(count: u64) -> Vec<LogEntry> {
95 (1..=count)
96 .map(|i| LogEntry {
97 term: (i + 1) / 2,
98 index: i,
99 command: format!("cmd-{i}"),
100 })
101 .collect()
102 }
103
104 #[test]
105 fn test_compact_removes_entries_up_to_index() {
106 let mut log = make_log(10);
107 let snap = LogCompactor::compact(&mut log, 5);
108 assert_eq!(snap.last_included_index, 5);
109 assert!(log.iter().all(|e| e.index > 5));
110 assert_eq!(log.len(), 5);
111 }
112
113 #[test]
114 fn test_compact_all_entries() {
115 let mut log = make_log(5);
116 let snap = LogCompactor::compact(&mut log, 5);
117 assert_eq!(snap.last_included_index, 5);
118 assert!(log.is_empty());
119 }
120
121 #[test]
122 fn test_compact_no_matching_entries() {
123 let mut log = make_log(5);
124 let snap = LogCompactor::compact(&mut log, 0);
126 assert_eq!(snap.last_included_index, 0);
127 assert_eq!(snap.last_included_term, 0);
128 assert_eq!(log.len(), 5);
129 }
130
131 #[test]
132 fn test_compact_empty_log() {
133 let mut log: Vec<LogEntry> = Vec::new();
134 let snap = LogCompactor::compact(&mut log, 10);
135 assert_eq!(snap.last_included_index, 10);
136 assert_eq!(snap.last_included_term, 0);
137 assert!(log.is_empty());
138 }
139
140 #[test]
141 fn test_install_snapshot_truncates_older_log() {
142 let mut log = make_log(5);
143 let snap = Snapshot {
144 last_included_index: 10,
145 last_included_term: 5,
146 data: Vec::new(),
147 };
148 let applied = LogCompactor::install_snapshot(&mut log, &snap);
149 assert!(applied);
150 assert!(log.is_empty());
151 }
152
153 #[test]
154 fn test_install_snapshot_stale_does_not_clear_all() {
155 let mut log = make_log(10);
156 let snap = Snapshot {
157 last_included_index: 5,
158 last_included_term: 3,
159 data: Vec::new(),
160 };
161 LogCompactor::install_snapshot(&mut log, &snap);
162 assert!(log.iter().all(|e| e.index > 5));
164 }
165}