feat: add live-reload for immutable dense vector (#9392)

This commit is contained in:
Daniel Boros
2026-06-09 16:59:19 +02:00
committed by GitHub
parent c79ca1bdd1
commit dd7cd18bab
11 changed files with 519 additions and 95 deletions

View File

@@ -66,6 +66,14 @@ impl InMemoryBitvecFlags {
Ok(Self { bitvec, count })
}
/// Wrap an already-materialized deletion `bitvec`, computing the set-flag
/// count. For flags coming from an on-disk format other than the dynamic
/// flags read by [`Self::open`] (e.g. the immutable dense `deleted.dat`).
pub fn from_bitvec(bitvec: BitVec) -> Self {
let count = bitvec.count_ones();
Self { bitvec, count }
}
/// Whether the flag at `key` is set; out-of-range keys read as unset.
pub fn get(&self, key: PointOffsetType) -> bool {
self.bitvec.get(key as usize).is_some_and(|bit| *bit)

View File

@@ -25,8 +25,8 @@ use crate::vector_storage::{
DenseVectorStorage, DenseVectorStorageRead, VectorStorage, VectorStorageEnum, VectorStorageRead,
};
const VECTORS_PATH: &str = "matrix.dat";
const DELETED_PATH: &str = "deleted.dat";
pub(crate) const VECTORS_PATH: &str = "matrix.dat";
pub(crate) const DELETED_PATH: &str = "deleted.dat";
/// Stores all dense vectors in mem-mapped file
///
@@ -216,7 +216,7 @@ where
S: UniversalRead,
{
fn vector_dim(&self) -> usize {
self.vectors.as_ref().unwrap().dim
self.vectors.as_ref().unwrap().dim()
}
fn get_dense<P: AccessPattern>(&self, key: PointOffsetType) -> Cow<'_, [T]> {
@@ -244,7 +244,7 @@ where
stopped: &AtomicBool,
) -> OperationResult<Range<PointOffsetType>> {
let dim = self.vector_dim();
let start_index = self.vectors.as_ref().unwrap().num_vectors as PointOffsetType;
let start_index = self.vectors.as_ref().unwrap().num_vectors() as PointOffsetType;
let mut end_index = start_index;
// Extend vectors file, write other vectors into it
@@ -318,7 +318,7 @@ where
}
fn total_vector_count(&self) -> usize {
self.vectors.as_ref().unwrap().num_vectors
self.vectors.as_ref().unwrap().num_vectors()
}
fn get_vector<P: AccessPattern>(&self, key: PointOffsetType) -> CowVector<'_> {

View File

@@ -26,9 +26,11 @@ const HEADER_SIZE: usize = 4;
const VECTORS_HEADER: &[u8; HEADER_SIZE] = b"data";
const DELETED_HEADER: &[u8; HEADER_SIZE] = b"drop";
/// Immutable storage for dense vectors.
/// Immutable dense vector blob, shared by the writable [`ImmutableDenseVectors`]
/// and the read-only dense storage. Provides typed read access for `T` through
/// the [`UniversalRead`] backend `S`; holds no deletion flags.
#[derive(Debug)]
pub struct ImmutableDenseVectors<T, S = MmapFile>
pub struct ImmutableDenseVectorData<T, S = MmapFile>
where
T: PrimitiveVectorElement,
S: UniversalRead,
@@ -37,24 +39,17 @@ where
pub num_vectors: usize,
/// Vector data storage, providing typed read access for `T`.
storage: TypedStorage<ReadOnly<S>, T>,
/// Memory mapped deletion flags
deleted: MmapBitSlice,
/// Current number of deleted vectors.
pub deleted_count: usize,
}
impl<T: PrimitiveVectorElement, S: UniversalRead> ImmutableDenseVectors<T, S> {
impl<T: PrimitiveVectorElement, S: UniversalRead> ImmutableDenseVectorData<T, S> {
/// Open the immutable vector blob read-only through `fs`. The file must
/// already exist (the writer creates it); nothing is created here.
pub fn open(
fs: &S::Fs,
vectors_path: &Path,
deleted_path: &Path,
dim: usize,
populate: bool,
) -> OperationResult<Self> {
// Allocate/open vectors file
ensure_mmap_file_size(vectors_path, VECTORS_HEADER, None)
.describe("Create mmap data file")?;
let file_len = fs_err::metadata(vectors_path)?.len() as usize;
let num_vectors = file_len.saturating_sub(HEADER_SIZE) / dim / size_of::<T>();
@@ -73,36 +68,13 @@ impl<T: PrimitiveVectorElement, S: UniversalRead> ImmutableDenseVectors<T, S> {
})?;
let storage = TypedStorage::<ReadOnly<S>, T>::wrap(read_only);
// Allocate/open deleted mmap
let deleted_mmap_size = deleted_mmap_size(num_vectors);
ensure_mmap_file_size(deleted_path, DELETED_HEADER, Some(deleted_mmap_size as u64))
.describe("Create mmap deleted file")?;
let deleted_mmap = mmap::open_write_mmap(deleted_path, AdviceSetting::Global, false)
.describe("Open mmap deleted for writing")?;
// Advise kernel that we'll need this page soon so the kernel can prepare
#[cfg(unix)]
if let Err(err) = deleted_mmap.advise(memmap2::Advice::WillNeed) {
log::error!("Failed to advise MADV_WILLNEED for deleted flags: {err}");
}
// Transform into mmap BitSlice
let deleted = MmapBitSlice::try_from(deleted_mmap, deleted_mmap_data_start())?;
let deleted_count = deleted.count_ones();
Ok(Self {
dim,
num_vectors,
storage,
deleted,
deleted_count,
})
}
pub fn flusher(&self) -> MmapFlusher {
self.deleted.flusher()
}
/// Returns the byte offset within the file at which the vector for `key` begins.
///
/// File layout:
@@ -201,6 +173,97 @@ impl<T: PrimitiveVectorElement, S: UniversalRead> ImmutableDenseVectors<T, S> {
.expect("vectors read");
}
pub fn populate(&self) {
if let Err(err) = self.storage.populate() {
log::error!("Failed to populate vector storage: {err}");
}
}
pub fn clear_cache(&self) -> OperationResult<()> {
self.storage.clear_ram_cache()?;
Ok(())
}
}
/// Immutable storage for dense vectors.
///
/// Wraps the shared [`ImmutableDenseVectorData`] blob with a writable deletion
/// bitmap, so it can mark vectors as removed even though the vector data itself
/// is append-only and can only be constructed from another storage.
#[derive(Debug)]
pub struct ImmutableDenseVectors<T, S = MmapFile>
where
T: PrimitiveVectorElement,
S: UniversalRead,
{
/// Vector data blob, read-only through `S`.
data: ImmutableDenseVectorData<T, S>,
/// Memory mapped deletion flags
deleted: MmapBitSlice,
/// Current number of deleted vectors.
pub deleted_count: usize,
}
impl<T: PrimitiveVectorElement, S: UniversalRead> ImmutableDenseVectors<T, S> {
pub fn open(
fs: &S::Fs,
vectors_path: &Path,
deleted_path: &Path,
dim: usize,
populate: bool,
) -> OperationResult<Self> {
// Allocate/open vectors file
ensure_mmap_file_size(vectors_path, VECTORS_HEADER, None)
.describe("Create mmap data file")?;
let data = ImmutableDenseVectorData::open(fs, vectors_path, dim, populate)?;
let num_vectors = data.num_vectors;
// Allocate/open deleted mmap
let deleted_mmap_size = deleted_mmap_size(num_vectors);
ensure_mmap_file_size(deleted_path, DELETED_HEADER, Some(deleted_mmap_size as u64))
.describe("Create mmap deleted file")?;
let deleted_mmap = mmap::open_write_mmap(deleted_path, AdviceSetting::Global, false)
.describe("Open mmap deleted for writing")?;
// Advise kernel that we'll need this page soon so the kernel can prepare
#[cfg(unix)]
if let Err(err) = deleted_mmap.advise(memmap2::Advice::WillNeed) {
log::error!("Failed to advise MADV_WILLNEED for deleted flags: {err}");
}
// Transform into mmap BitSlice
let deleted = MmapBitSlice::try_from(deleted_mmap, deleted_mmap_data_start())?;
let deleted_count = deleted.count_ones();
Ok(Self {
data,
deleted,
deleted_count,
})
}
pub fn dim(&self) -> usize {
self.data.dim
}
pub fn num_vectors(&self) -> usize {
self.data.num_vectors
}
pub fn flusher(&self) -> MmapFlusher {
self.deleted.flusher()
}
/// Returns an optional vector data by key
pub fn get_vector_opt<P: AccessPattern>(&self, key: PointOffsetType) -> Option<Cow<'_, [T]>> {
self.data.get_vector_opt::<P>(key)
}
pub fn for_each_in_batch<F: FnMut(usize, &[T])>(&self, keys: &[PointOffsetType], f: F) {
self.data.for_each_in_batch(keys, f);
}
/// Marks the key as deleted.
///
/// Returns true if the key was not deleted before, and it is now deleted.
@@ -225,21 +288,12 @@ impl<T: PrimitiveVectorElement, S: UniversalRead> ImmutableDenseVectors<T, S> {
}
pub fn populate(&self) {
if let Err(err) = self.storage.populate() {
log::error!("Failed to populate vector storage: {err}");
}
self.data.populate();
}
pub fn clear_cache(&self) -> OperationResult<()> {
let Self {
dim: _,
num_vectors: _,
storage,
deleted,
deleted_count: _,
} = self;
storage.clear_ram_cache()?;
deleted.clear_cache()?;
self.data.clear_cache()?;
self.deleted.clear_cache()?;
Ok(())
}
}
@@ -273,7 +327,7 @@ fn ensure_mmap_file_size(path: &Path, header: &[u8], size: Option<u64>) -> Opera
/// Get start position of flags `BitSlice` in deleted mmap.
#[inline]
const fn deleted_mmap_data_start() -> usize {
pub(crate) const fn deleted_mmap_data_start() -> usize {
let align = mem::align_of::<usize>();
HEADER_SIZE.div_ceil(align) * align
}

View File

@@ -0,0 +1,73 @@
use std::path::Path;
use common::bitvec::BitVec;
use common::mmap::AdviceSetting;
use common::stored_bitslice::StoredBitSlice;
use common::universal_io::{OpenOptions, Populate, UniversalRead};
use super::ReadOnlyImmutableDenseVectorStorage;
use crate::common::flags::in_memory_bitvec_flags::InMemoryBitvecFlags;
use crate::common::operation_error::{OperationError, OperationResult};
use crate::data_types::primitive::PrimitiveVectorElement;
use crate::types::Distance;
use crate::vector_storage::dense::dense_vector_storage::{DELETED_PATH, VECTORS_PATH};
use crate::vector_storage::dense::immutable_dense_vectors::{
ImmutableDenseVectorData, deleted_mmap_data_start,
};
/// Read-only mmap options: never writable, lazily paged, nothing populated.
const READ_ONLY_OPTIONS: OpenOptions = OpenOptions {
writeable: false,
need_sequential: false,
populate: Populate::No,
advice: AdviceSetting::Global,
};
impl<T: PrimitiveVectorElement, S: UniversalRead> ReadOnlyImmutableDenseVectorStorage<T, S> {
/// Open the read-only counterpart of the immutable dense storage at `path`,
/// threading every file open through `fs`; reads the existing layout but
/// creates and writes nothing. `populate` warms the vector data.
pub fn open(
fs: &S::Fs,
path: &Path,
dim: usize,
distance: Distance,
populate: bool,
) -> OperationResult<Self> {
let vectors = ImmutableDenseVectorData::open(fs, &path.join(VECTORS_PATH), dim, populate)?;
let deleted = open_deleted_flags::<S>(fs, &path.join(DELETED_PATH), vectors.num_vectors)?;
Ok(Self {
vectors,
deleted,
distance,
populate,
})
}
}
/// Read the immutable storage's `deleted.dat` through `fs` into an in-memory flag
/// set. The file holds a header padded to `deleted_mmap_data_start()` bytes
/// followed by the deletion `BitSlice`; the leading header bits are dropped and
/// `num_vectors` deletion flags are kept.
fn open_deleted_flags<S: UniversalRead>(
fs: &S::Fs,
deleted_path: &Path,
num_vectors: usize,
) -> OperationResult<InMemoryBitvecFlags> {
let stored =
StoredBitSlice::<S>::open(fs, deleted_path, READ_ONLY_OPTIONS, Default::default())?;
let all = stored.read_all()?;
let start = deleted_mmap_data_start() * 8;
let bits = all.get(start..start + num_vectors).ok_or_else(|| {
OperationError::service_error(format!(
"Deleted flags file {} holds fewer than {num_vectors} bits",
deleted_path.display(),
))
})?;
Ok(InMemoryBitvecFlags::from_bitvec(BitVec::from_bitslice(
bits,
)))
}

View File

@@ -0,0 +1,29 @@
use common::counter::hardware_counter::HardwareCounterCell;
use common::sorted_slice::SortedSlice;
use common::types::PointOffsetType;
use common::universal_io::UniversalRead;
use super::ReadOnlyImmutableDenseVectorStorage;
use crate::common::live_reload::LiveReload;
use crate::common::operation_error::OperationResult;
use crate::data_types::primitive::PrimitiveVectorElement;
impl<T: PrimitiveVectorElement, S: UniversalRead> LiveReload
for ReadOnlyImmutableDenseVectorStorage<T, S>
{
type Fs = S::Fs;
/// Vector data is immutable, so only the in-memory deletion flags are patched
/// from the authoritative `deleted_points`; `fs` and `new_points` are unused.
fn live_reload(
&mut self,
_fs: &S::Fs,
deleted_points: &SortedSlice<'_, PointOffsetType>,
_new_points: &SortedSlice<'_, PointOffsetType>,
_hw_counter: &HardwareCounterCell,
) -> OperationResult<()> {
self.deleted.insert_all(deleted_points);
Ok(())
}
}

View File

@@ -0,0 +1,181 @@
use common::universal_io::UniversalRead;
use crate::common::flags::in_memory_bitvec_flags::InMemoryBitvecFlags;
use crate::data_types::primitive::PrimitiveVectorElement;
use crate::types::Distance;
use crate::vector_storage::dense::immutable_dense_vectors::ImmutableDenseVectorData;
mod lifecycle;
mod live_reload;
mod read_ops;
/// Read-only counterpart of the immutable (mmap) dense vector storage.
///
/// Vector data is immutable, so it is read straight from the shared
/// [`ImmutableDenseVectorData`] blob; deletions are tracked in memory and folded
/// from the live-reload delta.
#[derive(Debug)]
pub struct ReadOnlyImmutableDenseVectorStorage<T: PrimitiveVectorElement, S: UniversalRead> {
vectors: ImmutableDenseVectorData<T, S>,
/// Flags marking deleted vectors.
deleted: InMemoryBitvecFlags,
distance: Distance,
/// Whether vector data is populated into RAM (drives `is_on_disk`).
populate: bool,
}
#[cfg(test)]
mod tests {
use common::counter::hardware_counter::HardwareCounterCell;
use common::generic_consts::Random;
use common::sorted_slice::SortedSlice;
use common::types::PointOffsetType;
use common::universal_io::{MmapFile, MmapFs};
use rand::rngs::StdRng;
use rand::{RngExt, SeedableRng};
use tempfile::Builder;
use super::*;
use crate::common::live_reload::LiveReload;
use crate::data_types::vectors::{DenseVector, VectorElementType, VectorRef};
use crate::segment_constructor::batched_reader::merge_from_single_source;
use crate::vector_storage::dense::dense_vector_storage::open_dense_vector_storage;
use crate::vector_storage::dense::volatile_dense_vector_storage::new_volatile_dense_vector_storage;
use crate::vector_storage::{VectorStorage, VectorStorageRead};
fn rand_vec(rng: &mut StdRng, dim: usize) -> DenseVector {
std::iter::repeat_with(|| rng.random_range(-1.0..1.0))
.take(dim)
.collect()
}
/// Build the immutable storage (deleting some) from a staging storage, then
/// reopen the same directory read-only and assert it mirrors the state.
#[test]
fn read_only_immutable_dense_round_trip() {
const DIM: usize = 32;
const POINT_COUNT: PointOffsetType = 300;
let dir = Builder::new()
.prefix("ro_immutable_dense")
.tempdir()
.unwrap();
let mut rng = StdRng::seed_from_u64(42);
let hw = HardwareCounterCell::disposable();
let vectors: Vec<DenseVector> = (0..POINT_COUNT).map(|_| rand_vec(&mut rng, DIM)).collect();
let mut deleted_ids = Vec::new();
{
let mut storage =
open_dense_vector_storage(dir.path(), DIM, Distance::Dot, false).unwrap();
let mut staging = new_volatile_dense_vector_storage(DIM, Distance::Dot);
for (id, vector) in vectors.iter().enumerate() {
staging
.insert_vector(id as PointOffsetType, VectorRef::from(vector), &hw)
.unwrap();
}
for id in (0..POINT_COUNT).step_by(11) {
staging.delete_vector(id).unwrap();
deleted_ids.push(id);
}
merge_from_single_source(&mut storage, &staging, POINT_COUNT).unwrap();
storage.flusher()().unwrap();
}
let storage = ReadOnlyImmutableDenseVectorStorage::<VectorElementType, MmapFile>::open(
&MmapFs,
dir.path(),
DIM,
Distance::Dot,
false,
)
.unwrap();
assert_eq!(storage.total_vector_count(), POINT_COUNT as usize);
assert_eq!(storage.distance(), Distance::Dot);
assert_eq!(storage.deleted_vector_count(), deleted_ids.len());
// The immutable storage keeps deleted vectors' data, so every vector reads back.
for id in 0..POINT_COUNT {
assert_eq!(storage.is_deleted_vector(id), deleted_ids.contains(&id));
let got: DenseVector = storage
.get_vector::<Random>(id)
.to_owned()
.try_into()
.unwrap();
assert_eq!(got, vectors[id as usize], "vector {id} mismatch");
}
}
/// After `live_reload`, deletions made through the writer are reflected;
/// vector data is immutable so nothing else changes.
#[test]
fn live_reload_picks_up_deletions() {
const DIM: usize = 24;
const POINT_COUNT: PointOffsetType = 200;
let dir = Builder::new()
.prefix("ro_immutable_dense_reload")
.tempdir()
.unwrap();
let mut rng = StdRng::seed_from_u64(7);
let hw = HardwareCounterCell::disposable();
let vectors: Vec<DenseVector> = (0..POINT_COUNT).map(|_| rand_vec(&mut rng, DIM)).collect();
let mut writer = open_dense_vector_storage(dir.path(), DIM, Distance::Dot, false).unwrap();
{
let mut staging = new_volatile_dense_vector_storage(DIM, Distance::Dot);
for (id, vector) in vectors.iter().enumerate() {
staging
.insert_vector(id as PointOffsetType, VectorRef::from(vector), &hw)
.unwrap();
}
merge_from_single_source(&mut writer, &staging, POINT_COUNT).unwrap();
writer.flusher()().unwrap();
}
let mut reader = ReadOnlyImmutableDenseVectorStorage::<VectorElementType, MmapFile>::open(
&MmapFs,
dir.path(),
DIM,
Distance::Dot,
false,
)
.unwrap();
assert_eq!(reader.deleted_vector_count(), 0);
// Delete points through the writer, then feed the delta to the reader.
let deleted_ids: Vec<PointOffsetType> = vec![1, 7, 42, 199];
for &id in &deleted_ids {
writer.delete_vector(id).unwrap();
}
writer.flusher()().unwrap();
reader
.live_reload(
&MmapFs,
&SortedSlice::new(&deleted_ids).unwrap(),
&SortedSlice::new(&[]).unwrap(),
&hw,
)
.unwrap();
assert_eq!(reader.total_vector_count(), POINT_COUNT as usize);
assert_eq!(reader.deleted_vector_count(), deleted_ids.len());
for &id in &deleted_ids {
assert!(reader.is_deleted_vector(id));
}
assert!(!reader.is_deleted_vector(0));
assert!(!reader.is_deleted_vector(2));
// Vector data is immutable: an untouched point still reads back correctly.
let got: DenseVector = reader
.get_vector::<Random>(2)
.to_owned()
.try_into()
.unwrap();
assert_eq!(got, vectors[2]);
}
}

View File

@@ -0,0 +1,91 @@
use std::borrow::Cow;
use common::bitvec::BitSlice;
use common::generic_consts::AccessPattern;
use common::types::PointOffsetType;
use common::universal_io::UniversalRead;
use super::ReadOnlyImmutableDenseVectorStorage;
use crate::data_types::named_vectors::CowVector;
use crate::data_types::primitive::PrimitiveVectorElement;
use crate::types::{Distance, VectorStorageDatatype};
use crate::vector_storage::{DenseVectorStorageRead, VectorStorageRead};
impl<T: PrimitiveVectorElement, S: UniversalRead> DenseVectorStorageRead<T>
for ReadOnlyImmutableDenseVectorStorage<T, S>
{
fn vector_dim(&self) -> usize {
self.vectors.dim
}
fn get_dense<P: AccessPattern>(&self, key: PointOffsetType) -> Cow<'_, [T]> {
self.vectors
.get_vector_opt::<P>(key)
.expect("vector not found")
}
}
impl<T: PrimitiveVectorElement, S: UniversalRead> VectorStorageRead
for ReadOnlyImmutableDenseVectorStorage<T, S>
{
fn size_of_available_vectors_in_bytes(&self) -> usize {
self.available_vector_count() * self.vector_dim() * std::mem::size_of::<T>()
}
fn distance(&self) -> Distance {
self.distance
}
fn datatype(&self) -> VectorStorageDatatype {
T::datatype()
}
fn is_on_disk(&self) -> bool {
!self.populate
}
fn total_vector_count(&self) -> usize {
self.vectors.num_vectors
}
fn get_vector<P: AccessPattern>(&self, key: PointOffsetType) -> CowVector<'_> {
self.vectors
.get_vector_opt::<P>(key)
.map(|vector| T::slice_to_float_cow(vector).into())
.expect("Vector not found")
}
fn read_vectors<P: AccessPattern, U: Copy>(
&self,
keys: impl IntoIterator<Item = (U, PointOffsetType)>,
mut callback: impl FnMut(U, PointOffsetType, CowVector<'_>),
) {
// Split into parallel arrays in one pass: `for_each_in_batch` needs an
// offsets slice, but we still want `user_data[idx]` in the callback.
let (user_data, point_offsets): (Vec<U>, Vec<PointOffsetType>) = keys.into_iter().unzip();
self.vectors
.for_each_in_batch(&point_offsets, |idx, vector| {
let vector = CowVector::from(T::slice_to_float_cow(Cow::Borrowed(vector)));
callback(user_data[idx], point_offsets[idx], vector);
});
}
fn get_vector_opt<P: AccessPattern>(&self, key: PointOffsetType) -> Option<CowVector<'_>> {
self.vectors
.get_vector_opt::<P>(key)
.map(|vector| T::slice_to_float_cow(vector).into())
}
fn is_deleted_vector(&self, key: PointOffsetType) -> bool {
self.deleted.get(key)
}
fn deleted_vector_count(&self) -> usize {
self.deleted.count()
}
fn deleted_vector_bitslice(&self) -> &BitSlice {
self.deleted.as_bitslice()
}
}

