포스트그레SQL의 내장 전문검색은 tsvector로 문서를 토큰화하고 GIN 인덱스로 찾은 뒤 ts_rank로 정렬한다. 소규모 검색에는 충분하고, 별도 인프라가 필요 없다는 큰 장점이 있다.
한계는 랭킹에서 먼저 드러난다. ts_rank는 문서 길이 정규화와 용어 희소성 가중을 현대적인 방식으로 다루지 않는다. 검색 결과가 "찾아지긴 하는데 순서가 이상한" 상태가 되는 이유가 대개 여기다.
사내 문서 검색을 내장 전문검색으로 만들었다가 한 달 만에 불만이 쏟아진 적이 있다. 원하는 문서가 결과에 있긴 한데 8번째쯤 나왔다. 사용자는 3번째까지만 본다. 그때 배운 게 있다. 검색에서 재현율(찾아내기)은 기본이고 정밀도(순서)가 품질이다.
내장 전문검색이 잘하는 일
-- 기본 구성
ALTER TABLE docs ADD COLUMN tsv tsvector
GENERATED ALWAYS AS (
setweight(to_tsvector('simple', coalesce(title,'')), 'A') ||
setweight(to_tsvector('simple', coalesce(body,'')), 'B')
) STORED;
CREATE INDEX idx_docs_tsv ON docs USING GIN (tsv);
-- 검색
SELECT id, title, ts_rank(tsv, q) AS rank
FROM docs, websearch_to_tsquery('simple', '포스트그레 인덱스') q
WHERE tsv @@ q
ORDER BY rank DESC
LIMIT 20;
- 별도 인프라 없이 DB 안에서 해결된다 — 동기화 문제가 없다.
- 트랜잭션 일관성 — 방금 쓴 문서가 즉시 검색된다.
- 필드별 가중치(setweight)로 제목 우선 같은 기본 요구는 처리된다.
- 다른 컬럼 조건과 자연스럽게 결합된다 (권한 필터, 날짜 범위 등).
어디서 부족해지나
| 요구 | 내장 전문검색 | 필요한 것 |
|---|---|---|
| 좋은 랭킹 순서 | ts_rank는 단순 — 용어 희소성·길이 정규화 약함 | BM25 계열 스코어링 |
| 오타 보정 | 기본 미지원 | pg_trgm 유사도 또는 별도 엔진 |
| 자동완성(접두 검색) | 부분 지원(:* 연산자) | 접두 인덱스 또는 전용 구조 |
| 한국어 형태소 분석 | 기본 설정으로는 부족 | 형태소 분석기 연동 또는 n-gram |
| 동의어·불용어 관리 | 사전 파일로 가능하나 운영 번거로움 | 운영 UI가 있는 엔진 |
| 하이라이팅 | ts_headline 제공(성능 주의) | 충분한 편 |
| 패싯 집계 | SQL로 직접 작성 | 엔진의 집계 기능 |
BM25가 무엇을 다르게 하나
BM25는 세 가지를 함께 고려한다. 용어 빈도(문서에 몇 번 나오나, 단 포화 곡선 적용), 역문서빈도(전체에서 얼마나 희귀한 단어인가), 문서 길이 정규화(긴 문서가 단지 길다는 이유로 유리해지지 않게).
실무에서 체감되는 차이는 이렇다. "인덱스"처럼 흔한 단어 열 번 나오는 긴 문서보다, "BRIN"처럼 희귀한 단어가 제목에 있는 짧은 문서가 위로 온다. 사용자가 기대하는 순서에 가깝다.
-- BM25 확장을 쓰는 경우의 개념적 형태 (확장마다 문법은 다르다)
-- CREATE INDEX idx_docs_bm25 ON docs USING bm25 (id, title, body);
--
-- SELECT id, title, score
-- FROM docs
-- WHERE docs @@@ 'postgres AND 인덱스'
-- ORDER BY score DESC
-- LIMIT 20;
-- 확장 도입 전 확인할 것:
-- 1) 관리형 DB(RDS/Cloud SQL 등)에서 해당 확장을 설치할 수 있는가
-- 2) 업그레이드 시 확장 호환성은 누가 책임지는가
-- 3) 인덱스 크기와 갱신 비용이 쓰기 성능에 미치는 영향
한국어 검색이라는 별개의 문제
한국어는 교착어라 어미가 붙는다. "검색하다", "검색했고", "검색에서"가 모두 다른 토큰이 된다. simple 설정으로는 어간 추출이 안 되므로, 사용자가 다른 어형으로 검색하면 못 찾는다.
- 형태소 분석기 없이 동작
- 부분 일치와 오타에 강함
- 구현이 단순
- 인덱스 크기가 크게 증가
- 노이즈 매칭 증가
- 랭킹 품질이 떨어질 수 있음
접근 2는 형태소 분석기 연동이다. 품질은 좋지만 확장 설치와 사전 관리가 필요하고, 관리형 DB에서는 어려운 경우가 많다. 현실적인 타협으로 제목은 n-gram, 본문은 단순 토큰화 같은 조합을 쓰기도 한다.
-- pg_trgm 을 활용한 유사도 검색 (오타·부분 일치 보완)
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX idx_docs_title_trgm ON docs USING GIN (title gin_trgm_ops);
-- 유사도 기반 검색 (임계값 조정 필요)
SELECT id, title, similarity(title, '포스트그래스') AS sim
FROM docs
WHERE title % '포스트그래스'
ORDER BY sim DESC
LIMIT 10;
-- 실무에서는 전문검색 결과와 유사도 결과를 합치는 경우가 많다.
하이브리드로 가는 길
전문검색과 벡터 검색을 함께 쓰는 구성이 최근의 기본값이 됐다. 키워드 검색은 정확한 용어 매칭에 강하고, 벡터 검색은 의미가 비슷한 표현을 잡는다. 두 결과를 합치면 각각의 약점이 보완된다.
언제 전용 검색엔진으로 넘어가야 하나
반대로 이 신호가 없다면 이전하지 않는 게 낫다. 검색엔진을 추가하면 색인 파이프라인, 일관성 지연, 장애 시 폴백, 권한 이중 관리라는 문제가 새로 생긴다. 내장 전문검색의 가장 큰 가치는 그 문제들이 아예 없다는 것이다.
자주 묻는 질문
ts_rank로는 왜 순서가 이상한가요?
ts_rank는 용어 희소성 가중과 문서 길이 정규화를 현대적인 방식으로 다루지 않습니다. 그래서 흔한 단어가 여러 번 등장하는 긴 문서가 상위로 올라오는 현상이 생깁니다. BM25 계열 스코어링은 이 부분을 개선합니다.
BM25 확장을 쓰려면 무엇을 확인해야 하나요?
가장 먼저 사용 중인 관리형 데이터베이스에서 해당 확장을 설치할 수 있는지 확인해야 합니다. 그다음 인덱스 크기와 쓰기 성능 영향, 메이저 버전 업그레이드 시 확장 호환성 책임 소재를 확인하세요.
한국어 검색은 어떻게 처리하나요?
기본 설정으로는 어미 변화를 다루지 못합니다. 형태소 분석기를 연동하면 품질이 좋지만 확장 설치와 사전 관리가 필요하고, n-gram 방식은 설치 부담이 없는 대신 인덱스가 커지고 노이즈가 늘어납니다. 제목과 본문에 다른 전략을 적용하는 절충도 자주 쓰입니다.
오타 보정은 어떻게 하나요?
pg_trgm의 유사도 검색을 병행하는 방법이 일반적입니다. 전문검색 결과가 비었거나 적을 때 유사도 검색으로 보완하고, 임계값은 실제 데이터로 조정하세요. 자동완성에는 접두 검색을 별도로 구성하는 것이 좋습니다.
벡터 검색과 함께 쓰는 게 좋나요?
키워드 검색은 정확한 용어에, 벡터 검색은 의미가 유사한 표현에 강해 서로를 보완합니다. 두 결과를 순위 기반으로 융합하면 품질이 개선되며, 권한이나 기간 같은 필터는 SQL에서 먼저 적용해 후보를 줄이는 구성이 효율적입니다.
언제 전용 검색엔진으로 옮겨야 하나요?
검색이 제품의 핵심이고 문서 수가 수백만을 넘거나, 패싯·복잡한 랭킹·비개발자 운영 요구가 계속 늘어날 때입니다. 그렇지 않다면 색인 동기화와 이중 권한 관리 비용을 새로 떠안게 되므로 내장 전문검색을 유지하는 편이 낫습니다.

댓글 0