검색을 공부하다 보면 역색인, Nori, 동의어, fuzzy, ILM 같은 키워드가 따로따로 흩어진다. 면접에서 “검색 시스템을 어떻게 설계하겠나”라는 질문을 받으면, 이 조각들이 하나로 안 꿰지면 답이 산만해진다.
검색 시스템은 사실 하나의 파이프라인이다. 사용자가 검색창에 단어를 치면, 그 단어가 어떻게 저장된 데이터와 만나는지의 흐름. 이 흐름을 4개 계층으로 보면 모든 조각이 제자리를 찾는다.
검색 시스템의 4개 계층
사용자 입력 "겔럭시 케이스"
│
▼
┌─────────────────────────────────────────────┐
│ ① 저장 계층 — 데이터를 어떻게 저장하나 │
│ 역색인, 세그먼트, 샤드 │
├─────────────────────────────────────────────┤
│ ② 이해 계층 — 텍스트를 어떻게 이해하나 │
│ 형태소 분석(Nori), 동의어 │
├─────────────────────────────────────────────┤
│ ③ 보정 계층 — 불완전한 입력을 어떻게 메우나 │
│ 오타(fuzzy), 자모 분해, 자동완성 │
├─────────────────────────────────────────────┤
│ ④ 운영 계층 — 시간에 따라 어떻게 관리하나 │
│ 핫/콜드 티어, ILM │
└─────────────────────────────────────────────┘
│
▼
관련도 순 검색 결과
각 계층은 독립적이지 않다. 위 계층의 선택이 아래 계층의 동작을 바꾼다. 형태소 분석을 어떻게 하느냐(②)가 동의어 매칭(②)과 오타 보정(③)의 토큰 단위를 결정하고, 저장 방식(①)이 운영 전략(④)의 전제가 된다.
① 저장 계층 — 왜 빠른가
RDB의 LIKE '%...%'는 풀스캔이라 1000만 건에서 죽는다. ES는 데이터를 “문서→단어”가 아니라 “단어→문서”로 뒤집어 저장한다(역색인). 그래서 문서 수와 거의 무관하게 빠르다.
여기서 알아야 할 것: 역색인의 구조(term dictionary + posting list), 색인이 바로 검색에 안 보이는 이유(세그먼트 + refresh), 불변 세그먼트와 머지, 분산(샤드 + scatter-gather)과 샤드 수가 변경 불가인 이유.
→ 자세히: Elasticsearch 동작 원리 — 역색인부터 세그먼트까지
② 이해 계층 — 텍스트를 단어로
저장하려면 문장을 단어로 쪼개야 한다. 영어는 공백이면 되지만 한국어는 교착어라 형태소 분석(Nori)이 필요하다. “갤럭시를”을 “갤럭시”로, “삼성전자갤럭시케이스”를 의미 단위로 쪼갠다.
그 위에 의미의 동일성을 더하는 게 동의어다. “노트북”과 “랩탑”을 같게 본다. 핵심 운영 포인트는 동의어를 검색 타임에 둬서 사전이 바뀌어도 재색인 없이 리로드하는 것.
→ 자세히: Elasticsearch 한국어 검색 — 형태소 분석과 동의어
이해 계층의 기반이 되는 analyzer 자체의 내부 구조(char_filter→tokenizer→token_filter 파이프라인)와 ngram 메커니즘은 따로 깊이 다룬다.
→ 자세히: Elasticsearch Analyzer 내부 구조와 ngram 원리
③ 보정 계층 — 불완전한 입력 메우기
사용자는 오타를 내고(“겔럭시”), 일부만 치고(“갤”), 초성만 친다(“ㅅㅅ”). 정확 매칭만 하면 이 입력들이 전부 0건이 된다. 검색 트래픽의 10~20%가 날아간다.
편집 거리(fuzzy)로 오타를 잡고, 한글은 자모 분해로 정밀도를 높이고, edge_ngram/completion으로 자동완성을 하고, suggester로 “이거 찾으세요?”를 띄운다. 단, fuzzy는 비싸서 정확 매칭 실패 시 fallback으로 쓴다.
→ 자세히: Elasticsearch 오타 처리 — 퍼지 검색과 한글 자모 분해
④ 운영 계층 — 시간에 따른 관리
검색 데이터, 특히 로그성 시계열은 계속 쌓인다. 전부 비싼 SSD에 두면 비용이 선형으로 증가하는데, 검색 트래픽은 최근 데이터에 몰린다.
데이터를 온도(hot/warm/cold/frozen)로 나눠 다른 하드웨어에 두고, ILM으로 rollover→shrink→force_merge→삭제를 자동화한다. 오래된 데이터는 인덱스 단위로 통째 삭제하는 게 핵심.
→ 자세히: Elasticsearch 핫/콜드 데이터 운영 — ILM과 데이터 티어
그래서 언제 Elasticsearch인가 — 대안과 비교
“검색이면 그냥 DB로 하면 되지 않나”에 답할 수 있어야 한다.
- vs RDB 풀텍스트(LIKE / FULLTEXT 인덱스): 데이터가 작고 단순 부분일치면 RDB의 FULLTEXT 인덱스로 충분하다(별도 인프라 없음, 트랜잭션 일관성). 하지만
LIKE '%..%'는 풀스캔이라 대규모에서 죽고, 형태소·동의어·오타·관련도 랭킹·로그 분석이 필요하면 RDB로는 한계다. 그 지점이 ES다. - vs 매니지드 검색(Algolia 등): 빠른 도입·최소 운영·오타/자동완성 UX 기본 제공이면 Algolia 같은 SaaS가 편하다. 세밀한 제어·온프렘·대량 데이터 비용·로그/관측 통합이 필요하면 ES.
- vs 벡터 DB(시맨틱 검색): 키워드(역색인) 매칭이면 ES, 의미 기반 임베딩 검색이면 벡터 검색이다. ES도
dense_vector로 하이브리드가 가능해 둘을 섞기도 한다.
안 맞는 경우: 1차 원본 저장소로 쓰는 것(ES는 검색용 사본, 원본은 RDB), 강한 트랜잭션·조인, 쓰기 직후 즉시 읽기 일관성(refresh 지연), 소규모 단순 검색은 ES가 과하다.
이 그림이 면접에서 왜 유용한가
“상품 검색을 어떻게 설계하겠나”를 받으면 이 4계층 순서로 답하면 빠짐없이 구조적으로 말할 수 있다.
- 저장: RDB는 LIKE 풀스캔이라 부적합 → ES 역색인. 원본은 RDB, ES는 CDC로 동기화하는 검색용 사본.
- 이해: 한국어니까 Nori 형태소 분석 + 사용자 사전(신상품/브랜드) + 검색 타임 동의어.
- 보정: 자동완성(completion), 오타(fuzzy AUTO + 자모 분해), 교정 제안(suggester).
- 랭킹: BM25 텍스트 점수 + 비즈니스 시그널(판매량/재고/광고/개인화)을 function_score로 결합.
- 운영: 카탈로그는 Hot 유지, 로그성은 ILM으로 티어 관리. 매핑 변경은 alias 스위칭으로 무중단 reindex.
각 항목에서 본인이 실제로 겪은 이슈(refresh 지연, 사용자 사전 누락, 동의어-형태소 충돌, deep pagination, 샤드 설계 실수)를 하나씩 붙이면, “개념만 아는 사람”과 “운영해본 사람”의 차이가 드러난다.
구현 계층 — Spring Boot / JPA / Java
위 5단계는 개념이고, 실제로는 Spring Boot 애플리케이션 안에서 돌아간다. 매핑(@Document), 검색 쿼리 조립(NativeQuery + function_score), 대량 색인(bulk + refresh 제어), 깊은 페이징(search_after), 그리고 가장 중요한 JPA(RDB)와 ES를 같은 트랜잭션에 묶지 않는 동기화 설계까지가 구현의 핵심이다.
→ 자세히: Spring Boot에서 Elasticsearch 다루기 — JPA와 함께, Java로 튜닝하기