View File

@@ -5,10 +5,13 @@ use crate::data_types::primitive::PrimitiveVectorElement;
use crate::types::Distance;
use crate::vector_storage::chunked_vectors::ChunkedVectorsRead;
mod immutable;
mod lifecycle;
mod live_reload;
mod read_ops;
pub use immutable::ReadOnlyImmutableDenseVectorStorage;
#[derive(Debug)]
pub struct ReadOnlyChunkedDenseVectorStorage<T: PrimitiveVectorElement, S: UniversalRead> {
vectors: ChunkedVectorsRead<T, S>,

View File

@@ -5,10 +5,10 @@ use common::universal_io::UniversalRead;
use super::VectorStorageReadEnum;
use crate::common::operation_error::{OperationError, OperationResult};
use crate::data_types::vectors::{VectorElementType, VectorElementTypeByte, VectorElementTypeHalf};
use crate::types::{VectorDataConfig, VectorStorageDatatype, VectorStorageType};
use crate::vector_storage::dense::dense_vector_storage::open_dense_vector_storage_impl;
use crate::vector_storage::dense::read_only::ReadOnlyChunkedDenseVectorStorage;
use crate::vector_storage::dense::read_only::{
ReadOnlyChunkedDenseVectorStorage, ReadOnlyImmutableDenseVectorStorage,
};
use crate::vector_storage::multi_dense::read_only::ReadOnlyChunkedMultiDenseVectorStorage;
impl<S: UniversalRead> VectorStorageReadEnum<S> {
@@ -20,10 +20,7 @@ impl<S: UniversalRead> VectorStorageReadEnum<S> {
fs: &S::Fs,
vector_config: &VectorDataConfig,
path: &Path,
) -> OperationResult<Option<Self>>
where
S::Fs: Clone,
{
) -> OperationResult<Option<Self>> {
let dim = vector_config.size;
let distance = vector_config.distance;
let datatype = vector_config.datatype.unwrap_or_default();
@@ -109,30 +106,15 @@ impl<S: UniversalRead> VectorStorageReadEnum<S> {
}
} else {
match datatype {
VectorStorageDatatype::Float32 => {
Self::Dense(Box::new(open_dense_vector_storage_impl::<
VectorElementType,
S,
>(
fs.clone(), path, dim, distance, populate
)?))
}
VectorStorageDatatype::Uint8 => {
Self::DenseByte(Box::new(open_dense_vector_storage_impl::<
VectorElementTypeByte,
S,
>(
fs.clone(), path, dim, distance, populate
)?))
}
VectorStorageDatatype::Float16 => {
Self::DenseHalf(Box::new(open_dense_vector_storage_impl::<
VectorElementTypeHalf,
S,
>(
fs.clone(), path, dim, distance, populate
)?))
}
VectorStorageDatatype::Float32 => Self::Dense(Box::new(
ReadOnlyImmutableDenseVectorStorage::open(fs, path, dim, distance, populate)?,
)),
VectorStorageDatatype::Uint8 => Self::DenseByte(Box::new(
ReadOnlyImmutableDenseVectorStorage::open(fs, path, dim, distance, populate)?,
)),
VectorStorageDatatype::Float16 => Self::DenseHalf(Box::new(
ReadOnlyImmutableDenseVectorStorage::open(fs, path, dim, distance, populate)?,
)),
VectorStorageDatatype::Turbo4 => {
return Err(OperationError::service_error(
"Turbo4 datatype storage is not yet supported",

View File

@@ -18,12 +18,14 @@ impl<S: UniversalRead> LiveReload for VectorStorageReadEnum<S> {
hw_counter: &HardwareCounterCell,
) -> OperationResult<()> {
match self {
// Immutable dense (mmap) live-reload is postponed: needs the deleted
// flags threaded through `ImmutableDenseVectors` (Dense* step).
VectorStorageReadEnum::Dense(_)
| VectorStorageReadEnum::DenseByte(_)
| VectorStorageReadEnum::DenseHalf(_) => {
todo!("live_reload for immutable dense (mmap) storage is not yet implemented")
VectorStorageReadEnum::Dense(s) => {
s.live_reload(fs, deleted_points, new_points, hw_counter)
}
VectorStorageReadEnum::DenseByte(s) => {
s.live_reload(fs, deleted_points, new_points, hw_counter)
}
VectorStorageReadEnum::DenseHalf(s) => {
s.live_reload(fs, deleted_points, new_points, hw_counter)
}
VectorStorageReadEnum::DenseChunked(s) => {
s.live_reload(fs, deleted_points, new_points, hw_counter)

View File

@@ -5,8 +5,9 @@ use crate::common::operation_error::OperationResult;
use crate::data_types::vectors::{
QueryVector, VectorElementType, VectorElementTypeByte, VectorElementTypeHalf,
};
use crate::vector_storage::dense::dense_vector_storage::DenseVectorStorageImpl;
use crate::vector_storage::dense::read_only::ReadOnlyChunkedDenseVectorStorage;
use crate::vector_storage::dense::read_only::{
ReadOnlyChunkedDenseVectorStorage, ReadOnlyImmutableDenseVectorStorage,
};
use crate::vector_storage::multi_dense::read_only::ReadOnlyChunkedMultiDenseVectorStorage;
use crate::vector_storage::sparse::read_only::ReadOnlySparseVectorStorage;
use crate::vector_storage::{
@@ -22,9 +23,9 @@ mod read_ops;
/// Wraps each on-disk storage type with its [`super`] read-only variant.
/// Volatile, empty and test-only variants are intentionally absent.
pub enum VectorStorageReadEnum<S: UniversalRead> {
Dense(Box<DenseVectorStorageImpl<VectorElementType, S>>),
DenseByte(Box<DenseVectorStorageImpl<VectorElementTypeByte, S>>),
DenseHalf(Box<DenseVectorStorageImpl<VectorElementTypeHalf, S>>),
Dense(Box<ReadOnlyImmutableDenseVectorStorage<VectorElementType, S>>),
DenseByte(Box<ReadOnlyImmutableDenseVectorStorage<VectorElementTypeByte, S>>),
DenseHalf(Box<ReadOnlyImmutableDenseVectorStorage<VectorElementTypeHalf, S>>),
DenseChunked(Box<ReadOnlyChunkedDenseVectorStorage<VectorElementType, S>>),
DenseChunkedByte(Box<ReadOnlyChunkedDenseVectorStorage<VectorElementTypeByte, S>>),
DenseChunkedHalf(Box<ReadOnlyChunkedDenseVectorStorage<VectorElementTypeHalf, S>>),