카테고리 없음
검색 시스템 개선을 위한 Elasticsearch 도입기
heesoohi
2025. 4. 17. 13:26
🏛️ 도입 배경
가게 검색 기능은 우리 서비스에서 핵심 기능 중 하나이며, 초기에는 JPA를 사용해 RDS에서 검색 기능을 구현했지만 다음과 같은 한계가 있었습니다:
🛠️ JPA 검색의 한계와 문제점
- LIKE 조건 기반 검색은 정확도와 유연성이 낮음
- 검색 조건이 많아질수록 쿼리 메서드의 복잡도 증가 및 검색 로직의 확장성 부족 문제 발생
- 기본 검색 조건만으로도 파라미터가 많아져 **파라미터 객체화(@ModelAttribute)**로 구조를 정리했지만, 이는 구조 문제 해결일 뿐 검색 최적화와는 무관
- 데이터가 많아지면 쿼리 응답 속도가 느려질 가능성
🆚 DB 인덱스 vs Elasticsearch vs OpenSearch
기준 | DB 인덱스 최적화 | Elasticsearch | OpenSearch |
한국어 지원 | 기본 SQL LIKE 사용 (형태소 분석 불가) | ✅ 노리 플러그인으로 한국어 형태소 분석 가능 | ❌ 기본 한국어 분석기 없음 |
비용 및 라이선스 | ✅ 기존 DB 활용 (추가 비용 없음) | ⚠️ 일부 기능 유료 (SSPL 라이선스) | ✅ 완전 오픈소스 (Apache 2.0) |
기능 제공 | ✅ 필터링, 정렬, prefix 검색 중심 기능 제공 | ✅ 고급 검색, 정렬, 집계 모두 제공 | ✅ Elasticsearch와 유사한 검색 기능 제공 |
성능 | ⚠️ LIKE 연산, 정렬이 느릴 수 있음 (인덱스로 개선 가능) | ✅ 빠른 전문 검색 및 벡터 검색 가능 | ✅ 벡터 검색 엔진(Faiss 등) 선택 가능 |
확장성 | ⚠️ 대규모 데이터셋에선 성능 한계 있음 | ✅ 4,096차원 벡터 지원 | ✅ 16,000차원 고차원 벡터 처리 가능 |
도입 난이도 | ✅ 매우 낮음 (기존 아키텍처 유지) | ⚠️ 별도 인프라 구성 필요 | ⚠️ 별도 인프라 구성 필요 |
유지보수 | ✅ 단일 DB 관리로 단순 | ⚠️ 운영 및 모니터링 추가 필요 | ⚠️ 운영 및 보안 설정 필요 |
레퍼런스/자료 | ✅ SQL 중심 자료 풍부 | ✅ 문서/커뮤니티 풍부 | ✅ ES 문서 활용 가능 (약간의 차이 있음) |
추천 상황 | ✅ 현재 아키텍처에서 빠른 개선 원할 때 ✅ 검색 조건 복잡하지 않을 때 |
✅ 한국어 검색 정교화 필요 ✅ 빠른 전문 검색 or AI 검색 필요할 때 |
✅ 고차원 벡터 검색 or 무료 보안 기능 필요할 때 |
초기 MVP, 빠른 개선 → DB 인덱스 최적화
정교한 검색 품질 필요 → Elasticsearch
AI 검색 확장 → OpenSearch
➡️ ElasticSearch를 도입하기로 최종 결정.
🤔 Elasticsearch를 도입한 이유
- 풀 텍스트 검색에 최적화되어 있어, 일반 RDBMS의 LIKE 검색보다 정확하고 유연한 검색이 가능
: tokenizing + analyzer 기반으로 텍스트를 분석해서 훨씬 더 유연하고 정교한 검색이 가능함.
ex) “서울 파스타 맛집” → 단어 단위로 쪼개서 검색, 중간에 단어가 포함되어도 검색 가능 - 다양한 검색 옵션
: 단순 키워드 검색 외에도, 오타 허용(fuzziness), 자동완성, 유사도 기반 정렬, 가중치 검색 등 다양한 고급 검색 옵션 제공 - 한국어 분석기(nori 등) 적용으로 자연스러운 한국어 검색 가능
- 분산 아키텍처 기반으로 대용량 검색 처리에 강함
- 분산 구조로 되어 있어서, 수십만~수백만 건의 데이터도 빠르게 처리 가능
- 실시간 인덱싱 및 검색이 가능해서 사용자 경험이 매우 부드러움
- 풍부한 레퍼런스와 학습 자료 접근성이 좋음
- 무료 기능으로도 충분
- 인덱스 크기 제한 X
- 프로젝트에 필요한 필터링/정렬/검색 등 기능 모두 제공
🧱 Elasticsearch 기본 개념 정리
🔹 인덱스(Index)
- ES의 Index = RDS의 테이블
- 토큰화(Tokenization): "서울", "분위기", "카페"처럼 단어를 나누는 작업
🔹 역색인(Inverted Index)
- 토큰화된 단어들을 기반으로 “해당 단어가 등장하는 문서들”을 찾을 수 있도록 구조화
- 즉, ES는 행이 아닌 '단어 중심'의 검색을 수행
🔹 행(Row) vs 인덱스 기반 검색 비교
구분 | RDS (JPA) | Elasticsearch |
검색 기준 | 행(Row) 기반 | 분석된 토큰 기반 역색인 |
구조 | 정형화된 스키마, PK 중심 | 분산형 인덱스, 역색인 |
성능 | 데이터가 많아지면 느려짐 | 검색 최적화 구조 |
검색 유연성 | 낮음 (LIKE) | 높음 (유사도 기반 검색 등 |
🔎 분석기 선택: Nori vs nGram
- Nori 분석기는 한국어 전용 분석기로 형태소(일정한 의미를 가지는 가장 작은 단위) 분석을 통해 검색어를 처리
- nGram 분석기는 단어를 일정 단위로 잘라 검색하는 방식으로, 오타 감지나 자동완성 기능에 특화
Nori를 선택한 이유:
- 우리 서비스는 주로 식당명, 음식 종류, 위치 등 비교적 명확한 키워드를 검색합니다.
- 사용자의 오타나 자동완성 필요성이 낮다고 판단되어, nGram은 과한 스펙이라 판단했습니다.
- 따라서 Nori 분석기만으로도 충분히 사용자 니즈를 충족할 수 있다고 판단했습니다.
🏗️ 구현 구조 요약
- 인덱스 생성
- 초기 설정 시 사용할 분석기(Nori) 및 필드 매핑 정의
- 도메인 클래스 구성
@Entity
public class StoreDocument { ... }
public interface StoreSearchRepository extends ElasticsearchRepository<StoreDocument, String> { ... }
3. 데이터 저장 흐름
기존 storeCreate() 메서드에서 RDS 저장 외에 Elasticsearch에도 동시에 저장되도록 로직 확장
🗂️ 데이터 저장 전략
- RDS: StoreRepository → 기존 DB 저장
- ES: StoreSearchRepository → 검색용 인덱스 저장
이는 일종의 이중 저장 전략(Dual Write Strategy) 으로, 검색 성능과 데이터 일관성을 동시에 확보하기 위한 구조입니다.
🚨 Dual Write의 문제점
- RDS 저장은 성공했지만 ES 저장은 실패한 경우 → 🔥 데이터 불일치 발생
- RDS의 데이터가 업데이트되었지만 ES에는 반영 안 됨 → 🔍 검색 결과가 실제 데이터와 다름
따라서 정합성 보장(Consistency)을 위한 전략이 필요하다!
(트랜잭션 분리? 비동기 이벤트 기반 처리? 동기화 스케줄러? 정합성 문제에 대한 해결책은 좀 더 고민 후 결정하기로..!)
🔁 ES 도입 시 검색 동작 흐름
- 사용자가 검색어 입력
- Controller → Service → StoreSearchRepository로 검색 위임
- ES에서 토큰화된 인덱스 기반으로 검색 후 결과 반환
💡예상 결과 및 효과
특히 대용량 데이터 처리 시 평균 응답 시간이 수배~ 십수배 개선될 것으로 기대됨.