mod build;
mod payload_index;
mod read_view;
pub use read_view::{IdsConditionChecker, StructPayloadIndexReadView};
pub mod read_only;
#[cfg(test)]
mod tests;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Instant;
use atomic_refcell::AtomicRefCell;
use crate::common::counter::hardware_counter::HardwareCounterCell;
use crate::common::defaults::log_load_timing;
use fs_err as fs;
use super::field_index::FieldIndex;
use super::field_index::index_selector::IndexSelector;
use super::payload_config::{FullPayloadIndexType, PayloadFieldSchemaWithIndexType};
use crate::segment::common::operation_error::OperationResult;
use crate::segment::common::utils::IndexesMap;
use crate::segment::id_tracker::{IdTrackerEnum, IdTrackerRead};
use crate::segment::index::payload_config::{self, PayloadConfig};
use crate::segment::index::visited_pool::VisitedPool;
use crate::segment::payload_storage::payload_storage_enum::PayloadStorageEnum;
use crate::segment::types::{Memory, PayloadFieldSchema, PayloadKeyType, VectorNameBuf};
use crate::segment::vector_storage::VectorStorageEnum;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StorageType {
Appendable,
NonAppendable,
}
impl StorageType {
pub fn from_appendable(appendable: bool) -> Self {
if appendable {
StorageType::Appendable
} else {
StorageType::NonAppendable
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum IndexLoadMode {
CreateIfMissing,
LoadExisting,
}
#[derive(Debug)]
pub struct StructPayloadIndex {
pub(super) payload: Arc<AtomicRefCell<PayloadStorageEnum>>,
pub(super) id_tracker: Arc<AtomicRefCell<IdTrackerEnum>>,
pub(super) vector_storages: HashMap<VectorNameBuf, Arc<AtomicRefCell<VectorStorageEnum>>>,
pub field_indexes: IndexesMap,
config: PayloadConfig,
path: PathBuf,
pub(super) visited_pool: VisitedPool,
storage_type: StorageType,
}
impl StructPayloadIndex {
fn config_path(&self) -> PathBuf {
PayloadConfig::get_config_path(&self.path)
}
pub(super) fn save_config(&self) -> OperationResult<()> {
let config_path = self.config_path();
self.config.save(&config_path)
}
fn load_all_fields(&mut self, create_if_missing: bool) -> OperationResult<()> {
let mut field_indexes: IndexesMap = Default::default();
let mut indices = std::mem::take(&mut self.config.indices);
let mut is_dirty = false;
for (field, payload_schema) in indices.iter_mut() {
let started = Instant::now();
let (field_index, dirty) =
self.load_from_db(field, payload_schema, create_if_missing)?;
log_load_timing(&self.path, &format!("field `{field}`"), started);
field_indexes.insert(field.clone(), field_index);
is_dirty |= dirty;
}
self.config.indices = indices;
if is_dirty {
self.save_config()?;
}
self.field_indexes = field_indexes;
Ok(())
}
fn load_from_db(
&self,
field: &PayloadKeyType,
payload_schema: &mut PayloadFieldSchemaWithIndexType,
create_if_missing: bool,
) -> OperationResult<(Vec<FieldIndex>, bool)> {
let id_tracker_borrow = self.id_tracker.borrow();
let deleted_points = id_tracker_borrow.deleted_point_bitslice();
let mut rebuild = false;
let mut is_dirty = false;
let mut indexes = if payload_schema.types.is_empty() {
let selector = self.selector(&payload_schema.schema);
let indexes = selector.new_index(
field,
&payload_schema.schema,
create_if_missing,
deleted_points,
)?;
if let Some(mut indexes) = indexes {
debug_assert!(
!indexes
.iter()
.any(|index| matches!(index, FieldIndex::NullIndex(_))),
"index selector is not expected to provide null index",
);
if let Some(null_index) = selector.new_null_index(
field,
create_if_missing,
&id_tracker_borrow,
selector.default_mutability(),
)? {
indexes.push(null_index);
}
is_dirty = true;
payload_schema.types = indexes.iter().map(|i| i.get_full_index_type()).collect();
indexes
} else {
rebuild = true;
vec![]
}
} else {
payload_schema
.types
.iter()
.map(|index| {
let selector = self.selector_with_type(index, &payload_schema.schema);
selector.new_index_with_type(
field,
&payload_schema.schema,
index,
create_if_missing,
&id_tracker_borrow,
deleted_points,
)
})
.take_while(|index| {
let is_loaded = index.as_ref().is_ok_and(|index| index.is_some());
rebuild |= !is_loaded;
is_loaded
})
.filter_map(|index| index.transpose())
.collect::<OperationResult<Vec<_>>>()?
};
if rebuild {
log::debug!("Rebuilding payload index for field `{field}`...");
indexes.clear();
indexes = self.build_field_indexes(
field,
&payload_schema.schema,
&HardwareCounterCell::disposable(), )?;
for index in &indexes {
index.flusher()()?;
}
is_dirty = true;
payload_schema.types = indexes.iter().map(|i| i.get_full_index_type()).collect();
}
Ok((indexes, is_dirty))
}
pub fn open(
payload: Arc<AtomicRefCell<PayloadStorageEnum>>,
id_tracker: Arc<AtomicRefCell<IdTrackerEnum>>,
vector_storages: HashMap<VectorNameBuf, Arc<AtomicRefCell<VectorStorageEnum>>>,
path: &Path,
storage_type: StorageType,
load_mode: IndexLoadMode,
) -> OperationResult<Self> {
fs::create_dir_all(path)?;
let config_path = PayloadConfig::get_config_path(path);
let config = if config_path.exists() {
PayloadConfig::load(&config_path)?
} else {
PayloadConfig::default()
};
let mut index = StructPayloadIndex {
payload,
id_tracker,
vector_storages,
field_indexes: Default::default(),
config,
path: path.to_owned(),
visited_pool: Default::default(),
storage_type,
};
if !index.config_path().exists() {
index.save_config()?;
}
index.load_all_fields(load_mode == IndexLoadMode::CreateIfMissing)?;
Ok(index)
}
pub fn register_vector_storage(
&mut self,
vector_name: VectorNameBuf,
vector_storage: Arc<AtomicRefCell<VectorStorageEnum>>,
) {
self.vector_storages.insert(vector_name, vector_storage);
}
pub fn unregister_vector_storage(&mut self, vector_name: &str) {
self.vector_storages.remove(vector_name);
}
pub fn available_point_count(&self) -> usize {
self.id_tracker.borrow().available_point_count()
}
pub fn config(&self) -> &PayloadConfig {
&self.config
}
pub fn is_tenant(&self, field: &PayloadKeyType) -> bool {
self.config
.indices
.get(field)
.map(|indexed_field| indexed_field.schema.is_tenant())
.unwrap_or(false)
}
pub(super) fn selector(&self, payload_schema: &PayloadFieldSchema) -> IndexSelector<'_> {
let memory = payload_schema.memory_placement();
match &self.storage_type {
StorageType::Appendable => IndexSelector::Appendable { dir: &self.path },
StorageType::NonAppendable => IndexSelector::NonAppendable {
dir: &self.path,
memory,
},
}
}
fn selector_with_type(
&self,
index_type: &FullPayloadIndexType,
payload_schema: &PayloadFieldSchema,
) -> IndexSelector<'_> {
match index_type.storage_type {
payload_config::StorageType::Gridstore => IndexSelector::Appendable { dir: &self.path },
payload_config::StorageType::Mmap { is_on_disk } => {
let memory = if is_on_disk {
match payload_schema.memory_placement() {
Memory::Cached => Memory::Cached,
Memory::Cold | Memory::Pinned => Memory::Cold,
}
} else {
Memory::Pinned
};
IndexSelector::NonAppendable {
dir: &self.path,
memory,
}
}
}
}
pub fn populate(&self) -> OperationResult<()> {
for field_indexes in self.field_indexes.values() {
for index in field_indexes {
index.populate()?;
}
}
Ok(())
}
pub fn clear_cache(&self) -> OperationResult<()> {
for field_indexes in self.field_indexes.values() {
for index in field_indexes {
index.clear_cache()?;
}
}
Ok(())
}
pub fn clear_cache_if_on_disk(&self) -> OperationResult<()> {
for field_indexes in self.field_indexes.values() {
for index in field_indexes {
if index.is_on_disk() {
index.clear_cache()?;
}
}
}
Ok(())
}
pub fn with_view<R>(
&self,
f: impl FnOnce(
StructPayloadIndexReadView<
'_,
PayloadStorageEnum,
IdTrackerEnum,
VectorStorageEnum,
FieldIndex,
>,
) -> R,
) -> R {
let id_tracker = self.id_tracker.borrow();
let view = StructPayloadIndexReadView {
payload: &self.payload,
id_tracker: &*id_tracker,
vector_storages: &self.vector_storages,
field_indexes: &self.field_indexes,
config: &self.config,
visited_pool: &self.visited_pool,
};
f(view)
}
}