이번 글에서는 내가 KBooks 사이트에 검색 기능을 처음 추가했던 과정을 공유한다.
국립중앙도서관 API에서 수집한 모든 도서 데이터는 이미 dbt 파이프라인을 통해 Supabase(Postgres) 로 들어오고 있었다. 이제 검색을 붙여보겠다.
아래는 데이터베이스 수준에서 검색을 준비한 단계별 과정과, 각각의 결정이 왜 타당했는지에 대한 설명이다.
1. 깨끗한 출발점: silver_books
원본 데이터는 raw_nl_books에 JSON 형태로 저장된다.
여기서 dbt를 통해 silver_books 테이블을 만들었고, 각 행은 ISBN-13으로 식별되는 고유한 책 하나를 나타낸다.
유효한 ISBN만 남겨 중복을 제거했다.
이 테이블에는 이미 사용자가 검색할 만한 주요 필드가 있었다:
title, author, publisher_name, subject, book_introduction
그래서 가장 자연스러운 생각은 이거였다.
“이 테이블 위에 그냥 검색을 얹으면 되겠네?”
2. Postgres에서 “검색”의 의미 이해하기
Postgres는 텍스트 검색을 위해 두 가지 주요 방식을 제공한다.
🔍 Full-Text Search (FTS)
FTS는 텍스트를 단어 단위로 쪼개어(예: “Harry”, “Potter”) 단순 문자열 비교가 아닌 의미 기반 검색을 수행한다.
또한 관련도(ts_rank) 에 따라 결과를 정렬할 수 있어 가장 적합한 결과가 상단에 온다.
✏️ Trigram 검색
Trigram은 텍스트를 세 글자 단위로 나누는 방식이다. 이 인덱스를 사용하면 오타나 부분 일치가 있는 경우에도 검색이 가능하다. 예를 들어, “해리포”라고 쳐도 “해리 포터”를 찾을 수 있다. 한국어처럼 띄어쓰기나 철자 변형이 잦은 언어에도 유용하다.
이 두 가지를 함께 쓰면, 외부 검색 엔진 없이도 정확 검색 + 유사 검색을 모두 커버할 수 있다.
3. 무엇을 인덱싱할지 (그리고 무엇을 뺄지) 결정하기
처음에는 모든 텍스트 필드(title, author, publisher, subject, introduction)를 인덱싱했다. 잘 작동했지만, 불필요한 결과가 많았다. 예를 들어, ‘해리 포터’가 단 한 번 소개글(book_introduction) 에 등장한 책도 검색 결과에 포함됐다.
그래서 다음처럼 단순화했다:
- 포함:
title,author - 제외:
subject,book_introduction,publisher_name
이 변경만으로도 결과가 훨씬 깔끔해졌고, 사용자가 기대하는 “정확한 검색 결과”가 나왔다.
4. dbt로 구현하기
dbt에서 데이터 변환과 인덱스 생성을 하나의 파이프라인으로 관리했다.
-
FTS 컬럼 생성:
tsv = to_tsvector('simple', title || ' ' || author)→ 검색 가능한 단어들을 압축 요약한 컬럼을 만든다. -
인덱스 생성 (dbt
post_hook사용):unique(isbn13)→ 중복 제거용 기본 키GIN(tsv)→ 전체 텍스트 검색용GIN(title gin_trgm_ops)→ 오타나 부분 일치 탐색용
dbt는 테이블을 재생성할 때마다 자동으로 인덱스를 다시 만든다. 즉, 데이터 갱신과 인덱스 관리가 완전히 자동화된다.
5. 결과 검증 (Sanity Check)
다음 쿼리로 테스트했다:
select
isbn13, title, author_display,
ts_rank(tsv, plainto_tsquery('simple', '해리 포터')) as rank
from kbooks_dbt.silver_books
where tsv @@ plainto_tsquery('simple', '해리 포터')
order by rank desc
limit 20;
결과: ‘해리 포터’ 관련 도서만 나오고, 설명에 잠깐 언급된 책은 제외됐다. 정확성과 관련도 모두 만족.
6. 아키텍처 선택 이유 요약
| 목표 | 선택 | 이유 |
|---|---|---|
| 빠른 검색 | GIN 인덱스 | 수백만 행을 스캔하지 않고 O(1)에 가까운 검색 |
| 오타·한국어 대응 | Trigram | 띄어쓰기·부분 일치 지원 |
| 단순 구조 | silver_books 내부 처리 |
별도 테이블이나 동기화 작업 불필요 |
| 깔끔한 결과 | title + author만 포함 | 노이즈 최소화 |
| 유지보수 용이 | dbt에서 변환·인덱스 동시 관리 | 단일 소스 관리 |
이 구조는 수백만 행 규모로 확장해도 충분히 버틸 수 있다.
추후 검색 랭킹 페이지나 Meilisearch 같은 외부 엔진을 붙이더라도,
silver_books에서 그대로 내보내면 된다 — 웹 로직을 바꿀 필요가 없다.
7. 결과
이제 사용자는 KBooks 웹사이트에서 “해리 포터” 를 입력하면, Supabase에서 즉시 관련 도서를 빠르고 정확하게 찾아볼 수 있다. 서드파티 검색엔진이나 동기화 지연 없이, 잘 인덱싱된 SQL만으로 완전한 검색 경험을 제공한다.
8. 핵심 요약
좋은 검색은 꼭 검색엔진이 필요한 게 아니다. Postgres에 데이터를 이미 저장하고 있다면,
to_tsvector와 GIN 인덱스를 배우는 것만으로도 80%는 달성 가능하다.
이 기능을 dbt 파이프라인 안에 직접 넣어두면,
- 검색은 항상 최신 클린 데이터에서 작동하고
- 모든 변환·성능 최적화가 버전 관리된 하나의 repo에 담긴다.
그게 바로 내가 KBooks에서 원했던 방식이다 — 깔끔하고, 최소한으로, 그리고 빠르게.