1use parking_lot::RwLock;
16use std::collections::HashMap;
17use std::sync::atomic::{AtomicU64, Ordering};
18use std::sync::Arc;
19
20use super::entity::{EntityId, UnifiedEntity};
21use super::metadata::{Metadata, MetadataFilter};
22use super::segment::{
23 GrowingSegment, SegmentConfig, SegmentError, SegmentId, SegmentState, SegmentStats,
24 UnifiedSegment, ZoneColPred, ZoneColPredKind,
25};
26use crate::storage::btree::visibility_map::VisibilityMap;
27
28pub const CONSOLIDATION_TOMBSTONE_RATIO: f64 = 0.20;
35
36pub const CONSOLIDATION_FRAGMENTATION_RATIO: f64 = 0.30;
41
42pub const CONSOLIDATION_ENTITIES_PER_TICK: usize = 4_096;
48
49#[derive(Debug, Clone)]
51pub struct ManagerConfig {
52 pub segment_config: SegmentConfig,
54 pub max_sealed_segments: usize,
56 pub idle_seal_secs: u64,
58 pub enable_consolidation: bool,
60 pub consolidation_entities_per_tick: usize,
62 pub enable_archival: bool,
64 pub archive_age_secs: u64,
66}
67
68impl Default for ManagerConfig {
69 fn default() -> Self {
70 Self {
71 segment_config: SegmentConfig::default(),
72 max_sealed_segments: 10,
73 idle_seal_secs: 300, enable_consolidation: true,
75 consolidation_entities_per_tick: CONSOLIDATION_ENTITIES_PER_TICK,
76 enable_archival: true,
77 archive_age_secs: 86400 * 7, }
79 }
80}
81
82#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
87pub struct ConsolidationStats {
88 pub runs_started: u64,
90 pub runs_completed: u64,
92 pub segments_merged: u64,
94 pub tombstones_reclaimed: u64,
96 pub bytes_reclaimed: u64,
98}
99
100#[derive(Debug, Clone, Default)]
102pub struct ManagerStats {
103 pub total_entities: usize,
105 pub growing_count: usize,
107 pub sealed_count: usize,
109 pub archived_count: usize,
111 pub total_memory_bytes: usize,
113 pub seal_ops: u64,
115 pub consolidation: ConsolidationStats,
117}
118
119#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
120pub struct SegmentScanStats {
121 pub segments_total: u64,
122 pub segments_scanned: u64,
123 pub segments_pruned: u64,
124}
125
126#[derive(Debug, Clone)]
128pub enum LifecycleEvent {
129 SegmentCreated(SegmentId),
130 SegmentSealed(SegmentId),
131 SegmentConsolidated {
132 source: Vec<SegmentId>,
133 target: SegmentId,
134 },
135 SegmentArchived(SegmentId),
136 EntityInserted(EntityId, SegmentId),
137 EntityDeleted(EntityId, SegmentId),
138}
139
140struct Consolidation {
147 sources: Vec<SourceProgress>,
149 merged_id: SegmentId,
151 merged: GrowingSegment,
153 cursor: usize,
155 pending_ids: Vec<EntityId>,
157 pending_cursor: usize,
159 entities_copied: u64,
161}
162
163struct SourceProgress {
165 id: SegmentId,
166 copied: Vec<EntityId>,
168 tombstones_at_open: Option<usize>,
177}
178
179pub struct SegmentManager {
181 collection: String,
183 config: ManagerConfig,
185 next_segment_id: AtomicU64,
187 next_entity_id: AtomicU64,
189 next_row_id: AtomicU64,
191 total_entities_atomic: AtomicU64,
195 growing: RwLock<Option<Arc<RwLock<GrowingSegment>>>>,
197 sealed: RwLock<Vec<Arc<RwLock<GrowingSegment>>>>,
199 archived: RwLock<Vec<SegmentId>>,
201 entity_segment: RwLock<HashMap<EntityId, SegmentId>>,
205 column_schema: RwLock<Option<Arc<Vec<String>>>>,
208 stats: RwLock<ManagerStats>,
210 consolidation: RwLock<Option<Consolidation>>,
213 events: RwLock<Vec<LifecycleEvent>>,
215 visibility_map: VisibilityMap,
219}
220
221impl SegmentManager {
222 pub fn new(collection: impl Into<String>) -> Self {
224 Self::with_config(collection, ManagerConfig::default())
225 }
226
227 pub fn with_config(collection: impl Into<String>, config: ManagerConfig) -> Self {
229 Self {
230 collection: collection.into(),
231 config,
232 next_segment_id: AtomicU64::new(1),
233 next_entity_id: AtomicU64::new(1),
234 next_row_id: AtomicU64::new(1),
235 total_entities_atomic: AtomicU64::new(0),
236 growing: RwLock::new(None),
237 sealed: RwLock::new(Vec::new()),
238 archived: RwLock::new(Vec::new()),
239 entity_segment: RwLock::new(HashMap::new()),
240 column_schema: RwLock::new(None),
241 stats: RwLock::new(ManagerStats::default()),
242 consolidation: RwLock::new(None),
243 events: RwLock::new(Vec::new()),
244 visibility_map: VisibilityMap::new(),
245 }
246 }
247
248 pub fn get_or_init_schema(
250 &self,
251 named: &HashMap<String, crate::storage::schema::Value>,
252 ) -> Arc<Vec<String>> {
253 {
254 let schema = self.column_schema.read();
255 if let Some(ref s) = *schema {
256 return Arc::clone(s);
257 }
258 }
259 let cols: Vec<String> = named.keys().cloned().collect();
260 let arc = Arc::new(cols);
261 *self.column_schema.write() = Some(Arc::clone(&arc));
262 arc
263 }
264
265 pub fn column_schema(&self) -> Option<Arc<Vec<String>>> {
267 self.column_schema.read().clone()
268 }
269
270 pub(crate) fn set_column_schema_if_empty(&self, columns: Vec<String>) {
271 if columns.is_empty() {
272 return;
273 }
274 let mut schema = self.column_schema.write();
275 if schema.is_none() {
276 *schema = Some(Arc::new(columns));
277 }
278 }
279
280 pub fn collection(&self) -> &str {
282 &self.collection
283 }
284
285 pub fn config(&self) -> &ManagerConfig {
287 &self.config
288 }
289
290 pub fn stats(&self) -> ManagerStats {
295 let mut s = self.stats.read().clone();
296 s.total_entities = self.total_entities_atomic.load(Ordering::Relaxed) as usize;
297 s.total_memory_bytes = self.resident_bytes() as usize;
298 s
299 }
300
301 pub fn resident_bytes(&self) -> u64 {
305 let mut bytes = 0;
306 if let Some(growing_arc) = self.growing.read().as_ref() {
307 bytes += growing_arc.read().resident_bytes();
308 }
309 for segment in self.sealed.read().iter() {
310 bytes += segment.read().resident_bytes();
311 }
312 bytes
313 }
314
315 pub fn memory_bytes(&self) -> u64 {
322 let growing = self
323 .growing
324 .read()
325 .as_ref()
326 .map_or(0, |segment| segment.read().memory_bytes());
327
328 self.sealed
329 .read()
330 .iter()
331 .map(|segment| segment.read().memory_bytes())
332 .fold(growing, u64::saturating_add)
333 }
334
335 pub fn reclaimable_bytes(&self) -> u64 {
337 self.sealed
338 .read()
339 .iter()
340 .map(|segment| segment.read().reclaimable_bytes())
341 .fold(0, u64::saturating_add)
342 }
343
344 pub fn next_entity_id(&self) -> EntityId {
346 EntityId::new(self.next_entity_id.fetch_add(1, Ordering::SeqCst))
347 }
348
349 pub fn next_row_id(&self) -> u64 {
351 self.next_row_id.fetch_add(1, Ordering::SeqCst)
352 }
353
354 pub fn reserve_row_ids(&self, n: u64) -> std::ops::Range<u64> {
358 let start = self.next_row_id.fetch_add(n, Ordering::SeqCst);
359 start..start + n
360 }
361
362 pub fn register_row_id(&self, id: u64) {
365 let candidate = id.saturating_add(1);
366 let mut current = self.next_row_id.load(Ordering::SeqCst);
367 while candidate > current {
368 match self.next_row_id.compare_exchange(
369 current,
370 candidate,
371 Ordering::SeqCst,
372 Ordering::SeqCst,
373 ) {
374 Ok(_) => break,
375 Err(updated) => current = updated,
376 }
377 }
378 }
379
380 fn get_or_create_growing(&self) -> Arc<RwLock<GrowingSegment>> {
387 {
389 let growing = self.growing.read();
390 if let Some(segment) = growing.as_ref() {
391 return Arc::clone(segment);
392 }
393 }
394
395 let mut growing = self.growing.write();
397 if let Some(segment) = growing.as_ref() {
399 return Arc::clone(segment);
400 }
401
402 let id = self.next_segment_id.fetch_add(1, Ordering::SeqCst);
403 let segment = GrowingSegment::new(id, &self.collection);
404 let segment_arc = Arc::new(RwLock::new(segment));
405 *growing = Some(Arc::clone(&segment_arc));
406
407 self.emit(LifecycleEvent::SegmentCreated(id));
408
409 self.stats.write().growing_count += 1;
412
413 segment_arc
414 }
415
416 pub fn insert(&self, mut entity: UnifiedEntity) -> Result<EntityId, SegmentError> {
418 self.maybe_seal_growing()?;
420
421 let segment_arc = self.get_or_create_growing();
422 let mut segment = segment_arc.write();
423
424 if entity.id.raw() == 0 {
426 entity.id = self.next_entity_id();
427 }
428
429 let entity_id = entity.id;
430 let segment_id = segment.id();
431
432 segment.insert(entity)?;
433
434 self.total_entities_atomic.fetch_add(1, Ordering::Relaxed);
436
437 self.emit(LifecycleEvent::EntityInserted(entity_id, segment_id));
446
447 Ok(entity_id)
448 }
449
450 pub fn insert_batch(
452 &self,
453 entities: Vec<UnifiedEntity>,
454 ) -> Result<Vec<EntityId>, SegmentError> {
455 let mut ids = Vec::with_capacity(entities.len());
456 for entity in entities {
457 ids.push(self.insert(entity)?);
458 }
459 Ok(ids)
460 }
461
462 pub fn bulk_insert(
465 &self,
466 mut entities: Vec<UnifiedEntity>,
467 ) -> Result<Vec<EntityId>, SegmentError> {
468 for entity in &mut entities {
470 if entity.id.raw() == 0 {
471 entity.id = self.next_entity_id();
472 }
473 if let super::entity::EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
474 if *row_id == 0 {
475 *row_id = self.next_row_id();
476 } else {
477 self.register_row_id(*row_id);
478 }
479 }
480 }
481
482 if let Some(first_row) = entities.first() {
485 if let super::entity::EntityData::Row(ref row) = first_row.data {
486 if let Some(ref named) = row.named {
487 let schema = self.get_or_init_schema(named);
488 for entity in &mut entities {
489 if let super::entity::EntityData::Row(ref mut row) = entity.data {
490 if let Some(named) = row.named.take() {
491 let mut cols = Vec::with_capacity(schema.len());
492 for col_name in schema.iter() {
493 cols.push(
494 named
495 .get(col_name)
496 .cloned()
497 .unwrap_or(crate::storage::schema::Value::Null),
498 );
499 }
500 row.columns = cols;
501 row.schema = Some(Arc::clone(&schema));
502 }
503 }
504 }
505 }
506 }
507 }
508
509 let segment_arc = self.get_or_create_growing();
510 let mut segment = segment_arc.write();
511 let segment_id = segment.id();
512
513 let ids = segment.bulk_insert(entities)?;
515
516 self.total_entities_atomic
521 .fetch_add(ids.len() as u64, Ordering::Relaxed);
522
523 Ok(ids)
524 }
525
526 pub fn get(&self, id: EntityId) -> Option<UnifiedEntity> {
528 if let Some(growing_arc) = self.growing.read().as_ref() {
530 let growing = growing_arc.read();
531 if let Some(entity) = growing.get(id) {
532 return Some(entity.clone());
533 }
534 }
535
536 let sealed = self.sealed.read();
538 for segment in sealed.iter() {
539 let seg = segment.read();
540 if let Some(entity) = seg.get(id) {
541 return Some(entity.clone());
542 }
543 }
544
545 None
546 }
547
548 pub fn get_many(&self, ids: &[EntityId]) -> Vec<Option<UnifiedEntity>> {
553 let mut out: Vec<Option<UnifiedEntity>> = vec![None; ids.len()];
554 let mut remaining: Vec<usize> = (0..ids.len()).collect(); if let Some(growing_arc) = self.growing.read().as_ref() {
559 let growing = if let Some(g) = growing_arc.try_read() {
560 g
561 } else {
562 growing_arc.read()
563 };
564 remaining.retain(|&i| {
565 if let Some(entity) = growing.get(ids[i]) {
566 out[i] = Some(entity.clone());
567 false } else {
569 true }
571 });
572 }
573
574 if remaining.is_empty() {
575 return out;
576 }
577
578 let sealed = self.sealed.read();
580 for segment in sealed.iter() {
581 if remaining.is_empty() {
582 break;
583 }
584 let seg = segment.read();
585 remaining.retain(|&i| {
586 if let Some(entity) = seg.get(ids[i]) {
587 out[i] = Some(entity.clone());
588 false
589 } else {
590 true
591 }
592 });
593 }
594
595 out
596 }
597
598 pub fn for_each_id<F>(&self, ids: &[EntityId], mut f: F)
611 where
612 F: FnMut(usize, &UnifiedEntity),
613 {
614 thread_local! {
623 static REMAINING_SCRATCH: std::cell::Cell<Vec<usize>> =
624 const { std::cell::Cell::new(Vec::new()) };
625 }
626
627 let mut remaining: Vec<usize> = REMAINING_SCRATCH.with(|cell| cell.take());
628 remaining.clear();
629
630 if let Some(growing_arc) = self.growing.read().as_ref() {
631 let growing = if let Some(g) = growing_arc.try_read() {
632 g
633 } else {
634 growing_arc.read()
635 };
636 for (i, id) in ids.iter().enumerate() {
637 if let Some(entity) = growing.get(*id) {
638 f(i, entity);
639 } else {
640 remaining.push(i);
641 }
642 }
643 } else {
644 remaining.reserve(ids.len());
645 remaining.extend(0..ids.len());
646 }
647
648 if !remaining.is_empty() {
649 let sealed = self.sealed.read();
650 for segment in sealed.iter() {
651 if remaining.is_empty() {
652 break;
653 }
654 let seg = segment.read();
655 remaining.retain(|&i| {
656 if let Some(entity) = seg.get(ids[i]) {
657 f(i, entity);
658 false
659 } else {
660 true
661 }
662 });
663 }
664 }
665
666 REMAINING_SCRATCH.with(|cell| cell.set(remaining));
667 }
668
669 fn scan_for_entity(&self, id: EntityId) -> Option<UnifiedEntity> {
671 if let Some(growing_arc) = self.growing.read().as_ref() {
673 let growing = growing_arc.read();
674 if let Some(entity) = growing.get(id) {
675 return Some(entity.clone());
676 }
677 }
678
679 let sealed = self.sealed.read();
681 for segment in sealed.iter() {
682 if let Some(entity) = segment.get(id) {
683 return Some(entity.clone());
684 }
685 }
686
687 None
688 }
689
690 fn find_sealed_segment_arc(&self, id: EntityId) -> Option<Arc<RwLock<GrowingSegment>>> {
691 let sealed = self.sealed.read();
692 sealed
693 .iter()
694 .find(|segment_arc| segment_arc.read().contains(id))
695 .map(Arc::clone)
696 }
697
698 fn rewrite_sealed_entity_into_growing(
699 &self,
700 entity: UnifiedEntity,
701 metadata: Option<&Metadata>,
702 ) -> Result<(), SegmentError> {
703 let entity_id = entity.id;
704 let sealed_arc = self
705 .find_sealed_segment_arc(entity_id)
706 .ok_or(SegmentError::NotFound(entity_id))?;
707
708 let metadata_to_apply = {
709 let mut sealed = sealed_arc.write();
710 let existing_metadata = sealed.get_metadata(entity_id);
711 if !sealed.force_delete(entity_id) {
712 return Err(SegmentError::NotFound(entity_id));
713 }
714 metadata.cloned().or(existing_metadata)
715 };
716
717 let growing_arc = self.get_or_create_growing();
718 let growing_id = {
719 let mut growing = growing_arc.write();
720 growing.insert(entity)?;
721 if let Some(metadata) = metadata_to_apply {
722 growing.set_metadata(entity_id, metadata)?;
723 }
724 growing.id()
725 };
726
727 self.entity_segment.write().insert(entity_id, growing_id);
728 Ok(())
729 }
730
731 pub fn update(&self, entity: UnifiedEntity) -> Result<(), SegmentError> {
733 let entity_id = entity.id;
734 let mut entity = Some(entity);
735
736 if let Some(growing_arc) = self.growing.read().as_ref() {
738 let mut growing = growing_arc.write();
739 if growing.contains(entity_id) && growing.state().is_writable() {
740 return growing.update(entity.take().expect("entity already moved"));
741 }
742 }
743
744 let segment_id = self.entity_segment.read().get(&entity_id).copied();
746 if let Some(seg_id) = segment_id {
747 if let Some(growing_arc) = self.growing.read().as_ref() {
748 let mut growing = growing_arc.write();
749 if growing.id() == seg_id && growing.state().is_writable() {
750 return growing.update(entity.take().expect("entity already moved"));
751 }
752 }
753 }
754
755 if let Some(entity) = entity.take() {
756 return self.rewrite_sealed_entity_into_growing(entity, None);
757 }
758
759 Err(SegmentError::NotFound(entity_id))
760 }
761
762 pub fn update_with_metadata(
765 &self,
766 entity: UnifiedEntity,
767 metadata: Option<&Metadata>,
768 ) -> Result<(), SegmentError> {
769 let entity_id = entity.id;
770 let mut entity = Some(entity);
771
772 if let Some(growing_arc) = self.growing.read().as_ref() {
774 let mut growing = growing_arc.write();
775 if growing.contains(entity_id) && growing.state().is_writable() {
776 growing.update(entity.take().expect("entity already moved"))?;
777 if let Some(metadata) = metadata {
778 growing.set_metadata(entity_id, metadata.clone())?;
779 }
780 return Ok(());
781 }
782 }
783
784 let segment_id = self.entity_segment.read().get(&entity_id).copied();
786 if let Some(seg_id) = segment_id {
787 if let Some(growing_arc) = self.growing.read().as_ref() {
788 let mut growing = growing_arc.write();
789 if growing.id() == seg_id && growing.state().is_writable() {
790 growing.update(entity.take().expect("entity already moved"))?;
791 if let Some(metadata) = metadata {
792 growing.set_metadata(entity_id, metadata.clone())?;
793 }
794 return Ok(());
795 }
796 }
797 }
798
799 if let Some(entity) = entity.take() {
800 return self.rewrite_sealed_entity_into_growing(entity, metadata);
801 }
802
803 Err(SegmentError::NotFound(entity_id))
804 }
805
806 pub fn update_hot(
810 &self,
811 entity: UnifiedEntity,
812 modified_columns: &[String],
813 ) -> Result<(), SegmentError> {
814 let entity_id = entity.id;
815 let mut entity = Some(entity);
816
817 if let Some(growing_arc) = self.growing.read().as_ref() {
818 let mut growing = growing_arc.write();
819 if growing.contains(entity_id) && growing.state().is_writable() {
820 return growing.update_hot(
821 entity.take().expect("entity already moved"),
822 modified_columns,
823 );
824 }
825 }
826
827 let segment_id = self.entity_segment.read().get(&entity_id).copied();
828 if let Some(seg_id) = segment_id {
829 if let Some(growing_arc) = self.growing.read().as_ref() {
830 let mut growing = growing_arc.write();
831 if growing.id() == seg_id && growing.state().is_writable() {
832 return growing.update_hot(
833 entity.take().expect("entity already moved"),
834 modified_columns,
835 );
836 }
837 }
838 }
839
840 if let Some(entity) = entity.take() {
841 return self.rewrite_sealed_entity_into_growing(entity, None);
842 }
843
844 Err(SegmentError::NotFound(entity_id))
845 }
846
847 pub fn update_hot_with_metadata(
850 &self,
851 entity: UnifiedEntity,
852 modified_columns: &[String],
853 metadata: Option<&Metadata>,
854 ) -> Result<(), SegmentError> {
855 let entity_id = entity.id;
856 let mut entity = Some(entity);
857
858 if let Some(growing_arc) = self.growing.read().as_ref() {
859 let mut growing = growing_arc.write();
860 if growing.contains(entity_id) && growing.state().is_writable() {
861 growing.update_hot(
862 entity.take().expect("entity already moved"),
863 modified_columns,
864 )?;
865 if let Some(metadata) = metadata {
866 growing.set_metadata(entity_id, metadata.clone())?;
867 }
868 return Ok(());
869 }
870 }
871
872 let segment_id = self.entity_segment.read().get(&entity_id).copied();
873 if let Some(seg_id) = segment_id {
874 if let Some(growing_arc) = self.growing.read().as_ref() {
875 let mut growing = growing_arc.write();
876 if growing.id() == seg_id && growing.state().is_writable() {
877 growing.update_hot(
878 entity.take().expect("entity already moved"),
879 modified_columns,
880 )?;
881 if let Some(metadata) = metadata {
882 growing.set_metadata(entity_id, metadata.clone())?;
883 }
884 return Ok(());
885 }
886 }
887 }
888
889 if let Some(entity) = entity.take() {
890 return self.rewrite_sealed_entity_into_growing(entity, metadata);
891 }
892
893 Err(SegmentError::NotFound(entity_id))
894 }
895
896 pub fn update_hot_batch_with_metadata<'a, I>(&self, items: I) -> Result<(), SegmentError>
899 where
900 I: IntoIterator<Item = (&'a UnifiedEntity, &'a [String], Option<&'a Metadata>)>,
901 {
902 let items: Vec<(&UnifiedEntity, &[String], Option<&Metadata>)> =
903 items.into_iter().collect();
904 if items.is_empty() {
905 return Ok(());
906 }
907
908 if let Some(growing_arc) = self.growing.read().as_ref() {
909 let mut growing = growing_arc.write();
910 if growing.state().is_writable() {
911 match growing.update_hot_batch_with_metadata(items.iter().copied()) {
912 Ok(()) => return Ok(()),
913 Err(SegmentError::NotFound(_)) => {}
914 Err(other) => return Err(other),
915 }
916 }
917 }
918
919 for (entity, modified_columns, metadata) in items {
920 self.update_hot_with_metadata(entity.clone(), modified_columns, metadata)?;
921 }
922 Ok(())
923 }
924
925 pub fn delete(&self, id: EntityId) -> Result<bool, SegmentError> {
927 if let Some(growing_arc) = self.growing.read().as_ref() {
930 let mut growing = growing_arc.write();
931 if growing.contains(id) && growing.state().is_writable() {
932 let seg_id = growing.id();
933 let deleted = growing.delete(id)?;
934 if deleted {
935 self.entity_segment.write().remove(&id);
936 self.total_entities_atomic.fetch_sub(1, Ordering::Relaxed);
937 self.emit(LifecycleEvent::EntityDeleted(id, seg_id));
938 }
939 return Ok(deleted);
940 }
941 }
942
943 let segment_id = self.entity_segment.read().get(&id).copied();
946
947 if let Some(seg_id) = segment_id {
948 if let Some(growing_arc) = self.growing.read().as_ref() {
949 let mut growing = growing_arc.write();
950 if growing.id() == seg_id && growing.state().is_writable() {
951 let deleted = growing.delete(id)?;
952 if deleted {
953 self.entity_segment.write().remove(&id);
954 self.total_entities_atomic.fetch_sub(1, Ordering::Relaxed);
955 self.emit(LifecycleEvent::EntityDeleted(id, seg_id));
956 }
957 return Ok(deleted);
958 }
959 }
960 }
961
962 {
965 let sealed = self.sealed.read();
966 for segment_arc in sealed.iter() {
967 let mut seg = segment_arc.write();
968 let seg_id = seg.id();
969 if seg.contains(id) {
970 let deleted = seg.force_delete(id);
971 drop(seg);
972 if deleted {
973 self.entity_segment.write().remove(&id);
974 self.total_entities_atomic.fetch_sub(1, Ordering::Relaxed);
975 self.emit(LifecycleEvent::EntityDeleted(id, seg_id));
976 }
977 return Ok(deleted);
978 }
979 }
980 }
981
982 Ok(false)
983 }
984
985 pub fn delete_batch(&self, ids: &[EntityId]) -> Result<Vec<EntityId>, SegmentError> {
986 if ids.is_empty() {
987 return Ok(Vec::new());
988 }
989
990 let mut deleted_ids = Vec::with_capacity(ids.len());
991
992 if let Some(growing_arc) = self.growing.read().as_ref() {
993 let mut growing = growing_arc.write();
994 if growing.state().is_writable() {
995 let seg_id = growing.id();
996 let deleted = growing.delete_batch(ids)?;
997 if !deleted.is_empty() {
998 {
999 let mut entity_segment = self.entity_segment.write();
1000 for id in &deleted {
1001 entity_segment.remove(id);
1002 }
1003 }
1004 self.total_entities_atomic
1005 .fetch_sub(deleted.len() as u64, Ordering::Relaxed);
1006 for id in &deleted {
1007 self.emit(LifecycleEvent::EntityDeleted(*id, seg_id));
1008 }
1009 deleted_ids.extend(deleted);
1010 }
1011 }
1012 }
1013
1014 if deleted_ids.len() == ids.len() {
1015 return Ok(deleted_ids);
1016 }
1017
1018 let deleted_set: std::collections::HashSet<EntityId> =
1019 deleted_ids.iter().copied().collect();
1020 for &id in ids {
1021 if deleted_set.contains(&id) {
1022 continue;
1023 }
1024 if self.delete(id)? {
1025 deleted_ids.push(id);
1026 }
1027 }
1028
1029 Ok(deleted_ids)
1030 }
1031
1032 pub fn get_metadata(&self, id: EntityId) -> Option<Metadata> {
1034 if let Some(growing_arc) = self.growing.read().as_ref() {
1036 let growing = growing_arc.read();
1037 if growing.contains(id) {
1038 return growing.get_metadata(id);
1039 }
1040 }
1041
1042 let segment_id = self.entity_segment.read().get(&id).copied();
1044
1045 if let Some(seg_id) = segment_id {
1046 if let Some(growing_arc) = self.growing.read().as_ref() {
1047 let growing = growing_arc.read();
1048 if growing.id() == seg_id {
1049 return growing.get_metadata(id);
1050 }
1051 }
1052
1053 let sealed = self.sealed.read();
1054 for segment in sealed.iter() {
1055 if segment.id() == seg_id {
1056 return segment.get_metadata(id);
1057 }
1058 }
1059 }
1060
1061 if let Some(segment_arc) = self.find_sealed_segment_arc(id) {
1062 return segment_arc.read().get_metadata(id);
1063 }
1064
1065 None
1066 }
1067
1068 pub fn set_metadata(&self, id: EntityId, metadata: Metadata) -> Result<(), SegmentError> {
1070 if let Some(growing_arc) = self.growing.read().as_ref() {
1073 let mut growing = growing_arc.write();
1074 if growing.contains(id) && growing.state().is_writable() {
1075 return growing.set_metadata(id, metadata);
1076 }
1077 }
1078
1079 let segment_id = self.entity_segment.read().get(&id).copied();
1081
1082 if let Some(seg_id) = segment_id {
1083 if let Some(growing_arc) = self.growing.read().as_ref() {
1084 let mut growing = growing_arc.write();
1085 if growing.id() == seg_id && growing.state().is_writable() {
1086 return growing.set_metadata(id, metadata);
1087 }
1088 }
1089 }
1090
1091 if let Some(entity) = self.get(id) {
1092 return self.rewrite_sealed_entity_into_growing(entity, Some(&metadata));
1093 }
1094
1095 Err(SegmentError::NotFound(id))
1096 }
1097
1098 fn maybe_seal_growing(&self) -> Result<(), SegmentError> {
1100 let should_seal = {
1101 let growing_opt = self.growing.read();
1102 if let Some(growing_arc) = growing_opt.as_ref() {
1103 let growing = growing_arc.read();
1104 growing.should_seal(&self.config.segment_config)
1105 || growing.idle_secs() >= self.config.idle_seal_secs
1106 } else {
1107 false
1108 }
1109 };
1110
1111 if should_seal {
1112 self.seal_current()?;
1113 }
1114
1115 Ok(())
1116 }
1117
1118 pub fn seal_current(&self) -> Result<SegmentId, SegmentError> {
1120 let growing_opt = self.growing.write().take();
1121
1122 if let Some(growing_arc) = growing_opt {
1123 let mut growing = growing_arc.write();
1124 let seg_id = growing.id();
1125 let entity_count = growing.stats().entity_count as u64;
1126
1127 growing.seal()?;
1129
1130 drop(growing); self.sealed.write().push(growing_arc);
1136
1137 self.mark_sealed_pages_visible(entity_count);
1139
1140 {
1142 let mut stats = self.stats.write();
1143 stats.growing_count = stats.growing_count.saturating_sub(1);
1144 stats.sealed_count += 1;
1145 stats.seal_ops += 1;
1146 }
1147
1148 self.emit(LifecycleEvent::SegmentSealed(seg_id));
1149
1150 return Ok(seg_id);
1151 }
1152
1153 Err(SegmentError::InvalidState(SegmentState::Sealed))
1154 }
1155
1156 pub fn force_seal(&self) -> Result<Option<SegmentId>, SegmentError> {
1158 let has_growing = self.growing.read().is_some();
1159 if has_growing {
1160 Ok(Some(self.seal_current()?))
1161 } else {
1162 Ok(None)
1163 }
1164 }
1165
1166 pub fn all_visible_fraction(&self) -> f64 {
1176 const ROWS_PER_PAGE: u32 = 256;
1177 let sealed = self.sealed.read();
1178 if sealed.is_empty() {
1179 return 0.0;
1180 }
1181 let mut total_pages: u64 = 0;
1182 for seg_arc in sealed.iter() {
1183 let seg = seg_arc.read();
1184 let entity_count = seg.stats().entity_count as u64;
1185 let pages = entity_count.div_ceil(ROWS_PER_PAGE as u64);
1186 total_pages += pages;
1187 }
1188 if total_pages == 0 {
1189 return 0.0;
1190 }
1191 let visible = self.visibility_map.all_visible_count();
1192 (visible as f64 / total_pages as f64).min(1.0)
1193 }
1194
1195 fn mark_sealed_pages_visible(&self, seg_entity_count: u64) {
1198 const ROWS_PER_PAGE: u32 = 256;
1199 let existing_visible = self.visibility_map.all_visible_count();
1200 let start_page = existing_visible as u32;
1202 let new_pages = seg_entity_count.div_ceil(ROWS_PER_PAGE as u64);
1203 let end_page = start_page + new_pages as u32;
1204 self.visibility_map.mark_range_visible(start_page, end_page);
1205 }
1206
1207 pub fn for_each_entity<F>(&self, mut callback: F)
1213 where
1214 F: FnMut(&UnifiedEntity) -> bool,
1215 {
1216 if let Some(growing_arc) = self.growing.read().as_ref() {
1220 let growing = if let Some(g) = growing_arc.try_read() {
1221 g
1222 } else {
1223 growing_arc.read()
1224 };
1225 if !growing.for_each_fast(&mut callback) {
1226 return;
1227 }
1228 }
1229
1230 let sealed = self.sealed.read();
1232 for segment_arc in sealed.iter() {
1233 let segment = segment_arc.read();
1234 if !segment.for_each_fast(&mut callback) {
1235 return;
1236 }
1237 }
1238 }
1239
1240 pub fn fold_entities_parallel<T, FInit, FFold, FReduce>(
1256 &self,
1257 init: FInit,
1258 fold: FFold,
1259 reduce: FReduce,
1260 ) -> T
1261 where
1262 T: Send,
1263 FInit: Fn() -> T + Send + Sync,
1264 FFold: Fn(T, &UnifiedEntity) -> T + Send + Sync,
1265 FReduce: Fn(T, T) -> T + Send + Sync,
1266 {
1267 use rayon::prelude::*;
1268
1269 let mut acc = init();
1272 if let Some(growing_arc) = self.growing.read().as_ref() {
1273 let growing = if let Some(g) = growing_arc.try_read() {
1274 g
1275 } else {
1276 growing_arc.read()
1277 };
1278 growing.for_each_fast(|entity| {
1279 acc = fold(std::mem::replace(&mut acc, init()), entity);
1280 true
1281 });
1282 }
1283
1284 let segments: Vec<_> = {
1288 let sealed = self.sealed.read();
1289 sealed.iter().cloned().collect()
1290 };
1291
1292 if segments.len() <= 1 {
1293 for seg_arc in &segments {
1294 let seg = seg_arc.read();
1295 seg.for_each_fast(|entity| {
1296 acc = fold(std::mem::replace(&mut acc, init()), entity);
1297 true
1298 });
1299 }
1300 return acc;
1301 }
1302
1303 let sealed_acc = segments
1304 .into_par_iter()
1305 .map(|seg_arc| {
1306 let mut local = init();
1307 let seg = seg_arc.read();
1308 seg.for_each_fast(|entity| {
1309 local = fold(std::mem::replace(&mut local, init()), entity);
1310 true
1311 });
1312 local
1313 })
1314 .reduce(&init, &reduce);
1315
1316 reduce(acc, sealed_acc)
1317 }
1318
1319 pub fn for_each_entity_zoned<F>(&self, zone_preds: &[(&str, ZoneColPred<'_>)], mut callback: F)
1328 where
1329 F: FnMut(&UnifiedEntity) -> bool,
1330 {
1331 let _ = self.for_each_entity_zoned_with_stats(zone_preds, &mut callback);
1332 }
1333
1334 pub fn for_each_entity_zoned_with_stats<F>(
1335 &self,
1336 zone_preds: &[(&str, ZoneColPred<'_>)],
1337 mut callback: F,
1338 ) -> SegmentScanStats
1339 where
1340 F: FnMut(&UnifiedEntity) -> bool,
1341 {
1342 let mut scan_stats = SegmentScanStats::default();
1343 if let Some(growing_arc) = self.growing.read().as_ref() {
1349 scan_stats.segments_total += 1;
1350 scan_stats.segments_scanned += 1;
1351 let growing = if let Some(g) = growing_arc.try_read() {
1352 g
1353 } else {
1354 growing_arc.read()
1355 };
1356 if !growing.for_each_fast(&mut callback) {
1357 return scan_stats;
1358 }
1359 }
1360
1361 let sealed = self.sealed.read();
1363 for segment_arc in sealed.iter() {
1364 scan_stats.segments_total += 1;
1365 let segment = segment_arc.read();
1366 if !zone_preds.is_empty() && segment.can_skip_zone_preds(zone_preds) {
1367 scan_stats.segments_pruned += 1;
1368 continue; }
1370 scan_stats.segments_scanned += 1;
1371 if !segment.for_each_fast(&mut callback) {
1372 return scan_stats;
1373 }
1374 }
1375 scan_stats
1376 }
1377
1378 pub fn query_all_zoned<F>(
1388 &self,
1389 zone_preds: &[(&str, ZoneColPred<'_>)],
1390 filter: F,
1391 ) -> Vec<UnifiedEntity>
1392 where
1393 F: Fn(&UnifiedEntity) -> bool + Sync,
1394 {
1395 self.query_all_zoned_with_stats(zone_preds, filter).0
1396 }
1397
1398 pub fn query_all_zoned_with_stats<F>(
1399 &self,
1400 zone_preds: &[(&str, ZoneColPred<'_>)],
1401 filter: F,
1402 ) -> (Vec<UnifiedEntity>, SegmentScanStats)
1403 where
1404 F: Fn(&UnifiedEntity) -> bool + Sync,
1405 {
1406 let mut results = Vec::new();
1407 let mut scan_stats = SegmentScanStats::default();
1408
1409 if let Some(growing_arc) = self.growing.read().as_ref() {
1412 scan_stats.segments_total += 1;
1413 scan_stats.segments_scanned += 1;
1414 let growing = if let Some(g) = growing_arc.try_read() {
1415 g
1416 } else {
1417 growing_arc.read()
1418 };
1419 results.extend(growing.iter().filter(|e| filter(e)).cloned());
1420 }
1421
1422 let sealed = self.sealed.read();
1424 let surviving: Vec<_> = sealed
1426 .iter()
1427 .filter(|seg_arc| {
1428 scan_stats.segments_total += 1;
1429 if zone_preds.is_empty() {
1430 scan_stats.segments_scanned += 1;
1431 return true;
1432 }
1433 let seg = seg_arc.read();
1434 if seg.can_skip_zone_preds(zone_preds) {
1435 scan_stats.segments_pruned += 1;
1436 false
1437 } else {
1438 scan_stats.segments_scanned += 1;
1439 true
1440 }
1441 })
1442 .collect();
1443
1444 let use_parallel = surviving.len() > 1 && crate::runtime::SystemInfo::should_parallelize();
1445
1446 if use_parallel {
1447 let filter_ref = &filter;
1448 let segment_results: Vec<Vec<UnifiedEntity>> = std::thread::scope(|s| {
1449 surviving
1450 .iter()
1451 .map(|segment| {
1452 s.spawn(move || {
1453 segment
1454 .read()
1455 .iter()
1456 .filter(|e| filter_ref(e))
1457 .cloned()
1458 .collect::<Vec<_>>()
1459 })
1460 })
1461 .collect::<Vec<_>>()
1462 .into_iter()
1463 .map(|handle| handle.join().unwrap_or_default())
1464 .collect()
1465 });
1466 for batch in segment_results {
1467 results.extend(batch);
1468 }
1469 } else {
1470 for segment_arc in surviving {
1471 let seg = segment_arc.read();
1472 results.extend(seg.iter().filter(|e| filter(e)).cloned());
1473 }
1474 }
1475
1476 (results, scan_stats)
1477 }
1478
1479 pub fn query_all<F>(&self, filter: F) -> Vec<UnifiedEntity>
1482 where
1483 F: Fn(&UnifiedEntity) -> bool + Sync,
1484 {
1485 let mut results = Vec::new();
1486
1487 if let Some(growing_arc) = self.growing.read().as_ref() {
1490 let growing = if let Some(g) = growing_arc.try_read() {
1491 g
1492 } else {
1493 growing_arc.read()
1494 };
1495 results.extend(growing.iter().filter(|e| filter(e)).cloned());
1496 }
1497
1498 let sealed = self.sealed.read();
1500 let use_parallel = sealed.len() > 1 && crate::runtime::SystemInfo::should_parallelize();
1501 if use_parallel {
1502 let filter_ref = &filter;
1503 let segment_results: Vec<Vec<UnifiedEntity>> = std::thread::scope(|s| {
1504 sealed
1505 .iter()
1506 .map(|segment| {
1507 s.spawn(move || {
1508 segment
1509 .read()
1510 .iter()
1511 .filter(|e| filter_ref(e))
1512 .cloned()
1513 .collect::<Vec<_>>()
1514 })
1515 })
1516 .collect::<Vec<_>>()
1517 .into_iter()
1518 .map(|handle| handle.join().unwrap_or_default())
1519 .collect()
1520 });
1521 for batch in segment_results {
1522 results.extend(batch);
1523 }
1524 } else {
1525 for segment in sealed.iter() {
1526 let seg = segment.read();
1527 results.extend(seg.iter().filter(|e| filter(e)).cloned());
1528 }
1529 }
1530
1531 results
1532 }
1533
1534 pub fn bloom_may_contain_key(&self, key: &[u8]) -> bool {
1539 if let Some(growing_arc) = self.growing.read().as_ref() {
1540 let growing = growing_arc.read();
1541 if growing.may_contain_exact_key(key) {
1542 return true;
1543 }
1544 } else {
1545 return true;
1546 }
1547
1548 let sealed = self.sealed.read();
1549 for segment_arc in sealed.iter() {
1550 let segment = segment_arc.read();
1551 if segment.may_contain_exact_key(key) {
1552 return true;
1553 }
1554 }
1555
1556 false
1557 }
1558
1559 pub fn filter_metadata(&self, filters: &[(String, MetadataFilter)]) -> Vec<EntityId> {
1561 let mut results = Vec::new();
1562
1563 if let Some(growing_arc) = self.growing.read().as_ref() {
1565 let growing = growing_arc.read();
1566 results.extend(growing.filter_metadata(filters));
1567 }
1568
1569 let sealed = self.sealed.read();
1571 for segment in sealed.iter() {
1572 results.extend(segment.filter_metadata(filters));
1573 }
1574
1575 results
1576 }
1577
1578 pub fn get_by_kind(&self, kind: &str) -> Vec<UnifiedEntity> {
1580 let mut results = Vec::new();
1581
1582 if let Some(growing_arc) = self.growing.read().as_ref() {
1584 let growing = growing_arc.read();
1585 for entity in growing.iter_kind(kind) {
1586 results.push(entity.clone());
1587 }
1588 }
1589
1590 let sealed = self.sealed.read();
1595 for segment_arc in sealed.iter() {
1596 let segment = segment_arc.read();
1597 for entity in segment.iter_kind(kind) {
1598 results.push(entity.clone());
1599 }
1600 }
1601
1602 results
1603 }
1604
1605 pub fn count(&self) -> usize {
1607 self.total_entities_atomic.load(Ordering::Relaxed) as usize
1608 }
1609
1610 pub fn segment_ids(&self) -> Vec<SegmentId> {
1612 let mut ids = Vec::new();
1613
1614 if let Some(growing_arc) = self.growing.read().as_ref() {
1615 ids.push(growing_arc.read().id());
1616 }
1617
1618 let sealed = self.sealed.read();
1619 for segment in sealed.iter() {
1620 ids.push(segment.id());
1621 }
1622
1623 ids.extend(self.archived.read().iter().copied());
1624
1625 ids
1626 }
1627
1628 #[inline]
1641 #[allow(clippy::unused_self)]
1642 fn emit(&self, _event: LifecycleEvent) {}
1643
1644 pub fn drain_events(&self) -> Vec<LifecycleEvent> {
1647 std::mem::take(&mut *self.events.write())
1648 }
1649
1650 pub fn run_maintenance(&self) -> Result<(), SegmentError> {
1652 self.maybe_seal_growing()?;
1654
1655 if self.config.enable_consolidation {
1656 self.consolidation_tick();
1657 }
1658
1659 Ok(())
1660 }
1661
1662 fn consolidation_tick(&self) {
1673 let running = self.consolidation.read().is_some();
1676 if !running && !self.start_consolidation() {
1677 return;
1678 }
1679
1680 let finished = {
1681 let mut guard = self.consolidation.write();
1682 let Some(run) = guard.as_mut() else {
1683 return;
1684 };
1685 self.copy_bounded(run);
1686 run.cursor >= run.sources.len()
1687 };
1688
1689 if finished {
1690 let run = self.consolidation.write().take();
1691 if let Some(run) = run {
1692 self.finish_consolidation(run);
1693 }
1694 }
1695 }
1696
1697 fn start_consolidation(&self) -> bool {
1701 let sources = self.select_candidates();
1702 self.start_consolidation_from_sources(sources)
1703 }
1704
1705 fn start_consolidation_from_sources(&self, sources: Vec<SegmentId>) -> bool {
1706 if sources.len() < 2 && !self.candidates_carry_tombstones(&sources) {
1707 return false;
1708 }
1709
1710 let merged_id = self.next_segment_id.fetch_add(1, Ordering::SeqCst);
1711 let merged = GrowingSegment::new(merged_id, &self.collection);
1712
1713 *self.consolidation.write() = Some(Consolidation {
1714 sources: sources
1715 .into_iter()
1716 .map(|id| SourceProgress {
1717 id,
1718 copied: Vec::new(),
1719 tombstones_at_open: None,
1720 })
1721 .collect(),
1722 merged_id,
1723 merged,
1724 cursor: 0,
1725 pending_ids: Vec::new(),
1726 pending_cursor: 0,
1727 entities_copied: 0,
1728 });
1729
1730 self.stats.write().consolidation.runs_started += 1;
1731 true
1732 }
1733
1734 pub fn pressure_consolidation_tick(&self) -> bool {
1739 if !self.config.enable_consolidation {
1740 return false;
1741 }
1742
1743 let running = self.consolidation.read().is_some();
1744 if !running {
1745 let sources = self.dirty_sealed_segments();
1746 if !self.start_consolidation_from_sources(sources) {
1747 return false;
1748 }
1749 }
1750
1751 self.consolidation_tick();
1752 true
1753 }
1754
1755 fn select_candidates(&self) -> Vec<SegmentId> {
1770 let sealed = self.sealed.read();
1771 if sealed.is_empty() {
1772 return Vec::new();
1773 }
1774
1775 if sealed.len() > self.config.max_sealed_segments {
1776 return sealed.iter().map(|segment| segment.read().id()).collect();
1777 }
1778
1779 let mut live = 0usize;
1780 let mut tombstones = 0usize;
1781 let mut held = 0u64;
1782 let mut reclaimable = 0u64;
1783 let mut dirty = Vec::new();
1784
1785 for segment_arc in sealed.iter() {
1786 let segment = segment_arc.read();
1787 live += segment.live_entity_count();
1788 tombstones += segment.tombstone_count();
1789 held += segment.resident_bytes();
1790 reclaimable += segment.reclaimable_bytes();
1791 if segment.tombstone_count() > 0 {
1792 dirty.push(segment.id());
1793 }
1794 }
1795
1796 let total = live + tombstones;
1797 let tombstone_ratio = if total == 0 {
1798 0.0
1799 } else {
1800 tombstones as f64 / total as f64
1801 };
1802 let fragmentation = if held == 0 {
1803 0.0
1804 } else {
1805 reclaimable as f64 / held as f64
1806 };
1807
1808 if tombstone_ratio >= CONSOLIDATION_TOMBSTONE_RATIO
1809 || fragmentation >= CONSOLIDATION_FRAGMENTATION_RATIO
1810 {
1811 return dirty;
1812 }
1813
1814 Vec::new()
1815 }
1816
1817 fn dirty_sealed_segments(&self) -> Vec<SegmentId> {
1818 self.sealed
1819 .read()
1820 .iter()
1821 .filter_map(|segment_arc| {
1822 let segment = segment_arc.read();
1823 (segment.reclaimable_bytes() > 0).then(|| segment.id())
1824 })
1825 .collect()
1826 }
1827
1828 fn candidates_carry_tombstones(&self, sources: &[SegmentId]) -> bool {
1831 if sources.is_empty() {
1832 return false;
1833 }
1834 let sealed = self.sealed.read();
1835 sealed.iter().any(|segment_arc| {
1836 let segment = segment_arc.read();
1837 sources.contains(&segment.id()) && segment.tombstone_count() > 0
1838 })
1839 }
1840
1841 fn sealed_segment(&self, id: SegmentId) -> Option<Arc<RwLock<GrowingSegment>>> {
1842 let sealed = self.sealed.read();
1843 sealed
1844 .iter()
1845 .find(|segment| segment.read().id() == id)
1846 .map(Arc::clone)
1847 }
1848
1849 fn copy_bounded(&self, run: &mut Consolidation) {
1854 let mut budget = self.config.consolidation_entities_per_tick;
1855
1856 while budget > 0 && run.cursor < run.sources.len() {
1857 let source_id = run.sources[run.cursor].id;
1858 let Some(source_arc) = self.sealed_segment(source_id) else {
1859 run.cursor += 1;
1863 run.pending_ids.clear();
1864 run.pending_cursor = 0;
1865 continue;
1866 };
1867
1868 if run.sources[run.cursor].tombstones_at_open.is_none() {
1869 let source = source_arc.read();
1870 run.pending_ids = source.live_entity_ids();
1871 run.pending_cursor = 0;
1872 run.sources[run.cursor].tombstones_at_open = Some(source.tombstone_count());
1873 }
1874
1875 {
1876 let source = source_arc.read();
1877 while budget > 0 && run.pending_cursor < run.pending_ids.len() {
1878 let id = run.pending_ids[run.pending_cursor];
1879 run.pending_cursor += 1;
1880 budget -= 1;
1881
1882 let Some(entity) = source.get(id) else {
1885 continue;
1886 };
1887 let metadata = source.get_metadata(id);
1888 run.merged.adopt_entity(entity.clone(), metadata);
1889 run.sources[run.cursor].copied.push(id);
1890 run.entities_copied += 1;
1891 }
1892 }
1893
1894 if run.pending_cursor >= run.pending_ids.len() {
1895 run.cursor += 1;
1896 run.pending_ids.clear();
1897 run.pending_cursor = 0;
1898 }
1899 }
1900 }
1901
1902 fn finish_consolidation(&self, mut run: Consolidation) {
1908 let mut sealed = self.sealed.write();
1909
1910 let mut positions = Vec::with_capacity(run.sources.len());
1911 for source in &run.sources {
1912 if let Some(index) = sealed
1913 .iter()
1914 .position(|segment| segment.read().id() == source.id)
1915 {
1916 positions.push(index);
1917 }
1918 }
1919 if positions.is_empty() {
1920 return;
1921 }
1922 positions.sort_unstable();
1923
1924 let mut stale = Vec::new();
1928 for source in &run.sources {
1929 let Some(source_arc) = sealed
1930 .iter()
1931 .find(|segment| segment.read().id() == source.id)
1932 else {
1933 continue;
1934 };
1935 let segment = source_arc.read();
1936 if source.tombstones_at_open == Some(segment.tombstone_count()) {
1937 continue;
1938 }
1939 stale.extend(
1940 source
1941 .copied
1942 .iter()
1943 .copied()
1944 .filter(|id| !segment.contains(*id)),
1945 );
1946 }
1947 for id in stale {
1948 run.merged.evict_entity(id);
1949 }
1950
1951 let reclaimed_bytes: u64 = positions
1952 .iter()
1953 .map(|&index| sealed[index].read().resident_bytes())
1954 .sum();
1955 let reclaimed_tombstones: u64 = positions
1956 .iter()
1957 .map(|&index| sealed[index].read().tombstone_count() as u64)
1958 .sum();
1959
1960 if run.merged.seal().is_err() {
1961 return;
1962 }
1963 let merged_bytes = run.merged.resident_bytes();
1964 let merged_arc = Arc::new(RwLock::new(run.merged));
1965
1966 for &index in positions.iter().rev() {
1967 sealed.remove(index);
1968 }
1969 let insert_at = positions[0].min(sealed.len());
1970 sealed.insert(insert_at, Arc::clone(&merged_arc));
1971 drop(sealed);
1972
1973 let source_ids: Vec<SegmentId> = run.sources.iter().map(|source| source.id).collect();
1975 {
1976 let mut entity_segment = self.entity_segment.write();
1977 for segment_id in entity_segment.values_mut() {
1978 if source_ids.contains(segment_id) {
1979 *segment_id = run.merged_id;
1980 }
1981 }
1982 }
1983
1984 {
1985 let mut stats = self.stats.write();
1986 stats.sealed_count = stats
1987 .sealed_count
1988 .saturating_sub(positions.len())
1989 .saturating_add(1);
1990 let consolidation = &mut stats.consolidation;
1991 consolidation.runs_completed += 1;
1992 consolidation.segments_merged += positions.len() as u64;
1993 consolidation.tombstones_reclaimed += reclaimed_tombstones;
1994 consolidation.bytes_reclaimed += reclaimed_bytes.saturating_sub(merged_bytes);
1995 }
1996
1997 self.emit(LifecycleEvent::SegmentConsolidated {
1998 source: source_ids,
1999 target: run.merged_id,
2000 });
2001 }
2002
2003 #[cfg(test)]
2006 fn consolidation_progress(&self) -> Option<u64> {
2007 self.consolidation
2008 .read()
2009 .as_ref()
2010 .map(|run| run.entities_copied)
2011 }
2012}
2013
2014impl UnifiedSegment for Arc<RwLock<GrowingSegment>> {
2017 fn id(&self) -> SegmentId {
2018 self.read().id()
2019 }
2020
2021 fn state(&self) -> SegmentState {
2022 self.read().state()
2023 }
2024
2025 fn collection(&self) -> &str {
2026 "unknown"
2028 }
2029
2030 fn stats(&self) -> SegmentStats {
2031 self.read().stats()
2032 }
2033
2034 fn entity_count(&self) -> usize {
2035 self.read().entity_count()
2036 }
2037
2038 fn contains(&self, id: EntityId) -> bool {
2039 self.read().contains(id)
2040 }
2041
2042 fn get(&self, id: EntityId) -> Option<&UnifiedEntity> {
2043 None
2046 }
2047
2048 fn get_mut(&mut self, _id: EntityId) -> Option<&mut UnifiedEntity> {
2049 None
2050 }
2051
2052 fn insert(&mut self, entity: UnifiedEntity) -> Result<EntityId, SegmentError> {
2053 self.write().insert(entity)
2054 }
2055
2056 fn update(&mut self, entity: UnifiedEntity) -> Result<(), SegmentError> {
2057 self.write().update(entity)
2058 }
2059
2060 fn update_hot(
2061 &mut self,
2062 entity: UnifiedEntity,
2063 modified_columns: &[String],
2064 ) -> Result<(), SegmentError> {
2065 self.write().update_hot(entity, modified_columns)
2066 }
2067
2068 fn delete(&mut self, id: EntityId) -> Result<bool, SegmentError> {
2069 self.write().delete(id)
2070 }
2071
2072 fn get_metadata(&self, id: EntityId) -> Option<Metadata> {
2073 self.read().get_metadata(id)
2074 }
2075
2076 fn set_metadata(&mut self, id: EntityId, metadata: Metadata) -> Result<(), SegmentError> {
2077 self.write().set_metadata(id, metadata)
2078 }
2079
2080 fn seal(&mut self) -> Result<(), SegmentError> {
2081 self.write().seal()
2082 }
2083
2084 fn should_seal(&self, config: &SegmentConfig) -> bool {
2085 self.read().should_seal(config)
2086 }
2087
2088 fn iter(&self) -> Box<dyn Iterator<Item = &UnifiedEntity> + '_> {
2089 Box::new(std::iter::empty())
2091 }
2092
2093 fn iter_kind(&self, _kind_filter: &str) -> Box<dyn Iterator<Item = &UnifiedEntity> + '_> {
2094 Box::new(std::iter::empty())
2095 }
2096
2097 fn filter_metadata(&self, filters: &[(String, MetadataFilter)]) -> Vec<EntityId> {
2098 self.read().filter_metadata(filters)
2099 }
2100}
2101
2102#[cfg(test)]
2103mod tests {
2104 use super::*;
2105 use crate::storage::schema::Value;
2106 use crate::storage::unified::entity::{EntityData, EntityKind, RowData};
2107
2108 #[test]
2109 fn test_manager_basic() {
2110 let manager = SegmentManager::new("test_collection");
2111
2112 let entity = UnifiedEntity::table_row(
2113 manager.next_entity_id(),
2114 "users",
2115 1,
2116 vec![Value::text("Alice".to_string())],
2117 );
2118
2119 let id = manager.insert(entity).unwrap();
2120 assert!(manager.get(id).is_some());
2121 assert_eq!(manager.count(), 1);
2122 }
2123
2124 #[test]
2125 fn bloom_may_contain_key_probes_inserted_rid_bytes() {
2126 let manager = SegmentManager::new("test_collection");
2127
2128 let entity = UnifiedEntity::table_row(
2129 manager.next_entity_id(),
2130 "users",
2131 1,
2132 vec![Value::text("Alice".to_string())],
2133 );
2134
2135 let id = manager.insert(entity).unwrap();
2136
2137 assert!(manager.bloom_may_contain_key(&id.raw().to_le_bytes()));
2138 assert!(manager.bloom_may_contain_key(id.raw().to_string().as_bytes()));
2139 assert!(!manager.bloom_may_contain_key(&u64::MAX.to_le_bytes()));
2140
2141 assert!(manager.delete(id).unwrap());
2142 assert!(!manager.bloom_may_contain_key(&id.raw().to_le_bytes()));
2143 }
2144
2145 #[test]
2146 fn zoned_scan_reports_pruned_segments_and_preserves_results() {
2147 let config = ManagerConfig {
2148 segment_config: SegmentConfig {
2149 max_entities: 2,
2150 ..Default::default()
2151 },
2152 ..Default::default()
2153 };
2154 let manager = SegmentManager::with_config("events", config);
2155
2156 for (idx, value) in [10, 20, 100, 110, 200].into_iter().enumerate() {
2157 let row = RowData::with_names(vec![Value::Integer(value)], vec!["ts".to_string()]);
2158 let entity = UnifiedEntity::new(
2159 manager.next_entity_id(),
2160 EntityKind::TableRow {
2161 table: "events".into(),
2162 row_id: (idx + 1) as u64,
2163 },
2164 EntityData::Row(row),
2165 );
2166 manager.insert(entity).unwrap();
2167 }
2168
2169 let full = manager.query_all(|entity| match &entity.data {
2170 EntityData::Row(row) => matches!(row.get_field("ts"), Some(Value::Integer(100))),
2171 _ => false,
2172 });
2173 let probe = Value::Integer(100);
2174 let (pruned, stats) =
2175 manager.query_all_zoned_with_stats(&[("ts", ZoneColPred::Eq(&probe))], |entity| {
2176 match &entity.data {
2177 EntityData::Row(row) => {
2178 matches!(row.get_field("ts"), Some(Value::Integer(100)))
2179 }
2180 _ => false,
2181 }
2182 });
2183
2184 assert_eq!(pruned.len(), full.len());
2185 assert_eq!(pruned[0].id, full[0].id);
2186 assert_eq!(stats.segments_total, 3);
2187 assert_eq!(stats.segments_scanned, 2);
2188 assert_eq!(stats.segments_pruned, 1);
2189 }
2190
2191 #[test]
2192 fn test_manager_auto_seal() {
2193 let config = ManagerConfig {
2194 segment_config: SegmentConfig {
2195 max_entities: 2,
2196 ..Default::default()
2197 },
2198 ..Default::default()
2199 };
2200
2201 let manager = SegmentManager::with_config("test", config);
2202
2203 manager
2205 .insert(UnifiedEntity::vector(
2206 manager.next_entity_id(),
2207 "v",
2208 vec![0.1],
2209 ))
2210 .unwrap();
2211
2212 manager
2214 .insert(UnifiedEntity::vector(
2215 manager.next_entity_id(),
2216 "v",
2217 vec![0.2],
2218 ))
2219 .unwrap();
2220
2221 manager
2223 .insert(UnifiedEntity::vector(
2224 manager.next_entity_id(),
2225 "v",
2226 vec![0.3],
2227 ))
2228 .unwrap();
2229
2230 let stats = manager.stats();
2231 assert_eq!(stats.total_entities, 3);
2232 }
2233
2234 #[test]
2235 fn test_manager_delete() {
2236 let manager = SegmentManager::new("test");
2237
2238 let id = manager
2239 .insert(UnifiedEntity::vector(
2240 manager.next_entity_id(),
2241 "v",
2242 vec![0.1],
2243 ))
2244 .unwrap();
2245
2246 assert!(manager.get(id).is_some());
2247 assert!(manager.delete(id).unwrap());
2248 assert!(manager.get(id).is_none());
2249 }
2250
2251 #[test]
2252 fn test_manager_metadata() {
2253 let manager = SegmentManager::new("test");
2254
2255 let id = manager
2256 .insert(UnifiedEntity::table_row(
2257 manager.next_entity_id(),
2258 "hosts",
2259 1,
2260 vec![Value::text("192.168.1.1".to_string())],
2261 ))
2262 .unwrap();
2263
2264 let mut meta = Metadata::new();
2265 meta.set(
2266 "os",
2267 super::super::metadata::MetadataValue::String("linux".to_string()),
2268 );
2269
2270 manager.set_metadata(id, meta).unwrap();
2271
2272 let retrieved = manager.get_metadata(id).unwrap();
2273 assert!(retrieved.has("os"));
2274 }
2275
2276 #[test]
2277 fn test_manager_query_by_kind() {
2278 let manager = SegmentManager::new("test");
2279
2280 manager
2281 .insert(UnifiedEntity::table_row(
2282 manager.next_entity_id(),
2283 "hosts",
2284 1,
2285 vec![],
2286 ))
2287 .unwrap();
2288
2289 manager
2290 .insert(UnifiedEntity::vector(
2291 manager.next_entity_id(),
2292 "embeddings",
2293 vec![0.1],
2294 ))
2295 .unwrap();
2296
2297 manager
2298 .insert(UnifiedEntity::table_row(
2299 manager.next_entity_id(),
2300 "hosts",
2301 2,
2302 vec![],
2303 ))
2304 .unwrap();
2305
2306 let rows = manager.get_by_kind("table");
2307 assert_eq!(rows.len(), 2);
2308
2309 let vectors = manager.get_by_kind("vector");
2310 assert_eq!(vectors.len(), 1);
2311 }
2312
2313 fn consolidating_manager(entities_per_tick: usize) -> SegmentManager {
2320 SegmentManager::with_config(
2321 "test",
2322 ManagerConfig {
2323 max_sealed_segments: 1_000,
2324 consolidation_entities_per_tick: entities_per_tick,
2325 ..Default::default()
2326 },
2327 )
2328 }
2329
2330 fn seal_bulk_segment(manager: &SegmentManager, count: usize) -> Vec<EntityId> {
2334 let entities: Vec<UnifiedEntity> = (0..count)
2335 .map(|i| {
2336 UnifiedEntity::table_row(
2337 manager.next_entity_id(),
2338 "users",
2339 i as u64,
2340 vec![Value::Integer(i as i64), Value::text(format!("row-{i}"))],
2341 )
2342 })
2343 .collect();
2344 let ids = manager.bulk_insert(entities).expect("bulk insert");
2345 manager.force_seal().expect("seal");
2346 ids
2347 }
2348
2349 fn live_ids(manager: &SegmentManager) -> Vec<u64> {
2350 let mut ids: Vec<u64> = manager
2351 .query_all(|_| true)
2352 .into_iter()
2353 .map(|entity| entity.id.raw())
2354 .collect();
2355 ids.sort_unstable();
2356 ids
2357 }
2358
2359 fn drain_maintenance(manager: &SegmentManager) -> usize {
2360 let mut ticks = 0;
2361 while manager.consolidation.read().is_some() || ticks == 0 {
2362 manager.run_maintenance().expect("maintenance");
2363 ticks += 1;
2364 assert!(ticks < 1_000, "consolidation failed to converge");
2365 }
2366 ticks
2367 }
2368
2369 #[test]
2370 fn delete_heavy_workload_reclaims_tombstoned_memory_and_counts_it() {
2371 let manager = consolidating_manager(CONSOLIDATION_ENTITIES_PER_TICK);
2372 let ids = seal_bulk_segment(&manager, 100);
2373
2374 let bytes_before_delete = manager.resident_bytes();
2375
2376 for id in ids.iter().take(40) {
2378 assert!(manager.delete(*id).expect("delete"));
2379 }
2380
2381 let bytes_with_tombstones = manager.resident_bytes();
2384 assert!(bytes_with_tombstones > bytes_before_delete);
2385
2386 let survivors = live_ids(&manager);
2387 assert_eq!(survivors.len(), 60);
2388
2389 drain_maintenance(&manager);
2390
2391 let stats = manager.stats();
2392 assert_eq!(stats.consolidation.runs_started, 1);
2393 assert_eq!(stats.consolidation.runs_completed, 1);
2394 assert_eq!(stats.consolidation.segments_merged, 1);
2395 assert_eq!(stats.consolidation.tombstones_reclaimed, 40);
2396 assert!(stats.consolidation.bytes_reclaimed > 0);
2397
2398 let bytes_after = manager.resident_bytes();
2400 assert!(
2401 bytes_after <= bytes_before_delete,
2402 "consolidation must return the tombstones' memory: {bytes_after} > {bytes_before_delete}"
2403 );
2404 assert_eq!(
2405 bytes_with_tombstones - bytes_after,
2406 stats.consolidation.bytes_reclaimed
2407 );
2408 assert_eq!(stats.total_memory_bytes, bytes_after as usize);
2409
2410 assert_eq!(live_ids(&manager), survivors);
2411 assert_eq!(manager.sealed.read().len(), 1);
2412 assert_eq!(manager.sealed.read()[0].read().tombstone_count(), 0);
2413 }
2414
2415 #[test]
2416 fn query_results_are_identical_before_during_and_after_consolidation() {
2417 let manager = consolidating_manager(3);
2418 let ids = seal_bulk_segment(&manager, 20);
2419 seal_bulk_segment(&manager, 20);
2420 for id in ids.iter().take(10) {
2421 assert!(manager.delete(*id).expect("delete"));
2422 }
2423
2424 let before = live_ids(&manager);
2425 let survivor = *ids.last().expect("at least one row");
2426 let tombstoned = ids[0];
2427
2428 let mut mid_tick_observations = 0;
2431 while manager.consolidation.read().is_some() || mid_tick_observations == 0 {
2432 manager.run_maintenance().expect("maintenance");
2433 assert_eq!(live_ids(&manager), before);
2434 assert!(manager.get(survivor).is_some());
2435 assert!(manager.get(tombstoned).is_none());
2436 assert_eq!(manager.get_by_kind("table").len(), before.len());
2437 mid_tick_observations += 1;
2438 assert!(mid_tick_observations < 1_000);
2439 }
2440
2441 assert!(
2442 mid_tick_observations > 2,
2443 "expected the run to span several ticks, saw {mid_tick_observations}"
2444 );
2445 assert_eq!(live_ids(&manager), before);
2446 assert!(manager.get(survivor).is_some());
2447 assert!(manager.get(tombstoned).is_none());
2448 assert_eq!(manager.get_by_kind("table").len(), before.len());
2449 }
2450
2451 #[test]
2452 fn get_by_kind_reaches_sealed_segments() {
2453 let manager = consolidating_manager(CONSOLIDATION_ENTITIES_PER_TICK);
2454 let ids = seal_bulk_segment(&manager, 5);
2455 assert!(manager.delete(ids[0]).expect("delete"));
2456
2457 assert_eq!(manager.get_by_kind("table").len(), 4);
2461 assert_eq!(manager.get_by_kind("vector").len(), 0);
2462 }
2463
2464 #[test]
2465 fn consolidation_is_paced_by_the_per_tick_entity_bound() {
2466 const PER_TICK: usize = 4;
2467 let manager = consolidating_manager(PER_TICK);
2468 let ids = seal_bulk_segment(&manager, 60);
2469 for id in ids.iter().take(30) {
2470 assert!(manager.delete(*id).expect("delete"));
2471 }
2472
2473 manager.run_maintenance().expect("maintenance");
2475 assert_eq!(manager.consolidation_progress(), Some(PER_TICK as u64));
2476
2477 let mut previous = PER_TICK as u64;
2480 let mut ticks = 1;
2481 while let Some(copied) = manager.consolidation_progress() {
2482 assert!(
2483 copied - previous <= PER_TICK as u64,
2484 "tick copied {} entities, bound is {PER_TICK}",
2485 copied - previous
2486 );
2487 previous = copied;
2488 manager.run_maintenance().expect("maintenance");
2489 ticks += 1;
2490 assert!(ticks < 100);
2491 }
2492
2493 assert!(ticks >= 8, "consolidation completed in only {ticks} ticks");
2496 assert_eq!(manager.stats().consolidation.runs_completed, 1);
2497 assert_eq!(live_ids(&manager).len(), 30);
2498 }
2499
2500 #[test]
2501 fn sealed_segment_count_above_the_limit_triggers_a_merge() {
2502 let manager = SegmentManager::with_config(
2503 "test",
2504 ManagerConfig {
2505 max_sealed_segments: 3,
2506 ..Default::default()
2507 },
2508 );
2509 for _ in 0..4 {
2510 seal_bulk_segment(&manager, 5);
2511 }
2512 assert_eq!(manager.sealed.read().len(), 4);
2513
2514 let before = live_ids(&manager);
2515 drain_maintenance(&manager);
2516
2517 assert_eq!(manager.sealed.read().len(), 1);
2520 assert_eq!(manager.stats().consolidation.segments_merged, 4);
2521 assert_eq!(manager.stats().consolidation.tombstones_reclaimed, 0);
2522 assert_eq!(live_ids(&manager), before);
2523 }
2524
2525 #[test]
2526 fn a_clean_sealed_set_below_the_limit_is_never_consolidated() {
2527 let manager = consolidating_manager(CONSOLIDATION_ENTITIES_PER_TICK);
2528 seal_bulk_segment(&manager, 10);
2529 seal_bulk_segment(&manager, 10);
2530
2531 for _ in 0..5 {
2532 manager.run_maintenance().expect("maintenance");
2533 }
2534
2535 assert_eq!(manager.stats().consolidation.runs_started, 0);
2536 assert_eq!(manager.sealed.read().len(), 2);
2537 }
2538
2539 #[test]
2540 fn concurrent_readers_never_observe_a_missing_or_duplicated_entity() {
2541 use std::sync::atomic::AtomicBool;
2542
2543 let manager = Arc::new(consolidating_manager(2));
2544 let ids = seal_bulk_segment(&manager, 40);
2545 seal_bulk_segment(&manager, 40);
2546 for id in ids.iter().take(20) {
2547 assert!(manager.delete(*id).expect("delete"));
2548 }
2549
2550 let expected = live_ids(&manager);
2551 let stop = Arc::new(AtomicBool::new(false));
2552
2553 let readers: Vec<_> = (0..4)
2554 .map(|_| {
2555 let manager = Arc::clone(&manager);
2556 let stop = Arc::clone(&stop);
2557 let expected = expected.clone();
2558 std::thread::spawn(move || {
2559 let mut scans = 0u64;
2560 loop {
2563 let mut seen: Vec<u64> = manager
2564 .query_all(|_| true)
2565 .into_iter()
2566 .map(|entity| entity.id.raw())
2567 .collect();
2568 seen.sort_unstable();
2569 assert_eq!(seen, expected, "reader saw a torn sealed set");
2572 scans += 1;
2573 if stop.load(Ordering::Relaxed) {
2574 break;
2575 }
2576 }
2577 scans
2578 })
2579 })
2580 .collect();
2581
2582 drain_maintenance(&manager);
2583 stop.store(true, Ordering::Relaxed);
2584
2585 for reader in readers {
2586 let scans = reader.join().expect("reader thread");
2587 assert!(scans > 0, "reader never got to scan");
2588 }
2589
2590 assert_eq!(manager.stats().consolidation.runs_completed, 1);
2591 assert_eq!(live_ids(&manager), expected);
2592 }
2593
2594 #[test]
2595 fn a_row_deleted_mid_consolidation_is_not_resurrected_by_the_swap() {
2596 let manager = consolidating_manager(4);
2597 let ids = seal_bulk_segment(&manager, 20);
2598 for id in ids.iter().take(6) {
2599 assert!(manager.delete(*id).expect("delete"));
2600 }
2601
2602 manager.run_maintenance().expect("maintenance");
2604 assert_eq!(manager.consolidation_progress(), Some(4));
2605 let doomed = ids[6];
2606 assert!(manager.delete(doomed).expect("delete"));
2607
2608 let expected = live_ids(&manager);
2609 drain_maintenance(&manager);
2610
2611 assert!(manager.get(doomed).is_none(), "deleted row came back");
2612 assert_eq!(live_ids(&manager), expected);
2613 }
2614
2615 #[test]
2616 fn discarding_an_in_flight_consolidation_leaves_the_data_untouched() {
2617 let manager = consolidating_manager(3);
2618 let ids = seal_bulk_segment(&manager, 30);
2619 for id in ids.iter().take(12) {
2620 assert!(manager.delete(*id).expect("delete"));
2621 }
2622
2623 let before = live_ids(&manager);
2624 let sealed_before: Vec<SegmentId> = manager
2625 .sealed
2626 .read()
2627 .iter()
2628 .map(|segment| segment.read().id())
2629 .collect();
2630
2631 manager.run_maintenance().expect("maintenance");
2632 manager.run_maintenance().expect("maintenance");
2633 assert!(manager.consolidation.read().is_some());
2634
2635 *manager.consolidation.write() = None;
2639
2640 assert_eq!(live_ids(&manager), before);
2641 let sealed_after: Vec<SegmentId> = manager
2642 .sealed
2643 .read()
2644 .iter()
2645 .map(|segment| segment.read().id())
2646 .collect();
2647 assert_eq!(sealed_after, sealed_before);
2648 assert_eq!(manager.stats().consolidation.runs_completed, 0);
2649
2650 drain_maintenance(&manager);
2651 assert_eq!(live_ids(&manager), before);
2652 assert_eq!(manager.stats().consolidation.runs_completed, 1);
2653 }
2654
2655 #[test]
2656 fn consolidation_preserves_entity_payload_metadata_and_sequence() {
2657 let manager = consolidating_manager(CONSOLIDATION_ENTITIES_PER_TICK);
2658
2659 let entities: Vec<UnifiedEntity> = (0..10)
2663 .map(|i| {
2664 UnifiedEntity::table_row(
2665 manager.next_entity_id(),
2666 "users",
2667 i as u64,
2668 vec![Value::Integer(i as i64)],
2669 )
2670 })
2671 .collect();
2672 let ids = manager.bulk_insert(entities).expect("bulk insert");
2673
2674 let mut meta = Metadata::new();
2675 meta.set(
2676 "os",
2677 super::super::metadata::MetadataValue::String("linux".to_string()),
2678 );
2679 manager.set_metadata(ids[9], meta).expect("set metadata");
2680 manager.force_seal().expect("seal");
2681
2682 assert!(manager.delete(ids[0]).expect("delete"));
2683 assert!(manager.delete(ids[1]).expect("delete"));
2684 assert!(manager.delete(ids[2]).expect("delete"));
2685
2686 let before: Vec<UnifiedEntity> = {
2687 let mut entities = manager.query_all(|_| true);
2688 entities.sort_by_key(|entity| entity.id.raw());
2689 entities
2690 };
2691
2692 drain_maintenance(&manager);
2693
2694 let after: Vec<UnifiedEntity> = {
2695 let mut entities = manager.query_all(|_| true);
2696 entities.sort_by_key(|entity| entity.id.raw());
2697 entities
2698 };
2699
2700 let columns = |entity: &UnifiedEntity| match &entity.data {
2701 super::super::entity::EntityData::Row(row) => row.columns.clone(),
2702 _ => Vec::new(),
2703 };
2704
2705 assert_eq!(before.len(), after.len());
2706 for (old, new) in before.iter().zip(after.iter()) {
2707 assert_eq!(old.id, new.id);
2708 assert_eq!(columns(old), columns(new));
2709 assert_eq!(
2710 old.sequence_id, new.sequence_id,
2711 "merge must not re-issue sequence ids"
2712 );
2713 }
2714 assert!(manager
2715 .get_metadata(ids[9])
2716 .expect("metadata survives the merge")
2717 .has("os"));
2718 }
2719
2720 #[test]
2721 #[ignore = "lifecycle events intentionally no-op since the emit-channel refactor; drain_events returns empty — see SegmentManager::emit"]
2722 fn test_lifecycle_events() {
2723 let manager = SegmentManager::new("test");
2724
2725 manager
2726 .insert(UnifiedEntity::vector(
2727 manager.next_entity_id(),
2728 "v",
2729 vec![0.1],
2730 ))
2731 .unwrap();
2732
2733 let events = manager.drain_events();
2734
2735 assert!(events
2737 .iter()
2738 .any(|e| matches!(e, LifecycleEvent::SegmentCreated(_))));
2739 assert!(events
2740 .iter()
2741 .any(|e| matches!(e, LifecycleEvent::EntityInserted(_, _))));
2742 }
2743}