use std::sync::Arc;
use crate::error::Result;
use crate::storage::Storage;
use crate::vector::core::vector::Vector;
use crate::vector::index::segment::manager::ManagedSegmentInfo;
use crate::vector::index::segment::merge::{MergeConfig, MergeResult, MergeStats};
use crate::maintenance::deletion::DeletionBitmap;
use crate::vector::index::config::IvfIndexConfig;
use crate::vector::index::ivf::reader::IvfIndexReader;
use crate::vector::index::ivf::writer::IvfIndexWriter;
use crate::vector::reader::VectorIndexReader;
use crate::vector::writer::{VectorIndexWriter, VectorIndexWriterConfig};
pub struct MergeEngine {
config: MergeConfig,
storage: Arc<dyn Storage>,
index_config: IvfIndexConfig,
writer_config: VectorIndexWriterConfig,
deletion_bitmap: Option<Arc<DeletionBitmap>>,
}
impl MergeEngine {
pub fn new(
config: MergeConfig,
storage: Arc<dyn Storage>,
index_config: IvfIndexConfig,
writer_config: VectorIndexWriterConfig,
) -> Self {
Self {
config,
storage,
index_config,
writer_config,
deletion_bitmap: None,
}
}
pub fn set_deletion_bitmap(&mut self, bitmap: Arc<DeletionBitmap>) {
self.deletion_bitmap = Some(bitmap);
}
pub fn merge_segments(
&self,
segments: Vec<ManagedSegmentInfo>,
new_segment_id: String,
) -> Result<MergeResult> {
let start_time = crate::util::time::Timer::now();
let segments_merged = segments.len() as u32;
let mut deletions_removed = 0;
let mut duplicates_removed = 0u64;
let mut all_vectors: Vec<(u64, String, Vector)> = Vec::new();
let mut sources = segments.clone();
sources.sort_by_key(|s| std::cmp::Reverse(s.generation));
let mut seen: std::collections::HashSet<(u64, String)> = std::collections::HashSet::new();
for segment in &sources {
let reader = IvfIndexReader::load(
self.storage.clone(),
&segment.segment_id,
self.index_config.distance_metric,
)?;
let mut iterator = reader.vector_iterator()?;
while let Some((doc_id, field, vector)) = iterator.next()? {
if let Some(bitmap) = &self.deletion_bitmap
&& bitmap.is_deleted(doc_id)
{
deletions_removed += 1;
continue;
}
if !seen.insert((doc_id, field.clone())) {
duplicates_removed += 1;
continue;
}
all_vectors.push((doc_id, field, vector));
}
}
let vectors_merged = all_vectors.len() as u64;
let mut writer = IvfIndexWriter::with_storage(
self.index_config.clone(),
self.writer_config.clone(),
&new_segment_id,
self.storage.clone(),
)?;
writer.add_vectors(all_vectors)?;
writer.finalize()?;
writer.write()?;
let total_size = vectors_merged * 128;
let merge_time_ms = start_time.elapsed_ms();
let merged_segment = ManagedSegmentInfo {
segment_id: new_segment_id,
vector_count: vectors_merged,
vector_offset: 0,
generation: segments.iter().map(|s| s.generation).max().unwrap_or(0),
has_deletions: false,
size_bytes: total_size,
};
let stats = MergeStats {
segments_merged,
vectors_merged,
deletions_removed,
duplicates_removed,
merge_time_ms,
merged_size_bytes: total_size,
};
Ok(MergeResult {
merged_segment,
stats,
merged_segment_ids: segments.iter().map(|s| s.segment_id.clone()).collect(),
})
}
pub fn storage(&self) -> &Arc<dyn Storage> {
&self.storage
}
pub fn config(&self) -> &MergeConfig {
&self.config
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_merge_engine_basic() {
let config = MergeConfig::default();
let storage = Arc::new(crate::storage::memory::MemoryStorage::new(
crate::storage::memory::MemoryStorageConfig::default(),
));
let index_config = IvfIndexConfig::default();
let writer_config = VectorIndexWriterConfig::default();
let engine = MergeEngine::new(config, storage, index_config, writer_config);
assert_eq!(engine.config.max_merge_segments, 10);
}
}