Skip to content

perf(vector/search): stop materializing vector_ids() on count and warmup paths (#672) - #930

Merged
mosuka merged 1 commit into
mainfrom
perf/672-vector-ids-clone
Aug 1, 2026
Merged

perf(vector/search): stop materializing vector_ids() on count and warmup paths (#672)#930
mosuka merged 1 commit into
mainfrom
perf/672-vector-ids-clone

Conversation

@mosuka

@mosuka mosuka commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Summary

Since #633 the audit-flagged "clone" in vector_ids() is really an O(N) per-call String rehydration at the trait boundary (readers store interned (u64, u16) + a field dictionary). Rather than breaking the public vector_ids() signature, this PR stops every per-query and startup call site from using it:

  • Count paths (hnsw/flat/ivf searchers, no-field branch): .len() of a full materialization → vector_count() (equal by construction: one entry per (doc, field) record).
  • SegmentFanoutSearcher::count: per-segment full materialization → per-field iteration over doc_ids_for_field (an O(1) Arc clone on all three reader types, perf(vector): cache vector_ids per field at reader load #405), with dictionary-backed field_names() supplying the field list for unfiltered counts. Newest-wins masking and deletion filtering unchanged.
  • HNSW warmup (OnDemand page-fault pass): new pub(crate) HnswIndexReader::interned_vector_ids() borrows dictionary names — the code already held the concrete reader, so the 10M-String startup spike disappears with no trait change.
  • Left as-is, documented (per the plan comment): the unfiltered brute-force fallbacks and facade construction, where the materialization immediately precedes an O(N) scan that reads every vector.

Measured

10 segments × 2k docs, 100 count calls, release, best of 3: count(field) 380.7 → 352.6ms, count(None) 378.5 → 350.4ms (-7.4% both). Honest read: at this in-memory scale the containment masking dominates and the allocation was ~7% — the real win is that the removed allocation grows linearly with corpus size (~32 B/record/segment; the audit priced 320 MB per call at 10M records), so a scale-proportional allocation spike is gone rather than a large steady-state percentage.

Tests

Verification

  • cargo fmt --check and cargo clippy --all-targets -- -D warnings clean on stable and 1.97.0
  • cargo test -p laurus --lib: 1258 passed; --tests: all 64 binaries ok
  • wasm32 cargo check -p laurus-wasm --target wasm32-unknown-unknown: clean

Closes #672. Builds on #633 (interning) / #405 (doc_ids_for_field) / #926 (shared fan-out).

@mosuka
mosuka merged commit f0b5146 into main Aug 1, 2026
22 checks passed
@mosuka
mosuka deleted the perf/672-vector-ids-clone branch August 1, 2026 13:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf(vector/search): vector_ids() clones full Vec<(u64, String)> per call

1 participant