Skip to content

perf(fuzzy): match only the previous results when the query gets longer - #914

Open
dmtrKovalenko wants to merge 1 commit into
worktree-bridge-cse_01XRhTouiwMpb58un1nw46qgfrom
perf/fuzzy-search-pool-incremental
Open

dmtrKovalenko wants to merge 1 commit into
worktree-bridge-cse_01XRhTouiwMpb58un1nw46qgfrom
perf/fuzzy-search-pool-incremental

Conversation

@dmtrKovalenko

@dmtrKovalenko dmtrKovalenko commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Fuzzy file search keeps the set of files that the last plain-text query matched. When the next query extends that query and the typo budget (len / 4, clamped to 2..=6) stays the same, the search matches only that set instead of all files. Adding or replacing file rows clears the cache. Each layer tracks its own candidates, and each query reapplies deletion and ranking filters.

Depends on #873. Regression tests compare incremental and cold results after compaction, same-size base replacement and restoration of a deleted file.

On a synthetic 100,000-file index with four search threads, Criterion measured the final keystroke at 17.44 ms with a cold query cache and 11.66 ms after typing the prefix. Prefix preparation is outside the timed interval. Each layer retains one 32-bit row number per matching candidate.


A path that matches a needle within k typos also matches every prefix of that needle within k typos, so the reduced set gives the same results. Scoring, filename fallback, sorting and pagination run unchanged on the matched files.

Chromium, ~500k files, max_threads = 4 main this PR
typing, p50 per keystroke (2 runs) 28.4 / 31.3 ms 19.4 / 19.6 ms
typing, p90 54.0 / 55.8 ms 41.4 / 40.6 ms
typing, p99 99.9 / 81.4 ms 66.4 / 65.8 ms
single query, p50 (no previous query) 35.4 / 36.5 ms 36.3 / 35.2 ms

Typing = each of 60 file names typed one character at a time with a 40 ms gap. Single queries do not change, because they have no previous query to reuse.

Note

The cache applies only to plain-text queries without constraints. Multi-part queries (dir name) and queries with constraints (*.rs, status:modified) use the full search as before.

Correctness

  • New test incremental_typing_matches_full_search: 40k paths, typing with typos, backspace and a typo budget change. Each keystroke must give the same items, order and scores as a full search.
  • Chromium check: 11,163 keystrokes over 450 typing sessions, each compared with a full search on the same picker: 0 mismatches.
  • make test-rust and make lint-rust pass.

Cache invalidation

Event Cache
file appended (watcher) cleared
file restored after delete cleared
full rescan / commit_new_sync / collect_files cleared
get_file_mut (public) cleared
file deleted kept, deleted files are excluded from results
file count or base count changed cache not used

Summary by CodeRabbit

  • New Features
    • Create and manage index layers, add paths with optional metadata, and save or load layer files.
    • Build a layer file independently or load a saved snapshot as the base index.
    • Node.js, Bun, and C interfaces provide layer management and indexing-completion checks.
  • Performance
    • Refined plain-text searches can reuse relevant matches from earlier queries; large searches also use optimized ranking and pagination.
  • Bug Fixes
    • File scans exclude .git metadata, shortened path reads preserve valid UTF-8, and deleted files are omitted from search results.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@coderabbitai

coderabbitai Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1d612367-10f5-4589-bed1-c99779b91f16

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review

No actionable comments were generated in the recent review.

Walkthrough

The fuzzy scorer now reuses cached candidates for eligible incremental text queries. FilePicker stores the cache and clears it when indexed files or layers change.

Changes: Incremental fuzzy search

