mirror of
https://github.com/qdrant/qdrant.git
synced 2026-08-07 02:20:55 -05:00
* feat: use 0 as disable for indexing and memmap * amend: edit test, update doc comments, remove validation * fix: use constant instead of literal, reword doc comments * test: update test for loop and timeout * fix: remove validation on OptimizerConfigDiff and fix test_validatioin.py * fix: remove validation from build.rs, remove internal hack from doccomments * fix: update grpc docs * revert max_optimization_threads validation in test * update config comments and minor test change --------- Co-authored-by: Andrey Vasnetsov <andrey@vasnetsov.com>
168 lines
6.4 KiB
Rust
168 lines
6.4 KiB
Rust
use std::path::Path;
|
|
use std::sync::Arc;
|
|
|
|
use schemars::JsonSchema;
|
|
use segment::common::cpu::get_num_cpus;
|
|
use segment::types::{HnswConfig, QuantizationConfig};
|
|
use serde::{Deserialize, Serialize};
|
|
use validator::Validate;
|
|
|
|
use crate::collection_manager::optimizers::indexing_optimizer::IndexingOptimizer;
|
|
use crate::collection_manager::optimizers::merge_optimizer::MergeOptimizer;
|
|
use crate::collection_manager::optimizers::segment_optimizer::OptimizerThresholds;
|
|
use crate::collection_manager::optimizers::vacuum_optimizer::VacuumOptimizer;
|
|
use crate::config::CollectionParams;
|
|
use crate::update_handler::Optimizer;
|
|
|
|
const DEFAULT_MAX_SEGMENT_PER_CPU_KB: usize = 200_000;
|
|
const DEFAULT_INDEXING_THRESHOLD_KB: usize = 20_000;
|
|
|
|
#[derive(Debug, Deserialize, Serialize, JsonSchema, Validate, Clone, PartialEq)]
|
|
pub struct OptimizersConfig {
|
|
/// The minimal fraction of deleted vectors in a segment, required to perform segment optimization
|
|
#[validate(range(min = 0.0, max = 1.0))]
|
|
pub deleted_threshold: f64,
|
|
/// The minimal number of vectors in a segment, required to perform segment optimization
|
|
#[validate(range(min = 100))]
|
|
pub vacuum_min_vector_number: usize,
|
|
/// Target amount of segments optimizer will try to keep.
|
|
/// Real amount of segments may vary depending on multiple parameters:
|
|
/// - Amount of stored points
|
|
/// - Current write RPS
|
|
///
|
|
/// It is recommended to select default number of segments as a factor of the number of search threads,
|
|
/// so that each segment would be handled evenly by one of the threads.
|
|
/// If `default_segment_number = 0`, will be automatically selected by the number of available CPUs.
|
|
pub default_segment_number: usize,
|
|
/// Do not create segments larger this size (in kilobytes).
|
|
/// Large segments might require disproportionately long indexation times,
|
|
/// therefore it makes sense to limit the size of segments.
|
|
///
|
|
/// If indexing speed is more important - make this parameter lower.
|
|
/// If search speed is more important - make this parameter higher.
|
|
/// Note: 1Kb = 1 vector of size 256
|
|
/// If not set, will be automatically selected considering the number of available CPUs.
|
|
#[serde(alias = "max_segment_size_kb")]
|
|
#[serde(default)]
|
|
pub max_segment_size: Option<usize>,
|
|
/// Maximum size (in kilobytes) of vectors to store in-memory per segment.
|
|
/// Segments larger than this threshold will be stored as read-only memmaped file.
|
|
///
|
|
/// Memmap storage is disabled by default, to enable it, set this threshold to a reasonable value.
|
|
///
|
|
/// To disable memmap storage, set this to `0`. Internally it will use the largest threshold possible.
|
|
///
|
|
/// Note: 1Kb = 1 vector of size 256
|
|
#[serde(alias = "memmap_threshold_kb")]
|
|
#[serde(default)]
|
|
pub memmap_threshold: Option<usize>,
|
|
/// Maximum size (in kilobytes) of vectors allowed for plain index, exceeding this threshold will enable vector indexing
|
|
///
|
|
/// Default value is 20,000, based on <https://github.com/google-research/google-research/blob/master/scann/docs/algorithms.md>.
|
|
///
|
|
/// To disable vector indexing, set to `0`.
|
|
///
|
|
/// Note: 1kB = 1 vector of size 256.
|
|
#[serde(alias = "indexing_threshold_kb")]
|
|
#[serde(default)]
|
|
pub indexing_threshold: Option<usize>,
|
|
/// Minimum interval between forced flushes.
|
|
pub flush_interval_sec: u64,
|
|
/// Maximum available threads for optimization workers
|
|
pub max_optimization_threads: usize,
|
|
}
|
|
|
|
impl OptimizersConfig {
|
|
#[cfg(test)]
|
|
pub fn fixture() -> Self {
|
|
Self {
|
|
deleted_threshold: 0.1,
|
|
vacuum_min_vector_number: 1000,
|
|
default_segment_number: 0,
|
|
max_segment_size: None,
|
|
memmap_threshold: None,
|
|
indexing_threshold: Some(100_000),
|
|
flush_interval_sec: 60,
|
|
max_optimization_threads: 0,
|
|
}
|
|
}
|
|
|
|
pub fn get_number_segments(&self) -> usize {
|
|
if self.default_segment_number == 0 {
|
|
let num_cpus = get_num_cpus();
|
|
// Do not configure less than 2 and more than 8 segments
|
|
// until it is not explicitly requested
|
|
num_cpus.clamp(2, 8)
|
|
} else {
|
|
self.default_segment_number
|
|
}
|
|
}
|
|
|
|
pub fn get_max_segment_size(&self) -> usize {
|
|
if let Some(max_segment_size) = self.max_segment_size {
|
|
max_segment_size
|
|
} else {
|
|
let num_cpus = get_num_cpus();
|
|
num_cpus.saturating_mul(DEFAULT_MAX_SEGMENT_PER_CPU_KB)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn build_optimizers(
|
|
shard_path: &Path,
|
|
collection_params: &CollectionParams,
|
|
optimizers_config: &OptimizersConfig,
|
|
hnsw_config: &HnswConfig,
|
|
quantization_config: &Option<QuantizationConfig>,
|
|
) -> Arc<Vec<Arc<Optimizer>>> {
|
|
let segments_path = shard_path.join("segments");
|
|
let temp_segments_path = shard_path.join("temp_segments");
|
|
|
|
let indexing_threshold = match optimizers_config.indexing_threshold {
|
|
None => DEFAULT_INDEXING_THRESHOLD_KB, // default value
|
|
Some(0) => usize::MAX, // disable vector index
|
|
Some(custom) => custom,
|
|
};
|
|
|
|
let memmap_threshold = match optimizers_config.memmap_threshold {
|
|
None | Some(0) => usize::MAX, // default | disable memmap
|
|
Some(custom) => custom,
|
|
};
|
|
|
|
let threshold_config = OptimizerThresholds {
|
|
memmap_threshold,
|
|
indexing_threshold,
|
|
max_segment_size: optimizers_config.get_max_segment_size(),
|
|
};
|
|
|
|
Arc::new(vec![
|
|
Arc::new(MergeOptimizer::new(
|
|
optimizers_config.get_number_segments(),
|
|
threshold_config.clone(),
|
|
segments_path.clone(),
|
|
temp_segments_path.clone(),
|
|
collection_params.clone(),
|
|
hnsw_config.clone(),
|
|
quantization_config.clone(),
|
|
)),
|
|
Arc::new(IndexingOptimizer::new(
|
|
threshold_config.clone(),
|
|
segments_path.clone(),
|
|
temp_segments_path.clone(),
|
|
collection_params.clone(),
|
|
hnsw_config.clone(),
|
|
quantization_config.clone(),
|
|
)),
|
|
Arc::new(VacuumOptimizer::new(
|
|
optimizers_config.deleted_threshold,
|
|
optimizers_config.vacuum_min_vector_number,
|
|
threshold_config,
|
|
segments_path,
|
|
temp_segments_path,
|
|
collection_params.clone(),
|
|
hnsw_config.clone(),
|
|
quantization_config.clone(),
|
|
)),
|
|
])
|
|
}
|