feat(keyword): add local FTS5 keyword retrieval - #3967
Conversation
80c22b1 to
05ae946
Compare
|
Hi @ByteDanceLiuYang @zhoujh01 @qin-ctx, would appreciate a review of this PR. This PR implements "search-time BM25 over a local SQLite FTS5 sidecar" as
Design doc: Three things I'd especially like feedback on:
Thanks in advance! |
Add an opt-in per-account SQLite FTS5 keyword sidecar that provides search-time BM25 recall for deployments without a remote VikingDB full-text index. - grep gains local/vikingdb engine modes and falls back from remote BM25 to the local sidecar to filesystem scan - KeywordQueue/KeywordProcessor keep the sidecar in sync alongside the embedding pipeline, including rm/mv/restore paths - find/search gain an opt-in hybrid mode fusing dense results with keyword candidates via RRF or weighted blend - /api/v1/observer/keyword exposes sidecar health
05ae946 to
aee124c
Compare
|
I ran local performance benchmarks to get a baseline. Environment: Python 3.14 / SQLite 3.53.2 with FTS5 / single-threaded / Indexing throughput
The ~3x space overhead is expected for an inverted index with tokenized BM25 lookup latency
Sub-millisecond across the board — lookup is dominated by SQLite Grep: FTS5 recall vs brute-force scanThis is the main value proposition — how much does the sidecar narrow
The FTS5 path stays flat (~0.04ms) while brute-force scales linearly. CJK tokenizer overhead
Tokenization is a meaningful but not dominant cost during indexing. Notes / caveats
Happy to take suggestions on what additional benchmarks would be useful. |
Description
Adds an opt-in per-account SQLite FTS5 keyword sidecar that provides backend-owned, search-time BM25 recall for deployments without a remote VikingDB full-text index.
The local keyword sidecar is a recall accelerator only:
grepstill performs its final regex match against on-disk content, andfind/searchfuse keyword candidates with dense results. When the sidecar is missing or incomplete, both paths degrade to their existing behavior.This follows the direction maintainers indicated in #1857: search-time BM25 over a local SQLite FTS5 sidecar instead of precomputed sparse vectors.
Human Involvement
Related Issue
local_bm25sparse-provider attempt, closed without merge)Type of Change
Changes Made
openviking/storage/keywordfs/: per-accountKeywordFSSQLite FTS5 store with search-timebm25()recall, CJK-aware tokenizer (char/bigram/optionaljieba),KeywordMsg,KeywordQueue, andKeywordProcessorautoprefers remote VikingDB BM25 when available, falls back to the local sidecar, then to a filesystem scan; add explicitlocalandvikingdbengine modes_grep_local_then_fs: local keyword recall +_grep_in_filesexact matchingKeywordQueuesetup toQueueManagerand co-enqueue keyword upserts alongside embedding messages so the sidecar stays in sync with vector coveragerm/mvand prefix cleanup into snapshot restorefind/searchviaHybridKeywordRecallerwith RRF and weighted fusion, exposed throughretrieval.hybrid.enabledand a request-levelhybridboolean/api/v1/observer/keywordfor sidecar health and documentkeyword/retrieval.hybridconfigurationdocs/design/local-keyword-fts5-sidecar-design.mdTesting
Added tests cover
KeywordFSupsert/delete/move/prefix-delete/rebuild,KeywordProcessor,KeywordMsg.from_embedding, local grep engine resolution and recall, hybrid RRF/weighted fusion, and the keyword observer component.71 passedacross the new and adjacent test suites.Checklist
Additional Notes
The keyword sidecar is disabled by default (
keyword.enabled=false), and it is automatically disabled on encrypted deployments whenrespect_encryption=truebecause it stores plaintext. Fullov reindex --mode keywordrebuild wiring is left as a follow-up.