카테고리 없음

검색 시스템 개선을 위한 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를 도입한 이유

  1. 풀 텍스트 검색에 최적화되어 있어, 일반 RDBMS의 LIKE 검색보다 정확하고 유연한 검색이 가능
    : tokenizing + analyzer 기반으로 텍스트를 분석해서 훨씬 더 유연하고 정교한 검색이 가능함.
    ex) “서울 파스타 맛집” → 단어 단위로 쪼개서 검색, 중간에 단어가 포함되어도 검색 가능

  2. 다양한 검색 옵션
    : 단순 키워드 검색 외에도, 오타 허용(fuzziness), 자동완성, 유사도 기반 정렬, 가중치 검색 등 다양한 고급 검색 옵션 제공

  3. 한국어 분석기(nori 등) 적용으로 자연스러운 한국어 검색 가능

  4. 분산 아키텍처 기반으로 대용량 검색 처리에 강함
    • 분산 구조로 되어 있어서, 수십만~수백만 건의 데이터도 빠르게 처리 가능
    • 실시간 인덱싱 및 검색이 가능해서 사용자 경험이 매우 부드러움
  5. 풍부한 레퍼런스와 학습 자료 접근성이 좋음

  6. 무료 기능으로도 충분
    • 인덱스 크기 제한 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 분석기만으로도 충분히 사용자 니즈를 충족할 수 있다고 판단했습니다.

 

🏗️ 구현 구조 요약

  1. 인덱스 생성
    • 초기 설정 시 사용할 분석기(Nori) 및 필드 매핑 정의
  2. 도메인 클래스 구성
@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 도입 시 검색 동작 흐름

  1. 사용자가 검색어 입력
  2. Controller → Service → StoreSearchRepository로 검색 위임
  3. ES에서 토큰화된 인덱스 기반으로 검색 후 결과 반환

 

💡예상 결과 및 효과

특히 대용량 데이터 처리 시 평균 응답 시간이 수배~ 십수배 개선될 것으로 기대됨.