nodedb_vector/collection/
lifecycle_compact.rs1use nodedb_types::Surrogate;
6
7use super::lifecycle::VectorCollection;
8use crate::flat::FlatIndex;
9
10pub type ExportedVector = (u32, Vec<f32>, Option<Surrogate>);
12
13impl VectorCollection {
14 pub fn truncate(&mut self) -> usize {
28 let dropped = self.live_count();
29 self.clear_segments();
30 self.ivf = None;
31 self.surrogate_map.clear();
32 self.surrogate_to_local.clear();
33 self.multi_doc_map.clear();
34 self.payload.clear_rows();
35 dropped
36 }
37
38 pub(super) fn clear_segments(&mut self) {
42 self.growing = FlatIndex::new(self.dim, self.params.metric);
43 self.growing_base_id = self.next_id;
44 for seg in self.sealed.drain(..) {
45 let mmap_path = seg.mmap_vectors.as_ref().map(|m| m.path().to_path_buf());
46 drop(seg);
48 if let Some(path) = mmap_path
49 && let Err(e) = std::fs::remove_file(&path)
50 {
51 tracing::warn!(
52 path = %path.display(),
53 error = %e,
54 "vector truncate: mmap segment file not removed"
55 );
56 }
57 }
58 self.building.clear();
59 self.mmap_segment_count = 0;
60 self.codec_dispatch = None;
61 }
62
63 pub fn compact(&mut self) -> usize {
70 let mut total_removed = self.ivf.as_mut().map_or(0, |ivf| ivf.compact());
71 for seg in &mut self.sealed {
72 let base_id = seg.base_id;
73 let (removed, id_map) = seg.index.compact_with_map();
74 total_removed += removed;
75 if removed == 0 {
76 continue;
77 }
78
79 let segment_end = base_id as u64 + id_map.len() as u64;
80 let global_keys: Vec<u32> = self
81 .surrogate_map
82 .keys()
83 .copied()
84 .filter(|&k| (k as u64) >= base_id as u64 && (k as u64) < segment_end)
85 .collect();
86 let mut new_entries: Vec<(u32, Surrogate)> = Vec::with_capacity(global_keys.len());
90 for old_global in &global_keys {
91 let surrogate = self.surrogate_map.remove(old_global);
92 let old_local = (old_global - base_id) as usize;
93 let new_local = id_map[old_local];
94 if new_local != u32::MAX
95 && let Some(s) = surrogate
96 {
97 new_entries.push((base_id + new_local, s));
98 } else if let Some(s) = surrogate {
99 self.surrogate_to_local.remove(&s);
101 }
102 }
103 for (k, s) in new_entries {
104 self.surrogate_map.insert(k, s);
105 self.surrogate_to_local.insert(s, k);
106 }
107
108 for ids in self.multi_doc_map.values_mut() {
110 ids.retain_mut(|vid| {
111 let v = *vid;
112 if (v as u64) >= base_id as u64 && (v as u64) < segment_end {
113 let old_local = (v - base_id) as usize;
114 let new_local = id_map[old_local];
115 if new_local == u32::MAX {
116 false
117 } else {
118 *vid = base_id + new_local;
119 true
120 }
121 } else {
122 true
123 }
124 });
125 }
126 }
127 total_removed
128 }
129
130 pub fn multi_vector_documents(&self) -> Vec<Surrogate> {
135 let mut documents: Vec<Surrogate> = self.multi_doc_map.keys().copied().collect();
136 documents.sort_unstable();
137 documents
138 }
139
140 pub fn export_snapshot(&self) -> Result<Vec<ExportedVector>, crate::error::VectorError> {
148 let mut result = Vec::new();
149
150 if let Some(ivf) = &self.ivf {
151 for (vid, data) in ivf.live_vectors() {
152 let surrogate = self.surrogate_map.get(&vid).copied();
153 result.push((vid, data, surrogate));
154 }
155 }
156
157 for i in 0..self.growing.len() as u32 {
158 let vid = self.growing_base_id + i;
159 if let Some(data) = self.growing.get_vector(i) {
160 let surrogate = self.surrogate_map.get(&vid).copied();
161 result.push((vid, data.to_vec(), surrogate));
162 }
163 }
164
165 for seg in &self.sealed {
166 let vectors = seg.index.export_vectors()?;
167 for (i, vec_data) in vectors.into_iter().enumerate() {
168 let vid = seg.base_id + i as u32;
169 let surrogate = self.surrogate_map.get(&vid).copied();
170 result.push((vid, vec_data, surrogate));
171 }
172 }
173
174 for seg in &self.building {
175 for i in 0..seg.flat.len() as u32 {
176 let vid = seg.base_id + i;
177 if let Some(data) = seg.flat.get_vector(i) {
178 let surrogate = self.surrogate_map.get(&vid).copied();
179 result.push((vid, data.to_vec(), surrogate));
180 }
181 }
182 }
183
184 Ok(result)
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use crate::hnsw::HnswParams;
192 use nodedb_types::{PayloadIndexKind, Value};
193 use std::collections::HashMap;
194
195 #[test]
196 fn truncate_drops_every_row_and_keeps_config() {
197 let mut coll = VectorCollection::with_seal_threshold(2, HnswParams::default(), 2);
198 coll.payload.add_index("owner", PayloadIndexKind::Equality);
199 let mut fields = HashMap::new();
200 fields.insert("owner".to_string(), Value::String("a".into()));
201 for (i, v) in [[1.0, 0.0], [0.0, 1.0], [1.0, 1.0]].into_iter().enumerate() {
202 let s = Surrogate::new(i as u32 + 1);
203 let id = coll.insert_with_surrogate(v.to_vec(), s).unwrap();
204 coll.payload.insert_row(id, &fields);
205 }
206 assert!(
207 coll.seal("k").is_some(),
208 "threshold reached, growing sealed"
209 );
210 assert_eq!(coll.live_count(), 3);
211 let next_before = coll.next_id;
212
213 assert_eq!(coll.truncate(), 3);
214 assert_eq!(coll.live_count(), 0);
215 assert!(coll.surrogate_to_local.is_empty());
216 assert!(coll.surrogate_map.is_empty());
217 assert!(coll.building.is_empty());
218 assert!(coll.sealed.is_empty());
219 assert_eq!(coll.dim(), 2);
220 assert_eq!(coll.next_id, next_before, "ids stay monotonic");
221 assert_eq!(coll.growing_base_id, next_before);
222 assert!(
223 coll.payload.field_names().any(|f| f == "owner"),
224 "registered payload index survives"
225 );
226 let hits = coll
227 .payload
228 .pre_filter(&super::super::payload_index::FilterPredicate::Eq {
229 field: "owner".to_string(),
230 value: Value::String("a".into()),
231 })
232 .expect("owner is indexed");
233 assert!(hits.is_empty(), "payload rows cleared");
234
235 let s = Surrogate::new(42);
236 let id = coll.insert_with_surrogate(vec![0.5, 0.5], s).unwrap();
237 assert_eq!(coll.local_for_surrogate(s), Some(id));
238 assert_eq!(coll.live_count(), 1);
239 }
240
241 #[test]
242 fn a_one_vector_multi_vector_document_is_listed() {
243 let mut coll = VectorCollection::with_seal_threshold(2, HnswParams::default(), 64);
244 coll.insert_with_surrogate(vec![1.0, 0.0], Surrogate::new(1))
245 .unwrap();
246 let one: [&[f32]; 1] = [&[0.0, 1.0]];
247 coll.insert_multi_vector(&one, Surrogate::new(9)).unwrap();
248 let two: [&[f32]; 2] = [&[0.5, 0.5], &[0.2, 0.8]];
249 coll.insert_multi_vector(&two, Surrogate::new(5)).unwrap();
250
251 assert_eq!(
252 coll.multi_vector_documents(),
253 vec![Surrogate::new(5), Surrogate::new(9)],
254 "every multi-vector document is listed, the one-vector one included; \
255 the single-vector row is not"
256 );
257 }
258}