hermes_core/segment/
tracker.rs1use 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 tracker: Arc<SegmentTracker>,
177 segment_ids: Vec<String>,
178 generation: Option<Arc<PublishedIndexGeneration>>,
182 delete_fn: Option<Arc<dyn Fn(Vec<SegmentId>) + Send + Sync>>,
184}
185
186impl SegmentSnapshot {
187 pub fn new(tracker: Arc<SegmentTracker>, segment_ids: Vec<String>) -> Self {
189 Self {
190 tracker,
191 segment_ids,
192 generation: None,
193 delete_fn: None,
194 }
195 }
196
197 pub fn with_delete_fn(
199 tracker: Arc<SegmentTracker>,
200 segment_ids: Vec<String>,
201 delete_fn: Arc<dyn Fn(Vec<SegmentId>) + Send + Sync>,
202 ) -> Self {
203 Self {
204 tracker,
205 segment_ids,
206 generation: None,
207 delete_fn: Some(delete_fn),
208 }
209 }
210
211 pub(crate) fn with_generation(
214 tracker: Arc<SegmentTracker>,
215 segment_ids: Vec<String>,
216 generation: Arc<PublishedIndexGeneration>,
217 delete_fn: Arc<dyn Fn(Vec<SegmentId>) + Send + Sync>,
218 ) -> Self {
219 Self {
220 tracker,
221 segment_ids,
222 generation: Some(generation),
223 delete_fn: Some(delete_fn),
224 }
225 }
226
227 pub fn segment_ids(&self) -> &[String] {
229 &self.segment_ids
230 }
231
232 pub(crate) fn published_generation(&self) -> Option<Arc<PublishedIndexGeneration>> {
233 self.generation.clone()
234 }
235
236 pub fn is_empty(&self) -> bool {
238 self.segment_ids.is_empty()
239 }
240
241 pub fn len(&self) -> usize {
243 self.segment_ids.len()
244 }
245}
246
247impl Drop for SegmentSnapshot {
248 fn drop(&mut self) {
249 let to_delete = self.tracker.release(&self.segment_ids);
250 if !to_delete.is_empty() {
251 if let Some(delete_fn) = &self.delete_fn {
252 log::info!(
253 "[segment_snapshot] dropping snapshot, deleting {} deferred segments",
254 to_delete.len()
255 );
256 delete_fn(to_delete);
257 } else {
258 log::warn!(
259 "[segment_snapshot] {} segments ready for deletion but no delete_fn provided",
260 to_delete.len()
261 );
262 }
263 }
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270
271 const SEG1: &str = "00000000000000000000000000000001";
272 const SEG2: &str = "00000000000000000000000000000002";
273
274 #[test]
275 fn test_tracker_register_and_acquire() {
276 let tracker = SegmentTracker::new();
277
278 tracker.register(SEG1);
279 tracker.register(SEG2);
280
281 let acquired = tracker.acquire(&[SEG1.to_string(), SEG2.to_string()]);
282 assert_eq!(acquired.len(), 2);
283
284 assert_eq!(tracker.ref_count(SEG1), 1);
285 assert_eq!(tracker.ref_count(SEG2), 1);
286 }
287
288 #[test]
289 fn test_tracker_release() {
290 let tracker = SegmentTracker::new();
291
292 tracker.register(SEG1);
293 tracker.acquire(&[SEG1.to_string()]);
294 tracker.acquire(&[SEG1.to_string()]);
295
296 assert_eq!(tracker.ref_count(SEG1), 2);
297
298 tracker.release(&[SEG1.to_string()]);
299 assert_eq!(tracker.ref_count(SEG1), 1);
300
301 tracker.release(&[SEG1.to_string()]);
302 assert_eq!(tracker.ref_count(SEG1), 0);
303 }
304
305 #[test]
306 fn test_tracker_mark_for_deletion_no_refs() {
307 let tracker = SegmentTracker::new();
308
309 tracker.register(SEG1);
310
311 let ready = tracker.mark_for_deletion(&[SEG1.to_string()]);
312 assert_eq!(ready.len(), 1);
313 assert!(!tracker.is_pending_deletion(SEG1));
314 assert!(tracker.is_deletion_protected(SEG1));
315
316 tracker.complete_deletion(&ready);
317 assert!(!tracker.is_deletion_protected(SEG1));
318 }
319
320 #[test]
321 fn test_tracker_mark_for_deletion_with_refs() {
322 let tracker = SegmentTracker::new();
323
324 tracker.register(SEG1);
325 tracker.acquire(&[SEG1.to_string()]);
326
327 let ready = tracker.mark_for_deletion(&[SEG1.to_string()]);
328 assert!(ready.is_empty());
329 assert!(tracker.is_pending_deletion(SEG1));
330
331 let deleted = tracker.release(&[SEG1.to_string()]);
332 assert_eq!(deleted.len(), 1);
333 assert!(!tracker.is_pending_deletion(SEG1));
334 assert!(tracker.is_deletion_protected(SEG1));
335
336 tracker.complete_deletion(&deleted);
337 assert!(!tracker.is_deletion_protected(SEG1));
338 }
339
340 #[test]
341 fn test_tracker_double_mark_for_deletion() {
342 let tracker = SegmentTracker::new();
343 tracker.register(SEG1);
344
345 let ready1 = tracker.mark_for_deletion(&[SEG1.to_string()]);
346 assert_eq!(ready1.len(), 1);
347
348 let ready2 = tracker.mark_for_deletion(&[SEG1.to_string()]);
350 assert!(ready2.is_empty());
351 }
352
353 #[test]
354 fn test_tracker_acquire_unregistered() {
355 let tracker = SegmentTracker::new();
356
357 let acquired = tracker.acquire(&[SEG1.to_string()]);
359 assert_eq!(acquired.len(), 1);
360 assert_eq!(tracker.ref_count(SEG1), 1);
361 }
362
363 #[test]
364 fn test_tracker_release_without_acquire() {
365 let tracker = SegmentTracker::new();
366 tracker.register(SEG1);
367
368 let deleted = tracker.release(&[SEG1.to_string()]);
370 assert!(deleted.is_empty());
371 assert_eq!(tracker.ref_count(SEG1), 0);
372 }
373
374 #[test]
375 fn test_snapshot_drop_triggers_deferred_delete() {
376 use std::sync::atomic::{AtomicUsize, Ordering};
377
378 let tracker = Arc::new(SegmentTracker::new());
379 tracker.register(SEG1);
380 tracker.register(SEG2);
381
382 let delete_count = Arc::new(AtomicUsize::new(0));
383 let dc = Arc::clone(&delete_count);
384 let delete_fn: Arc<dyn Fn(Vec<SegmentId>) + Send + Sync> = Arc::new(move |ids| {
385 dc.fetch_add(ids.len(), Ordering::SeqCst);
386 });
387
388 let acquired = tracker.acquire(&[SEG1.to_string(), SEG2.to_string()]);
390 let snapshot =
391 SegmentSnapshot::with_delete_fn(Arc::clone(&tracker), acquired, Arc::clone(&delete_fn));
392
393 let ready = tracker.mark_for_deletion(&[SEG1.to_string(), SEG2.to_string()]);
395 assert!(ready.is_empty());
396 assert!(tracker.is_pending_deletion(SEG1));
397 assert!(tracker.is_pending_deletion(SEG2));
398
399 drop(snapshot);
401 assert_eq!(delete_count.load(Ordering::SeqCst), 2);
402 assert!(!tracker.is_pending_deletion(SEG1));
403 assert!(!tracker.is_pending_deletion(SEG2));
404 assert!(tracker.is_deletion_protected(SEG1));
405 assert!(tracker.is_deletion_protected(SEG2));
406
407 tracker.complete_deletion(&[
408 SegmentId::from_hex(SEG1).unwrap(),
409 SegmentId::from_hex(SEG2).unwrap(),
410 ]);
411 assert!(!tracker.is_deletion_protected(SEG1));
412 assert!(!tracker.is_deletion_protected(SEG2));
413 }
414}