feat(search): Phase 1.2-AB — migration 016 + trigram retrieval

migration 016: documents FTS 확장 + trigram 인덱스 (1.5초 빌드)
- idx_documents_fts_full — title+ai_tags+ai_summary+user_note+extracted_text 통합 FTS
- idx_documents_title_trgm — title 단독 trigram
- idx_documents_extracted_text_trgm — 본문 trigram (NULL 제외)
- idx_documents_ai_summary_trgm — AI 요약 trigram
- CONCURRENTLY 불필요 (765 docs / 6.5MB)

retrieval_service.search_text: ILIKE 완전 제거 → trigram % + similarity()
- WHERE: title %, ai_summary %, FTS @@ (모두 인덱스 활용)
- ORDER BY: 5컬럼 similarity 가중 합산 + ts_rank * 2.0
- 가중치 그대로 (title 3.0 / tags 2.5 / note 2.0 / summary 1.5 / extracted 1.0)
- threshold default 0.3 (필요 시 set_limit으로 조정)

목표: text_ms 470ms → 100~200ms (ILIKE 풀스캔 제거 효과)
This commit is contained in:
Hyungi Ahn
2026-04-07 14:36:08 +09:00
parent 0c63c0b6ab
commit 22117a2a6d
2 changed files with 92 additions and 32 deletions

View File

@@ -0,0 +1,47 @@
-- Phase 1.2: documents 테이블 FTS 확장 + trigram 인덱스
--
-- 목적:
-- 1) FTS 인덱스를 title + ai_tags + ai_summary + user_note + extracted_text 통합 범위로 확장
-- 현재 retrieval_service.search_text의 SQL 안 to_tsvector(...)는 인덱스 없이 동작.
-- 2) trigram 인덱스로 ILIKE 풀스캔(text_ms 470ms)을 similarity() + GIN 인덱스로 대체.
--
-- 데이터 규모 (2026-04-07 측정): documents 765 / 평균 본문 8.5KB / 총 6.5MB
-- 인덱스 빌드 시간 추산: 5~30초 (CONCURRENTLY 불필요, 짧은 lock 수용 가능)
--
-- Phase 1.2-A 단독 적용. 1.2-B에서 retrieval_service.search_text의 SQL을
-- ILIKE → similarity() + `%` 연산자로 전환하면서 이 인덱스들을 활용.
-- pg_trgm extension (014에서 이미 활성화, IF NOT EXISTS로 안전)
CREATE EXTENSION IF NOT EXISTS pg_trgm;
-- ─── 1) 통합 FTS 인덱스 ────────────────────────────────────
-- title + ai_tags(JSONB→text) + ai_summary + user_note + extracted_text를 한 번에 토큰화.
-- retrieval_service.search_text의 ts_rank 호출이 이 인덱스를 사용하도록 SQL 갱신 예정.
CREATE INDEX IF NOT EXISTS idx_documents_fts_full ON documents
USING GIN (
to_tsvector('simple',
coalesce(title, '') || ' ' ||
coalesce(ai_tags::text, '') || ' ' ||
coalesce(ai_summary, '') || ' ' ||
coalesce(user_note, '') || ' ' ||
coalesce(extracted_text, '')
)
);
-- ─── 2) title trigram 인덱스 ───────────────────────────────
-- 가장 자주 매칭되는 컬럼. similarity(title, query) > threshold + ORDER BY로 사용.
CREATE INDEX IF NOT EXISTS idx_documents_title_trgm ON documents
USING GIN (title gin_trgm_ops);
-- ─── 3) extracted_text trigram 인덱스 ──────────────────────
-- ILIKE의 dominant cost를 trigram GIN 인덱스로 대체.
-- WHERE 절로 NULL/빈 본문 제외해 인덱스 크기 절감.
CREATE INDEX IF NOT EXISTS idx_documents_extracted_text_trgm ON documents
USING GIN (extracted_text gin_trgm_ops)
WHERE extracted_text IS NOT NULL AND length(extracted_text) > 0;
-- ─── 4) ai_summary trigram 인덱스 ──────────────────────────
-- summary는 짧지만 의미 매칭에 자주 활용 (가중치 1.5).
CREATE INDEX IF NOT EXISTS idx_documents_ai_summary_trgm ON documents
USING GIN (ai_summary gin_trgm_ops)
WHERE ai_summary IS NOT NULL AND length(ai_summary) > 0;