use std::path::{Path, PathBuf};
use std::sync::Arc;
use itertools::Itertools;
use parking_lot::Mutex;
use crate::segment::common::operation_time_statistics::OperationDurationsAggregator;
use crate::segment::entry::ReadSegmentEntry as _;
use crate::segment::types::HnswGlobalConfig;
use super::config::SegmentOptimizerConfig;
use super::segment_optimizer::{OptimizationPlanner, SegmentOptimizer};
use crate::shard::operations::optimization::OptimizerThresholds;
const BYTES_IN_KB: usize = 1024;
pub struct MergeOptimizer {
default_segments_number: usize,
thresholds_config: OptimizerThresholds,
segments_path: PathBuf,
temp_path: PathBuf,
segment_optimizer_config: SegmentOptimizerConfig,
hnsw_global_config: HnswGlobalConfig,
telemetry_durations_aggregator: Arc<Mutex<OperationDurationsAggregator>>,
}
impl MergeOptimizer {
#[allow(clippy::too_many_arguments)]
pub fn new(
default_segments_number: usize,
thresholds_config: OptimizerThresholds,
segments_path: PathBuf,
temp_path: PathBuf,
segment_config: SegmentOptimizerConfig,
hnsw_global_config: HnswGlobalConfig,
) -> Self {
Self {
default_segments_number,
thresholds_config,
segments_path,
temp_path,
segment_optimizer_config: segment_config,
hnsw_global_config,
telemetry_durations_aggregator: OperationDurationsAggregator::new(),
}
}
#[cfg(any(test, feature = "testing"))]
pub fn threshold_config_mut_for_test(&mut self) -> &mut OptimizerThresholds {
&mut self.thresholds_config
}
#[cfg(any(test, feature = "testing"))]
pub fn set_default_segments_number_for_test(&mut self, value: usize) {
self.default_segments_number = value;
}
}
impl SegmentOptimizer for MergeOptimizer {
fn name(&self) -> &'static str {
"merge"
}
fn segments_path(&self) -> &Path {
self.segments_path.as_path()
}
fn temp_path(&self) -> &Path {
self.temp_path.as_path()
}
fn segment_optimizer_config(&self) -> &SegmentOptimizerConfig {
&self.segment_optimizer_config
}
fn hnsw_global_config(&self) -> &HnswGlobalConfig {
&self.hnsw_global_config
}
fn threshold_config(&self) -> &OptimizerThresholds {
&self.thresholds_config
}
fn plan_optimizations(&self, planner: &mut OptimizationPlanner) {
let mut candidates = planner
.remaining()
.iter()
.map(|(&segment_id, segment)| {
let size = segment
.read()
.max_available_vectors_size_in_bytes()
.unwrap_or_default();
(segment_id, size)
})
.collect_vec();
candidates.sort_by_key(|(_segment_id, size)| *size);
let threshold = self
.thresholds_config
.max_segment_size_kb
.saturating_mul(BYTES_IN_KB);
let mut first_batch = None;
let mut taken_candidates = 0;
let mut last_candidate =
(planner.expected_segments_number() + 2).saturating_sub(self.default_segments_number);
while taken_candidates < last_candidate.min(candidates.len()) {
let batch = candidates[taken_candidates..last_candidate.min(candidates.len())]
.iter()
.scan(0, |size_sum, &(segment_id, size)| {
*size_sum += size;
(*size_sum < threshold).then_some(segment_id)
})
.collect_vec();
if batch.len() < 2 {
return;
}
let is_first_batch = taken_candidates == 0;
taken_candidates += batch.len();
last_candidate += 1;
if is_first_batch && batch.len() < 3 {
first_batch = Some(batch);
continue;
}
if let Some(first_batch) = first_batch.take() {
planner.plan(first_batch);
}
planner.plan(batch);
}
}
fn get_telemetry_counter(&self) -> &Mutex<OperationDurationsAggregator> {
&self.telemetry_durations_aggregator
}
}