use std::cmp::Reverse;
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;
use crate::segment::index::sparse_index::sparse_index_config::SparseIndexType;
use crate::segment::types::{HnswConfig, HnswGlobalConfig, Indexes, Memory, VectorName};
use super::config::SegmentOptimizerConfig;
use super::segment_optimizer::{OptimizationPlanner, SegmentOptimizer};
use crate::shard::operations::optimization::OptimizerThresholds;
pub struct ConfigMismatchOptimizer {
thresholds_config: OptimizerThresholds,
segments_path: PathBuf,
temp_path: PathBuf,
segment_optimizer_config: SegmentOptimizerConfig,
global_hnsw_config: HnswConfig,
hnsw_global_config: HnswGlobalConfig,
telemetry_durations_aggregator: Arc<Mutex<OperationDurationsAggregator>>,
}
impl ConfigMismatchOptimizer {
pub fn new(
thresholds_config: OptimizerThresholds,
segments_path: PathBuf,
temp_path: PathBuf,
segment_config: SegmentOptimizerConfig,
global_hnsw_config: HnswConfig,
hnsw_global_config: HnswGlobalConfig,
) -> Self {
ConfigMismatchOptimizer {
thresholds_config,
segments_path,
temp_path,
segment_optimizer_config: segment_config,
global_hnsw_config,
hnsw_global_config,
telemetry_durations_aggregator: OperationDurationsAggregator::new(),
}
}
fn requested_vectors_memory(&self, vector_name: &VectorName) -> Option<Memory> {
self.segment_optimizer_config
.dense_vector
.get(vector_name)
.and_then(|cfg| cfg.memory_placement())
}
fn requested_sparse_index_memory(&self, vector_name: &VectorName) -> Option<Memory> {
self.segment_optimizer_config
.sparse_vector
.get(vector_name)
.and_then(|cfg| cfg.memory_placement())
}
fn has_config_mismatch(&self, segment: &dyn ReadSegmentEntry) -> bool {
let segment_config = segment.config();
if self
.segment_optimizer_config
.payload_storage_type
.is_on_disk()
!= segment_config.payload_storage_type.is_on_disk()
{
return true; }
let dense_has_mismatch =
segment_config
.vector_data
.iter()
.any(|(vector_name, vector_data)| {
match &vector_data.index {
Indexes::Plain {} => {}
Indexes::Hnsw(effective_hnsw) => {
let target_hnsw = self
.segment_optimizer_config
.dense_vector
.get(vector_name)
.map(|cfg| cfg.hnsw_config)
.unwrap_or(self.global_hnsw_config);
if effective_hnsw.mismatch_requires_rebuild(&target_hnsw) {
return true;
}
}
}
if !vector_data.storage_type.is_empty()
&& let Some(required_memory) = self.requested_vectors_memory(vector_name)
&& required_memory.is_on_disk() != vector_data.storage_type.is_on_disk()
{
return true;
}
let target_quantization = self
.segment_optimizer_config
.dense_vector
.get(vector_name)
.and_then(|cfg| cfg.quantization_config.as_ref());
vector_data
.quantization_config
.as_ref()
.zip(target_quantization)
.map(|(current, target)| current.mismatch_requires_rebuild(target))
.unwrap_or_else(|| {
let vector_data_quantization_appendable = vector_data
.quantization_config
.as_ref()
.map(|q| q.supports_appendable())
.unwrap_or(false);
let target_quantization_appendable = target_quantization
.map(|q| q.supports_appendable())
.unwrap_or(false);
let unindexed_changed = crate::common::flags::feature_flags()
.appendable_quantization
&& (vector_data_quantization_appendable
|| target_quantization_appendable);
(vector_data.quantization_config.is_some()
!= target_quantization.is_some())
&& (vector_data.index.is_indexed() || unindexed_changed)
})
});
let sparse_has_mismatch =
segment_config
.sparse_vector_data
.iter()
.any(|(vector_name, vector_data)| {
let Some(required_memory) = self.requested_sparse_index_memory(vector_name)
else {
return false; };
match vector_data.index.index_type {
SparseIndexType::MutableRam => false,
SparseIndexType::ImmutableRam | SparseIndexType::Mmap => {
required_memory != vector_data.index.memory_placement()
}
}
});
sparse_has_mismatch || dense_has_mismatch
}
}
impl SegmentOptimizer for ConfigMismatchOptimizer {
fn name(&self) -> &'static str {
"config mismatch"
}
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 to_optimize = planner
.remaining()
.iter()
.filter_map(|(&segment_id, segment)| {
let segment = segment.read();
self.has_config_mismatch(&*segment).then(|| {
let vector_size = segment
.max_available_vectors_size_in_bytes()
.unwrap_or_default();
(segment_id, vector_size)
})
})
.sorted_by_key(|(_segment_id, vector_size)| Reverse(*vector_size))
.collect_vec();
for (segment_id, _) in to_optimize {
planner.plan(vec![segment_id]);
}
}
fn get_telemetry_counter(&self) -> &Mutex<OperationDurationsAggregator> {
&self.telemetry_durations_aggregator
}
}