Search: answers at 100k notes without scoring every match #249

Closed
kayg wants to merge 1 commit from issue/44-search-speed into dev
Owner

Part of #44. Independent of #227 and the trim PR.

Search at 100k notes was p95 ~340-460 ms on the perf VM (budget 100). Profiling (perf + phase timings) found three costs outside the index itself:

  • Typo vocabulary rebuilt on the query path whenever the index had changed; building it reads the whole index (1.6 s at 100k). Now asked for only when exact matches are few; a big one is rebuilt on its own thread while searches use the one they have; a small one (quick to build, or < 5,000 items) is still rebuilt in place, so small Homes and the tests behave as before.
  • Ranking with many matches: the 4 ms budget couldn't stop it, because FTS5 merges a common word's whole posting list in one step that never reaches the progress handler (0.3-0.8 s). With many matches, titles/recency/frecency already cover the candidates, so ranking is skipped.
  • Frecency: MATCH ... AND rowid IN (200 ids) re-runs the whole match per listed row (0.6-1.6 s for 30 rows). Recently used items are now checked on their own text, stopping as soon as the query is satisfied (unit test against the index's matching rules).

Also: the search tests' settle helper waits 250 ms (was 30) for the indexer to see an emitted change; the_index_follows_changes_live flaked on loaded CI hosts. Perf-VM numbers will be posted on #44.

🤖 Generated with Claude Code

Part of #44. Independent of #227 and the trim PR. Search at 100k notes was p95 ~340-460 ms on the perf VM (budget 100). Profiling (perf + phase timings) found three costs outside the index itself: - **Typo vocabulary rebuilt on the query path** whenever the index had changed; building it reads the whole index (1.6 s at 100k). Now asked for only when exact matches are few; a big one is rebuilt on its own thread while searches use the one they have; a small one (quick to build, or < 5,000 items) is still rebuilt in place, so small Homes and the tests behave as before. - **Ranking with many matches**: the 4 ms budget couldn't stop it, because FTS5 merges a common word's whole posting list in one step that never reaches the progress handler (0.3-0.8 s). With many matches, titles/recency/frecency already cover the candidates, so ranking is skipped. - **Frecency**: `MATCH ... AND rowid IN (200 ids)` re-runs the whole match per listed row (0.6-1.6 s for 30 rows). Recently used items are now checked on their own text, stopping as soon as the query is satisfied (unit test against the index's matching rules). Also: the search tests' settle helper waits 250 ms (was 30) for the indexer to see an emitted change; `the_index_follows_changes_live` flaked on loaded CI hosts. Perf-VM numbers will be posted on #44. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Search: answers at 100k notes without scoring every match
Some checks failed
CI / web (pull_request) Failing after 20s
CI / rust (pull_request) Successful in 1m49s
58c0f86596
Profiling search on the 100,000-note fixture (perf VM p95 ~460 ms)
found three costs, none of them the index itself:

- Typo vocabulary: it was rebuilt on the query path whenever the index
  had changed, and building it reads the whole index (1.6 s at 100k).
  It is now asked for only when exact matches are few, and a big one is
  rebuilt on a thread of its own while searches use the one they have; a
  small one (quick to build, or under 5,000 items) is still rebuilt in
  place.
- Ranking with many matches: the 4 ms budget couldn't stop it, because
  FTS5 merges a common word's whole posting list in one step that never
  reaches the progress handler: 0.3 to 0.8 s. With many matches, titles,
  recency and frecency already cover the candidates, so it is skipped.
- Frecency: `MATCH ... AND rowid IN (200 ids)` runs the whole match
  again for every listed row (0.6 to 1.6 s for 30 rows). The recently
  used items are now checked on their text, stopping at the first words
  that satisfy the query.

The search tests' settle helper waits 250 ms (was 30) for the indexer to
pick up an emitted change; on a loaded CI host 30 ms was sometimes too
short (`the_index_follows_changes_live`).

Part of #44

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
Author
Owner

Superseded by the rebased search PR (two more fixes).

Superseded by the rebased search PR (two more fixes).
kayg closed this pull request 2026-10-14 16:27:16 +00:00
Author
Owner

-> #315

-> #315
Some checks failed
CI / web (pull_request) Failing after 20s
CI / rust (pull_request) Successful in 1m49s

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal!249
No description provided.