Elasticsearch 검색 설계 시 흔히 저지르는 치명적 실수와 해결 방안
Elasticsearch 기반 검색 프로젝트에서 엔지니어링 팀은 반복적으로 동일한 근본적 오류를 범합니다. 이러한 문제는 팀의 경험 수준과 무관하게 다양한 시스템 전반에 걸쳐 확산됩니다. 본 글에서는 이들을 ‘반패턴(anti-pattern)’으로 분석합니다—즉, 성능 저하, 관련성 약화, 운영 비용 증가를 확실히 초래하는 검증된 접근 방식입니다.
이러한 패턴을 조기에 인식하는 것은 중·고급 엔지니어에게 필수적입니다. 예시는 Elasticsearch에 집중하지만, 그 원리는 Solr, OpenSearch, 또는 자체 개발 검색 스택에도 동일하게 적용됩니다.
데이터 품질 무시하기
검색은 알고리즘보다 먼저 콘텐츠에서 시작됩니다. 더러운 데이터는 검색 시스템 실패의 가장 큰 원인입니다. 중복 레코드, 비구조화된 텍스트, 불일치하는 포맷은 관련성 자체를 직접 훼손합니다.
흔한 문제들:
- 제품명 표준화 부재 (예: "아이폰 14" vs. "애플 아이폰14")
- 오타와 잡음 문자가 가득한 정제되지 않은 설명 문구
- 중복 제거 없이 쌓인 동일 상품 레코드
데이터 품질 개선은 순위 조정(tuning)보다 훨씬 큰 효과를 줍니다. 이 작업은 주로 주니어 엔지니어가 담당하지만, 체계적인 접근이 필요합니다: 견고한 ETL 파이프라인, 인제스트 시점 검증, 지속적인 인덱스 품질 모니터링.
깨끗한 데이터 없이는 BM25나 신경망 리랭킹도 구원하지 못합니다. 실제 프로젝트에서는 전체 노력을 70–80%가 데이터 품질 확보에 쓰이며, 쿼리 로직에는 거의 시간을 쓰지 않습니다.
요구사항 및 평가 지표 부재
"야후처럼 작동하게 해주세요" 같은 모호한 요청은 고전적 사례입니다. 문서화되고 측정 가능한 요구사항이 없으면 검색은 주관적이 되며, 팀은 핵심 비즈니스 목표—상위 K개 결과의 관련성, 0건 반환률, 클릭률(CTR)—조차 정의하지 못합니다.
핵심 평가 지표:
- Precision@K: 상위 K개 결과 중 관련 문서의 비율
- Recall@K: 전체 관련 문서 중 상위 K개에 포함된 비율
- NDCG: 등급별 관련성과 위치 정보를 반영한 정밀한 점수
- CTR 및 전환율: 검색 세션 단위로 측정
- 지연 시간(Latency) 및 QPS: 현실적인 부하 하에서 측정
빈번한 쿼리에 대한 수작업 평가는 A/B 테스트나 오프라인 지표를 대체할 수 없습니다. 이를 생략하면 서서히 악화되는 순위 변화를 감지하지 못하고, 사용자는 아무 말 없이 검색을 포기합니다.
"왕 쿼리(King Query)": 일체형 vs. 확장성
필터링, 집계, 순위 산정, 페이징까지 모두 하나의 거대한 쿼리로 처리하는 것은 널리 퍼진 반패턴입니다. 일관성 확보와 네트워크 호출 감소라는 장점처럼 보이지만, 대가가 매우 큽니다.
일체형 쿼리의 단점:
- 복잡한 쿼리 실행 시간이 1초 이상 소요됨
- 한 기능 추가만으로도 전체 쿼리 재설계가 필요해 유지보수가 고통스러움
- 쿼리 크기와 자원 소모로 인해 샤딩 및 복제 효율이 급격히 저하됨
| 접근 방식 | 지연 시간 | 유지보수 용이성 | 일관성 |
|----------|---------|-----------------|-------------|
| 왕 쿼리 | 1–3초 | 낮음 | 높음 |
| 마이크로 쿼리 + 캐싱 | 50–200ms | 높음 | 중간 (캐시 활용 시) |
검색을 단계별로 분리하세요: 매칭+필터 → 강화(enrich) → 리랭킹. 최적화에는 ESQL이나 인제스트 파이프라인을 적극 활용하세요.
퍼지 검색을 만능 해결책으로 오해하기
자동 오타 교정을 위해 fuzziness에 의존하는 것은 Elasticsearch에 대한 흔한 오해입니다. "apliphone" 같은 쿼리에 대해 Elasticsearch는 수백 가지 변형("applesiphone", "aplfone" 등)을 생성합니다.
그 결과:
- 검출률(Recall)은 올라가지만, 정밀도(Precision)는 급락합니다.
- 관련성이 약화되며, 상위 결과에 잡음이 넘쳐납니다.
- 쿼리 비용이 10–50배까지 증가합니다.
파라미터 조정으로 근본 원인을 해결할 수 없습니다:
{
"query": {
"multi_match": {
"query": "apliphone",
"fuzziness": "AUTO",
"prefix_length": 2,
"max_expansions": 50
}
}
}
prefix_length=2는 잡음을 줄이지만, 근본 문제는 여전히 남습니다. 더 나은 해결책: 프론트엔드 스펠체커(Hunspell/야후 스펠러)와 인제스트 시점 동의어 그래프 적용입니다.
인덱스 내 중첩 객체 및 조인 사용
이커머스는 제품 → 가격 → 재고 → 리뷰 같은 관계를 필요로 합니다. 부모-자식 조인(parent-child join)을 통한 정규화는 논리적으로 보이지만, 성능을 완전히 망가뜨립니다.
has_child/parent의 문제점:
- 심층 페이징이 아예 작동하지 않음
- 관계 간 점수 집계가 제대로 이루어지지 않음
- 스키마 변경 시 전체 재인덱싱이 불가피함
권장 방안:
- 정규화 대신 비정규화: 가격과 재고를 제품 문서에 직접 포함
- 별도 인덱스 사용 (예:
goods,prices) 및 클라이언트 측 조인 nested필드는 밀접하게 결합된, 카디널리티가 낮은 데이터에만 제한적으로 사용
핵심 요약
- 데이터 > 알고리즘: 검색 품질의 80%는 데이터 품질에서 나옵니다.
- 지표는 선택이 아닌 필수: 모든 사용 사례에 대해 Precision, Recall, NDCG를 반드시 추적해야 합니다.
- 쿼리를 분해하세요: 왕 쿼리는 2초 이상 지연을 보장합니다.
- 퍼지 검색은 절제해서 사용: 드문 용어에만 적용하며, 반드시 스펠체커와 병행하세요.
- 비정규화는 적극적으로: Elasticsearch 내 조인은 운영 환경에서 피해야 할 반패턴입니다.
— Editorial Team
아직 댓글이 없습니다.