이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
LIKE '%keyword%'는 왜 대용량 검색에 약한가?- Prefix 검색, 부분 검색, full-text search는 어떻게 다른가?
- 언제 DB 검색으로 충분하고 언제 검색 엔진을 검토해야 하는가?
개요
검색은 단순 조건 조회와 다르다. 사용자는 부분 일치, 오타 허용, 형태소, 관련도순, 하이라이트를 기대할 수 있다. 그런데 백엔드에서 이를 모두 LIKE '%keyword%'로 처리하면 데이터가 늘면서 곧 한계가 온다.
검색 요구사항은 초기에 수준을 정해야 한다.
원리
검색 설계의 원리는 “문자열을 포함하는 row를 찾는다”가 아니라 사용자가 기대하는 검색 경험을 DB가 감당할 수 있는 접근 방식으로 나누는 것이다. exact, prefix, partial, full-text, ranking, typo tolerance는 서로 다른 인덱스와 운영 책임을 요구한다.
백엔드 개발자는 검색 요구사항을 처음부터 분해해야 한다. 관리자 임시 검색인지, 사용자 주력 검색인지, 최신순인지 관련도순인지, 데이터가 10만 건인지 1억 건인지, 검색 인덱스와 원본 DB의 불일치를 허용할 수 있는지에 따라 선택이 달라진다.
Prefix 검색
Prefix 검색은 앞부분이 고정된 검색이다.
SELECT id, name
FROM products
WHERE name LIKE 'mac%';일반 B-Tree 인덱스가 도움될 수 있다. 자동완성, 코드 검색, 이메일 prefix 검색에 적합하다.
다만 collation과 대소문자 정책을 확인해야 한다. 대소문자 무시 검색을 위해 매번 lower(name) LIKE 'mac%'를 쓰면 일반 인덱스를 못 쓸 수 있다. PostgreSQL expression index, MySQL collation/generator column, normalize 저장 중 어떤 방식이 맞는지 정한다.
부분 검색
부분 검색은 앞뒤 어디든 keyword가 나올 수 있다.
SELECT id, name
FROM products
WHERE name LIKE '%book%';일반 B-Tree 인덱스는 시작 위치를 좁히기 어렵다. PostgreSQL에서는 trigram 인덱스 같은 선택지가 있고, MySQL에서는 full-text index나 ngram parser를 검토할 수 있다.
Full Text Search
Full-text search는 단어 단위 검색, 랭킹, 언어 처리와 연결된다.
PostgreSQL 예시는 다음과 같다.
CREATE INDEX idx_posts_search
ON posts USING GIN (to_tsvector('simple', title || ' ' || content));
SELECT id, title
FROM posts
WHERE to_tsvector('simple', title || ' ' || content)
@@ plainto_tsquery('simple', :keyword);실무에서는 generated column이나 별도 search vector 컬럼으로 관리하는 방식을 검토한다.
위 예시는 개념적으로는 쉽지만 매 요청마다 to_tsvector(title || content)를 계산하면 비용이 커질 수 있다. 쓰기 시점에 search vector를 갱신하거나 generated column/trigger/materialized column을 쓰는 방식을 검토한다. DB별 기능과 migration 도구 지원도 확인해야 한다.
검색 엔진 검토 기준
Elasticsearch/OpenSearch 같은 검색 엔진을 검토할 기준은 다음이다.
- 형태소 분석이 필요하다.
- 관련도순 랭킹이 중요하다.
- 오타 허용, 초성 검색, 동의어가 필요하다.
- 검색 트래픽이 DB를 압박한다.
- 복잡한 필터와 facet이 필요하다.
- 검색 인덱스와 원본 DB의 eventual consistency를 감당할 수 있다.
검색 엔진은 강력하지만 운영 복잡도가 증가한다. DB와 검색 인덱스의 동기화, 재색인, 장애 대응이 필요하다.
검색 엔진 도입의 핵심 질문은 “검색이 빨라지는가”만이 아니다. 원본 DB commit 이후 검색 인덱스 반영까지 지연을 허용할 수 있는지, 재색인 중 검색 품질을 어떻게 유지할지, 장애 시 DB fallback이 가능한지, 검색 결과가 원본 권한 정책을 정확히 반영하는지까지 봐야 한다.
실전 팁
- 검색 요구사항을 prefix, exact, partial, full-text로 나눈다.
- 관리자 검색과 사용자 검색의 SLA를 다르게 본다.
%keyword%는 데이터가 작을 때만 임시로 허용한다.- 검색 결과 정렬이 최신순인지 관련도순인지 먼저 정한다.
- 검색 엔진 도입 시 원본 DB와 인덱스 불일치 처리를 설계한다.
- 검색어 최소 길이, 최대 길이, rate limit을 둔다. 짧은 부분 검색은 DB와 검색 엔진 모두에 비쌀 수 있다.
- 대소문자/공백/특수문자 normalize 정책을 입력 저장 시점과 검색 시점 중 어디에 둘지 정한다.
- 검색 인덱스에는 재색인 절차와 원본과의 검증 방법이 필요하다.
위험 신호!
- 모든 검색을
LIKE '%keyword%'로 처리한다. - 검색어 길이 제한이 없다.
- 검색 API가 운영 DB CPU를 크게 사용한다.
- 관련도순을 단순 최신순으로 대체하면서 제품 요구와 어긋난다.
- 검색 인덱스 재생성 절차가 없다.
- 검색어 길이 제한이 없어 한 글자 부분 검색이 DB를 흔든다.
- 검색 엔진 결과가 원본 DB 권한 필터와 달라 비공개 데이터가 노출된다.
- full-text index를 만들었지만 언어, tokenizer, stop word 정책을 제품과 맞추지 않았다.
확인 질문
- 앞쪽 와일드카드 검색이 B-Tree 인덱스와 잘 맞지 않는 이유는 무엇인가?
- 검색 시작 위치가 고정되어 있지 않아 정렬된 인덱스에서 탐색 시작점을 좁히기 어렵기 때문이다.
- DB full-text search와 검색 엔진의 차이는 무엇인가?
- DB full-text는 DB 안에서 검색 기능을 제공하고, 검색 엔진은 더 풍부한 분석과 랭킹을 제공하지만 별도 동기화와 운영 복잡도가 필요하다.
- 검색 기능 설계에서 가장 먼저 정해야 하는 것은 무엇인가?
- 검색 종류, 정렬 기준, 데이터 규모, 지연 허용 범위, 일관성 요구사항이다.
- 검색 엔진 도입 전에 합의해야 할 운영 조건은 무엇인가?
- 원본 DB와 검색 인덱스의 동기화 지연 허용 범위, 재색인 절차, 장애 시 fallback, 권한 필터 적용 방식, 검색 품질 검증 기준이다.