use crate::common::universal_io::UniversalRead;
use crate::quantization::EncodedStorage;
use crate::quantization::turboquant::quantization::TurboQuantizer;
use crate::segment::common::flags::in_memory_bitvec_flags::InMemoryBitvecFlags;
use crate::segment::types::Distance;
use crate::segment::vector_storage::quantized::quantized_chunked_mmap_storage::QuantizedChunkedStorageRead;
mod immutable;
mod lifecycle;
mod live_reload;
mod read_ops;
pub use immutable::ReadOnlyImmutableTurboVectorStorage;
pub struct ReadOnlyChunkedTurboVectorStorage<S: UniversalRead> {
storage: QuantizedChunkedStorageRead<S>,
quantizer: TurboQuantizer,
deleted: InMemoryBitvecFlags,
distance: Distance,
dim: usize,
}
impl<S: UniversalRead> std::fmt::Debug for ReadOnlyChunkedTurboVectorStorage<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ReadOnlyChunkedTurboVectorStorage")
.field("dim", &self.dim)
.field("distance", &self.distance)
.field("total_vector_count", &self.storage.vectors_count())
.field("deleted_count", &self.deleted.count())
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use crate::common::counter::hardware_counter::HardwareCounterCell;
use crate::common::sorted_slice::SortedSlice;
use crate::common::types::PointOffsetType;
use crate::common::universal_io::{MmapFile, MmapFs, Populate};
use tempfile::Builder;
use super::*;
use crate::segment::common::live_reload::LiveReload;
use crate::segment::data_types::vectors::VectorRef;
use crate::segment::vector_storage::turbo::open_appendable_turbo_vector_storage;
use crate::segment::vector_storage::{VectorStorage, VectorStorageRead};
#[test]
fn live_reload_picks_up_appended_vector_deletion() {
const DIM: usize = 4;
let dir = Builder::new()
.prefix("ro_turbo_appended_deleted")
.tempdir()
.unwrap();
let hw = HardwareCounterCell::disposable();
let mut writer =
open_appendable_turbo_vector_storage(dir.path(), DIM, Distance::Dot, false).unwrap();
writer
.insert_vector(0, VectorRef::from(&vec![1.0; DIM]), &hw)
.unwrap();
writer.flusher()().unwrap();
let mut reader = ReadOnlyChunkedTurboVectorStorage::<MmapFile>::open(
&MmapFs,
dir.path(),
DIM,
Distance::Dot,
Populate::No,
)
.unwrap();
writer
.insert_vector(1, VectorRef::from(&vec![0.0; DIM]), &hw)
.unwrap();
writer.delete_vector(1).unwrap();
writer.flusher()().unwrap();
let deleted_ids: Vec<PointOffsetType> = vec![];
let new_ids: Vec<PointOffsetType> = vec![1];
reader
.live_reload(
&MmapFs,
&SortedSlice::new(&deleted_ids).unwrap(),
&SortedSlice::new(&new_ids).unwrap(),
&hw,
)
.unwrap();
assert_eq!(reader.total_vector_count(), 2);
assert!(reader.is_deleted_vector(1));
}
#[test]
fn live_reload_batches_appended_vector_deletions() {
const DIM: usize = 4;
let dir = Builder::new()
.prefix("ro_turbo_appended_batch")
.tempdir()
.unwrap();
let hw = HardwareCounterCell::disposable();
let mut writer =
open_appendable_turbo_vector_storage(dir.path(), DIM, Distance::Dot, false).unwrap();
for id in 0..3u32 {
writer
.insert_vector(id, VectorRef::from(&vec![1.0; DIM]), &hw)
.unwrap();
}
writer.flusher()().unwrap();
let mut reader = ReadOnlyChunkedTurboVectorStorage::<MmapFile>::open(
&MmapFs,
dir.path(),
DIM,
Distance::Dot,
Populate::No,
)
.unwrap();
for id in 3..8u32 {
writer
.insert_vector(id, VectorRef::from(&vec![0.0; DIM]), &hw)
.unwrap();
}
let deleted_appended: Vec<PointOffsetType> = vec![4, 6];
for &id in &deleted_appended {
writer.delete_vector(id).unwrap();
}
writer.flusher()().unwrap();
let deleted_ids: Vec<PointOffsetType> = vec![];
let new_ids: Vec<PointOffsetType> = (3..8).collect();
reader
.live_reload(
&MmapFs,
&SortedSlice::new(&deleted_ids).unwrap(),
&SortedSlice::new(&new_ids).unwrap(),
&hw,
)
.unwrap();
assert_eq!(reader.total_vector_count(), 8);
for id in 3..8 {
assert_eq!(
reader.is_deleted_vector(id),
deleted_appended.contains(&id),
"appended offset {id}",
);
}
assert!(!reader.is_deleted_vector(0));
}
}