mirror of
https://github.com/qdrant/qdrant.git
synced 2026-08-06 18:10:58 -05:00
* Don't account for deferred points in some places # Conflicts: # lib/collection/src/shards/local_shard/scroll.rs * Add in QueryContext * Cover more places * Coderabbit review remarks * Properly count amount of deleted deferred points (#8386) * Properly count amount of deleted deferred points * Prevent double-counting of the same point * Remove hints to estimations * Properly handle counts in ProxySegment * Add tests and fix deleted point count issue * Adjusts tests + fix issues * Separte fields for deferred points in telemetry (SegmentInfo) * Remove deferred_points_count() * openapi * Fix test by manually calculating visible points * Adjust ProxySegment test to revertion of SegmentInfo * Throw error if collection was not found in telemetry (e2e Test) * Update lib/segment/src/segment/segment_ops.rs Co-authored-by: Andrey Vasnetsov <andrey@vasnetsov.com> * Make new fields in API optional * Don't take range if no deferred point exist * Review remarks --------- Co-authored-by: Andrey Vasnetsov <andrey@vasnetsov.com>
335 lines
11 KiB
Rust
335 lines
11 KiB
Rust
use std::collections::HashSet;
|
|
use std::sync::atomic::AtomicBool;
|
|
|
|
use common::counter::hardware_accumulator::HwMeasurementAcc;
|
|
use common::types::DeferredBehavior;
|
|
use itertools::Itertools as _;
|
|
use rand::RngExt;
|
|
use rand::distr::weighted::WeightedIndex;
|
|
use rand::rngs::StdRng;
|
|
use segment::common::operation_error::{OperationError, OperationResult};
|
|
use segment::data_types::order_by::{Direction, OrderBy};
|
|
use segment::types::*;
|
|
use shard::query::scroll::{QueryScrollRequestInternal, ScrollOrder};
|
|
use shard::retrieve::record_internal::RecordInternal;
|
|
use shard::retrieve::retrieve_blocking::retrieve_blocking;
|
|
use shard::scroll::ScrollRequestInternal;
|
|
|
|
use super::EdgeShard;
|
|
use crate::DEFAULT_EDGE_TIMEOUT;
|
|
|
|
impl EdgeShard {
|
|
pub fn scroll(
|
|
&self,
|
|
request: ScrollRequestInternal,
|
|
) -> OperationResult<(Vec<RecordInternal>, Option<PointIdType>)> {
|
|
let ScrollRequestInternal {
|
|
offset,
|
|
limit,
|
|
filter,
|
|
with_payload,
|
|
with_vector,
|
|
order_by,
|
|
} = request;
|
|
|
|
let limit = limit.unwrap_or(ScrollRequestInternal::default_limit());
|
|
let with_payload = with_payload.unwrap_or(ScrollRequestInternal::default_with_payload());
|
|
|
|
match order_by.map(OrderBy::from) {
|
|
None => {
|
|
let limit_plus_one = limit.saturating_add(1);
|
|
let mut records = self.scroll_by_id(
|
|
offset,
|
|
limit_plus_one,
|
|
&with_payload,
|
|
&with_vector,
|
|
filter.as_ref(),
|
|
HwMeasurementAcc::disposable_edge(),
|
|
)?;
|
|
let next_offset = if records.len() > limit {
|
|
let last_record = records.pop().unwrap();
|
|
Some(last_record.id)
|
|
} else {
|
|
None
|
|
};
|
|
Ok((records, next_offset))
|
|
}
|
|
|
|
Some(order_by) => {
|
|
if offset.is_some() {
|
|
return Err(OperationError::validation_error(
|
|
"Offset is not supported when ordering by field",
|
|
));
|
|
}
|
|
let records = self.scroll_by_field(
|
|
limit,
|
|
&with_payload,
|
|
&with_vector,
|
|
filter.as_ref(),
|
|
&order_by,
|
|
HwMeasurementAcc::disposable_edge(),
|
|
)?;
|
|
Ok((records, None))
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn query_scroll(
|
|
&self,
|
|
request: &QueryScrollRequestInternal,
|
|
) -> OperationResult<Vec<ScoredPoint>> {
|
|
let QueryScrollRequestInternal {
|
|
limit,
|
|
with_vector,
|
|
filter,
|
|
scroll_order,
|
|
with_payload,
|
|
} = request;
|
|
|
|
let records = match scroll_order {
|
|
ScrollOrder::ById => self.scroll_by_id(
|
|
None,
|
|
*limit,
|
|
with_payload,
|
|
with_vector,
|
|
filter.as_ref(),
|
|
HwMeasurementAcc::disposable_edge(),
|
|
)?,
|
|
ScrollOrder::ByField(order_by) => self.scroll_by_field(
|
|
*limit,
|
|
with_payload,
|
|
with_vector,
|
|
filter.as_ref(),
|
|
order_by,
|
|
HwMeasurementAcc::disposable_edge(),
|
|
)?,
|
|
ScrollOrder::Random => self.scroll_randomly(
|
|
*limit,
|
|
with_payload,
|
|
with_vector,
|
|
filter.as_ref(),
|
|
HwMeasurementAcc::disposable_edge(),
|
|
)?,
|
|
};
|
|
|
|
let point_results = records
|
|
.into_iter()
|
|
.map(|record| ScoredPoint {
|
|
id: record.id,
|
|
version: 0,
|
|
score: 1.0,
|
|
payload: record.payload,
|
|
vector: record.vector,
|
|
shard_key: record.shard_key,
|
|
order_value: record.order_value,
|
|
})
|
|
.collect();
|
|
|
|
Ok(point_results)
|
|
}
|
|
|
|
fn scroll_by_id(
|
|
&self,
|
|
offset: Option<ExtendedPointId>,
|
|
limit: usize,
|
|
with_payload_interface: &WithPayloadInterface,
|
|
with_vector: &WithVector,
|
|
filter: Option<&Filter>,
|
|
hw_measurement_acc: HwMeasurementAcc,
|
|
) -> OperationResult<Vec<RecordInternal>> {
|
|
let (non_appendable, appendable) = self.segments.read().split_segments();
|
|
let hw_counter = hw_measurement_acc.get_counter_cell();
|
|
|
|
let point_ids: Vec<_> = non_appendable
|
|
.into_iter()
|
|
.chain(appendable)
|
|
.map(|segment| {
|
|
segment.get().read().read_filtered(
|
|
offset,
|
|
Some(limit),
|
|
filter,
|
|
&AtomicBool::new(false),
|
|
&hw_counter,
|
|
DeferredBehavior::Exclude,
|
|
)
|
|
})
|
|
.process_results(|iter| iter.flatten().sorted().dedup().take(limit).collect_vec())?;
|
|
|
|
let mut points = retrieve_blocking(
|
|
self.segments.clone(),
|
|
&point_ids,
|
|
&WithPayload::from(with_payload_interface),
|
|
with_vector,
|
|
DEFAULT_EDGE_TIMEOUT,
|
|
&AtomicBool::new(false),
|
|
hw_measurement_acc,
|
|
DeferredBehavior::Exclude,
|
|
)?;
|
|
|
|
let ordered_points = point_ids
|
|
.iter()
|
|
.filter_map(|point_id| points.remove(point_id))
|
|
.collect();
|
|
|
|
Ok(ordered_points)
|
|
}
|
|
|
|
fn scroll_by_field(
|
|
&self,
|
|
limit: usize,
|
|
with_payload_interface: &WithPayloadInterface,
|
|
with_vector: &WithVector,
|
|
filter: Option<&Filter>,
|
|
order_by: &OrderBy,
|
|
hw_measurement_acc: HwMeasurementAcc,
|
|
) -> OperationResult<Vec<RecordInternal>> {
|
|
let (non_appendable, appendable) = self.segments.read().split_segments();
|
|
let hw_counter = hw_measurement_acc.get_counter_cell();
|
|
|
|
let read_results: Vec<_> = non_appendable
|
|
.into_iter()
|
|
.chain(appendable)
|
|
.map(|segment| {
|
|
segment.get().read().read_ordered_filtered(
|
|
Some(limit),
|
|
filter,
|
|
order_by,
|
|
&AtomicBool::new(false),
|
|
&hw_counter,
|
|
DeferredBehavior::Exclude,
|
|
)
|
|
})
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
let (order_values, point_ids): (Vec<_>, Vec<_>) = read_results
|
|
.into_iter()
|
|
.kmerge_by(|a, b| match order_by.direction() {
|
|
Direction::Asc => a <= b,
|
|
Direction::Desc => a >= b,
|
|
})
|
|
.dedup()
|
|
.take(limit)
|
|
.unzip();
|
|
|
|
let points = retrieve_blocking(
|
|
self.segments.clone(),
|
|
&point_ids,
|
|
&WithPayload::from(with_payload_interface),
|
|
with_vector,
|
|
DEFAULT_EDGE_TIMEOUT,
|
|
&AtomicBool::new(false),
|
|
hw_measurement_acc,
|
|
DeferredBehavior::Exclude,
|
|
)?;
|
|
|
|
let ordered_points = point_ids
|
|
.iter()
|
|
.zip(order_values)
|
|
.filter_map(|(point_id, value)| {
|
|
let mut record = points.get(point_id).cloned()?;
|
|
record.order_value = Some(value);
|
|
Some(record)
|
|
})
|
|
.collect();
|
|
|
|
Ok(ordered_points)
|
|
}
|
|
|
|
fn scroll_randomly(
|
|
&self,
|
|
limit: usize,
|
|
with_payload_interface: &WithPayloadInterface,
|
|
with_vector: &WithVector,
|
|
filter: Option<&Filter>,
|
|
hw_measurement_acc: HwMeasurementAcc,
|
|
) -> OperationResult<Vec<RecordInternal>> {
|
|
let (non_appendable, appendable) = self.segments.read().split_segments();
|
|
let hw_counter = hw_measurement_acc.get_counter_cell();
|
|
|
|
let (point_count, mut point_ids): (Vec<_>, Vec<_>) = non_appendable
|
|
.into_iter()
|
|
.chain(appendable)
|
|
.map(|segment| {
|
|
let segment = segment.get();
|
|
let segment = segment.read();
|
|
|
|
let point_count = segment.available_point_count_without_deferred();
|
|
let point_ids = segment.read_random_filtered(
|
|
limit,
|
|
filter,
|
|
&AtomicBool::new(false),
|
|
&hw_counter,
|
|
)?;
|
|
|
|
OperationResult::Ok((point_count, point_ids))
|
|
})
|
|
.process_results(|iter| iter.unzip())?;
|
|
|
|
// Shortcut if all segments are empty
|
|
if point_count.iter().all(|&count| count == 0) {
|
|
return Ok(Vec::new());
|
|
}
|
|
|
|
// Select points in a weighted fashion from each segment, depending on how many points each segment has.
|
|
let distribution = WeightedIndex::new(point_count).map_err(|err| {
|
|
OperationError::service_error(format!(
|
|
"failed to create weighted index for random scroll: {err:?}"
|
|
))
|
|
})?;
|
|
|
|
let mut rng = rand::make_rng::<StdRng>();
|
|
let mut random_point_ids = HashSet::with_capacity(limit);
|
|
|
|
// Randomly sample points in two stages
|
|
//
|
|
// 1. This loop iterates <= LIMIT times, and either breaks early if we
|
|
// have enough points, or if some of the segments are exhausted.
|
|
//
|
|
// 2. If the segments are exhausted, we will fill up the rest of the
|
|
// points from other segments. In total, the complexity is guaranteed to
|
|
// be O(limit).
|
|
while random_point_ids.len() < limit {
|
|
let segment_idx = rng.sample(&distribution);
|
|
let segment_point_ids = &mut point_ids[segment_idx];
|
|
|
|
if let Some(point) = segment_point_ids.pop() {
|
|
random_point_ids.insert(point);
|
|
} else {
|
|
// It seems that some segments are empty early,
|
|
// so distribution does not make sense anymore.
|
|
// This is only possible if segments size < limit.
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we still need more points, we will get them from the rest of the segments.
|
|
// This is a rare case, as it seems we don't have enough points in individual segments.
|
|
// Therefore, we can ignore "proper" distribution, as it won't be accurate anyway.
|
|
if random_point_ids.len() < limit {
|
|
for point_id in point_ids.into_iter().flatten() {
|
|
random_point_ids.insert(point_id);
|
|
if random_point_ids.len() >= limit {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
let random_point_ids: Vec<_> = random_point_ids.into_iter().collect();
|
|
|
|
let random_points = retrieve_blocking(
|
|
self.segments.clone(),
|
|
&random_point_ids,
|
|
&WithPayload::from(with_payload_interface),
|
|
with_vector,
|
|
DEFAULT_EDGE_TIMEOUT,
|
|
&AtomicBool::new(false),
|
|
hw_measurement_acc,
|
|
DeferredBehavior::Exclude,
|
|
)?
|
|
.into_values()
|
|
.collect();
|
|
|
|
Ok(random_points)
|
|
}
|
|
}
|