use std::sync::Arc;
use crate::error::Result;
use crate::maintenance::deletion::DeletionBitmap;
use crate::vector::core::distance::DistanceMetric;
use crate::vector::core::vector::Vector;
use crate::vector::reader::{
ValidationReport, VectorIndexMetadata, VectorIndexReader, VectorIterator, VectorStats,
};
use crate::vector::search::searcher::{
VectorIndexQuery, VectorIndexQueryResults, VectorIndexSearcher,
};
#[derive(Debug)]
pub struct SegmentedReaderFacade {
readers: Vec<Arc<dyn VectorIndexReader>>,
entries: Vec<(u64, String, usize)>,
dimension: usize,
metric: DistanceMetric,
}
impl SegmentedReaderFacade {
pub fn new(
readers: Vec<Arc<dyn VectorIndexReader>>,
bitmap: Option<Arc<DeletionBitmap>>,
dimension: usize,
metric: DistanceMetric,
) -> Self {
let mut seen: std::collections::HashSet<(u64, String)> = std::collections::HashSet::new();
let mut entries = Vec::new();
for (idx, reader) in readers.iter().enumerate() {
if let Ok(ids) = reader.vector_ids() {
for (doc_id, field) in ids {
if let Some(b) = &bitmap
&& b.is_deleted(doc_id)
{
continue;
}
if seen.insert((doc_id, field.clone())) {
entries.push((doc_id, field, idx));
}
}
}
}
Self {
readers,
entries,
dimension,
metric,
}
}
fn owner_of(&self, doc_id: u64, field_name: &str) -> Option<&Arc<dyn VectorIndexReader>> {
self.entries
.iter()
.find(|(d, f, _)| *d == doc_id && f == field_name)
.map(|(_, _, idx)| &self.readers[*idx])
}
}
impl VectorIndexReader for SegmentedReaderFacade {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn get_vector(&self, doc_id: u64, field_name: &str) -> Result<Option<Vector>> {
match self.owner_of(doc_id, field_name) {
Some(reader) => reader.get_vector(doc_id, field_name),
None => Ok(None),
}
}
fn get_vectors_for_doc(&self, doc_id: u64) -> Result<Vec<(String, Vector)>> {
let mut out = Vec::new();
for (d, field, idx) in &self.entries {
if *d == doc_id
&& let Some(v) = self.readers[*idx].get_vector(doc_id, field)?
{
out.push((field.clone(), v));
}
}
Ok(out)
}
fn get_vectors(&self, doc_ids: &[(u64, String)]) -> Result<Vec<Option<Vector>>> {
doc_ids
.iter()
.map(|(d, f)| self.get_vector(*d, f))
.collect()
}
fn vector_ids(&self) -> Result<Vec<(u64, String)>> {
Ok(self
.entries
.iter()
.map(|(d, f, _)| (*d, f.clone()))
.collect())
}
fn vector_count(&self) -> usize {
self.entries.len()
}
fn dimension(&self) -> usize {
self.dimension
}
fn distance_metric(&self) -> DistanceMetric {
self.metric
}
fn stats(&self) -> VectorStats {
VectorStats {
vector_count: self.entries.len(),
dimension: self.dimension,
memory_usage: 0,
build_time_ms: 0,
}
}
fn contains_vector(&self, doc_id: u64, field_name: &str) -> bool {
self.entries
.iter()
.any(|(d, f, _)| *d == doc_id && f == field_name)
}
fn get_vector_range(
&self,
start_doc_id: u64,
end_doc_id: u64,
) -> Result<Vec<(u64, String, Vector)>> {
let mut out = Vec::new();
for (doc_id, field, idx) in &self.entries {
if *doc_id >= start_doc_id
&& *doc_id < end_doc_id
&& let Some(v) = self.readers[*idx].get_vector(*doc_id, field)?
{
out.push((*doc_id, field.clone(), v));
}
}
Ok(out)
}
fn get_vectors_by_field(&self, field_name: &str) -> Result<Vec<(u64, Vector)>> {
let mut out = Vec::new();
for (doc_id, field, idx) in &self.entries {
if field == field_name
&& let Some(v) = self.readers[*idx].get_vector(*doc_id, field)?
{
out.push((*doc_id, v));
}
}
Ok(out)
}
fn field_names(&self) -> Result<Vec<String>> {
let mut names: Vec<String> = Vec::new();
for (_, field, _) in &self.entries {
if !names.iter().any(|n| n == field) {
names.push(field.clone());
}
}
Ok(names)
}
fn vector_iterator(&self) -> Result<Box<dyn VectorIterator>> {
let mut items = Vec::with_capacity(self.entries.len());
for (doc_id, field, idx) in &self.entries {
if let Some(v) = self.readers[*idx].get_vector(*doc_id, field)? {
items.push((*doc_id, field.clone(), v));
}
}
Ok(Box::new(FacadeIterator { items, pos: 0 }))
}
fn metadata(&self) -> Result<VectorIndexMetadata> {
Ok(VectorIndexMetadata {
index_type: "segmented".to_string(),
created_at: chrono::Utc::now(),
modified_at: chrono::Utc::now(),
version: "1".to_string(),
build_config: serde_json::Value::Null,
custom_metadata: std::collections::HashMap::new(),
})
}
fn validate(&self) -> Result<ValidationReport> {
let mut errors = Vec::new();
for reader in &self.readers {
let report = reader.validate()?;
errors.extend(report.errors);
}
Ok(ValidationReport {
is_valid: errors.is_empty(),
errors,
warnings: Vec::new(),
repair_suggestions: Vec::new(),
})
}
}
#[derive(Debug)]
struct FacadeIterator {
items: Vec<(u64, String, Vector)>,
pos: usize,
}
impl VectorIterator for FacadeIterator {
fn next(&mut self) -> Result<Option<(u64, String, Vector)>> {
if self.pos >= self.items.len() {
return Ok(None);
}
let item = self.items[self.pos].clone();
self.pos += 1;
Ok(Some(item))
}
fn skip_to(&mut self, doc_id: u64, field_name: &str) -> Result<bool> {
while self.pos < self.items.len() {
let (d, f, _) = &self.items[self.pos];
if *d == doc_id && f == field_name {
return Ok(true);
}
self.pos += 1;
}
Ok(false)
}
fn reset(&mut self) -> Result<()> {
self.pos = 0;
Ok(())
}
fn position(&self) -> (u64, String) {
if self.pos < self.items.len() {
let (d, f, _) = &self.items[self.pos];
(*d, f.clone())
} else {
(u64::MAX, String::new())
}
}
}
type MakeSearcher =
Box<dyn Fn(Arc<dyn VectorIndexReader>) -> Result<Box<dyn VectorIndexSearcher>> + Send + Sync>;
pub struct SegmentFanoutSearcher {
readers: Vec<Arc<dyn VectorIndexReader>>,
bitmap: Option<Arc<DeletionBitmap>>,
make_searcher: MakeSearcher,
}
impl std::fmt::Debug for SegmentFanoutSearcher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SegmentFanoutSearcher")
.field("segments", &self.readers.len())
.field("has_bitmap", &self.bitmap.is_some())
.finish_non_exhaustive()
}
}
impl SegmentFanoutSearcher {
pub fn new(
readers: Vec<Arc<dyn VectorIndexReader>>,
bitmap: Option<Arc<DeletionBitmap>>,
make_searcher: impl Fn(Arc<dyn VectorIndexReader>) -> Result<Box<dyn VectorIndexSearcher>>
+ Send
+ Sync
+ 'static,
) -> Self {
Self {
readers,
bitmap,
make_searcher: Box::new(make_searcher),
}
}
fn shadowed(&self, idx: usize, doc_id: u64, field: &str) -> bool {
self.readers[..idx]
.iter()
.any(|r| r.contains_vector(doc_id, field))
}
fn probe_segment(
&self,
idx: usize,
request: &VectorIndexQuery,
limit: usize,
metric: DistanceMetric,
prepared_query: &crate::vector::core::distance::PreparedQuery<'_>,
) -> Result<(
Vec<crate::vector::search::searcher::VectorIndexQueryResult>,
usize,
)> {
let searcher = (self.make_searcher)(self.readers[idx].clone())?;
let mut probe = request.clone();
probe.params.top_k = limit.saturating_mul(2);
let mut candidates_examined = 0usize;
let mut exact_basis = false;
let mut kept = loop {
let results = searcher.search(&probe)?;
candidates_examined += results.candidates_examined;
exact_basis |= results
.query_metadata
.get(crate::vector::search::searcher::SCORE_BASIS_METADATA_KEY)
.is_some_and(|v| v == crate::vector::search::searcher::SCORE_BASIS_F32_RERANK);
let returned = results.results.len();
let kept: Vec<_> = results
.results
.into_iter()
.filter(|hit| !self.shadowed(idx, hit.doc_id, &hit.field_name))
.collect();
if kept.len() >= limit || returned < probe.params.top_k {
break kept;
}
let next = probe.params.top_k.saturating_mul(2);
if next == probe.params.top_k {
break kept; }
probe.params.top_k = next;
};
if !exact_basis {
self.rescore_on_shared_basis(&mut kept, idx, metric, prepared_query)?;
kept.retain(|hit| hit.similarity >= request.params.min_similarity);
}
Ok((kept, candidates_examined))
}
fn rescore_on_shared_basis(
&self,
hits: &mut [crate::vector::search::searcher::VectorIndexQueryResult],
idx: usize,
metric: DistanceMetric,
prepared_query: &crate::vector::core::distance::PreparedQuery,
) -> Result<()> {
for hit in hits.iter_mut() {
let distance = if let Some(v) = &hit.vector {
metric.distance_with_prepared(prepared_query, &v.data)?
} else if let Some(v) = self.readers[idx].get_vector(hit.doc_id, &hit.field_name)? {
metric.distance_with_prepared(prepared_query, &v.data)?
} else {
continue;
};
hit.distance = distance;
hit.similarity = metric.distance_to_similarity(distance);
}
Ok(())
}
}
impl VectorIndexSearcher for SegmentFanoutSearcher {
fn search(&self, request: &VectorIndexQuery) -> Result<VectorIndexQueryResults> {
let started = crate::util::time::Timer::now();
let limit = request.params.top_k;
let mut merged = VectorIndexQueryResults::new();
if limit == 0 || self.readers.is_empty() {
return Ok(merged);
}
let metric = self.readers[0].distance_metric();
let prepared_query = metric.prepare_query(&request.query.data);
let probe_all_serial = || -> Result<Vec<_>> {
(0..self.readers.len())
.map(|idx| self.probe_segment(idx, request, limit, metric, &prepared_query))
.collect()
};
#[cfg(not(target_arch = "wasm32"))]
let per_segment = if self.readers.len() > 1 {
use rayon::prelude::*;
(0..self.readers.len())
.into_par_iter()
.map(|idx| self.probe_segment(idx, request, limit, metric, &prepared_query))
.collect::<Result<Vec<_>>>()?
} else {
probe_all_serial()?
};
#[cfg(target_arch = "wasm32")]
let per_segment = probe_all_serial()?;
for (mut kept, candidates_examined) in per_segment {
merged.candidates_examined += candidates_examined;
merged.results.append(&mut kept);
}
merged.results.sort_unstable_by(|a, b| {
a.distance
.total_cmp(&b.distance)
.then(a.doc_id.cmp(&b.doc_id))
});
merged.results.truncate(limit);
merged.search_time_ms = started.elapsed_ms() as f64;
Ok(merged)
}
fn count(&self, request: VectorIndexQuery) -> Result<u64> {
let mut count = 0u64;
for (idx, reader) in self.readers.iter().enumerate() {
let fields: Vec<String> = match request.field_name {
Some(ref field_name) => vec![field_name.clone()],
None => reader.field_names()?,
};
for field in &fields {
for &doc_id in reader.doc_ids_for_field(field).iter() {
if let Some(bitmap) = &self.bitmap
&& bitmap.is_deleted(doc_id)
{
continue;
}
if !self.shadowed(idx, doc_id, field) {
count += 1;
}
}
}
}
Ok(count)
}
}