use std::collections::{HashMap, HashSet};
use std::fmt;
use std::num::NonZeroUsize;
use std::sync::Arc;
use crate::segment::common::BYTES_IN_KB;
use crate::segment::data_types::modifier::Modifier;
use crate::segment::index::sparse_index::sparse_index_config::{SparseIndexConfig, SparseIndexType};
use crate::segment::types::{
Distance, HnswConfig, Indexes, Memory, MultiVectorConfig, PayloadStorageType,
QuantizationConfig, SegmentConfig, SparseVectorDataConfig, SparseVectorStorageType,
VectorDataConfig, VectorNameBuf, VectorStorageDatatype, VectorStorageType,
};
pub const TEMP_SEGMENTS_PATH: &str = "temp_segments";
pub const DEFAULT_MAX_SEGMENT_PER_CPU_KB: usize = 256_000;
pub const DEFAULT_INDEXING_THRESHOLD_KB: usize = 10_000;
pub const DEFAULT_DELETED_THRESHOLD: f64 = 0.2;
pub const DEFAULT_VACUUM_MIN_VECTOR_NUMBER: usize = 1000;
#[derive(Debug, Clone, PartialEq)]
pub struct DenseVectorOptimizerConfig {
pub on_disk: Option<bool>,
pub memory: Option<Memory>,
pub hnsw_config: HnswConfig,
pub quantization_config: Option<QuantizationConfig>,
}
impl DenseVectorOptimizerConfig {
pub fn memory_placement(&self) -> Option<Memory> {
Memory::resolve(self.memory, self.on_disk.map(Memory::from_on_disk))
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SparseVectorOptimizerConfig {
pub on_disk: Option<bool>,
pub memory: Option<Memory>,
}
impl SparseVectorOptimizerConfig {
pub fn memory_placement(&self) -> Option<Memory> {
Memory::resolve(self.memory, self.on_disk.map(Memory::from_on_disk_heap))
}
}
#[derive(Clone)]
pub struct LiveVectorNamesProvider(Arc<dyn Fn() -> HashSet<VectorNameBuf> + Send + Sync>);
impl LiveVectorNamesProvider {
pub fn new(read: impl Fn() -> HashSet<VectorNameBuf> + Send + Sync + 'static) -> Self {
Self(Arc::new(read))
}
pub fn get(&self) -> HashSet<VectorNameBuf> {
self.0()
}
}
impl fmt::Debug for LiveVectorNamesProvider {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LiveVectorNamesProvider")
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone)]
pub struct SegmentOptimizerConfig {
pub payload_storage_type: PayloadStorageType,
pub plain_dense_vector_config: HashMap<VectorNameBuf, VectorDataConfig>,
pub plain_sparse_vector_config: HashMap<VectorNameBuf, SparseVectorDataConfig>,
pub dense_vector: HashMap<VectorNameBuf, DenseVectorOptimizerConfig>,
pub sparse_vector: HashMap<VectorNameBuf, SparseVectorOptimizerConfig>,
pub live_vector_names: Option<LiveVectorNamesProvider>,
}
impl SegmentOptimizerConfig {
pub fn plain_segment_config(&self) -> SegmentConfig {
SegmentConfig {
vector_data: self.plain_dense_vector_config.clone(),
sparse_vector_data: self.plain_sparse_vector_config.clone(),
payload_storage_type: self.payload_storage_type,
}
}
pub fn new(
payload_storage_type: PayloadStorageType,
dense_vectors: HashMap<VectorNameBuf, DenseVectorOptimizerInput>,
sparse_vectors: HashMap<VectorNameBuf, SparseVectorOptimizerInput>,
) -> SegmentOptimizerConfig {
let (mut plain_dense_vector_config, mut dense_vector) = (HashMap::new(), HashMap::new());
for (name, input) in dense_vectors {
let DenseVectorOptimizerInput {
size,
distance,
on_disk,
memory,
hnsw_config,
quantization_config,
multivector_config,
datatype,
} = input;
let plain_memory = Memory::resolve(
memory,
Some(Memory::from_on_disk(on_disk.unwrap_or_default())),
)
.unwrap_or(Memory::Cached);
plain_dense_vector_config.insert(
name.clone(),
VectorDataConfig {
size,
distance,
index: Indexes::Plain {},
storage_type: VectorStorageType::appendable_from_memory(plain_memory),
quantization_config: QuantizationConfig::for_appendable_segment(
quantization_config.as_ref(),
),
multivector_config,
datatype,
},
);
dense_vector.insert(
name,
DenseVectorOptimizerConfig {
on_disk,
memory,
hnsw_config,
quantization_config,
},
);
}
let (mut plain_sparse_vector_config, mut sparse_vector) = (HashMap::new(), HashMap::new());
for (name, input) in sparse_vectors {
let SparseVectorOptimizerInput {
on_disk,
memory,
full_scan_threshold,
index_datatype,
storage_type,
modifier,
} = input;
plain_sparse_vector_config.insert(
name.clone(),
SparseVectorDataConfig {
index: SparseIndexConfig {
full_scan_threshold,
index_type: SparseIndexType::MutableRam,
datatype: index_datatype,
memory,
},
storage_type,
modifier,
},
);
sparse_vector.insert(name, SparseVectorOptimizerConfig { on_disk, memory });
}
SegmentOptimizerConfig {
payload_storage_type,
plain_dense_vector_config,
plain_sparse_vector_config,
dense_vector,
sparse_vector,
live_vector_names: None,
}
}
#[must_use]
pub fn with_live_vector_names(mut self, provider: LiveVectorNamesProvider) -> Self {
self.live_vector_names = Some(provider);
self
}
pub fn live_vector_names(&self) -> Option<HashSet<VectorNameBuf>> {
self.live_vector_names
.as_ref()
.map(LiveVectorNamesProvider::get)
}
}
#[derive(Debug, Clone)]
pub struct DenseVectorOptimizerInput {
pub size: usize,
pub distance: Distance,
pub on_disk: Option<bool>,
pub memory: Option<Memory>,
pub hnsw_config: HnswConfig,
pub quantization_config: Option<QuantizationConfig>,
pub multivector_config: Option<MultiVectorConfig>,
pub datatype: Option<VectorStorageDatatype>,
}
#[derive(Debug, Clone)]
pub struct SparseVectorOptimizerInput {
pub on_disk: Option<bool>,
pub memory: Option<Memory>,
pub full_scan_threshold: Option<usize>,
pub index_datatype: Option<VectorStorageDatatype>,
pub storage_type: SparseVectorStorageType,
pub modifier: Option<Modifier>,
}
pub fn default_segment_number() -> usize {
let expected_segments = crate::common::cpu::get_num_cpus() / 2;
expected_segments.clamp(2, 8)
}
pub fn get_number_segments(requested_segment_number: usize) -> usize {
if requested_segment_number == 0 {
default_segment_number()
} else {
requested_segment_number
}
}
pub fn get_indexing_threshold_kb(indexing_threshold: Option<usize>) -> usize {
match indexing_threshold {
None => DEFAULT_INDEXING_THRESHOLD_KB,
Some(0) => usize::MAX,
Some(custom) => custom,
}
}
pub fn get_max_segment_size_kb(
max_segment_size: Option<usize>,
num_indexing_threads: usize,
) -> usize {
if let Some(max) = max_segment_size {
max
} else {
num_indexing_threads.saturating_mul(DEFAULT_MAX_SEGMENT_PER_CPU_KB)
}
}
pub fn get_deferred_points_threshold_bytes(
prevent_unoptimized: Option<bool>,
indexing_threshold_kb: usize,
) -> Option<NonZeroUsize> {
(prevent_unoptimized == Some(true))
.then(|| indexing_threshold_kb.saturating_mul(BYTES_IN_KB))
.and_then(NonZeroUsize::new)
}
#[cfg(test)]
mod tests {
use std::sync::Mutex;
use super::*;
#[test]
fn live_vector_names_provider_reads_current_state() {
let source = Arc::new(Mutex::new(HashSet::from(["a".to_owned(), "b".to_owned()])));
let provider = {
let source = source.clone();
LiveVectorNamesProvider::new(move || source.lock().unwrap().clone())
};
assert_eq!(
provider.get(),
HashSet::from(["a".to_owned(), "b".to_owned()])
);
source.lock().unwrap().remove("b");
assert_eq!(provider.get(), HashSet::from(["a".to_owned()]));
}
}