Skip to content

perf: eliminate N+1 metadata queries in vector search (searchL1Vector / searchL0Vector)#13

Open
yuanrengu wants to merge 1 commit into
Tencent:mainfrom
yuanrengu:fix/n1-query-batch-metadata-lookup
Open

perf: eliminate N+1 metadata queries in vector search (searchL1Vector / searchL0Vector)#13
yuanrengu wants to merge 1 commit into
Tencent:mainfrom
yuanrengu:fix/n1-query-batch-metadata-lookup

Conversation

@yuanrengu
Copy link
Copy Markdown

Summary

Replace per-row metadata lookups with a single batch query in searchL1Vector and searchL0Vector.

Previously, after vec0 returned KNN results, each record_id was looked up individually via stmtGetMeta.get(record_id) — an N+1 pattern that issues up to topK + buffer (default 30) separate SQLite round-trips per vector search.

Now, all valid record_ids are collected and fetched in one SELECT ... WHERE record_id IN (?, ...) query via new batchGetL1Meta / batchGetL0Meta helpers, then resolved from a Map in O(1).

Changes

  • batchGetL1Meta(recordIds) — new private method, batch-fetches L1 metadata, returns Map<string, meta>
  • batchGetL0Meta(recordIds) — new private method, batch-fetches L0 metadata, returns Map<string, meta>
  • searchL1Vector — refactored to use batch lookup instead of N individual stmtGetMeta.get() calls
  • searchL0Vector — refactored to use batch lookup instead of N individual stmtL0GetMeta.get() calls

Behavior

No functional change. The zero-vector filtering, orphan detection, result ordering, and fault-tolerant behavior are all preserved:

Aspect Before After
Metadata queries per search N (up to 30) 1
Zero-vector filtering continue in loop filter before loop
Orphan handling warn + skip warn + skip (same)
Result ordering vec0 distance order vec0 distance order (same)
Error handling catch → empty array catch → empty Map → empty array (same)

Test plan

  • Build passes (npx tsdown)
  • Verify vector search returns correct results with existing data
  • Verify orphan detection still logs warnings for records with vectors but no metadata
  • Verify degraded mode (sqlite-vec unavailable) still returns empty results gracefully

searchL1Vector and searchL0Vector previously issued one metadata query
per vec0 KNN result (N individual stmtGetMeta.get() calls). With topK=20
and a buffer of 10, this meant up to 30 round-trips per search.

Replace with a single `SELECT ... WHERE record_id IN (?, ...)` query
via new batchGetL1Meta/batchGetL0Meta helpers, then look up results
from a Map. This reduces SQLite round-trips from O(N) to O(1) per
vector search while preserving the existing fault-tolerant behavior.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: yuanrengu <heyonggang0811@126.com>
@yuanrengu yuanrengu force-pushed the fix/n1-query-batch-metadata-lookup branch from b6eb86a to ed126e6 Compare May 15, 2026 15:38
@Maxwell-Code07
Copy link
Copy Markdown
Collaborator

Hi @yuanrengu, thanks for the PR! 👍

The N+1 query optimization in vector search is an interesting area to look at. We'll review the implementation details — including edge cases like empty recordIds and SQLite parameter limits — and get back to you shortly.

Thanks for the contribution! 🙏

@yuanrengu
Copy link
Copy Markdown
Author

@Maxwell-Code07 Thanks for taking a look! Let me know if any changes are needed.

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.

2 participants