perf(server): prune thread search with a trigram FTS index

BO
2d ago
+121-2

Fork review PR (review only; the upstream submission will use this same diff).

Thread search drops from 184-226 ms to 1.6-82 ms per query on a real install's data, with identical results.

MetricLIKE scanFTS5 trigram + LIKEChange
"error"226 ms40.3 ms82% lower (5.6x)
"screenshot"196 ms8 ms96% lower (24.5x)
"worktree"190 ms81.8 ms57% lower (2.3x)
"useEffect"184 ms1.60 ms99.1% lower (115x)

Workload: VACUUM INTO copy of a real 18.7 GB statev2 database (131,298 searchable messages, 80 MB of message text), node:sqlite (Node 26.12, SQLite 3.54.0), warm cache, median of 5 alternating runs per arm; top-50 result rows hashed identical per query. Lower is better; variation band 5% of baseline, every gap far outside it. The reader-worker move (pingdotgg/t3code#18965) already took this scan off the event loop; this cuts the scan's total work, which still grows with every message ever written.

Problem: searchActiveThreadRows json_extracts and LIKE-tests every non-streaming message, ranks every match with ROW_NUMBER, then LIMITs 50. Cost follows total message bytes, not result count.

Change: migration 069 adds an FTS5 trigram index over message text (trigger-maintained on insert/update/delete, backfilled in the migration: 10 s and +429 MB on the measured database, about 2.3% of its size). ThreadSearch uses it only to prune candidates, and only for letter/digit/space queries whose every term is at least three characters; the LIKE predicate stays as the exact filter. Short, punctuation, and non-ASCII queries keep today's scan, because LIKE folds ASCII case only while the trigram tokenizer folds more broadly and drops punctuation/short terms. A two-character query still costs the full scan (275 ms measured for "pr") — not covered here.

Evidence: existing ThreadSearch suite passes unchanged in its expectations (its NEEDLE queries now exercise the index path against the in-memory migrated database), plus new cases pinning LIKE semantics for mid-word, two-character, punctuation, emoji, and accented queries (including CAFÉ not matching café). FoundationPersistence suite (39 tests) passes on head a313b2a052; server typecheck clean.

Muse Wonder Full 1.0, in Muse Code (via T3 Code)


Summary by cubic

Speeds up thread search by pruning candidates with an FTS5 trigram index instead of scanning and json_extract-ing every message body per query. Results are unchanged: the LIKE predicate remains the exact filter, and the index only narrows candidates. Measured query time drops from 184–226 ms to 1.6–82 ms on a real install's data.

  • Index applies only to queries whose terms are all letter, digit, or space characters with at least three characters per term; short, punctuation, and non-ASCII queries keep the existing full scan.
  • Migration 069 creates the trigger-maintained index and backfills it (~10 s and +429 MB on the measured database, about 2.3% of its size).
  • Adds tests pinning LIKE semantics for mid-word, two-character, punctuation, emoji, and accented queries.
Preview
Preview