use std::collections::HashMap;
use std::path::PathBuf;
use crate::common::counter::hardware_counter::HardwareCounterCell;
use crate::common::types::{PointOffsetType, ScoredPointOffset, TelemetryDetail};
use crate::sparse::common::types::DimId;
use super::HNSWIndex;
use crate::segment::common::operation_error::{OperationError, OperationResult};
use crate::segment::common::operation_time_statistics::ScopeDurationMeasurer;
use crate::segment::data_types::query_context::VectorQueryContext;
use crate::segment::data_types::vectors::{QueryVector, VectorRef};
use crate::segment::id_tracker::IdTrackerRead;
use crate::segment::index::hnsw_index::config::HnswGraphConfig;
use crate::segment::index::query_estimator::adjust_to_available_vectors;
use crate::segment::index::sample_estimation::sample_check_cardinality;
use crate::segment::index::{PayloadIndexRead, VectorIndex, VectorIndexRead};
use crate::segment::telemetry::VectorIndexSearchesTelemetry;
use crate::segment::types::{Filter, QuantizationSearchParams, SearchParams};
use crate::segment::vector_storage::VectorStorageRead;
impl VectorIndexRead for HNSWIndex {
fn search(
&self,
vectors: &[&QueryVector],
filter: Option<&Filter>,
top: usize,
params: Option<&SearchParams>,
query_context: &VectorQueryContext,
) -> OperationResult<Vec<Vec<ScoredPointOffset>>> {
if top == 0 {
return Ok(vec![vec![]; vectors.len()]);
}
let is_hnsw_disabled = self.config.m == 0 && self.config.payload_m.unwrap_or(0) == 0;
let exact = params.is_some_and(|params| params.exact);
let exact_params = if exact {
params.map(|params| {
let mut params = *params;
params.quantization = Some(QuantizationSearchParams {
ignore: true,
rescore: Some(false),
oversampling: None,
}); params
})
} else {
None
};
match filter {
None => {
let vector_storage = self.vector_storage.borrow();
let plain_search = exact
|| is_hnsw_disabled
|| vector_storage.available_vector_count() < self.config.full_scan_threshold;
if plain_search {
let _timer = ScopeDurationMeasurer::new(if exact {
&self.searches_telemetry.exact_unfiltered
} else {
&self.searches_telemetry.unfiltered_plain
});
let params_ref = if exact { exact_params.as_ref() } else { params };
self.search_plain_unfiltered_batched(vectors, top, params_ref, query_context)
} else {
let _timer =
ScopeDurationMeasurer::new(&self.searches_telemetry.unfiltered_hnsw);
self.search_vectors_with_graph(vectors, None, top, params, query_context)
}
}
Some(query_filter) => {
if exact || is_hnsw_disabled {
let _timer = ScopeDurationMeasurer::new(if exact {
&self.searches_telemetry.exact_filtered
} else {
&self.searches_telemetry.filtered_plain
});
let params_ref = if exact { exact_params.as_ref() } else { params };
return self.search_vectors_plain(
vectors,
query_filter,
top,
params_ref,
query_context,
);
}
let payload_index = self.payload_index.borrow();
let vector_storage = self.vector_storage.borrow();
let id_tracker = self.id_tracker.borrow();
let available_vector_count = vector_storage.available_vector_count();
let hw_counter = query_context.hardware_counter();
let query_point_cardinality = payload_index
.with_view(|v| v.estimate_cardinality(query_filter, &hw_counter))?;
let query_cardinality = adjust_to_available_vectors(
query_point_cardinality,
available_vector_count,
id_tracker.available_point_count(),
);
if query_cardinality.max < self.config.full_scan_threshold {
let _timer =
ScopeDurationMeasurer::new(&self.searches_telemetry.small_cardinality);
return self.search_vectors_plain(
vectors,
query_filter,
top,
params,
query_context,
);
}
if query_cardinality.min > self.config.full_scan_threshold {
let _timer =
ScopeDurationMeasurer::new(&self.searches_telemetry.large_cardinality);
return self.search_vectors_with_graph(
vectors,
filter,
top,
params,
query_context,
);
}
let use_graph = payload_index.with_view(|v| {
let filter_context = v.filter_context(query_filter, &hw_counter)?;
Ok::<_, OperationError>(sample_check_cardinality(
id_tracker.sample_ids(Some(vector_storage.deleted_vector_bitslice())),
|idx| filter_context.check(idx),
self.config.full_scan_threshold,
available_vector_count, ))
})?;
if use_graph {
let _timer =
ScopeDurationMeasurer::new(&self.searches_telemetry.large_cardinality);
self.search_vectors_with_graph(vectors, filter, top, params, query_context)
} else {
let _timer =
ScopeDurationMeasurer::new(&self.searches_telemetry.small_cardinality);
self.search_vectors_plain(vectors, query_filter, top, params, query_context)
}
}
}
}
fn get_telemetry_data(&self, detail: TelemetryDetail) -> VectorIndexSearchesTelemetry {
let tm = &self.searches_telemetry;
VectorIndexSearchesTelemetry {
index_name: None,
unfiltered_plain: tm.unfiltered_plain.lock().get_statistics(detail),
filtered_plain: tm.filtered_plain.lock().get_statistics(detail),
unfiltered_hnsw: tm.unfiltered_hnsw.lock().get_statistics(detail),
filtered_small_cardinality: tm.small_cardinality.lock().get_statistics(detail),
filtered_large_cardinality: tm.large_cardinality.lock().get_statistics(detail),
filtered_exact: tm.exact_filtered.lock().get_statistics(detail),
filtered_sparse: Default::default(),
unfiltered_exact: tm.exact_unfiltered.lock().get_statistics(detail),
unfiltered_sparse: Default::default(),
}
}
fn indexed_vector_count(&self) -> usize {
self.config
.indexed_vector_count
.unwrap_or_else(|| self.graph.num_points())
}
fn size_of_searchable_vectors_in_bytes(&self) -> usize {
self.vector_storage
.borrow()
.size_of_available_vectors_in_bytes()
}
fn fill_idf_statistics(
&self,
_idf: &mut HashMap<DimId, usize>,
_hw_counter: &HardwareCounterCell,
) -> OperationResult<()> {
Ok(())
}
fn is_index(&self) -> bool {
true
}
}
impl VectorIndex for HNSWIndex {
fn files(&self) -> Vec<PathBuf> {
let mut files = self.graph.files(&self.path);
let config_path = HnswGraphConfig::get_config_path(&self.path);
if config_path.exists() {
files.push(config_path);
}
files
}
fn immutable_files(&self) -> Vec<PathBuf> {
self.files() }
fn update_vector(
&mut self,
_id: PointOffsetType,
_vector: Option<VectorRef>,
_hw_counter: &HardwareCounterCell,
) -> OperationResult<()> {
Err(OperationError::service_error("Cannot update HNSW index"))
}
}