Skip to content

docs(search): hybrid-retrieval reshape plan (search won't scale as-is — proven)#922

Open
seonghobae wants to merge 1 commit into
developfrom
docs/search-scalability-hybrid-retrieval
Open

docs(search): hybrid-retrieval reshape plan (search won't scale as-is — proven)#922
seonghobae wants to merge 1 commit into
developfrom
docs/search-scalability-hybrid-retrieval

Conversation

@seonghobae

Copy link
Copy Markdown
Contributor

Summary

/api/search does a full sequential scan on every query and cannot use any index — the single largest scaling cliff in the product. The root cause is query shape, not just missing indexes: _search_score (backend/api/search.py) has no @@ text-match predicate and fuses FTS + vector into one ORDER BY, which independently defeats both GIN and ANN indexing.

Evidence (pgvector 0.8.2, EXPLAIN (COSTS OFF), spun up a throwaway PG to verify)

Query shape Plan
Current (ts_rank_cd - cosine_distance ORDER BY, no @@) Seq Scan
Same query after adding GIN + HNSW indexes Seq Scan (indexes ignored)
Reshaped: WHERE ... @@ plainto_tsquery Bitmap Index Scan on GIN
Reshaped: pure ORDER BY embedding <=> q LIMIT Index Scan using HNSW

So a naive "add indexes" migration would create indexes the planner never uses. The reshape and the indexes must land together.

What this PR is

A docs-only plan (matches docs/plans/ convention) for the correct fix: a two-arm hybrid retrieval (lexical arm gated by @@+GIN, vector arm via pure ANN+HNSW, fused with RRF/normalized score) plus the accompanying dialect-guarded, pgvector-version-aware, CONCURRENTLY index migration 0010. Because it changes ranking and result membership on a core path, it should land test-first (Postgres-backed EXPLAIN assertions), not autonomously.

Why now

This is a P0 trust-at-scale blocker: an enterprise mailbox import turns search into a full-table scan per keystroke. The plan de-risks it and corrects the intuitive-but-wrong "just add indexes" approach with proof.

🤖 Generated with Claude Code

/api/search seq-scans every query and cannot use FTS/vector indexes because
of query shape (no @@ predicate; FTS+vector fused into one ORDER BY), not just
missing indexes. Verified with pgvector 0.8.2 EXPLAIN: the current shape
seq-scans even WITH GIN+HNSW present; a @@-gated query uses the GIN index and
a pure ANN ORDER BY uses HNSW. Documents the correct hybrid-retrieval reshape
(lexical arm + vector arm + RRF fuse) plus the accompanying migration, to be
landed test-first through RED/GREEN.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RkKdtHRLG4wSLh6PVsp8J
@opencode-agent

opencode-agent Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

OpenCode Review Overview

  • Head SHA: 81146e9971ded522793aa5cb4e2e32980d6004ba
  • Workflow run: 28743405342
  • Workflow attempt: 1
  • Gate result: APPROVE (exit 0)

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Docs: 2026-07-05-search-scalability-hybrid-retrieval.md"]
  S1 --> I1["operator or user guidance"]
  I1 --> R1["Review risk: Docs: 2026-07-05-search-scalability-hybrid-retrieval.md"]
  R1 --> V1["docs review"]
Loading

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