1use std::collections::{HashMap, HashSet};
11use std::sync::Arc;
12
13use parking_lot::Mutex;
14
15use crate::segment::SegmentId;
16use crate::segment::TrainedVectorStructures;
17
18#[derive(Clone)]
22pub struct PublishedIndexGeneration {
23 pub publication_id: u64,
24 pub schema: Arc<crate::dsl::Schema>,
25 pub trained_vectors: Option<Arc<TrainedVectorStructures>>,
26}
27
28struct TrackerInner {
30 ref_counts: HashMap<String, usize>,
31 pending_deletions: HashMap<String, SegmentId>,
32 scheduled_deletions: HashSet<String>,
34}
35
36pub struct SegmentTracker {
38 inner: Mutex<TrackerInner>,
39}
40
41impl SegmentTracker {
42 pub fn new() -> Self {
44 Self {
45 inner: Mutex::new(TrackerInner {
46 ref_counts: HashMap::new(),
47 pending_deletions: HashMap::new(),
48 scheduled_deletions: HashSet::new(),
49 }),
50 }
51 }
52
53 pub fn register(&self, segment_id: &str) {
55 let mut inner = self.inner.lock();
56 inner.ref_counts.entry(segment_id.to_string()).or_insert(0);
57 }
58
59 pub fn acquire(&self, segment_ids: &[String]) -> Vec<String> {
62 let mut inner = self.inner.lock();
63 let mut acquired = Vec::with_capacity(segment_ids.len());
64 for id in segment_ids {
65 if inner.pending_deletions.contains_key(id) || inner.scheduled_deletions.contains(id) {
66 continue;
67 }
68 *inner.ref_counts.entry(id.clone()).or_insert(0) += 1;
69 acquired.push(id.clone());
70 }
71 acquired
72 }
73
74 pub fn release(&self, segment_ids: &[String]) -> Vec<SegmentId> {
77 let mut inner = self.inner.lock();
78 let mut ready_for_deletion = Vec::new();
79
80 for id in segment_ids {
81 if let Some(count) = inner.ref_counts.get_mut(id) {
82 *count = count.saturating_sub(1);
83
84 if *count == 0
85 && let Some(segment_id) = inner.pending_deletions.remove(id)
86 {
87 inner.ref_counts.remove(id);
88 inner.scheduled_deletions.insert(id.clone());
89 ready_for_deletion.push(segment_id);
90 }
91 }
92 }
93
94 ready_for_deletion
95 }
96
97 pub fn mark_for_deletion(&self, segment_ids: &[String]) -> Vec<SegmentId> {
101 let mut inner = self.inner.lock();
102 let mut ready_for_deletion = Vec::new();
103
104 for id_str in segment_ids {
105 let Some(segment_id) = SegmentId::from_hex(id_str) else {
106 continue;
107 };
108
109 let Some(&ref_count) = inner.ref_counts.get(id_str) else {
111 continue;
112 };
113
114 if ref_count == 0 {
115 inner.ref_counts.remove(id_str);
116 inner.scheduled_deletions.insert(id_str.clone());
117 ready_for_deletion.push(segment_id);
118 } else {
119 inner.pending_deletions.insert(id_str.clone(), segment_id);
120 }
121 }
122
123 ready_for_deletion
124 }
125
126 pub fn is_pending_deletion(&self, segment_id: &str) -> bool {
128 self.inner.lock().pending_deletions.contains_key(segment_id)
129 }
130
131 pub fn is_deletion_protected(&self, segment_id: &str) -> bool {
138 let inner = self.inner.lock();
139 inner.pending_deletions.contains_key(segment_id)
140 || inner.scheduled_deletions.contains(segment_id)
141 }
142
143 pub fn complete_deletion(&self, segment_ids: &[SegmentId]) {
148 let mut inner = self.inner.lock();
149 for segment_id in segment_ids {
150 inner.scheduled_deletions.remove(&segment_id.to_hex());
151 }
152 }
153
154 pub fn ref_count(&self, segment_id: &str) -> usize {
156 self.inner
157 .lock()
158 .ref_counts
159 .get(segment_id)
160 .copied()
161 .unwrap_or(0)
162 }
163}
164
165impl Default for SegmentTracker {
166 fn default() -> Self {
167 Self::new()
168 }
169}
170
171pub struct SegmentSnapshot {
176 deletions: std::collections::HashMap<String, (u32, super::DeletionMeta)>,
177 deletion_ids: Vec<String>,
178 tracker: Arc<SegmentTracker>,
179 segment_ids: Vec<String>,
180 generation: Option<Arc<PublishedIndexGeneration>>,
184 delete_fn: Option<Arc<dyn Fn(Vec<SegmentId>) + Send + Sync>>,
186}
187
188impl SegmentSnapshot {
189 pub fn new(tracker: Arc<SegmentTracker>, segment_ids: Vec<String>) -> Self {
191 Self {
192 tracker,
193 segment_ids,
194 generation: None,
195 deletions: Default::default(),
196 deletion_ids: Vec::new(),
197 delete_fn: None,
198 }
199 }
200
201 pub fn with_delete_fn(
203 tracker: Arc<SegmentTracker>,
204 segment_ids: Vec<String>,
205 delete_fn: Arc<dyn Fn(Vec<SegmentId>) + Send + Sync>,
206 ) -> Self {
207 Self {
208 tracker,
209 segment_ids,
210 generation: None,
211 deletions: Default::default(),
212 deletion_ids: Vec::new(),
213 delete_fn: Some(delete_fn),
214 }
215 }
216
217 pub(crate) fn with_generation(
220 tracker: Arc<SegmentTracker>,
221 segment_ids: Vec<String>,
222 generation: Arc<PublishedIndexGeneration>,
223 delete_fn: Arc<dyn Fn(Vec<SegmentId>) + Send + Sync>,
224 ) -> Self {
225 Self {
226 tracker,
227 segment_ids,
228 generation: Some(generation),
229 deletions: Default::default(),
230 deletion_ids: Vec::new(),
231 delete_fn: Some(delete_fn),
232 }
233 }
234
235 pub(crate) fn with_deletions(mut self, metadata: &crate::index::IndexMetadata) -> Self {
236 self.deletions = metadata
237 .segment_metas
238 .iter()
239 .filter_map(|(id, info)| {
240 info.deletions
241 .clone()
242 .map(|d| (id.clone(), (info.num_docs, d)))
243 })
244 .collect();
245 let ids: Vec<_> = self.deletions.values().map(|(_, d)| d.id.clone()).collect();
246 self.deletion_ids = self.tracker.acquire(&ids);
247 self
248 }
249
250 pub(crate) fn deletions(
251 &self,
252 ) -> &std::collections::HashMap<String, (u32, super::DeletionMeta)> {
253 &self.deletions
254 }
255
256 pub fn segment_ids(&self) -> &[String] {
258 &self.segment_ids
259 }
260
261 pub(crate) fn published_generation(&self) -> Option<Arc<PublishedIndexGeneration>> {
262 self.generation.clone()
263 }
264
265 pub fn is_empty(&self) -> bool {
267 self.segment_ids.is_empty()
268 }
269
270 pub fn len(&self) -> usize {
272 self.segment_ids.len()
273 }
274}
275
276impl Drop for SegmentSnapshot {
277 fn drop(&mut self) {
278 let mut to_delete = self.tracker.release(&self.segment_ids);
279 to_delete.extend(self.tracker.release(&self.deletion_ids));
280 if !to_delete.is_empty() {
281 if let Some(delete_fn) = &self.delete_fn {
282 log::info!(
283 "[segment_snapshot] dropping snapshot, deleting {} deferred segments",
284 to_delete.len()
285 );
286 delete_fn(to_delete);
287 } else {
288 log::warn!(
289 "[segment_snapshot] {} segments ready for deletion but no delete_fn provided",
290 to_delete.len()
291 );
292 }
293 }
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 const SEG1: &str = "00000000000000000000000000000001";
302 const SEG2: &str = "00000000000000000000000000000002";
303
304 #[test]
305 fn test_tracker_register_and_acquire() {
306 let tracker = SegmentTracker::new();
307
308 tracker.register(SEG1);
309 tracker.register(SEG2);
310
311 let acquired = tracker.acquire(&[SEG1.to_string(), SEG2.to_string()]);
312 assert_eq!(acquired.len(), 2);
313
314 assert_eq!(tracker.ref_count(SEG1), 1);
315 assert_eq!(tracker.ref_count(SEG2), 1);
316 }
317
318 #[test]
319 fn test_tracker_release() {
320 let tracker = SegmentTracker::new();
321
322 tracker.register(SEG1);
323 tracker.acquire(&[SEG1.to_string()]);
324 tracker.acquire(&[SEG1.to_string()]);
325
326 assert_eq!(tracker.ref_count(SEG1), 2);
327
328 tracker.release(&[SEG1.to_string()]);
329 assert_eq!(tracker.ref_count(SEG1), 1);
330
331 tracker.release(&[SEG1.to_string()]);
332 assert_eq!(tracker.ref_count(SEG1), 0);
333 }
334
335 #[test]
336 fn test_tracker_mark_for_deletion_no_refs() {
337 let tracker = SegmentTracker::new();
338
339 tracker.register(SEG1);
340
341 let ready = tracker.mark_for_deletion(&[SEG1.to_string()]);
342 assert_eq!(ready.len(), 1);
343 assert!(!tracker.is_pending_deletion(SEG1));
344 assert!(tracker.is_deletion_protected(SEG1));
345
346 tracker.complete_deletion(&ready);
347 assert!(!tracker.is_deletion_protected(SEG1));
348 }
349
350 #[test]
351 fn test_tracker_mark_for_deletion_with_refs() {
352 let tracker = SegmentTracker::new();
353
354 tracker.register(SEG1);
355 tracker.acquire(&[SEG1.to_string()]);
356
357 let ready = tracker.mark_for_deletion(&[SEG1.to_string()]);
358 assert!(ready.is_empty());
359 assert!(tracker.is_pending_deletion(SEG1));
360
361 let deleted = tracker.release(&[SEG1.to_string()]);
362 assert_eq!(deleted.len(), 1);
363 assert!(!tracker.is_pending_deletion(SEG1));
364 assert!(tracker.is_deletion_protected(SEG1));
365
366 tracker.complete_deletion(&deleted);
367 assert!(!tracker.is_deletion_protected(SEG1));
368 }
369
370 #[test]
371 fn test_tracker_double_mark_for_deletion() {
372 let tracker = SegmentTracker::new();
373 tracker.register(SEG1);
374
375 let ready1 = tracker.mark_for_deletion(&[SEG1.to_string()]);
376 assert_eq!(ready1.len(), 1);
377
378 let ready2 = tracker.mark_for_deletion(&[SEG1.to_string()]);
380 assert!(ready2.is_empty());
381 }
382
383 #[test]
384 fn test_tracker_acquire_unregistered() {
385 let tracker = SegmentTracker::new();
386
387 let acquired = tracker.acquire(&[SEG1.to_string()]);
389 assert_eq!(acquired.len(), 1);
390 assert_eq!(tracker.ref_count(SEG1), 1);
391 }
392
393 #[test]
394 fn test_tracker_release_without_acquire() {
395 let tracker = SegmentTracker::new();
396 tracker.register(SEG1);
397
398 let deleted = tracker.release(&[SEG1.to_string()]);
400 assert!(deleted.is_empty());
401 assert_eq!(tracker.ref_count(SEG1), 0);
402 }
403
404 #[test]
405 fn test_snapshot_drop_triggers_deferred_delete() {
406 use std::sync::atomic::{AtomicUsize, Ordering};
407
408 let tracker = Arc::new(SegmentTracker::new());
409 tracker.register(SEG1);
410 tracker.register(SEG2);
411
412 let delete_count = Arc::new(AtomicUsize::new(0));
413 let dc = Arc::clone(&delete_count);
414 let delete_fn: Arc<dyn Fn(Vec<SegmentId>) + Send + Sync> = Arc::new(move |ids| {
415 dc.fetch_add(ids.len(), Ordering::SeqCst);
416 });
417
418 let acquired = tracker.acquire(&[SEG1.to_string(), SEG2.to_string()]);
420 let snapshot =
421 SegmentSnapshot::with_delete_fn(Arc::clone(&tracker), acquired, Arc::clone(&delete_fn));
422
423 let ready = tracker.mark_for_deletion(&[SEG1.to_string(), SEG2.to_string()]);
425 assert!(ready.is_empty());
426 assert!(tracker.is_pending_deletion(SEG1));
427 assert!(tracker.is_pending_deletion(SEG2));
428
429 drop(snapshot);
431 assert_eq!(delete_count.load(Ordering::SeqCst), 2);
432 assert!(!tracker.is_pending_deletion(SEG1));
433 assert!(!tracker.is_pending_deletion(SEG2));
434 assert!(tracker.is_deletion_protected(SEG1));
435 assert!(tracker.is_deletion_protected(SEG2));
436
437 tracker.complete_deletion(&[
438 SegmentId::from_hex(SEG1).unwrap(),
439 SegmentId::from_hex(SEG2).unwrap(),
440 ]);
441 assert!(!tracker.is_deletion_protected(SEG1));
442 assert!(!tracker.is_deletion_protected(SEG2));
443 }
444}