Files
qdrant/lib/segment/tests/segment_tests.rs
Tim Visée 1c85c9b235 Add optimizer for many deleted points, make aware of deleted points and vectors (#1758)
* Minor collection optimizer cleanup

* Make optimizers better aware of available vs soft deleted points

* Fix incorrect deleted state on proxy segment for double delete

* Rename upsert_vector to upsert_point, because we work with points

* Refactor point methods for more clear and consistent naming

* Replace internal_size in IdTracker with total_point_count

* Keep track of vector deletion count on storage creation

* Add sparse index optimizer, to optimize indexes with high deletion count

* Add minimum vector count threshold to sparse index optimizer

* Add sparse index optimizer test

* Use consistent naming, write vector in full everywhere

* Simplify vacuum optimizer a bit

* Merge sparse index optimizer into vacuum optimizer

* Improve update_from in segment builder by returning early

* More accurately count vectors in segment optimizer

* Remove random from vacuum optimizer tests to make them more reliable

* Don't expose the total points in segment info, use available points

* Process review feedback

* Compare available vectors against indexed ones in vacuum optimizer

This is much better than using the number of soft-deleted vectors when
the segment was created for calculations. Not to mention that value had
other problems as well.

* Remove create_deleted_vector_count field, update vacuum test parameters

* Potentially solve out of bound panic when building index

* Review fixes:

- Propagate deleted flags into payload hnsw building
- Use `total` number of points for building HNSW instead of number of
  available points
- minor refactoring of `hnsw_config` copy -> clone
- Better detection of `indexed_points` in HNSW

* fix assert condition

* Optional named vectors optimizer reveiw 2 (#1794)

* review with Ivan

* fmt

* remove available_vector_count from segment entry

* remove total_point_count from segment entry

---------

Co-authored-by: Ivan Pleshkov <pleshkov.ivan@gmail.com>

* rollback changes in deleted count in proxy segment

* improve vector threshold detection logic in optimized_segment_builder

* style changes

* fix propagate deleted points to vectors

* Fix typo in method name

---------

Co-authored-by: Andrey Vasnetsov <andrey@vasnetsov.com>
Co-authored-by: Ivan Pleshkov <pleshkov.ivan@gmail.com>
2023-05-17 11:20:02 +02:00

213 lines
6.1 KiB
Rust

mod fixtures;
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use std::iter::FromIterator;
use segment::data_types::named_vectors::NamedVectors;
use segment::data_types::vectors::DEFAULT_VECTOR_NAME;
use segment::entry::entry_point::{OperationError, SegmentEntry};
use segment::segment_constructor::load_segment;
use segment::types::{Condition, Filter, WithPayload};
use tempfile::Builder;
use crate::fixtures::segment::{build_segment_1, build_segment_3};
#[test]
fn test_point_exclusion() {
let dir = Builder::new().prefix("segment_dir").tempdir().unwrap();
let segment = build_segment_1(dir.path());
assert!(segment.has_point(3.into()));
let query_vector = vec![1.0, 1.0, 1.0, 1.0];
let res = segment
.search(
DEFAULT_VECTOR_NAME,
&query_vector,
&WithPayload::default(),
&false.into(),
None,
1,
None,
)
.unwrap();
let best_match = res.get(0).expect("Non-empty result");
assert_eq!(best_match.id, 3.into());
let ids: HashSet<_> = HashSet::from_iter([3.into()]);
let frt = Filter {
should: None,
must: None,
must_not: Some(vec![Condition::HasId(ids.into())]),
};
let res = segment
.search(
DEFAULT_VECTOR_NAME,
&query_vector,
&WithPayload::default(),
&false.into(),
Some(&frt),
1,
None,
)
.unwrap();
let best_match = res.get(0).expect("Non-empty result");
assert_ne!(best_match.id, 3.into());
let point_ids1: Vec<_> = segment.iter_points().collect();
let point_ids2: Vec<_> = segment.iter_points().collect();
assert!(!point_ids1.is_empty());
assert!(!point_ids2.is_empty());
assert_eq!(&point_ids1, &point_ids2)
}
#[test]
fn test_named_vector_search() {
let dir = Builder::new().prefix("segment_dir").tempdir().unwrap();
let segment = build_segment_3(dir.path());
assert!(segment.has_point(3.into()));
let query_vector = vec![1.0, 1.0, 1.0, 1.0];
let res = segment
.search(
"vector1",
&query_vector,
&WithPayload::default(),
&false.into(),
None,
1,
None,
)
.unwrap();
let best_match = res.get(0).expect("Non-empty result");
assert_eq!(best_match.id, 3.into());
let ids: HashSet<_> = HashSet::from_iter([3.into()]);
let frt = Filter {
should: None,
must: None,
must_not: Some(vec![Condition::HasId(ids.into())]),
};
let res = segment
.search(
"vector1",
&query_vector,
&WithPayload::default(),
&false.into(),
Some(&frt),
1,
None,
)
.unwrap();
let best_match = res.get(0).expect("Non-empty result");
assert_ne!(best_match.id, 3.into());
let point_ids1: Vec<_> = segment.iter_points().collect();
let point_ids2: Vec<_> = segment.iter_points().collect();
assert!(!point_ids1.is_empty());
assert!(!point_ids2.is_empty());
assert_eq!(&point_ids1, &point_ids2)
}
#[test]
fn test_missed_vector_name() {
let dir = Builder::new().prefix("segment_dir").tempdir().unwrap();
let mut segment = build_segment_3(dir.path());
let exists = segment
.upsert_point(
7,
1.into(),
&NamedVectors::from([
("vector2".to_owned(), vec![10.]),
("vector3".to_owned(), vec![5., 6., 7., 8.]),
]),
)
.unwrap();
assert!(exists, "this partial vector should overwrite existing");
let exists = segment
.upsert_point(
8,
6.into(),
&NamedVectors::from([
("vector2".to_owned(), vec![10.]),
("vector3".to_owned(), vec![5., 6., 7., 8.]),
]),
)
.unwrap();
assert!(!exists, "this partial vector should not existing");
}
#[test]
fn test_vector_name_not_exists() {
let dir = Builder::new().prefix("segment_dir").tempdir().unwrap();
let mut segment = build_segment_3(dir.path());
let result = segment.upsert_point(
6,
6.into(),
&NamedVectors::from([
("vector1".to_owned(), vec![5., 6., 7., 8.]),
("vector2".to_owned(), vec![10.]),
("vector3".to_owned(), vec![5., 6., 7., 8.]),
("vector4".to_owned(), vec![5., 6., 7., 8.]),
]),
);
if let Err(OperationError::VectorNameNotExists { received_name }) = result {
assert!(received_name == "vector4");
} else {
panic!("wrong upsert result")
}
}
#[test]
fn ordered_deletion_test() {
let dir = Builder::new().prefix("segment_dir").tempdir().unwrap();
let path = {
let mut segment = build_segment_1(dir.path());
segment.delete_point(6, 5.into()).unwrap();
segment.delete_point(6, 4.into()).unwrap();
segment.flush(true).unwrap();
segment.current_path.clone()
};
let segment = load_segment(&path).unwrap().unwrap();
let query_vector = vec![1.0, 1.0, 1.0, 1.0];
let res = segment
.search(
DEFAULT_VECTOR_NAME,
&query_vector,
&WithPayload::default(),
&false.into(),
None,
1,
None,
)
.unwrap();
let best_match = res.get(0).expect("Non-empty result");
assert_eq!(best_match.id, 3.into());
}
}