mirror of
https://github.com/qdrant/qdrant.git
synced 2026-08-06 10:00:58 -05:00
* wip: chunked mmap * Fix typo * insert and get methods * dynamic bitvec * clippy * wip: vector storage * wip: fmt * wip: mmap chunks * wip: mmap problems * Share transmuted mutable reference over mmap * option to enable appendable mmap vectors * fmt * rename storage status file * update tests * fix get deleted value range * add recovery to vector storage tests * add flush to tests * fix transmute from immutable to mutable * make transmuted pointer private * remove unused unsafe functions * force WAL flush if wait=true * move wal flush into updater thread * remove flush from update api * Minimize pub visibility for specialized/dangerous functions * Allocate vector with predefined capacity * Inline format parameters * Assert we have multiple chunks while testing, test is useless otherwise * Remove unnecessary scope * Remove unnecessary dereference * Random bool has 0.5 as standard distribution, use iter::repeat_with * Replace RemovableMmap::new with Default derive * Rename len to num_flags * Use Option replace as it is convention alongside take * Add FileId enum to replace error prone manual ID rotating * Use debug_assert_eq where applicable * Refactor drop and set to replace * Change default chunk size for chunked mmap vectors to 32MB This change is made as per GitHub review, because allocating a few storages with 128MB would take a significant amount of time and storage. See: https://github.com/qdrant/qdrant/pull/1838#discussion_r1187215475 * Replace for-loops with iterators * Draft: add typed mmap to improve code safety (#1860) * Add typed mmap * Replace some crude mmap usages with typed mmap * Use typed mmap for deleted flags * Simplify dynamic mmap flags a lot with new typed mmap, remove flags option * Reformat * Remove old mmap functions that are now unused * Reimplement mmap locking for mmap_vectors * Add MmapBitSlice tests * Replace MmapChunk with new typed mmap * Update docs * Clean-up * Disable alignment assertions on Windows for now * Rename mmap lock to mlock to prevent confusion with lockable types * one more small test * Some review fixes * Add aliasing note * Add basic error handling in typed mmap constructors * Use typed mmap error handling throughout project * Move mmap type module to common * Fix transmute functions being unsound See https://github.com/qdrant/qdrant/pull/1860#discussion_r1188593854 --------- Co-authored-by: Andrey Vasnetsov <andrey@vasnetsov.com> --------- Co-authored-by: timvisee <tim@visee.me> Co-authored-by: Tim Visée <tim+github@visee.me>
148 lines
4.9 KiB
Rust
148 lines
4.9 KiB
Rust
mod fixtures;
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::collections::HashMap;
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
use std::sync::Arc;
|
|
use std::time::{Duration, Instant};
|
|
|
|
use itertools::Itertools;
|
|
use segment::data_types::vectors::{only_default_vector, DEFAULT_VECTOR_NAME};
|
|
use segment::entry::entry_point::{OperationError, SegmentEntry};
|
|
use segment::segment::Segment;
|
|
use segment::segment_constructor::segment_builder::SegmentBuilder;
|
|
use segment::types::{Indexes, SegmentConfig, VectorDataConfig};
|
|
use tempfile::Builder;
|
|
|
|
use crate::fixtures::segment::{build_segment_1, build_segment_2, empty_segment};
|
|
|
|
#[test]
|
|
fn test_building_new_segment() {
|
|
let dir = Builder::new().prefix("segment_dir").tempdir().unwrap();
|
|
let temp_dir = Builder::new().prefix("segment_temp_dir").tempdir().unwrap();
|
|
|
|
let stopped = AtomicBool::new(false);
|
|
|
|
// let segment1_dir = dir.path().join("segment_1");
|
|
// let segment2_dir = dir.path().join("segment_2");
|
|
|
|
let segment1 = build_segment_1(dir.path());
|
|
let mut segment2 = build_segment_2(dir.path());
|
|
|
|
let mut builder =
|
|
SegmentBuilder::new(dir.path(), temp_dir.path(), &segment1.segment_config).unwrap();
|
|
|
|
// Include overlapping with segment1 to check the
|
|
segment2
|
|
.upsert_point(100, 3.into(), &only_default_vector(&[0., 0., 0., 0.]))
|
|
.unwrap();
|
|
|
|
builder.update_from(&segment1, &stopped).unwrap();
|
|
builder.update_from(&segment2, &stopped).unwrap();
|
|
builder.update_from(&segment2, &stopped).unwrap();
|
|
|
|
// Check what happens if segment building fails here
|
|
|
|
let segment_count = dir.path().read_dir().unwrap().count();
|
|
|
|
assert_eq!(segment_count, 2);
|
|
|
|
let temp_segment_count = temp_dir.path().read_dir().unwrap().count();
|
|
|
|
assert_eq!(temp_segment_count, 1);
|
|
|
|
// Now we finalize building
|
|
|
|
let merged_segment: Segment = builder.build(&stopped).unwrap();
|
|
|
|
let new_segment_count = dir.path().read_dir().unwrap().count();
|
|
|
|
assert_eq!(new_segment_count, 3);
|
|
|
|
assert_eq!(
|
|
merged_segment.iter_points().count(),
|
|
merged_segment.available_point_count(),
|
|
);
|
|
assert_eq!(
|
|
merged_segment.available_point_count(),
|
|
segment1
|
|
.iter_points()
|
|
.chain(segment2.iter_points())
|
|
.unique()
|
|
.count(),
|
|
);
|
|
|
|
assert_eq!(merged_segment.point_version(3.into()), Some(100));
|
|
}
|
|
|
|
fn estimate_build_time(segment: &Segment, stop_timeout_millis: u64) -> (u64, bool) {
|
|
let stopped = Arc::new(AtomicBool::new(false));
|
|
|
|
let dir = Builder::new().prefix("segment_dir1").tempdir().unwrap();
|
|
let temp_dir = Builder::new().prefix("segment_temp_dir").tempdir().unwrap();
|
|
|
|
let segment_config = SegmentConfig {
|
|
vector_data: HashMap::from([(
|
|
DEFAULT_VECTOR_NAME.to_owned(),
|
|
VectorDataConfig {
|
|
size: segment.segment_config.vector_data[DEFAULT_VECTOR_NAME].size,
|
|
distance: segment.segment_config.vector_data[DEFAULT_VECTOR_NAME].distance,
|
|
hnsw_config: None,
|
|
quantization_config: None,
|
|
on_disk: None,
|
|
},
|
|
)]),
|
|
index: Indexes::Hnsw(Default::default()),
|
|
..Default::default()
|
|
};
|
|
|
|
let mut builder =
|
|
SegmentBuilder::new(dir.path(), temp_dir.path(), &segment_config).unwrap();
|
|
|
|
builder.update_from(segment, &stopped).unwrap();
|
|
|
|
let now = Instant::now();
|
|
|
|
let stopped_t = stopped.clone();
|
|
|
|
std::thread::Builder::new()
|
|
.name("build_estimator_timeout".to_string())
|
|
.spawn(move || {
|
|
std::thread::sleep(Duration::from_millis(stop_timeout_millis));
|
|
stopped_t.store(true, Ordering::Release);
|
|
})
|
|
.unwrap();
|
|
|
|
let res = builder.build(&stopped);
|
|
|
|
let is_cancelled = match res {
|
|
Ok(_) => false,
|
|
Err(err) => matches!(err, OperationError::Cancelled { .. }),
|
|
};
|
|
|
|
(now.elapsed().as_millis() as u64, is_cancelled)
|
|
}
|
|
|
|
#[test]
|
|
fn test_building_cancellation() {
|
|
let dir = Builder::new().prefix("segment_dir").tempdir().unwrap();
|
|
|
|
let mut segment = empty_segment(dir.path());
|
|
|
|
for idx in 0..1000 {
|
|
segment
|
|
.upsert_point(1, idx.into(), &only_default_vector(&[0., 0., 0., 0.]))
|
|
.unwrap();
|
|
}
|
|
|
|
// Checks that optimization with longed cancellation timeout will also finishes fast
|
|
let (time_fast, is_stopped_fast) = estimate_build_time(&segment, 20);
|
|
let (time_long, _is_stopped_long) = estimate_build_time(&segment, 200);
|
|
|
|
assert!(is_stopped_fast);
|
|
|
|
assert!(time_fast < time_long);
|
|
}
|
|
}
|