mirror of
https://github.com/qdrant/qdrant.git
synced 2026-08-06 01:50:57 -05:00
276 lines
11 KiB
Rust
276 lines
11 KiB
Rust
use std::cmp;
|
|
use std::collections::HashMap;
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::atomic::AtomicBool;
|
|
use std::sync::Arc;
|
|
|
|
use common::cpu::CpuPermit;
|
|
|
|
use super::get_vector_storage_path;
|
|
use crate::common::error_logging::LogError;
|
|
use crate::common::operation_error::{check_process_stopped, OperationError, OperationResult};
|
|
use crate::entry::entry_point::SegmentEntry;
|
|
use crate::index::hnsw_index::num_rayon_threads;
|
|
use crate::index::{PayloadIndex, VectorIndex};
|
|
use crate::segment::Segment;
|
|
use crate::segment_constructor::{build_segment, load_segment};
|
|
use crate::types::{Indexes, PayloadFieldSchema, PayloadKeyType, SegmentConfig};
|
|
use crate::vector_storage::quantized::quantized_vectors::QuantizedVectors;
|
|
use crate::vector_storage::VectorStorage;
|
|
|
|
/// Structure for constructing segment out of several other segments
|
|
pub struct SegmentBuilder {
|
|
pub segment: Option<Segment>,
|
|
pub destination_path: PathBuf,
|
|
pub temp_path: PathBuf,
|
|
pub indexed_fields: HashMap<PayloadKeyType, PayloadFieldSchema>,
|
|
}
|
|
|
|
impl SegmentBuilder {
|
|
pub fn new(
|
|
segment_path: &Path,
|
|
temp_dir: &Path,
|
|
segment_config: &SegmentConfig,
|
|
) -> OperationResult<Self> {
|
|
let segment = build_segment(temp_dir, segment_config, true)?;
|
|
let temp_path = segment.current_path.clone();
|
|
|
|
let destination_path = segment_path.join(temp_path.file_name().unwrap());
|
|
|
|
Ok(SegmentBuilder {
|
|
segment: Some(segment),
|
|
destination_path,
|
|
temp_path,
|
|
indexed_fields: Default::default(),
|
|
})
|
|
}
|
|
|
|
/// Update current segment builder with all (not deleted) vectors and payload form `other` segment
|
|
/// Perform index building at the end of update
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `other` - segment to add into construction
|
|
///
|
|
/// # Result
|
|
///
|
|
/// * `bool` - if `true` - data successfully added, if `false` - process was interrupted
|
|
///
|
|
pub fn update_from(&mut self, other: &Segment, stopped: &AtomicBool) -> OperationResult<bool> {
|
|
let self_segment = match &mut self.segment {
|
|
Some(segment) => segment,
|
|
None => {
|
|
return Err(OperationError::service_error(
|
|
"Segment building error: created segment not found",
|
|
));
|
|
}
|
|
};
|
|
self_segment.version = Some(cmp::max(self_segment.version(), other.version()));
|
|
|
|
let other_id_tracker = other.id_tracker.borrow();
|
|
let other_vector_storages: HashMap<_, _> = other
|
|
.vector_data
|
|
.iter()
|
|
.map(|(vector_name, vector_data)| {
|
|
(vector_name.to_owned(), vector_data.vector_storage.borrow())
|
|
})
|
|
.collect();
|
|
let other_payload_index = other.payload_index.borrow();
|
|
|
|
let mut id_tracker = self_segment.id_tracker.borrow_mut();
|
|
let mut vector_storages: HashMap<_, _> = self_segment
|
|
.vector_data
|
|
.iter()
|
|
.map(|(vector_name, vector_data)| {
|
|
(
|
|
vector_name.to_owned(),
|
|
vector_data.vector_storage.borrow_mut(),
|
|
)
|
|
})
|
|
.collect();
|
|
let mut payload_index = self_segment.payload_index.borrow_mut();
|
|
|
|
if vector_storages.len() != other_vector_storages.len() {
|
|
return Err(OperationError::service_error(
|
|
format!("Self and other segments have different vector names count. Self count: {}, other count: {}", vector_storages.len(), other_vector_storages.len()),
|
|
));
|
|
}
|
|
|
|
let mut new_internal_range = None;
|
|
for (vector_name, vector_storage) in &mut vector_storages {
|
|
check_process_stopped(stopped)?;
|
|
let other_vector_storage = other_vector_storages.get(vector_name).ok_or_else(|| {
|
|
OperationError::service_error(format!(
|
|
"Cannot update from other segment because if missing vector name {vector_name}"
|
|
))
|
|
})?;
|
|
let internal_range = vector_storage.update_from(
|
|
other_vector_storage,
|
|
&mut other_id_tracker.iter_ids(),
|
|
stopped,
|
|
)?;
|
|
match new_internal_range.clone() {
|
|
Some(new_internal_range) => {
|
|
if new_internal_range != internal_range {
|
|
return Err(OperationError::service_error(
|
|
"Internal ids range mismatch between self segment vectors and other segment vectors",
|
|
));
|
|
}
|
|
}
|
|
None => new_internal_range = Some(internal_range.clone()),
|
|
}
|
|
}
|
|
|
|
if let Some(new_internal_range) = new_internal_range {
|
|
let internal_id_iter = new_internal_range.zip(other_id_tracker.iter_ids());
|
|
|
|
for (new_internal_id, old_internal_id) in internal_id_iter {
|
|
check_process_stopped(stopped)?;
|
|
|
|
let external_id =
|
|
if let Some(external_id) = other_id_tracker.external_id(old_internal_id) {
|
|
external_id
|
|
} else {
|
|
log::warn!(
|
|
"Cannot find external id for internal id {old_internal_id}, skipping"
|
|
);
|
|
continue;
|
|
};
|
|
let other_version = other_id_tracker.internal_version(old_internal_id).unwrap();
|
|
|
|
match id_tracker.internal_id(external_id) {
|
|
None => {
|
|
// New point, just insert
|
|
id_tracker.set_link(external_id, new_internal_id)?;
|
|
id_tracker.set_internal_version(new_internal_id, other_version)?;
|
|
payload_index.assign(
|
|
new_internal_id,
|
|
&other_payload_index.payload(old_internal_id)?,
|
|
)?;
|
|
}
|
|
Some(existing_internal_id) => {
|
|
// Point exists in both: newly constructed and old segments, so we need to merge them
|
|
// Based on version
|
|
let existing_version =
|
|
id_tracker.internal_version(existing_internal_id).unwrap();
|
|
let remove_id = if existing_version < other_version {
|
|
// Other version is the newest, remove the existing one and replace
|
|
id_tracker.drop(external_id)?;
|
|
id_tracker.set_link(external_id, new_internal_id)?;
|
|
id_tracker.set_internal_version(new_internal_id, other_version)?;
|
|
payload_index.drop(existing_internal_id)?;
|
|
payload_index.assign(
|
|
new_internal_id,
|
|
&other_payload_index.payload(old_internal_id)?,
|
|
)?;
|
|
existing_internal_id
|
|
} else {
|
|
// Old version is still good, do not move anything else
|
|
// Mark newly added vector as removed
|
|
new_internal_id
|
|
};
|
|
for vector_storage in vector_storages.values_mut() {
|
|
vector_storage.delete_vector(remove_id)?;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (field, payload_schema) in other.payload_index.borrow().indexed_fields() {
|
|
self.indexed_fields.insert(field, payload_schema);
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
pub fn build(
|
|
mut self,
|
|
permit: CpuPermit,
|
|
stopped: &AtomicBool,
|
|
) -> Result<Segment, OperationError> {
|
|
{
|
|
// Arc permit to share it with each vector store
|
|
let permit = Arc::new(permit);
|
|
|
|
let mut segment = self.segment.take().ok_or(OperationError::service_error(
|
|
"Segment building error: created segment not found",
|
|
))?;
|
|
|
|
for (field, payload_schema) in &self.indexed_fields {
|
|
segment.create_field_index(segment.version(), field, Some(payload_schema))?;
|
|
check_process_stopped(stopped)?;
|
|
}
|
|
|
|
Self::update_quantization(&mut segment, stopped)?;
|
|
|
|
for vector_data in segment.vector_data.values_mut() {
|
|
vector_data
|
|
.vector_index
|
|
.borrow_mut()
|
|
.build_index(permit.clone(), stopped)?;
|
|
}
|
|
|
|
// We're done with CPU-intensive tasks, release CPU permit
|
|
debug_assert_eq!(
|
|
Arc::strong_count(&permit),
|
|
1,
|
|
"Must release CPU permit Arc everywhere",
|
|
);
|
|
drop(permit);
|
|
|
|
segment.flush(true)?;
|
|
drop(segment);
|
|
// Now segment is evicted from RAM
|
|
}
|
|
|
|
// Move fully constructed segment into collection directory and load back to RAM
|
|
std::fs::rename(&self.temp_path, &self.destination_path)
|
|
.describe("Moving segment data after optimization")?;
|
|
|
|
let loaded_segment = load_segment(&self.destination_path, stopped)?.ok_or_else(|| {
|
|
OperationError::service_error(format!(
|
|
"Segment loading error: {}",
|
|
self.destination_path.display()
|
|
))
|
|
})?;
|
|
Ok(loaded_segment)
|
|
}
|
|
|
|
fn update_quantization(segment: &mut Segment, stopped: &AtomicBool) -> OperationResult<()> {
|
|
let config = segment.config().clone();
|
|
|
|
for (vector_name, vector_data) in &mut segment.vector_data {
|
|
let max_threads = if let Some(config) = config.vector_data.get(vector_name) {
|
|
match &config.index {
|
|
Indexes::Hnsw(hnsw) => num_rayon_threads(hnsw.max_indexing_threads),
|
|
_ => 1,
|
|
}
|
|
} else {
|
|
// quantization is applied only for dense vectors
|
|
continue;
|
|
};
|
|
|
|
if let Some(quantization) = config.quantization_config(vector_name) {
|
|
let segment_path = segment.current_path.as_path();
|
|
check_process_stopped(stopped)?;
|
|
|
|
let vector_storage_path = get_vector_storage_path(segment_path, vector_name);
|
|
|
|
let vector_storage = vector_data.vector_storage.borrow();
|
|
|
|
let quantized_vectors = QuantizedVectors::create(
|
|
&vector_storage,
|
|
quantization,
|
|
&vector_storage_path,
|
|
max_threads,
|
|
stopped,
|
|
)?;
|
|
|
|
*vector_data.quantized_vectors.borrow_mut() = Some(quantized_vectors);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|