use std::fmt::Debug;
use crate::vector::index::segment::manager::{ManagedSegmentInfo, SegmentManagerConfig};
pub trait MergePolicy: Debug + Send + Sync {
fn candidates(
&self,
segments: &[ManagedSegmentInfo],
config: &SegmentManagerConfig,
) -> Option<Vec<String>>;
}
#[derive(Debug, Default)]
pub struct SimpleMergePolicy;
impl SimpleMergePolicy {
pub fn new() -> Self {
Self
}
}
impl MergePolicy for SimpleMergePolicy {
fn candidates(
&self,
segments: &[ManagedSegmentInfo],
config: &SegmentManagerConfig,
) -> Option<Vec<String>> {
if segments.len() < config.max_segments as usize {
return None;
}
let merge_factor = config.merge_factor as usize;
if segments.len() < merge_factor {
return None;
}
let mut by_generation: Vec<&ManagedSegmentInfo> = segments.iter().collect();
by_generation.sort_by_key(|s| s.generation);
let best = by_generation
.windows(merge_factor)
.min_by_key(|w| w.iter().map(|s| s.vector_count).sum::<u64>())?;
Some(best.iter().map(|s| s.segment_id.clone()).collect())
}
}
#[derive(Debug)]
pub struct TieredMergePolicy {
pub tier_ratio: f64,
}
impl TieredMergePolicy {
pub fn new() -> Self {
Self { tier_ratio: 8.0 }
}
}
impl Default for TieredMergePolicy {
fn default() -> Self {
Self::new()
}
}
impl MergePolicy for TieredMergePolicy {
fn candidates(
&self,
segments: &[ManagedSegmentInfo],
config: &SegmentManagerConfig,
) -> Option<Vec<String>> {
let merge_factor = (config.merge_factor as usize).max(2);
if segments.len() < merge_factor {
return None;
}
let mut by_generation: Vec<&ManagedSegmentInfo> = segments.iter().collect();
by_generation.sort_by_key(|s| s.generation);
let similar = by_generation
.windows(merge_factor)
.filter(|w| {
let min = w.iter().map(|s| s.vector_count.max(1)).min().unwrap_or(1);
let max = w.iter().map(|s| s.vector_count.max(1)).max().unwrap_or(1);
max as f64 <= min as f64 * self.tier_ratio
})
.filter(|w| {
w.iter().map(|s| s.vector_count).sum::<u64>() <= config.max_vectors_per_segment
})
.min_by_key(|w| w.iter().map(|s| s.vector_count).sum::<u64>());
if let Some(window) = similar {
return Some(window.iter().map(|s| s.segment_id.clone()).collect());
}
if segments.len() > config.max_segments as usize {
let best = by_generation
.windows(merge_factor)
.min_by_key(|w| w.iter().map(|s| s.vector_count).sum::<u64>())?;
return Some(best.iter().map(|s| s.segment_id.clone()).collect());
}
None
}
}
#[derive(Debug, Default)]
pub struct ForceMergePolicy;
impl ForceMergePolicy {
pub fn new() -> Self {
Self
}
}
impl MergePolicy for ForceMergePolicy {
fn candidates(
&self,
segments: &[ManagedSegmentInfo],
_config: &SegmentManagerConfig,
) -> Option<Vec<String>> {
if segments.is_empty() {
return None;
}
Some(segments.iter().map(|s| s.segment_id.clone()).collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vector::index::segment::manager::ManagedSegmentInfo;
fn create_info(id: &str, count: u64) -> ManagedSegmentInfo {
ManagedSegmentInfo {
segment_id: id.to_string(),
vector_count: count,
vector_offset: 0,
generation: 1,
has_deletions: false,
size_bytes: count * 100,
}
}
#[test]
fn test_simple_merge_policy_candidates() {
let policy = SimpleMergePolicy::new();
let config = SegmentManagerConfig {
max_segments: 5,
merge_factor: 3,
..Default::default()
};
let segments = vec![create_info("1", 100), create_info("2", 100)];
assert!(policy.candidates(&segments, &config).is_none());
let segments = vec![
create_info("1", 1000), create_info("2", 100), create_info("3", 100), create_info("4", 100), create_info("5", 1000), create_info("6", 1000), ];
let candidates = policy.candidates(&segments, &config).unwrap();
assert_eq!(candidates.len(), 3);
assert!(candidates.contains(&"2".to_string()));
assert!(candidates.contains(&"3".to_string()));
assert!(candidates.contains(&"4".to_string()));
}
}