Layer / File(s) Summary
Candidate reuse and scoring
crates/fff-core/src/score.rs
The scorer stores per-layer candidates for eligible queries and reuses them for query prefixes. Deleted files remain candidates in the cache but are excluded from current scoring. Tests compare cached and uncached results.
FilePicker cache lifecycle
crates/fff-core/src/file_picker.rs, crates/fff-core/src/file_picker/layer_ops.rs
FilePicker passes the cache to scoring and clears it when mutable file access, sync replacement, file additions, or layer operations can change indexed data.
Incremental-search validation and benchmarks
crates/fff-core/tests/*, crates/fff-core/benches/fuzzy_search_bench.rs
Tests cover deleted files, overlay-layer changes, base replacement, and disk rescans. Benchmarks measure cold and warm incremental queries.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Refactor

Sequence Diagram(s)

sequenceDiagram
  participant FilePicker
  participant FuzzyScorer
  participant CandidateCache
  FilePicker->>FuzzyScorer: Submit query and cache
  FuzzyScorer->>CandidateCache: Read eligible prior candidates
  CandidateCache-->>FuzzyScorer: Return candidates by layer
  FuzzyScorer->>CandidateCache: Store current candidates
  FuzzyScorer-->>FilePicker: Return scored results and updated cache
Loading

Possibly related PRs

  • dmtrKovalenko/fff#873: Introduces the layered-index APIs and layer operations that this change uses to maintain per-layer fuzzy-match candidates.

Suggested reviewers: gustav-fff

Merge Risk: Minimal · previous review 92892

No issue requiring a fix before merge was established.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed

Check name Status Explanation
Title check Passed The title clearly and concisely describes the main performance change: reusing previous fuzzy-search results for longer queries.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Full details: Docstring Coverage

Explanation

Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 21 functions across 6 files.

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches

Generate docstrings

  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

Recent review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: fdd8ea36-b5bf-4136-9ac5-8a905aefdbbb
Commits

Reviewing files that changed from the base of the PR and between be2acdd and 928924e.

Files selected for processing: 6

  • crates/fff-core/benches/fuzzy_search_bench.rs
  • crates/fff-core/src/file_picker.rs
  • crates/fff-core/src/file_picker/layer_ops.rs
  • crates/fff-core/src/score.rs
  • crates/fff-core/tests/bigram_overlay_coherence_test.rs
  • crates/fff-core/tests/index_layer_snapshots.rs

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔇 Additional comments (3)
crates/fff-core/src/score.rs (1)

160-203: LGTM!

Also applies to: 685-757, 1199-1264, 1452-1454, 1484-1527, 1529-1574, 2029-2029, 2108-2108, 2350-2350, 2438-2439, 2549-2549, 2600-2600

crates/fff-core/src/file_picker.rs (1)

41-43: LGTM!

Also applies to: 611-612, 725-725, 937-937, 1042-1042, 1152-1161, 1533-1533, 1780-1780, 1816-1816

crates/fff-core/src/score.rs-1528-1528 (1)

1528-1528: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

⚠️ Unverified finding
Verification ran but could not confirm this finding. It is shown for review, not as a verified issue.

The "Spell Check with Typos" check flags "omnibx_veiw".

The typo is on purpose and the test needs it. The check reports veiw, so it will keep firing. Exclude this word in the typos config, or build the string so the checker does not see it (for example, concat!("omnibx_v", "eiw")).

Source: Linters/SAST tools


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ba059de0-006f-4a8d-ab3f-ba1caabbeccd
📥 Commits

Reviewing files that changed from the base of the PR and between f64bab2 and 616787f.

📒 Files selected for processing (2)
  • crates/fff-core/src/file_picker.rs
  • crates/fff-core/src/score.rs

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

@dmtrKovalenko dmtrKovalenko added the code-reviewed Review completed; does not imply approval label Oct 27, 2026
@dmtrKovalenko
dmtrKovalenko changed the base branch from main to worktree-bridge-cse_01XRhTouiwMpb58un1nw46qg October 28, 2026 21:46
@dmtrKovalenko
dmtrKovalenko changed the base branch from worktree-bridge-cse_01XRhTouiwMpb58un1nw46qg to main October 28, 2026 22:03
@dmtrKovalenko
dmtrKovalenko changed the base branch from main to worktree-bridge-cse_01XRhTouiwMpb58un1nw46qg October 28, 2026 22:06
@dmtrKovalenko
dmtrKovalenko changed the base branch from worktree-bridge-cse_01XRhTouiwMpb58un1nw46qg to main October 28, 2026 22:28
@dmtrKovalenko
dmtrKovalenko changed the base branch from main to worktree-bridge-cse_01XRhTouiwMpb58un1nw46qg October 28, 2026 22:34
@dmtrKovalenko

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

github-actions Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

🥼 FFF core performance

🔴 FFF core performance comparison failed

Base 06d5e3f, PR 8c64788

🔴 2 slower, 🟢 6 faster, ⚪ 65 within ±10%

Benchmark Base PR Change
🟢 ext4/fff/fuzzy_search/fuzzy_search_repo/path/no_current_file 518.28 µs 453.15 µs -12.57%
🔴 ext4/vscode/fuzzy_search/fuzzy_search_repo/short/current_file 3.12 ms 3.68 ms +18.00%
🟢 btrfs/fff/fuzzy_search/fuzzy_search_repo/path/no_current_file 515.41 µs 455.16 µs -11.69%
🔴 btrfs/vscode/fuzzy_search/fuzzy_search_repo/short/current_file 3.15 ms 3.61 ms +14.48%
🟢 core/bigram/bigram_build/short_content/10000 9.71 ms 6.56 ms -32.38%
🟢 core/bigram/bigram_query_100000/long_14char 579 ns 328 ns -43.35%
🟢 core/bigram/bigram_query_100000/medium_6char 539 ns 287 ns -46.75%
🟢 core/bigram/bigram_query_100000/multi_word 561 ns 305 ns -45.63%

PR time against base for every benchmark

🔬 All 73 Criterion timings
Benchmark Base PR Change
⚪ ext4/fff/fuzzy_search/fuzzy_search_repo/empty/no_current_file 7.08 µs 7.20 µs +1.81%
⚪ ext4/fff/fuzzy_search/fuzzy_search_repo/extension/no_current_file 9.49 µs 9.30 µs -2.04%
⚪ ext4/fff/fuzzy_search/fuzzy_search_repo/multipart/current_file 36.99 µs 37.16 µs +0.47%
🟢 ext4/fff/fuzzy_search/fuzzy_search_repo/path/no_current_file 518.28 µs 453.15 µs -12.57%
⚪ ext4/fff/fuzzy_search/fuzzy_search_repo/short/current_file 106.03 µs 107.58 µs +1.46%
⚪ ext4/fff/grep/grep_repo/plain_sensitive 11.60 ms 11.03 ms -4.95%
⚪ ext4/fff/grep/grep_repo/plain_insensitive 7.49 ms 7.58 ms +1.12%
⚪ ext4/fff/grep/grep_repo/plain_no_matches 11.05 ms 10.95 ms -0.94%
⚪ ext4/fff/grep/grep_repo/regex 11.21 ms 11.16 ms -0.47%
⚪ ext4/fff/grep/grep_repo/fuzzy_exact 6.19 ms 6.20 ms +0.04%
⚪ ext4/fff/grep/grep_repo/fuzzy_typo 6.21 ms 6.38 ms +2.87%
⚪ ext4/vscode/fuzzy_search/fuzzy_search_repo/empty/no_current_file 412.82 µs 414.76 µs +0.47%
⚪ ext4/vscode/fuzzy_search/fuzzy_search_repo/extension/no_current_file 127.08 µs 132.93 µs +4.60%
⚪ ext4/vscode/fuzzy_search/fuzzy_search_repo/multipart/current_file 6.11 ms 6.26 ms +2.38%
⚪ ext4/vscode/fuzzy_search/fuzzy_search_repo/path/no_current_file 6.02 ms 5.65 ms -6.23%
🔴 ext4/vscode/fuzzy_search/fuzzy_search_repo/short/current_file 3.12 ms 3.68 ms +18.00%
⚪ ext4/vscode/grep/grep_repo/plain_sensitive 8.45 ms 8.48 ms +0.35%
⚪ ext4/vscode/grep/grep_repo/plain_insensitive 8.34 ms 8.51 ms +1.94%
⚪ ext4/vscode/grep/grep_repo/plain_no_matches 105.64 ms 105.24 ms -0.37%
⚪ ext4/vscode/grep/grep_repo/regex 8.76 ms 8.73 ms -0.36%
⚪ ext4/vscode/grep/grep_repo/fuzzy_exact 8.39 ms 8.27 ms -1.44%
⚪ ext4/vscode/grep/grep_repo/fuzzy_typo 13.41 ms 13.50 ms +0.63%
⚪ btrfs/fff/fuzzy_search/fuzzy_search_repo/empty/no_current_file 7.07 µs 7.17 µs +1.40%
⚪ btrfs/fff/fuzzy_search/fuzzy_search_repo/extension/no_current_file 9.42 µs 9.31 µs -1.11%
⚪ btrfs/fff/fuzzy_search/fuzzy_search_repo/multipart/current_file 36.63 µs 36.55 µs -0.22%
🟢 btrfs/fff/fuzzy_search/fuzzy_search_repo/path/no_current_file 515.41 µs 455.16 µs -11.69%
⚪ btrfs/fff/fuzzy_search/fuzzy_search_repo/short/current_file 106.73 µs 108.85 µs +1.99%
⚪ btrfs/fff/grep/grep_repo/plain_sensitive 11.29 ms 11.09 ms -1.78%
⚪ btrfs/fff/grep/grep_repo/plain_insensitive 7.82 ms 7.21 ms -7.80%
⚪ btrfs/fff/grep/grep_repo/plain_no_matches 11.41 ms 10.65 ms -6.64%
⚪ btrfs/fff/grep/grep_repo/regex 12.03 ms 11.08 ms -7.88%
⚪ btrfs/fff/grep/grep_repo/fuzzy_exact 6.47 ms 6.17 ms -4.58%
⚪ btrfs/fff/grep/grep_repo/fuzzy_typo 6.54 ms 6.24 ms -4.65%
⚪ btrfs/vscode/fuzzy_search/fuzzy_search_repo/empty/no_current_file 396.26 µs 389.33 µs -1.75%
⚪ btrfs/vscode/fuzzy_search/fuzzy_search_repo/extension/no_current_file 123.69 µs 127.74 µs +3.27%
⚪ btrfs/vscode/fuzzy_search/fuzzy_search_repo/multipart/current_file 6.10 ms 6.25 ms +2.42%
⚪ btrfs/vscode/fuzzy_search/fuzzy_search_repo/path/no_current_file 6.05 ms 5.63 ms -6.97%
🔴 btrfs/vscode/fuzzy_search/fuzzy_search_repo/short/current_file 3.15 ms 3.61 ms +14.48%
⚪ btrfs/vscode/grep/grep_repo/plain_sensitive 18.79 ms 18.51 ms -1.50%
⚪ btrfs/vscode/grep/grep_repo/plain_insensitive 8.47 ms 8.43 ms -0.52%
⚪ btrfs/vscode/grep/grep_repo/plain_no_matches 110.50 ms 102.88 ms -6.89%
⚪ btrfs/vscode/grep/grep_repo/regex 18.92 ms 19.67 ms +3.99%
⚪ btrfs/vscode/grep/grep_repo/fuzzy_exact 11.93 ms 12.35 ms +3.55%
⚪ btrfs/vscode/grep/grep_repo/fuzzy_typo 11.90 ms 12.27 ms +3.12%
⚪ core/parse/realistic_queries/file 56 ns 57 ns +1.79%
⚪ core/parse/realistic_queries/test 57 ns 57 ns +0.00%
⚪ core/parse/realistic_queries/mod.rs 69 ns 68 ns -1.45%
⚪ core/parse/realistic_queries/src/*.rs 86 ns 86 ns +0.00%
⚪ core/parse/realistic_queries/lib_test 130 ns 130 ns +0.00%
⚪ core/parse/realistic_queries/*.rs_!test 149 ns 154 ns +3.36%
⚪ core/parse/realistic_queries/src/lib/*.rs 99 ns 99 ns +0.00%
⚪ core/parse/realistic_queries//src/_name 131 ns 132 ns +0.76%
⚪ core/parse/realistic_queries/status:modified_*.rs 173 ns 175 ns +1.16%
⚪ core/parse/realistic_queries/type:rust_test_!node_modules 288 ns 292 ns +1.39%
⚪ core/definition/definition/rust/mixed_256 7.37 µs 7.40 µs +0.45%
⚪ core/definition/definition/javascript/mixed_256 14.66 µs 14.22 µs -3.04%
⚪ core/definition/definition/python/mixed_256 5.42 µs 5.42 µs -0.07%
⚪ core/glob/glob_reuse_10k/recompile_each_time 98.02 µs 98.42 µs +0.40%
⚪ core/glob/glob_reuse_10k/reuse_compiled 96.68 µs 97.00 µs +0.34%
⚪ core/glob/glob_mixed_100k/prepass_bitmap/ext_1of10 1.22 ms 1.23 ms +0.67%
⚪ core/glob/glob_mixed_100k/inline_compiled/ext_1of10 296.69 µs 296.88 µs +0.07%
⚪ core/glob/glob_mixed_100k/prepass_bitmap/ext_4of10 1.58 ms 1.59 ms +0.46%
⚪ core/glob/glob_mixed_100k/inline_compiled/ext_4of10 863.12 µs 854.35 µs -1.02%
⚪ core/glob/glob_mixed_100k/prepass_bitmap/ext_8of10 1.89 ms 1.90 ms +0.43%
⚪ core/glob/glob_mixed_100k/inline_compiled/ext_8of10 1.42 ms 1.41 ms -0.49%
⚪ core/glob/glob_mixed_100k/prepass_bitmap/ext_all 1.97 ms 1.97 ms +0.38%
⚪ core/glob/glob_mixed_100k/inline_compiled/ext_all 1.63 ms 1.65 ms +1.18%
🟢 core/bigram/bigram_build/short_content/10000 9.71 ms 6.56 ms -32.38%
🟢 core/bigram/bigram_query_100000/long_14char 579 ns 328 ns -43.35%
🟢 core/bigram/bigram_query_100000/medium_6char 539 ns 287 ns -46.75%
🟢 core/bigram/bigram_query_100000/multi_word 561 ns 305 ns -45.63%
⚪ core/memmem/simd_string_utils_memmem/find/file_picker.rs/53KB/medium/miss 4.04 µs 4.06 µs +0.62%
⚪ core/memmem/simd_string_utils_memmem/find/file_picker.rs/53KB/long/miss 3.66 µs 3.66 µs +0.19%

🔗 Workflow run

@dmtrKovalenko
dmtrKovalenko force-pushed the worktree-bridge-cse_01XRhTouiwMpb58un1nw46qg branch from 9568f10 to 1c06f83 Compare November 11, 2026 20:06
@dmtrKovalenko
dmtrKovalenko force-pushed the perf/fuzzy-search-pool-incremental branch from 8c64788 to d157dd6 Compare November 11, 2026 20:08

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

code-reviewed Review completed; does not imply approval

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant