use std::{
sync::{Arc, atomic::Ordering},
thread,
time::Duration,
};
use super::{
cleanup::vector_set_cleanup_work_channel::VectorSetCleanupWorkChannel,
vector_manager::VectorManager, vector_manager__locking::ReadIndexOutcome,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QuantizationStep {
Invalid = 0,
BuildQuantizationTable = 1,
BackfillQuantizedVectors = 2,
}
#[derive(Debug, Clone)]
pub struct QuantizationState {
pub key: Vec<u8>,
pub step: QuantizationStep,
pub step_index: usize,
}
impl QuantizationState {
pub fn new(key: Vec<u8>, step: QuantizationStep, step_index: usize) -> Self {
Self {
key,
step,
step_index,
}
}
}
pub type QuantizationChannel = VectorSetCleanupWorkChannel<QuantizationState>;
impl VectorManager {
pub fn start_quantization_tasks(self: &Arc<Self>, task_count: usize) {
let count = task_count.clamp(1, 64);
for _ in 0..count {
let manager = Arc::clone(self);
thread::Builder::new()
.name("vector-quantization".into())
.spawn(move || Self::quantization_task_async(&manager))
.expect("量化 worker 线程创建失败");
}
}
fn quantization_task_async(manager: &Arc<Self>) {
let channel = &manager.quantization_channel;
while channel.wait_to_read(250) {
let Some(state) = channel.try_read() else {
continue;
};
let mut attempt = 0u32;
loop {
if manager.try_process_quantization_request(&state) {
break;
}
thread::sleep(Duration::from_micros(50 * (attempt.min(16) as u64 + 1)));
if channel.is_completed() {
return;
}
attempt = attempt.saturating_add(1);
}
}
}
pub fn try_process_quantization_request(&self, state: &QuantizationState) -> bool {
let (index, _lock) = match self.read_vector_index_core(&state.key, true) {
ReadIndexOutcome::Hit(index, lock) => (index, lock),
ReadIndexOutcome::NotFound => return true,
ReadIndexOutcome::WouldBlock => return false,
};
let context = index.context;
match state.step {
QuantizationStep::BuildQuantizationTable => {
if self.service.build_quantization_table(context) {
self
.quantization_requests_processed
.fetch_add(1, Ordering::Relaxed);
for i in 0..self.quantization_task_count.max(1) {
let _ = self
.quantization_channel
.try_publish(QuantizationState::new(
state.key.clone(),
QuantizationStep::BackfillQuantizedVectors,
i,
));
}
}
}
QuantizationStep::BackfillQuantizedVectors => {
self.service.backfill_quantized_vectors(
context,
state.step_index,
self.quantization_task_count.max(1),
);
self
.quantization_backfills_processed
.fetch_add(1, Ordering::Relaxed);
}
QuantizationStep::Invalid => {
log::error!("量化请求包含未知阶段: {:?}", state.step);
}
}
true
}
}
#[cfg(test)]
mod tests {
use super::{super::vector_manager::VectorManagerOptions, *};
#[test]
fn quantization_pipeline_processes_requests() {
let manager = VectorManager::new(VectorManagerOptions {
is_enabled: true,
..Default::default()
});
let key = b"myset".to_vec();
let record = super::super::vector_manager__index::Index {
context: 8,
index_ptr: 0,
dimensions: 2,
reduce_dims: 0,
num_links: 4,
build_exploration_factor: 32,
quant_type: super::super::vector_types::VectorQuantType::Q8,
distance_metric: super::super::vector_types::VectorDistanceMetricType::L2,
flags: super::super::vector_types::VectorSetFlags::NONE,
};
manager
.key_index_registry
.lock()
.insert(key.clone(), record.to_bytes());
assert!(
manager
.quantization_channel
.try_publish(QuantizationState::new(
key.clone(),
QuantizationStep::BuildQuantizationTable,
0
))
);
while let Some(state) = manager.quantization_channel.try_read() {
assert!(manager.try_process_quantization_request(&state));
}
assert!(
manager
.quantization_requests_processed
.load(Ordering::Relaxed)
>= 1
);
assert!(
manager
.quantization_backfills_processed
.load(Ordering::Relaxed)
> 0
);
let stale = QuantizationState::new(
b"gone".to_vec(),
QuantizationStep::BuildQuantizationTable,
0,
);
assert!(manager.try_process_quantization_request(&stale));
let _held = manager.vector_set_locks.acquire_exclusive(&key);
let contended = QuantizationState::new(key, QuantizationStep::BuildQuantizationTable, 0);
assert!(!manager.try_process_quantization_request(&contended));
}
#[test]
fn quantization_step_codes() {
assert_eq!(QuantizationStep::Invalid as i32, 0);
assert_eq!(QuantizationStep::BuildQuantizationTable as i32, 1);
assert_eq!(QuantizationStep::BackfillQuantizedVectors as i32, 2);
}
}