Filtered Search & Prefiltering
Real-world applications frequently combine full-text BM25 ranking with structured relational filters (such as category_id = 5, tenant_id = 'acme', or date ranges). vchord_bm25 supports three strategies for filtered BM25 search.
1. Post-Filtering vs. Pre-Filtering
When you execute a query with a WHERE clause and ORDER BY embedding <&> to_bm25query(...) LIMIT K:
There are two ways vchord_bm25 can evaluate the WHERE predicate:
Post-Filtering (enable_prefilter = false)
- The
bm25index runs Block-WeakAnd over the entire corpus and returns the global Top-bm25_limitcandidates (e.g., Top 100). - PostgreSQL fetches those 100 heap tuples and applies
WHERE category = 'database'. - Caveat: If only 3 of the global Top-100 documents have
category = 'database', the query returns only 3 rows even if hundreds of other'database'documents exist with slightly lower BM25 scores!
Pre-Filtering (enable_prefilter = true)
When pre-filtering is enabled, vchord_bm25 checks heap visibility and evaluates filter acceptability before pushing candidate documents into the Block-WeakAnd Top-K heap:
- Non-matching documents never enter the Top-K heap and never raise the Block-WeakAnd pruning threshold .
- The index scan continues until it finds documents that both match the
WHEREfilter and have the highest BM25 scores among all matching rows.
Enabling Pre-Filtering
In v0.2.2 – v0.3.0:
In main branch:
2. Partial BM25 Indexes for High-Selectivity Partition Keys
If you frequently filter by a small set of known partition keys (for example, status = 'published' or specific tenant_id values), you can create partial bm25 indexes:
When querying a partial index, reference the partial index’s name in to_bm25query and include the matching WHERE condition:
Choosing the Right Filtering Strategy
| Filter Selectivity | Recommended Strategy | Why |
|---|---|---|
| High selectivity (50%–100% rows match) | Post-filtering (enable_prefilter = false) with slightly higher bm25_limit |
Avoids heap fetches during the inner WAND loop; global Top-100 easily contains 10+ matching rows. |
| Medium selectivity (5%–50% rows match) | Pre-filtering (enable_prefilter = true / bm25.prefilter = true) |
Guarantees exact Top-K recall over filtered rows without scanning irrelevant postings. |
| Fixed tenant / status subset | Partial bm25 index (CREATE INDEX ... WHERE ...) or table partitioning |
Smaller index size, dedicated corpus IDF statistics, and zero wasted checks on other partitions. |