refactor: hoist size_of onto VectorStorageRead, generalize plain read view search

Move size_of_available_vectors_in_bytes onto the VectorStorageRead trait
(it was split across DenseVectorStorageRead / MultiVectorStorage / the
enum inherent impls), implementing it for every VectorStorageRead impl.
This makes per-vector size queryable through a generic
V: VectorStorageRead, behavior-preserving.

With that, move the plain read view search/is_small_enough impl from the
concrete PlainVectorIndexReadViewEnum alias to the generic
PlainVectorIndexReadView<'a, I, V, Q, P>, mirroring the HNSW read view,
so PlainVectorIndex and ReadOnlyPlainVectorIndex share one search impl.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
generall
2026-08-04 11:16:49 +02:00
co-authored by Claude Opus 4.8
parent da1a18230b
commit 974ab87fcc
21 changed files with 163 additions and 126 deletions
@@ -12,6 +12,7 @@ use crate::data_types::vectors::QueryVector;
use crate::index::VectorIndexRead;
use crate::telemetry::VectorIndexSearchesTelemetry;
use crate::types::{Filter, SearchParams};
use crate::vector_storage::VectorStorageRead;
impl<S: UniversalRead> VectorIndexRead for ReadOnlyHNSWIndex<S> {
fn search(
@@ -13,6 +13,7 @@ use crate::index::hnsw_index::config::HnswGraphConfig;
use crate::index::{VectorIndex, VectorIndexRead};
use crate::telemetry::VectorIndexSearchesTelemetry;
use crate::types::{Filter, SearchParams};
use crate::vector_storage::VectorStorageRead;
impl VectorIndexRead for HNSWIndex {
fn search(
@@ -12,6 +12,7 @@ use crate::data_types::vectors::QueryVector;
use crate::index::VectorIndexRead;
use crate::telemetry::VectorIndexSearchesTelemetry;
use crate::types::{Filter, SearchParams};
use crate::vector_storage::VectorStorageRead;
impl PlainVectorIndex {
pub fn is_small_enough_for_unindexed_search(
@@ -1,7 +1,7 @@
use common::counter::hardware_counter::HardwareCounterCell;
use common::types::{DeferredBehavior, ScoredPointOffset};
use super::PlainVectorIndexReadViewEnum;
use super::PlainVectorIndexReadView;
use crate::common::BYTES_IN_KB;
use crate::common::operation_error::OperationResult;
use crate::common::operation_time_statistics::ScopeDurationMeasurer;
@@ -14,9 +14,16 @@ use crate::index::vector_index_search_common::{
get_oversampled_top, is_quantized_search, postprocess_search_result,
};
use crate::types::{Filter, SearchParams};
use crate::vector_storage::VectorStorageRead;
use crate::vector_storage::quantized::quantized_vectors::QuantizedVectorsReadAccess;
use crate::vector_storage::{RawScorerBuilder, VectorStorageRead};
impl PlainVectorIndexReadViewEnum<'_> {
impl<'a, I, V, Q, P> PlainVectorIndexReadView<'a, I, V, Q, P>
where
I: IdTrackerRead,
V: VectorStorageRead + RawScorerBuilder,
Q: QuantizedVectorsReadAccess,
P: PayloadIndexRead,
{
pub fn is_small_enough_for_unindexed_search(
&self,
search_optimized_threshold_kb: usize,
@@ -116,6 +116,10 @@ impl<T: PrimitiveVectorElement> DenseVectorStorage<T> for AppendableMmapDenseVec
}
impl<T: PrimitiveVectorElement> VectorStorageRead for AppendableMmapDenseVectorStorage<T> {
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
}
@@ -301,6 +301,10 @@ where
T: PrimitiveVectorElement,
S: UniversalRead,
{
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
}
@@ -111,6 +111,11 @@ impl DenseVectorStorage<VectorElementType> for EmptyDenseVectorStorage {
}
impl VectorStorageRead for EmptyDenseVectorStorage {
fn size_of_available_vectors_in_bytes(&self) -> usize {
// All vectors are deleted, so there are no available vectors.
0
}
fn distance(&self) -> Distance {
self.distance
}
@@ -28,6 +28,10 @@ impl<T: PrimitiveVectorElement, S: UniversalRead> DenseVectorStorageRead<T>
impl<T: PrimitiveVectorElement, S: UniversalRead> VectorStorageRead
for ReadOnlyChunkedDenseVectorStorage<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
}
@@ -104,6 +104,10 @@ impl<T: PrimitiveVectorElement> DenseVectorStorage<T> for VolatileDenseVectorSto
}
impl<T: PrimitiveVectorElement> VectorStorageRead for VolatileDenseVectorStorage<T> {
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
}
@@ -2,8 +2,7 @@ use std::path::PathBuf;
use crate::common::memory_usage::{ComponentMemoryUsage, FileStorageIntent, MemoryReporter};
use crate::vector_storage::vector_storage_base::{
DenseVectorStorageRead as _, MultiVectorStorage as _, VectorStorage as _, VectorStorageEnum,
VectorStorageRead as _,
VectorStorage as _, VectorStorageEnum, VectorStorageRead as _,
};
/// Determine the file storage intent for mmap-based vector storage.
@@ -268,16 +268,6 @@ impl<T: PrimitiveVectorElement> MultiVectorStorage<T> for AppendableMmapMultiDen
&self.multi_vector_config
}
fn size_of_available_vectors_in_bytes(&self) -> usize {
if self.total_vector_count() > 0 {
let total_size = self.vectors.len() * self.vector_dim() * std::mem::size_of::<T>();
(total_size as u128 * self.available_vector_count() as u128
/ self.total_vector_count() as u128) as usize
} else {
0
}
}
fn update_from<'a>(
&mut self,
other_vectors: &mut impl Iterator<Item = (CowMultiVector<'a, T>, bool)>,
@@ -297,6 +287,16 @@ impl<T: PrimitiveVectorElement> MultiVectorStorage<T> for AppendableMmapMultiDen
}
impl<T: PrimitiveVectorElement> VectorStorageRead for AppendableMmapMultiDenseVectorStorage<T> {
fn size_of_available_vectors_in_bytes(&self) -> usize {
if self.total_vector_count() > 0 {
let total_size = self.vectors.len() * self.vector_dim() * std::mem::size_of::<T>();
(total_size as u128 * self.available_vector_count() as u128
/ self.total_vector_count() as u128) as usize
} else {
0
}
}
fn distance(&self) -> Distance {
self.distance
}
@@ -12,16 +12,14 @@ use crate::vector_storage::multi_dense::appendable_mmap_multi_dense_vector_stora
flattened_to_multi_vector, read_multi_vector,
};
impl<T: PrimitiveVectorElement, S: UniversalRead> ReadOnlyChunkedMultiDenseVectorStorage<T, S> {
pub fn size_of_available_vectors_in_bytes(&self) -> usize {
// Total flattened element bytes across all stored multi-vectors.
self.vectors.len() * self.vectors.dim() * std::mem::size_of::<T>()
}
}
impl<T: PrimitiveVectorElement, S: UniversalRead> VectorStorageRead
for ReadOnlyChunkedMultiDenseVectorStorage<T, S>
{
fn size_of_available_vectors_in_bytes(&self) -> usize {
// Total flattened element bytes across all stored multi-vectors.
self.vectors.len() * self.vectors.dim() * std::mem::size_of::<T>()
}
fn distance(&self) -> Distance {
self.distance
}
@@ -237,16 +237,6 @@ impl<T: PrimitiveVectorElement> MultiVectorStorage<T> for VolatileMultiDenseVect
&self.multi_vector_config
}
fn size_of_available_vectors_in_bytes(&self) -> usize {
if self.total_vector_count() > 0 {
let total_size = self.vectors.len() * self.vector_dim() * std::mem::size_of::<T>();
(total_size as u128 * self.available_vector_count() as u128
/ self.total_vector_count() as u128) as usize
} else {
0
}
}
fn update_from<'a>(
&mut self,
other_vectors: &mut impl Iterator<Item = (CowMultiVector<'a, T>, bool)>,
@@ -269,6 +259,16 @@ impl<T: PrimitiveVectorElement> MultiVectorStorage<T> for VolatileMultiDenseVect
}
impl<T: PrimitiveVectorElement> VectorStorageRead for VolatileMultiDenseVectorStorage<T> {
fn size_of_available_vectors_in_bytes(&self) -> usize {
if self.total_vector_count() > 0 {
let total_size = self.vectors.len() * self.vector_dim() * std::mem::size_of::<T>();
(total_size as u128 * self.available_vector_count() as u128
/ self.total_vector_count() as u128) as usize
} else {
0
}
}
fn distance(&self) -> Distance {
self.distance
}
@@ -9,7 +9,7 @@ use crate::vector_storage::dense::dense_vector_storage::DenseVectorStorageImpl;
use crate::vector_storage::dense::read_only::ReadOnlyChunkedDenseVectorStorage;
use crate::vector_storage::multi_dense::read_only::ReadOnlyChunkedMultiDenseVectorStorage;
use crate::vector_storage::sparse::read_only::ReadOnlySparseVectorStorage;
use crate::vector_storage::{DenseVectorStorageRead, RawScorer, RawScorerBuilder, raw_scorer_impl};
use crate::vector_storage::{RawScorer, RawScorerBuilder, raw_scorer_impl};
mod lifecycle;
mod read_ops;
@@ -31,30 +31,6 @@ pub enum VectorStorageReadEnum<S: UniversalRead> {
Sparse(Box<ReadOnlySparseVectorStorage<S>>),
}
impl<S: UniversalRead> VectorStorageReadEnum<S> {
/// Size of all available (non-deleted) vectors in bytes.
pub fn size_of_available_vectors_in_bytes(&self) -> usize {
match self {
VectorStorageReadEnum::Dense(s) => s.size_of_available_vectors_in_bytes(),
VectorStorageReadEnum::DenseByte(s) => s.size_of_available_vectors_in_bytes(),
VectorStorageReadEnum::DenseHalf(s) => s.size_of_available_vectors_in_bytes(),
VectorStorageReadEnum::DenseChunked(s) => s.size_of_available_vectors_in_bytes(),
VectorStorageReadEnum::DenseChunkedByte(s) => s.size_of_available_vectors_in_bytes(),
VectorStorageReadEnum::DenseChunkedHalf(s) => s.size_of_available_vectors_in_bytes(),
VectorStorageReadEnum::MultiDenseChunked(s) => s.size_of_available_vectors_in_bytes(),
VectorStorageReadEnum::MultiDenseChunkedByte(s) => {
s.size_of_available_vectors_in_bytes()
}
VectorStorageReadEnum::MultiDenseChunkedHalf(s) => {
s.size_of_available_vectors_in_bytes()
}
VectorStorageReadEnum::Sparse(_) => {
unreachable!("Sparse storage does not know its total size, get from index instead")
}
}
}
}
impl<S: UniversalRead> RawScorerBuilder for VectorStorageReadEnum<S> {
fn build_raw_scorer<'a>(
&'a self,
@@ -9,6 +9,25 @@ use crate::types::{Distance, VectorStorageDatatype};
use crate::vector_storage::VectorStorageRead;
impl<S: UniversalRead> VectorStorageRead for VectorStorageReadEnum<S> {
fn size_of_available_vectors_in_bytes(&self) -> usize {
match self {
VectorStorageReadEnum::Dense(s) => s.size_of_available_vectors_in_bytes(),
VectorStorageReadEnum::DenseByte(s) => s.size_of_available_vectors_in_bytes(),
VectorStorageReadEnum::DenseHalf(s) => s.size_of_available_vectors_in_bytes(),
VectorStorageReadEnum::DenseChunked(s) => s.size_of_available_vectors_in_bytes(),
VectorStorageReadEnum::DenseChunkedByte(s) => s.size_of_available_vectors_in_bytes(),
VectorStorageReadEnum::DenseChunkedHalf(s) => s.size_of_available_vectors_in_bytes(),
VectorStorageReadEnum::MultiDenseChunked(s) => s.size_of_available_vectors_in_bytes(),
VectorStorageReadEnum::MultiDenseChunkedByte(s) => {
s.size_of_available_vectors_in_bytes()
}
VectorStorageReadEnum::MultiDenseChunkedHalf(s) => {
s.size_of_available_vectors_in_bytes()
}
VectorStorageReadEnum::Sparse(s) => s.size_of_available_vectors_in_bytes(),
}
}
fn distance(&self) -> Distance {
match self {
VectorStorageReadEnum::Dense(s) => s.distance(),
@@ -86,6 +86,10 @@ impl SparseVectorStorage for EmptySparseVectorStorage {
}
impl VectorStorageRead for EmptySparseVectorStorage {
fn size_of_available_vectors_in_bytes(&self) -> usize {
0
}
fn distance(&self) -> Distance {
SPARSE_VECTOR_DISTANCE
}
@@ -254,6 +254,10 @@ impl SparseVectorStorage for MmapSparseVectorStorage {
}
impl VectorStorageRead for MmapSparseVectorStorage {
fn size_of_available_vectors_in_bytes(&self) -> usize {
unreachable!("Mmap sparse storage does not know its total size, get from index instead")
}
fn distance(&self) -> crate::types::Distance {
super::SPARSE_VECTOR_DISTANCE
}
@@ -13,6 +13,10 @@ use crate::vector_storage::VectorStorageRead;
use crate::vector_storage::sparse::SPARSE_VECTOR_DISTANCE;
impl<S: UniversalRead> VectorStorageRead for ReadOnlySparseVectorStorage<S> {
fn size_of_available_vectors_in_bytes(&self) -> usize {
unreachable!("Sparse storage does not know its total size, get from index instead")
}
fn distance(&self) -> Distance {
SPARSE_VECTOR_DISTANCE
}
@@ -88,16 +88,6 @@ impl VolatileSparseVectorStorage {
*entry = vector.cloned();
}
}
pub fn size_of_available_vectors_in_bytes(&self) -> usize {
if self.total_vector_count == 0 {
return 0;
}
let available_fraction =
(self.total_vector_count - self.deleted_count) as f32 / self.total_vector_count as f32;
let available_size = (self.total_sparse_size as f32 * available_fraction) as usize;
available_size * (std::mem::size_of::<DimWeight>() + std::mem::size_of::<DimId>())
}
}
impl SparseVectorStorage for VolatileSparseVectorStorage {
@@ -153,6 +143,16 @@ impl SparseVectorStorage for VolatileSparseVectorStorage {
}
impl VectorStorageRead for VolatileSparseVectorStorage {
fn size_of_available_vectors_in_bytes(&self) -> usize {
if self.total_vector_count == 0 {
return 0;
}
let available_fraction =
(self.total_vector_count - self.deleted_count) as f32 / self.total_vector_count as f32;
let available_size = (self.total_sparse_size as f32 * available_fraction) as usize;
available_size * (std::mem::size_of::<DimWeight>() + std::mem::size_of::<DimId>())
}
fn distance(&self) -> Distance {
SPARSE_VECTOR_DISTANCE
}
+8 -3
View File
@@ -69,9 +69,10 @@ pub struct TurboVectorStorage {
}
impl TurboVectorStorage {
/// Bytes used by all available (non-deleted) vectors in their encoded form.
pub fn size_of_available_vectors_in_bytes(&self) -> usize {
self.available_vector_count() * self.quantized_vector_size()
/// Memory layout of a single encoded vector.
pub fn quantized_vector_layout(&self) -> OperationResult<Layout> {
// TODO: build from quantized_vector_size() with the encoding alignment.
unimplemented!("TODO: layout of one encoded vector")
}
/// Raw encoded vector blob for one vector (no dequantization/lloyd lookup).
@@ -200,6 +201,10 @@ fn open_turbo_vector_storage_impl(
}
impl VectorStorageRead for TurboVectorStorage {
fn size_of_available_vectors_in_bytes(&self) -> usize {
self.available_vector_count() * self.quantized_vector_size()
}
fn distance(&self) -> Distance {
self.distance
}
@@ -134,6 +134,9 @@ pub trait VectorStorageRead {
/// The size of this slice is not guaranteed. It may be smaller/larger than the number of
/// vectors in this segment.
fn deleted_vector_bitslice(&self) -> &BitSlice;
/// Size of all available (non-deleted) vectors in bytes.
fn size_of_available_vectors_in_bytes(&self) -> usize;
}
/// Trait for vector storage with mutating operations.
@@ -200,10 +203,6 @@ pub trait DenseVectorStorageRead<T: PrimitiveVectorElement>: VectorStorageRead {
f(idx, &self.get_dense::<Random>(key));
}
}
fn size_of_available_vectors_in_bytes(&self) -> usize {
self.available_vector_count() * self.vector_dim() * std::mem::size_of::<T>()
}
}
pub trait DenseVectorStorage<T: PrimitiveVectorElement>: DenseVectorStorageRead<T> {
@@ -264,8 +263,6 @@ pub trait MultiVectorStorage<T: PrimitiveVectorElement>: VectorStorageRead {
fn iterate_inner_vectors(&self) -> impl Iterator<Item = Cow<'_, [T]>> + Clone + Send;
fn multi_vector_config(&self) -> &MultiVectorConfig;
fn size_of_available_vectors_in_bytes(&self) -> usize;
/// Add the given multi-dense vectors to the storage.
///
/// # Returns
@@ -478,56 +475,6 @@ impl VectorStorageEnum {
}
}
pub fn size_of_available_vectors_in_bytes(&self) -> usize {
match self {
VectorStorageEnum::DenseVolatile(v) => v.size_of_available_vectors_in_bytes(),
#[cfg(test)]
VectorStorageEnum::DenseVolatileByte(v) => v.size_of_available_vectors_in_bytes(),
#[cfg(test)]
VectorStorageEnum::DenseVolatileHalf(v) => v.size_of_available_vectors_in_bytes(),
VectorStorageEnum::DenseMemmap(v) => v.size_of_available_vectors_in_bytes(),
VectorStorageEnum::DenseMemmapByte(v) => v.size_of_available_vectors_in_bytes(),
VectorStorageEnum::DenseMemmapHalf(v) => v.size_of_available_vectors_in_bytes(),
#[cfg(target_os = "linux")]
VectorStorageEnum::DenseUring(v) => v.size_of_available_vectors_in_bytes(),
#[cfg(target_os = "linux")]
VectorStorageEnum::DenseUringByte(v) => v.size_of_available_vectors_in_bytes(),
#[cfg(target_os = "linux")]
VectorStorageEnum::DenseUringHalf(v) => v.size_of_available_vectors_in_bytes(),
VectorStorageEnum::DenseAppendableMemmap(v) => v.size_of_available_vectors_in_bytes(),
VectorStorageEnum::DenseAppendableMemmapByte(v) => {
v.size_of_available_vectors_in_bytes()
}
VectorStorageEnum::DenseAppendableMemmapHalf(v) => {
v.size_of_available_vectors_in_bytes()
}
VectorStorageEnum::SparseVolatile(v) => v.size_of_available_vectors_in_bytes(),
VectorStorageEnum::SparseMmap(_v) => {
unreachable!(
"Mmap sparse storage does not know its total size, get from index instead"
)
}
VectorStorageEnum::MultiDenseVolatile(v) => v.size_of_available_vectors_in_bytes(),
#[cfg(test)]
VectorStorageEnum::MultiDenseVolatileByte(v) => v.size_of_available_vectors_in_bytes(),
#[cfg(test)]
VectorStorageEnum::MultiDenseVolatileHalf(v) => v.size_of_available_vectors_in_bytes(),
VectorStorageEnum::MultiDenseAppendableMemmap(v) => {
v.size_of_available_vectors_in_bytes()
}
VectorStorageEnum::MultiDenseAppendableMemmapByte(v) => {
v.size_of_available_vectors_in_bytes()
}
VectorStorageEnum::MultiDenseAppendableMemmapHalf(v) => {
v.size_of_available_vectors_in_bytes()
}
VectorStorageEnum::EmptyDense(_) => 0,
VectorStorageEnum::EmptySparse(_) => 0,
}
}
pub fn populate(&self) -> OperationResult<()> {
match self {
VectorStorageEnum::DenseVolatile(_) => {} // Can't populate as it is not mmap
@@ -689,6 +636,56 @@ impl VectorStorageEnum {
}
impl VectorStorageRead for VectorStorageEnum {
fn size_of_available_vectors_in_bytes(&self) -> usize {
match self {
VectorStorageEnum::DenseVolatile(v) => v.size_of_available_vectors_in_bytes(),
#[cfg(test)]
VectorStorageEnum::DenseVolatileByte(v) => v.size_of_available_vectors_in_bytes(),
#[cfg(test)]
VectorStorageEnum::DenseVolatileHalf(v) => v.size_of_available_vectors_in_bytes(),
VectorStorageEnum::DenseMemmap(v) => v.size_of_available_vectors_in_bytes(),
VectorStorageEnum::DenseMemmapByte(v) => v.size_of_available_vectors_in_bytes(),
VectorStorageEnum::DenseMemmapHalf(v) => v.size_of_available_vectors_in_bytes(),
#[cfg(target_os = "linux")]
VectorStorageEnum::DenseUring(v) => v.size_of_available_vectors_in_bytes(),
#[cfg(target_os = "linux")]
VectorStorageEnum::DenseUringByte(v) => v.size_of_available_vectors_in_bytes(),
#[cfg(target_os = "linux")]
VectorStorageEnum::DenseUringHalf(v) => v.size_of_available_vectors_in_bytes(),
VectorStorageEnum::DenseAppendableMemmap(v) => v.size_of_available_vectors_in_bytes(),
VectorStorageEnum::DenseAppendableMemmapByte(v) => {
v.size_of_available_vectors_in_bytes()
}
VectorStorageEnum::DenseAppendableMemmapHalf(v) => {
v.size_of_available_vectors_in_bytes()
}
VectorStorageEnum::SparseVolatile(v) => v.size_of_available_vectors_in_bytes(),
VectorStorageEnum::SparseMmap(_v) => {
unreachable!(
"Mmap sparse storage does not know its total size, get from index instead"
)
}
VectorStorageEnum::MultiDenseVolatile(v) => v.size_of_available_vectors_in_bytes(),
#[cfg(test)]
VectorStorageEnum::MultiDenseVolatileByte(v) => v.size_of_available_vectors_in_bytes(),
#[cfg(test)]
VectorStorageEnum::MultiDenseVolatileHalf(v) => v.size_of_available_vectors_in_bytes(),
VectorStorageEnum::MultiDenseAppendableMemmap(v) => {
v.size_of_available_vectors_in_bytes()
}
VectorStorageEnum::MultiDenseAppendableMemmapByte(v) => {
v.size_of_available_vectors_in_bytes()
}
VectorStorageEnum::MultiDenseAppendableMemmapHalf(v) => {
v.size_of_available_vectors_in_bytes()
}
VectorStorageEnum::EmptyDense(_) => 0,
VectorStorageEnum::EmptySparse(_) => 0,
}
}
fn distance(&self) -> Distance {
match self {
VectorStorageEnum::DenseVolatile(v) => v.distance(),