use std::borrow::Cow;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use atomic_refcell::AtomicRefCell;
#[cfg(feature = "testing")]
use crate::common::counter::hardware_counter::HardwareCounterCell;
use crate::common::generic_consts::Sequential;
use crate::common::storage_version::StorageVersion as _;
use crate::common::universal_io::{MmapFile, MmapFs, UniversalReadFs};
use fs_err as fs;
use crate::sparse::SearchScratchPool;
use crate::sparse::common::sparse_vector::SparseVector;
use crate::sparse::index::inverted_index::inverted_index_ram::InvertedIndexRam;
use crate::sparse::index::inverted_index::inverted_index_ram_builder::InvertedIndexBuilder;
use crate::sparse::index::inverted_index::{InvertedIndex, InvertedIndexReadWrite};
use self::read_view::{SparseVectorIndexReadView, SparseVectorIndexReadViewEnum};
use super::indices_tracker::IndicesTracker;
use crate::segment::common::operation_error::{OperationError, OperationResult, check_process_stopped};
use crate::segment::id_tracker::{IdTrackerEnum, IdTrackerRead};
use crate::segment::index::sparse_index::sparse_index_config::SparseIndexConfig;
use crate::segment::index::sparse_index::sparse_search_telemetry::SparseSearchesTelemetry;
use crate::segment::index::struct_payload_index::StructPayloadIndex;
use crate::segment::vector_storage::{VectorStorageEnum, VectorStorageRead};
mod read_view;
mod vector_index_impl;
pub mod read_only;
#[derive(Debug)]
pub struct SparseVectorIndex<TInvertedIndex: InvertedIndex> {
config: SparseIndexConfig,
id_tracker: Arc<AtomicRefCell<IdTrackerEnum>>,
vector_storage: Arc<AtomicRefCell<VectorStorageEnum>>,
payload_index: Arc<AtomicRefCell<StructPayloadIndex>>,
path: PathBuf,
inverted_index: TInvertedIndex,
searches_telemetry: SparseSearchesTelemetry,
indices_tracker: IndicesTracker,
search_scratch_pool: SearchScratchPool,
}
#[cfg(feature = "testing")]
impl<TInvertedIndex: InvertedIndex> SparseVectorIndex<TInvertedIndex> {
pub fn config(&self) -> SparseIndexConfig {
self.config
}
pub fn id_tracker(&self) -> &Arc<AtomicRefCell<IdTrackerEnum>> {
&self.id_tracker
}
pub fn vector_storage(&self) -> &Arc<AtomicRefCell<VectorStorageEnum>> {
&self.vector_storage
}
pub fn payload_index(&self) -> &Arc<AtomicRefCell<StructPayloadIndex>> {
&self.payload_index
}
pub fn indices_tracker(&self) -> &IndicesTracker {
&self.indices_tracker
}
}
pub struct SparseVectorIndexOpenArgs<'a, Fs: UniversalReadFs, F: FnMut()> {
pub fs: &'a Fs,
pub config: SparseIndexConfig,
pub id_tracker: Arc<AtomicRefCell<IdTrackerEnum>>,
pub vector_storage: Arc<AtomicRefCell<VectorStorageEnum>>,
pub payload_index: Arc<AtomicRefCell<StructPayloadIndex>>,
pub path: &'a Path,
pub stopped: &'a AtomicBool,
pub tick_progress: F,
}
pub enum SparseOpenPlan {
Load {
config: SparseIndexConfig,
indices_tracker: IndicesTracker,
},
Build {
config: SparseIndexConfig,
ram_index: InvertedIndexRam,
indices_tracker: IndicesTracker,
persist: bool,
},
}
fn build_ram_index(
id_tracker: &impl IdTrackerRead,
vector_storage: &impl VectorStorageRead,
stopped: &AtomicBool,
mut tick_progress: impl FnMut(),
) -> OperationResult<(InvertedIndexRam, IndicesTracker)> {
let deleted_bitslice = vector_storage.deleted_vector_bitslice();
let ids = id_tracker
.point_mappings()
.iter_internal_excluding(deleted_bitslice)
.map(|id| ((), id));
let mut ram_index_builder = InvertedIndexBuilder::new();
let mut indices_tracker = IndicesTracker::default();
let mut result: OperationResult<()> = Ok(());
vector_storage.read_vectors::<Sequential, _>(ids, |(), id, vector| {
if result.is_err() {
return;
}
if let Err(err) = check_process_stopped(stopped) {
result = Err(OperationError::from(err));
return;
}
let vector: &SparseVector = match vector.as_vec_ref().try_into() {
Ok(vector) => vector,
Err(err) => {
result = Err(err);
return;
}
};
if !vector.is_empty() {
indices_tracker.register_indices(vector);
let vector = indices_tracker.remap_vector(vector.to_owned());
ram_index_builder.add(id, vector);
}
tick_progress();
});
result?;
Ok((ram_index_builder.build(), indices_tracker))
}
impl<TInvertedIndex: InvertedIndex> SparseVectorIndex<TInvertedIndex> {
pub fn open<F: FnMut()>(args: SparseVectorIndexOpenArgs<'_, MmapFs, F>) -> OperationResult<Self>
where
TInvertedIndex: InvertedIndexReadWrite<MmapFile>,
{
let SparseVectorIndexOpenArgs {
fs,
config,
id_tracker,
vector_storage,
payload_index,
path,
stopped,
tick_progress,
} = args;
let plan = Self::plan(
config,
&id_tracker,
&vector_storage,
path,
stopped,
tick_progress,
)?;
let (inverted_index, config, indices_tracker, persist) = match plan {
SparseOpenPlan::Load {
config,
indices_tracker,
} => (
TInvertedIndex::open_rw(fs, path)?,
config,
indices_tracker,
false,
),
SparseOpenPlan::Build {
config,
ram_index,
indices_tracker,
persist,
} => (
TInvertedIndex::from_ram_index(fs, Cow::Owned(ram_index), path)?,
config,
indices_tracker,
persist,
),
};
if persist {
config.save(&SparseIndexConfig::get_config_path(path))?;
inverted_index.save(path)?;
indices_tracker.save(path)?;
TInvertedIndex::Version::save(path)?;
}
Ok(Self {
config,
id_tracker,
vector_storage,
payload_index,
path: path.to_path_buf(),
inverted_index,
searches_telemetry: SparseSearchesTelemetry::new(),
indices_tracker,
search_scratch_pool: SearchScratchPool::new(),
})
}
pub fn plan(
config: SparseIndexConfig,
id_tracker: &AtomicRefCell<IdTrackerEnum>,
vector_storage: &AtomicRefCell<VectorStorageEnum>,
path: &Path,
stopped: &AtomicBool,
tick_progress: impl FnMut(),
) -> OperationResult<SparseOpenPlan> {
if !config.index_type.is_persisted() {
fs::create_dir_all(path)?;
let (ram_index, indices_tracker) = build_ram_index(
&*id_tracker.borrow(),
&*vector_storage.borrow(),
stopped,
tick_progress,
)?;
return Ok(SparseOpenPlan::Build {
config,
ram_index,
indices_tracker,
persist: false,
});
}
let stored_version = TInvertedIndex::Version::load_universal(&MmapFs, path)?;
if stored_version == Some(TInvertedIndex::Version::current()) {
let config = SparseIndexConfig::load(&SparseIndexConfig::get_config_path(path))?;
let indices_tracker = IndicesTracker::open(path)?;
return Ok(SparseOpenPlan::Load {
config,
indices_tracker,
});
}
if fs::exists(path).unwrap_or(true) {
log::warn!(
"Sparse index at {path:?} is missing or outdated (found {stored_version:?}, expected {}), rebuilding",
TInvertedIndex::Version::current(),
);
fs::remove_dir_all(path)?;
}
fs::create_dir_all(path)?;
let (ram_index, indices_tracker) = build_ram_index(
&*id_tracker.borrow(),
&*vector_storage.borrow(),
stopped,
tick_progress,
)?;
Ok(SparseOpenPlan::Build {
config,
ram_index,
indices_tracker,
persist: true,
})
}
pub fn inverted_index(&self) -> &TInvertedIndex {
&self.inverted_index
}
pub fn with_view<R>(
&self,
f: impl FnOnce(SparseVectorIndexReadViewEnum<'_, TInvertedIndex>) -> R,
) -> R {
let id_tracker = self.id_tracker.borrow();
let vector_storage = self.vector_storage.borrow();
let payload_index = self.payload_index.borrow();
payload_index.with_view(|payload_index_view| {
let read_view = SparseVectorIndexReadView {
config: self.config,
id_tracker: &*id_tracker,
vector_storage: &*vector_storage,
payload_index: payload_index_view,
inverted_index: &self.inverted_index,
searches_telemetry: &self.searches_telemetry,
indices_tracker: &self.indices_tracker,
search_scratch_pool: &self.search_scratch_pool,
};
f(read_view)
})
}
#[cfg(feature = "testing")]
pub fn max_result_count(&self, query_vector: &SparseVector) -> OperationResult<usize> {
use crate::sparse::index::posting_list_common::PostingListIter as _;
let hw_counter = HardwareCounterCell::disposable();
let mut unique_record_ids = std::collections::HashSet::new();
let arena = blink_alloc::Blink::new();
let ids = query_vector
.indices
.iter()
.filter_map(|dim_id| Some(((), self.indices_tracker.remap_index(*dim_id)?)));
self.inverted_index
.get_batch(ids, &arena, &hw_counter, |(), posting_list_iter| {
for element in posting_list_iter.into_std_iter() {
unique_record_ids.insert(element.record_id);
}
Ok(())
})?;
Ok(unique_record_ids.len())
}
}