역인덱스와 Lucene — BM25 스코어링과 세그먼트
Elasticsearch 공식 문서(elastic.co/docs) 및 기술 블로그 종합 — 아키텍처부터 운영까지 (웹 리서치, 2026) — Elastic 공식 문서 + 여러 기술 블로그(Opster, Baeldung 등) 교차 확인
이 모듈을 다 읽으면
- 역인덱스가 '단어→문서 목록' 구조로 전문 검색을 빠르게 만드는 원리를 설명할 수 있다
- BM25가 TF-IDF와 비교해 어떤 점을 개선했는지 설명할 수 있다
- Lucene 세그먼트가 색인/검색 성능에 미치는 영향을 판단할 수 있다
Elasticsearch가 내부적으로 쓰는 Lucene의 역인덱스 구조와 BM25 스코어링, 세그먼트 개념을 정리한다.
역인덱스(Inverted Index) — 왜 LIKE 쿼리보다 훨씬 빠른가
일반 RDB의 LIKE '%검색어%'는 모든 행을 훑어야 한다(풀스캔). 역인덱스는 반대 방향으로 구조를 뒤집는다 — '문서→단어'가 아니라 '단어→그 단어가 등장하는 문서 목록'을 미리 만들어둔다. 텍스트를 색인할 때 토큰화(단어 분리)·소문자화·어간 추출(stemming) 과정을 거치므로, 'running'으로 색인된 문서가 'run'/'runs'라는 검색어로도 자동으로 매칭된다. 검색 시점에는 이 사전 구축된 단어→문서 매핑을 바로 조회하기만 하면 되므로, 전문 검색이 문서 전체 스캔 없이 빠르게 끝난다.
핵심 포인트
- 역인덱스 = '단어 → 그 단어가 등장하는 문서 목록'을 미리 만들어둔 구조
- 색인 시 토큰화·소문자화·어간추출을 거치므로 'running' 색인이 'run' 검색과도 매칭된다
BM25 — TF-IDF에서 무엇이 개선됐는가
BM25는 검색어와 문서의 관련도(relevance)를 점수화하는 Elasticsearch의 기본 스코어링 알고리즘이다. 기존 TF-IDF(단어 빈도×역문서빈도)와 기본 아이디어는 비슷하지만, BM25는 두 가지를 보정한다: (1) 단어 빈도(TF)가 무한정 점수를 올리지 않도록 포화(saturation)시킨다 — 같은 단어가 10번 나오는 문서가 1번 나오는 문서보다 무조건 10배 관련 있다고 보지 않는다. (2) 문서 길이로 정규화한다 — 파라미터 b(기본 0.75)가 문서가 평균보다 길 때 TF 점수를 얼마나 깎을지 조절한다(짧은 문서에 우연히 단어가 몇 번 나온 것과, 매우 긴 문서에 같은 횟수 나온 것을 같게 취급하지 않기 위함).
핵심 포인트
- BM25는 TF-IDF와 달리 단어 빈도 증가를 포화시켜(saturation) 무한정 점수가 오르지 않게 한다
- 문서 길이 정규화 파라미터 b(기본 0.75)로 긴 문서의 TF 점수를 적절히 깎는다
Lucene 세그먼트 — 각 샤드 내부의 실제 저장 단위
Elasticsearch의 샤드 하나는 내부적으로 Lucene 인덱스이고, Lucene 인덱스는 여러 개의 불변(immutable) 세그먼트로 구성된다. 새 문서가 들어오면 새 세그먼트가 만들어지고(색인된 데이터는 메모리 버퍼에 있다가 refresh 시점에 세그먼트로 구체화됨), 기존 세그먼트는 절대 수정되지 않는다 — 삭제나 업데이트도 실제로는 '삭제됨' 표시만 남기고 나중에 병합(merge) 시점에 실제로 제거된다. 세그먼트가 너무 많아지면(작은 세그먼트가 계속 쌓이면) 검색이 느려지므로(모든 세그먼트를 다 뒤져야 함) 백그라운드에서 작은 세그먼트들을 큰 것으로 합치는 merge가 계속 일어난다.
핵심 포인트
- 샤드 = Lucene 인덱스, Lucene 인덱스는 여러 불변 세그먼트로 구성된다
- 삭제/업데이트도 세그먼트 자체는 안 건드리고 '삭제됨' 표시만 남긴 뒤 merge 시점에 실제 제거
- 세그먼트가 많아지면 검색이 느려져 백그라운드 merge로 계속 합쳐진다