use std::sync::Arc;
use parking_lot::{
RwLock,
lock_api::{ArcRwLockReadGuard, ArcRwLockWriteGuard},
};
use whasher::GxPapayaMap as ConcurrentMap;
use super::{
vector_manager::{INDEX_SIZE_BYTES, VectorManager, VectorManagerResult},
vector_manager__index::Index,
vector_manager__quantization::{QuantizationChannel, QuantizationState, QuantizationStep},
vector_types::{VectorDistanceMetricType, VectorQuantType, VectorSetFlags},
};
pub type VectorSetSharedGuard = ArcRwLockReadGuard<parking_lot::RawRwLock, ()>;
pub type VectorSetLockGuard = ArcRwLockWriteGuard<parking_lot::RawRwLock, ()>;
#[derive(Default)]
pub struct VectorSetLocks {
locks: ConcurrentMap<u64, Arc<RwLock<()>>>,
}
impl VectorSetLocks {
fn lock_for(&self, key: &[u8]) -> Arc<RwLock<()>> {
let key_hash = key_hash_of(key);
if let Some(lock) = self.locks.pin().get(&key_hash) {
return Arc::clone(lock);
}
let lock = Arc::new(RwLock::new(()));
match self.locks.pin().try_insert(key_hash, Arc::clone(&lock)) {
Ok(_) => lock,
Err(_) => {
self
.locks
.pin()
.get(&key_hash)
.map(Arc::clone)
.unwrap_or(lock)
}
}
}
pub fn acquire_shared(&self, key: &[u8]) -> VectorSetSharedGuard {
self.lock_for(key).read_arc()
}
pub fn try_acquire_shared(&self, key: &[u8]) -> Option<VectorSetSharedGuard> {
self.lock_for(key).try_read_arc()
}
pub fn acquire_exclusive(&self, key: &[u8]) -> VectorSetLockGuard {
self.lock_for(key).write_arc()
}
pub fn try_acquire_exclusive(&self, key: &[u8]) -> Option<VectorSetLockGuard> {
self.lock_for(key).try_write_arc()
}
pub fn is_locked_exclusive(&self, key: &[u8]) -> bool {
self
.locks
.pin()
.get(&key_hash_of(key))
.is_some_and(|l| l.try_read().is_none())
}
}
#[derive(Debug)]
pub enum ReadIndexOutcome {
Hit(Index, VectorSetSharedGuard),
NotFound,
WouldBlock,
}
fn key_hash_of(key: &[u8]) -> u64 {
gxhash::gxhash64(key, 0)
}
#[derive(Debug, Clone, Copy)]
pub struct CreateIndexParams {
pub hash_slot: u16,
pub dims: u32,
pub reduce_dims: u32,
pub quant: VectorQuantType,
pub build_exploration_factor: u32,
pub num_links: u32,
pub distance_metric: VectorDistanceMetricType,
}
fn request_quantization_if_needed(
channel: &QuantizationChannel,
manager: &VectorManager,
key: &[u8],
context: u64,
) {
if manager.service.needs_quantization(context) {
let _ = channel.try_publish(QuantizationState::new(
key.to_vec(),
QuantizationStep::BuildQuantizationTable,
0,
));
}
}
impl VectorManager {
pub fn needs_recreate(&self, index_config: &[u8]) -> bool {
match Index::from_bytes(index_config) {
Some(index) => index.index_ptr == 0,
None => true,
}
}
pub fn read_vector_index(&self, key: &[u8]) -> (Option<Index>, Option<VectorSetSharedGuard>) {
match self.read_vector_index_core(key, false) {
ReadIndexOutcome::Hit(index, guard) => (Some(index), Some(guard)),
ReadIndexOutcome::NotFound | ReadIndexOutcome::WouldBlock => (None, None),
}
}
pub fn read_vector_index_core(&self, key: &[u8], non_blocking: bool) -> ReadIndexOutcome {
loop {
let shared = if non_blocking {
let Some(guard) = self.vector_set_locks.try_acquire_shared(key) else {
return ReadIndexOutcome::WouldBlock;
};
guard
} else {
self.vector_set_locks.acquire_shared(key)
};
let Some(bytes) = self.read_stored_index(key) else {
return ReadIndexOutcome::NotFound;
};
if !self.needs_recreate(&bytes) {
let index = Index::from_bytes(&bytes).unwrap_or_default();
return ReadIndexOutcome::Hit(index, shared);
}
drop(shared);
if self.drop_requested(key) {
if non_blocking {
return ReadIndexOutcome::WouldBlock;
}
self.wait_for_disk_ann_index_drop(key);
continue;
}
let _exclusive = if non_blocking {
let Some(guard) = self.vector_set_locks.try_acquire_exclusive(key) else {
return ReadIndexOutcome::WouldBlock;
};
guard
} else {
self.vector_set_locks.acquire_exclusive(key)
};
let Some(bytes) = self.read_stored_index(key) else {
return ReadIndexOutcome::NotFound;
};
if !self.needs_recreate(&bytes) {
continue;
}
let index = Index::from_bytes(&bytes).unwrap_or_default();
self.recreate_index_locked(key, &index);
continue;
}
}
pub fn read_or_create_vector_index(
&self,
key: &[u8],
create: Option<&CreateIndexParams>,
) -> Result<(Index, VectorSetSharedGuard), VectorManagerResult> {
let mut demand_exclusive = false;
loop {
if demand_exclusive {
let exclusive = self.vector_set_locks.acquire_exclusive(key);
return match self.create_or_recreate_under_exclusive(key, create) {
Ok(index) => {
drop(exclusive);
Ok((index, self.vector_set_locks.acquire_shared(key)))
}
Err(e) => Err(e),
};
}
let shared = self.vector_set_locks.acquire_shared(key);
if let Some(bytes) = self.read_stored_index(key)
&& !self.needs_recreate(&bytes)
{
let index = Index::from_bytes(&bytes).unwrap_or_default();
return Ok((index, shared));
}
drop(shared);
if let Some(exclusive) = self.vector_set_locks.try_acquire_exclusive(key) {
return match self.create_or_recreate_under_exclusive(key, create) {
Ok(index) => {
drop(exclusive);
Ok((index, self.vector_set_locks.acquire_shared(key)))
}
Err(e) => Err(e),
};
}
demand_exclusive = true;
}
}
fn create_or_recreate_under_exclusive(
&self,
key: &[u8],
create: Option<&CreateIndexParams>,
) -> Result<Index, VectorManagerResult> {
match self.read_stored_index(key) {
Some(bytes) => {
let index = Index::from_bytes(&bytes).unwrap_or_default();
if !self.needs_recreate(&bytes) {
Ok(index)
} else {
Ok(self.recreate_index_locked(key, &index))
}
}
None => match create {
None => Err(VectorManagerResult::Invalid),
Some(params) => self.create_index_locked(key, params),
},
}
}
fn recreate_index_locked(&self, key: &[u8], index: &Index) -> Index {
self.service.create_index(
index.context,
index.dimensions,
index.reduce_dims,
index.quant_type,
index.build_exploration_factor,
index.num_links,
index.distance_metric,
);
let mut rebuilt = *index;
rebuilt.index_ptr = 1;
self.write_stored_index(key, &rebuilt.to_bytes());
request_quantization_if_needed(&self.quantization_channel, self, key, rebuilt.context);
rebuilt
}
fn create_index_locked(
&self,
key: &[u8],
params: &CreateIndexParams,
) -> Result<Index, VectorManagerResult> {
let context = self
.next_vector_set_context(params.hash_slot)
.ok_or(VectorManagerResult::Invalid)?;
self.service.create_index(
context,
params.dims,
params.reduce_dims,
params.quant,
params.build_exploration_factor,
params.num_links,
params.distance_metric,
);
let index = Index {
context,
index_ptr: 1,
dimensions: params.dims,
reduce_dims: params.reduce_dims,
num_links: params.num_links,
build_exploration_factor: params.build_exploration_factor,
quant_type: params.quant,
distance_metric: params.distance_metric,
flags: VectorSetFlags::NONE,
};
self.write_stored_index(key, &index.to_bytes());
request_quantization_if_needed(&self.quantization_channel, self, key, context);
Ok(index)
}
pub fn acquire_exclusive_locks(&self, key: &[u8]) -> VectorSetLockGuard {
self.vector_set_locks.acquire_exclusive(key)
}
pub fn read_for_delete_vector_index(&self, key: &[u8]) -> Option<(Index, VectorSetLockGuard)> {
let exclusive = self.acquire_exclusive_locks(key);
let stored = self.read_stored_index(key)?;
let index = Index::from_bytes(&stored)?;
Some((index, exclusive))
}
pub(crate) fn read_stored_index(&self, key: &[u8]) -> Option<[u8; INDEX_SIZE_BYTES]> {
self.key_index_registry.lock().get(key).copied()
}
pub(crate) fn write_stored_index(&self, key: &[u8], bytes: &[u8; INDEX_SIZE_BYTES]) {
self.key_index_registry.lock().insert(key.to_vec(), *bytes);
}
pub fn remove_stored_index(&self, key: &[u8]) {
self.key_index_registry.lock().remove(key);
}
pub fn vadd_append_log_arg() -> i64 {
super::vector_manager::VADD_APPEND_LOG_ARG
}
pub fn index_has_suppress_cleanup(index: &Index) -> bool {
index.flags.contains(VectorSetFlags::SUPPRESS_CLEANUP)
}
}
#[cfg(test)]
mod tests {
use super::{super::vector_manager::VectorManagerOptions, *};
fn manager() -> VectorManager {
VectorManager::new(VectorManagerOptions {
is_enabled: true,
..Default::default()
})
}
fn q8_index(context: u64) -> Index {
Index {
context,
index_ptr: 1,
dimensions: 4,
reduce_dims: 0,
num_links: 8,
build_exploration_factor: 64,
quant_type: VectorQuantType::NoQuant,
distance_metric: VectorDistanceMetricType::L2,
flags: VectorSetFlags::NONE,
}
}
fn create_params() -> CreateIndexParams {
CreateIndexParams {
hash_slot: 5,
dims: 4,
reduce_dims: 0,
quant: VectorQuantType::NoQuant,
build_exploration_factor: 64,
num_links: 8,
distance_metric: VectorDistanceMetricType::L2,
}
}
#[test]
fn needs_recreate_semantics() {
let manager = manager();
let stub = Index {
context: 8,
index_ptr: 0,
..Index::default()
};
assert!(manager.needs_recreate(&stub.to_bytes()));
assert!(!manager.needs_recreate(&q8_index(8).to_bytes()));
assert!(manager.needs_recreate(&[0u8; 10]));
}
#[test]
fn read_create_and_recreate_flow() {
let manager = manager();
let key = b"flow".to_vec();
assert_eq!(
manager.read_or_create_vector_index(&key, None).unwrap_err(),
VectorManagerResult::Invalid
);
let (created, guard) = manager
.read_or_create_vector_index(&key, Some(&create_params()))
.unwrap();
assert_eq!(created.index_ptr, 1);
assert_ne!(created.context, 0);
assert_eq!(created.dimensions, 4);
drop(guard);
let (read, guard) = manager.read_vector_index(&key);
assert!(read.as_ref().is_some_and(|i| i.index_ptr == 1));
assert_eq!(read.as_ref().unwrap().context, created.context);
drop(guard);
let mut stub = created;
stub.index_ptr = 0;
manager.write_stored_index(&key, &stub.to_bytes());
let (read, guard) = manager.read_vector_index(&key);
assert!(read.as_ref().is_some_and(|i| i.index_ptr == 1));
drop(guard);
assert!(!manager.needs_recreate(&manager.read_stored_index(&key).unwrap()));
let mut stub2 = created;
stub2.index_ptr = 0;
manager.write_stored_index(&key, &stub2.to_bytes());
let (read, guard) = manager
.read_or_create_vector_index(&key, Some(&create_params()))
.unwrap();
assert_eq!(read.index_ptr, 1);
drop(guard);
}
#[test]
fn recreate_preserves_geometry_from_record() {
let manager = manager();
let key = b"geom".to_vec();
let mut stub = q8_index(512);
stub.index_ptr = 0;
manager.write_stored_index(&key, &stub.to_bytes());
let (read, _guard) = manager.read_vector_index(&key);
let read = read.unwrap();
assert_eq!(
(read.dimensions, read.num_links, read.quant_type),
(4, 8, VectorQuantType::NoQuant)
);
assert_eq!(read.context, 512);
}
#[test]
fn locks_serialize_writers() {
let manager = manager();
let key = b"locked".to_vec();
let (_index, guard) = manager
.read_or_create_vector_index(&key, Some(&create_params()))
.unwrap();
assert!(manager.vector_set_locks.try_acquire_shared(&key).is_some());
assert!(
manager
.vector_set_locks
.try_acquire_exclusive(&key)
.is_none()
);
assert!(!manager.vector_set_locks.is_locked_exclusive(&key));
drop(guard);
assert!(
manager
.vector_set_locks
.try_acquire_exclusive(&key)
.is_some()
);
let read_guard = manager.vector_set_locks.acquire_shared(&key);
assert!(manager.vector_set_locks.try_acquire_shared(&key).is_some());
assert!(
manager
.vector_set_locks
.try_acquire_exclusive(&key)
.is_none()
);
drop(read_guard);
assert!(
manager
.vector_set_locks
.try_acquire_exclusive(&key)
.is_some()
);
}
#[test]
fn delete_path_reads_under_lock() {
let manager = manager();
let key = b"del".to_vec();
assert!(manager.read_for_delete_vector_index(&key).is_none());
let (idx, guard) = manager
.read_or_create_vector_index(&key, Some(&create_params()))
.unwrap();
assert!(
manager
.vector_set_locks
.try_acquire_exclusive(&key)
.is_none()
);
drop(guard);
let (found, del_guard) = manager.read_for_delete_vector_index(&key).unwrap();
assert_eq!(found.context, idx.context);
assert!(manager.vector_set_locks.is_locked_exclusive(&key));
drop(del_guard);
assert!(!manager.vector_set_locks.is_locked_exclusive(&key));
assert_eq!(VectorManager::vadd_append_log_arg(), i64::MIN);
let mut flagged = q8_index(8);
flagged.flags = VectorSetFlags::SUPPRESS_CLEANUP;
assert!(VectorManager::index_has_suppress_cleanup(&flagged));
}
}