nodedb_vector/collection/
rollback.rs1use nodedb_types::Surrogate;
19
20use super::lifecycle::VectorCollection;
21use crate::flat::FlatIndex;
22
23#[derive(Debug, Clone)]
25pub struct VectorWriteMark {
26 next_id: u32,
27 growing_base_id: u32,
28 bindings: Vec<(Surrogate, Option<u32>)>,
30 live: Vec<u32>,
32 multi_docs: Vec<(Surrogate, Option<Vec<u32>>)>,
34}
35
36impl VectorCollection {
37 pub fn is_live(&self, id: u32) -> bool {
40 if let Some(ivf) = &self.ivf
41 && ivf.contains(id)
42 {
43 return !ivf.is_deleted(id);
44 }
45 if id >= self.growing_base_id {
46 let local = id - self.growing_base_id;
47 if (local as usize) < self.growing.len() {
48 return !self.growing.is_deleted(local);
49 }
50 }
51 for seg in &self.sealed {
52 if id >= seg.base_id {
53 let local = id - seg.base_id;
54 if (local as usize) < seg.index.len() {
55 return !seg.index.is_deleted(local);
56 }
57 }
58 }
59 for seg in &self.building {
60 if id >= seg.base_id {
61 let local = id - seg.base_id;
62 if (local as usize) < seg.flat.len() {
63 return !seg.flat.is_deleted(local);
64 }
65 }
66 }
67 false
68 }
69
70 pub fn write_mark(&self, surrogates: &[Surrogate], ids: &[u32]) -> VectorWriteMark {
72 let mut bindings = Vec::with_capacity(surrogates.len() + ids.len());
73 let mut live = Vec::new();
74 let mut multi_docs = Vec::with_capacity(surrogates.len());
75 for &surrogate in surrogates {
76 let bound = self.surrogate_to_local.get(&surrogate).copied();
77 bindings.push((surrogate, bound));
78 if let Some(id) = bound
79 && self.is_live(id)
80 {
81 live.push(id);
82 }
83 let doc = self.multi_doc_map.get(&surrogate).cloned();
84 if let Some(doc_ids) = &doc {
85 live.extend(doc_ids.iter().copied().filter(|id| self.is_live(*id)));
86 }
87 multi_docs.push((surrogate, doc));
88 }
89 for &id in ids {
90 if !self.is_live(id) {
91 continue;
92 }
93 live.push(id);
94 if let Some(&surrogate) = self.surrogate_map.get(&id) {
95 bindings.push((surrogate, Some(id)));
96 }
97 }
98 VectorWriteMark {
99 next_id: self.next_id,
100 growing_base_id: self.growing_base_id,
101 bindings,
102 live,
103 multi_docs,
104 }
105 }
106
107 pub fn roll_back_to(&mut self, mark: VectorWriteMark) -> bool {
111 if self.growing_base_id != mark.growing_base_id || mark.next_id < self.growing_base_id {
112 return false;
113 }
114 for id in mark.next_id..self.next_id {
115 if let Some(surrogate) = self.surrogate_map.remove(&id)
116 && self.surrogate_to_local.get(&surrogate) == Some(&id)
117 {
118 self.surrogate_to_local.remove(&surrogate);
119 }
120 }
121 self.growing
122 .truncate((mark.next_id - self.growing_base_id) as usize);
123 if let Some(ivf) = &mut self.ivf {
124 ivf.roll_back_to(mark.next_id);
125 }
126 self.next_id = mark.next_id;
127
128 for (surrogate, prior) in mark.bindings {
129 if let Some(current) = self.surrogate_to_local.remove(&surrogate)
130 && Some(current) != prior
131 {
132 self.surrogate_map.remove(¤t);
133 }
134 if let Some(id) = prior {
135 self.surrogate_to_local.insert(surrogate, id);
136 self.surrogate_map.insert(id, surrogate);
137 }
138 }
139 for (surrogate, prior) in mark.multi_docs {
140 match prior {
141 Some(ids) => {
142 for id in &ids {
143 self.surrogate_map.insert(*id, surrogate);
144 }
145 self.multi_doc_map.insert(surrogate, ids);
146 }
147 None => {
148 self.multi_doc_map.remove(&surrogate);
149 }
150 }
151 }
152 for id in mark.live {
153 if !self.is_live(id) {
154 self.undelete(id);
155 }
156 }
157 true
158 }
159
160 pub fn detached_empty(&self) -> Self {
166 let mut fresh = Self::with_seal_threshold_and_config(
167 self.dim,
168 self.index_config.clone(),
169 self.seal_threshold,
170 );
171 fresh.params = self.params.clone();
172 fresh.growing = FlatIndex::new(self.dim, self.params.metric);
173 fresh.next_id = self.next_id;
174 fresh.growing_base_id = self.next_id;
175 fresh.next_segment_id = self.next_segment_id;
176 fresh.data_dir = self.data_dir.clone();
177 fresh.ram_budget_bytes = self.ram_budget_bytes;
178 fresh.mmap_fallback_count = self.mmap_fallback_count;
179 fresh.quantization = self.quantization;
180 fresh.payload = self.payload.definitions_only();
181 fresh.arena_index = self.arena_index;
182 fresh.checkpoint_wal_lsn = self.checkpoint_wal_lsn;
183 fresh.applied_wal_lsn = self.applied_wal_lsn;
184 fresh
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use crate::hnsw::HnswParams;
192
193 fn collection() -> VectorCollection {
194 VectorCollection::new(2, HnswParams::default())
195 }
196
197 #[test]
198 fn a_rolled_back_insert_returns_the_id_counter_and_the_binding() {
199 let mut coll = collection();
200 coll.insert_with_surrogate(vec![0.1, 0.2], Surrogate::new(1))
201 .unwrap();
202 let mark = coll.write_mark(&[Surrogate::new(2)], &[]);
203 coll.insert_with_surrogate(vec![0.3, 0.4], Surrogate::new(2))
204 .unwrap();
205 assert!(coll.roll_back_to(mark));
206 assert_eq!(coll.local_for_surrogate(Surrogate::new(2)), None);
207 assert_eq!(
208 coll.insert_with_surrogate(vec![0.5, 0.6], Surrogate::new(3))
209 .unwrap(),
210 1,
211 "the next insert takes the id the rolled-back insert took"
212 );
213 }
214
215 #[test]
216 fn a_rolled_back_rebind_restores_the_replaced_node() {
217 let mut coll = collection();
218 let first = coll
219 .insert_with_surrogate(vec![0.1, 0.2], Surrogate::new(7))
220 .unwrap();
221 let mark = coll.write_mark(&[Surrogate::new(7)], &[]);
222 coll.insert_with_surrogate(vec![0.3, 0.4], Surrogate::new(7))
223 .unwrap();
224 assert!(!coll.is_live(first));
225 assert!(coll.roll_back_to(mark));
226 assert!(coll.is_live(first));
227 assert_eq!(coll.local_for_surrogate(Surrogate::new(7)), Some(first));
228 }
229
230 #[test]
231 fn a_rolled_back_delete_restores_the_node_and_its_binding() {
232 let mut coll = collection();
233 let id = coll
234 .insert_with_surrogate(vec![0.1, 0.2], Surrogate::new(4))
235 .unwrap();
236 let mark = coll.write_mark(&[], &[id]);
237 coll.delete(id);
238 assert!(coll.roll_back_to(mark));
239 assert!(coll.is_live(id));
240 assert_eq!(coll.local_for_surrogate(Surrogate::new(4)), Some(id));
241 }
242
243 #[test]
244 fn a_seal_after_the_mark_refuses_the_rollback() {
245 let mut coll = VectorCollection::with_seal_threshold(2, HnswParams::default(), 1);
246 let mark = coll.write_mark(&[Surrogate::new(1)], &[]);
247 coll.insert_with_surrogate(vec![0.1, 0.2], Surrogate::new(1))
248 .unwrap();
249 assert!(coll.seal("k").is_some());
250 assert!(!coll.roll_back_to(mark));
251 }
252
253 #[test]
254 fn a_detached_empty_collection_continues_the_id_counter() {
255 let mut coll = collection();
256 coll.insert(vec![0.1, 0.2]).unwrap();
257 coll.insert(vec![0.3, 0.4]).unwrap();
258 let mut fresh = coll.detached_empty();
259 assert_eq!(fresh.live_count(), 0);
260 assert_eq!(fresh.insert(vec![0.5, 0.6]).unwrap(), 2);
261 }
262}