lance-format / lance-format/lance
feat(ngram): add stop-trigram filtering for high-frequency tokens
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 7.1k
- Forks
- 852
- Avg merge
- 3d 18h
- Merged PRs (30d)
- 272
Description
Summary
N-gram (trigram) indexes in Lance currently index every alphanumeric trigram produced by the tokenizer. Many trigrams—such as those derived from common English stop words (the, and) or frequent suffix fragments (ing, ion)—appear in a large fraction of documents. Their posting lists are huge but provide little selectivity during query-time intersection, increasing index size, build cost, and read I/O without materially improving pruning.
This issue proposes adding stop-trigram filtering to skip indexing (and querying) ultra-high-frequency trigrams.
Motivation
Current N-gram index behavior (rust/lance-index/src/scalar/ngram.rs):
- Fixed trigrams (N=3) after lowercasing, ASCII folding, and alphanumeric-only filtering
- Tokens encoded as
u32; posting lists stored as RoaringTreemap - Query path: slice query into trigrams → load posting lists → intersect → recheck (AtMost)
High-frequency trigrams hurt performance because:
- Index bloat — posting lists for common trigrams dominate storage
- Low pruning value — intersecting a near-universal posting list barely narrows candidates
- Extra I/O — query path loads large posting lists that contribute little to intersection
Similar systems (e.g. PostgreSQL pg_trgm, search engines with n-gram analyzers) commonly omit or down-weight extremely common grams.
Proposed behavior
Build time
When tokenizing documents during index construction, do not insert trigrams that are classified as stop-trigrams into tokens_map / spill files.
Query time
When slicing a query string into trigrams:
- Skip stop-trigrams when collecting posting lists for intersection
- Do not treat a missing stop-trigram as "no results" (today, any missing token sets
missing = trueand returns an empty exact result) - If all query trigrams are stop-trigrams (e.g.
contains(text, 'the')), fall back to full recheck (AtLeast), same as queries shorter than N=3 today
Correctness
The N-gram index is inexact (AtMost) and always requires recheck. Skipping stop-trigrams at query time remains correct:
| Query | Behavior |
|---|---|
contains('ramen') |
No stop-trigrams → normal intersection + recheck |
contains('the') |
Only stop-trigram → full recheck |
contains('theory') |
Skip the; intersect remaining trigrams + recheck |
Design options (for discussion)
| Approach | Description | Trade-offs |
|---|---|---|
| A. Static stop-trigram set | Built-in list of ~30–50 common trigrams (the, and, ing, ion, …), encoded once via existing ngram_to_token |
Simple, predictable; does not adapt to corpus |
| B. Document-frequency threshold | Drop trigrams with DF > X% during build | Adaptive; requires extra statistics pass and tuning |
| C. Hybrid (recommended) | Static set on by default; optional max_document_frequency later |
Best of both; slightly more API surface |
Suggested first step: implement A (static set), with room to extend to C.
Candidate static stop-trigrams (initial draft)
- Whole 3-letter stop words:
the,and,for,are,but,not,you,all,can,her,was,one,our,out, … - Low-selectivity suffix fragments:
ing,ion,ati,tio,ent,ere,ter,ate,men,est, …
Exact list should be validated against AlphaNumOnlyFilter + ngram_to_token encoding.
Implementation touchpoints
rust/lance-index/src/scalar/ngram.rsNGramIndexBuilder::process_batch/tokenize_and_partition— skip stop tokens at buildNGramIndex::search— skip stop tokens at query; fixmissinglogic for stop vs. selective trigrams
- Optional: new module
ngram_stop_trigrams.rswithLazyLock<HashSet<u32>> - Future:
NGramIndexDetailsproto / builder options for custom stop lists or DF threshold - Tests: build size reduction, query correctness for stop-only / mixed / normal queries, regex/LIKE paths that derive trigram conditions
Non-goals (for initial PR)
- Changing N=3 or tokenizer pipeline
- Reusing FTS
StopWordFilterdirectly (stemming/stop-word tokenization is incompatible with n-gram substring semantics; see comments onNGramIndex::tokenizer)
References
- N-gram index format:
docs/src/format/index/scalar/ngram.md
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with rust/lance-index/src/scalar/ngram.rs, especially NGramIndexBuilder::process_batch, tokenize_and_partition, and NGramIndex::search, then read docs/src/format/index/scalar/ngram.md. Define and validate the static stop-trigram set against the existing tokenizer and encoding, covering build and query behavior. Done means stop-trigrams are skipped without false empty results, stop-only queries recheck fully, and tests cover normal, mixed, stop-only, and regex/LIKE paths.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- performance, search
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100