Skip to content

perf(search): stored tsvector column + saner short-term prefixing for ⌘K search - #597

Open
16francej wants to merge 2 commits into
mainfrom
perf/search-stored-tsv
Open

perf(search): stored tsvector column + saner short-term prefixing for ⌘K search#597
16francej wants to merge 2 commits into
mainfrom
perf/search-stored-tsv

Conversation

@16francej

@16francej 16francej commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

What

Speeds up the ⌘K chat search dramatically by fixing where the query actually spends its time.

  1. Stored search_tsv generated column, GIN-indexed. The previous index was an expression index over a plpgsql function that JSON-parses the payload. Prefix tsqueries force a recheck of every lossy bitmap candidate, and each recheck re-ran that parse. On a 1M-row / 878MB benchmark corpus this was ~96% of query time: 102.6s → 4.0s for a representative two-term query, identical result sets. The recheck now reads a stored column instead.
  2. Short terms match whole words instead of prefixes. A 1–2 char prefix (de:*) expands to an enormous GIN key set — 14.7s inside the index scan alone on the same corpus. Terms under 3 chars now match exactly; the in-memory store's mirror (matchesSearchTerms) follows the same rule so both stores agree.
  3. entry_search_text is marked PARALLEL SAFE (it's pure), unblocking parallel plans.

Migration notes

  • The new column is GENERATED ALWAYS … STORED, so the ALTER TABLE rewrites session_entries once at boot. The replacement index builds CONCURRENTLY (same machinery as before), then the old expression index is dropped.
  • searchEntries output text still uses entry_search_text(payload) for the ≤40 returned rows — cheap, and avoids storing the text twice.

Benchmarks (1M entries, worst-case dense vocabulary)

Query Before After
deploy:* & error:* 102.6 s 4.0 s
de:* 92.9 s 16.8 s (now exact-match, far cheaper still)

Tests

  • test/entry-search.test.ts — new cases for short-term exact matching in both tsPrefixQuery and matchesSearchTerms.
  • test/session-search.test.ts and test/postgres-store.test.ts (against a real Postgres) pass, exercising searchEntries through the new column and index.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Josh France added 2 commits August 19, 2026 03:08
The chat-search GIN index was built over an expression that calls a
plpgsql JSON parse per row, so every lossy bitmap recheck re-parsed
entry payloads — benchmarked at ~96% of query time on a 1M-row corpus
(102.6s -> 4.0s for a two-term query). Store the tsvector in a
generated column, index that, and query it directly.

Also stop prefix-expanding 1-2 character terms: a short prefix expands
to a huge GIN key set (14.7s inside the index scan alone for 'de:*').
Short terms now match whole words; the in-memory mirror follows suit.

The old expression index is dropped after the replacement index builds.
Note: adding the stored generated column rewrites session_entries once
at boot.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant