[Daily morning study] Full-text Search와 역인덱스(Inverted Index) — Elasticsearch 동작 원리
#daily morning study
Full-text Search란
Full-text Search(전문 검색)는 텍스트 데이터 전체를 분석해서 특정 단어나 구문이 포함된 문서를 찾는 검색 방식이다. 일반 RDBMS의 LIKE '%keyword%' 방식과 근본적으로 다른 접근을 사용한다.
LIKE 검색의 한계
LIKE '%검색어%'는 인덱스를 활용하지 못하고 전체 테이블 스캔(Full Table Scan)을 수행한다.- 동의어, 형태소 분석이 없어 “실행”으로 검색하면 “실행하다”, “실행됨”은 매칭되지 않는다.
- 관련성(relevance) 점수를 계산하지 않아 결과 정렬이 어렵다.
Full-text Search 엔진은 이 문제를 역인덱스(Inverted Index) 자료구조로 해결한다.
역인덱스(Inverted Index) 구조
역인덱스는 “어떤 문서에 어떤 단어가 있는가”가 아니라 “어떤 단어가 어느 문서에 있는가” 방향으로 만든 인덱스다.
예시 문서:
| 문서 ID | 내용 |
|---|---|
| doc1 | “Redis는 인메모리 데이터베이스다” |
| doc2 | “데이터베이스 인덱스는 성능을 높인다” |
| doc3 | “Redis 클러스터는 데이터를 분산한다” |
역인덱스:
| 토큰(Term) | 문서 목록 (Posting List) |
|---|---|
| redis | [doc1, doc3] |
| 인메모리 | [doc1] |
| 데이터베이스 | [doc1, doc2] |
| 인덱스 | [doc2] |
| 클러스터 | [doc3] |
| 분산 | [doc3] |
“Redis”를 검색하면 Posting List에서 [doc1, doc3]을 O(1)에 가까운 속도로 찾아낸다. 테이블 전체를 스캔할 필요가 없다.
텍스트 분석 파이프라인 (Text Analysis)
문서를 역인덱스에 넣기 전에 분석(Analysis) 단계를 거친다. 이 과정이 검색 품질을 결정한다.
원문 텍스트
↓
Character Filter → HTML 태그 제거, 특수문자 변환 등
↓
Tokenizer → 텍스트를 토큰(단어) 단위로 분리
↓
Token Filter → 소문자 변환, 불용어 제거, 형태소 분석 등
↓
역인덱스에 저장
예시: “Running faster than EVER!”
- Tokenizer →
["Running", "faster", "than", "EVER"] - Lowercase Filter →
["running", "faster", "than", "ever"] - Stop Word Filter (불용어 제거) →
["running", "faster", "ever"] - Stemming (어간 추출) →
["run", "fast", "ever"]
이렇게 하면 “run”, “runs”, “running” 모두 동일한 토큰 run으로 매칭된다.
Elasticsearch 아키텍처
Elasticsearch는 Apache Lucene 기반의 분산 검색 엔진으로, 역인덱스를 분산 환경에서 관리한다.
핵심 개념
| 개념 | 설명 | RDBMS 대응 |
|---|---|---|
| Index | 문서의 논리적 컨테이너 | Database / Table |
| Document | 검색 단위, JSON 형태 | Row |
| Field | 문서의 속성 | Column |
| Shard | 인덱스를 나눈 물리적 단위 | Partition |
| Replica | 샤드의 복제본 | Replication |
클러스터 구조
Elasticsearch Cluster
├── Node 1 (Primary)
│ ├── Shard 0 (Primary)
│ └── Shard 1 (Replica)
├── Node 2
│ ├── Shard 1 (Primary)
│ └── Shard 2 (Replica)
└── Node 3
├── Shard 2 (Primary)
└── Shard 0 (Replica)
- Primary Shard: 원본 데이터 저장 및 쓰기 처리
- Replica Shard: 읽기 부하 분산 및 장애 복구용 복제본
- Primary와 Replica는 항상 다른 노드에 위치해 고가용성 보장
샤드 수 설정
인덱스 생성 시 Primary Shard 수를 지정하며, 인덱스 생성 이후에는 변경 불가하다. (Replica는 변경 가능)
PUT /my_index
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}
검색 과정 (Query Execution)
Scatter-Gather 패턴
- Scatter: 클라이언트 요청을 받은 노드(Coordinating Node)가 관련 샤드 전체에 검색 요청 브로드캐스트
- Gather: 각 샤드의 결과를 수집, 관련성 점수(Relevance Score) 기준으로 병합 및 정렬
- 최종 결과 반환
관련성 점수 (TF-IDF / BM25)
Elasticsearch는 기본적으로 BM25 알고리즘으로 문서 관련성을 계산한다.
- TF (Term Frequency): 문서 내 검색어 등장 빈도 — 많이 나올수록 높은 점수
- IDF (Inverse Document Frequency): 전체 문서 대비 검색어 희소성 — 희귀한 단어일수록 높은 가중치
- BM25: TF-IDF를 개선한 알고리즘으로 문서 길이 보정(Field Length Normalization) 추가
BM25 Score = IDF × (TF × (k1 + 1)) / (TF + k1 × (1 - b + b × dl/avgdl))
k1: TF 포화 상수 (기본 1.2)b: 문서 길이 보정 계수 (기본 0.75)dl/avgdl: 현재 문서 길이 / 평균 문서 길이
Elasticsearch vs RDBMS 쿼리 비교
RDBMS의 LIKE 검색:
SELECT * FROM articles
WHERE content LIKE '%데이터베이스%'
ORDER BY created_at DESC;
Elasticsearch의 Full-text Search:
GET /articles/_search
{
"query": {
"match": {
"content": "데이터베이스"
}
},
"sort": [
{ "_score": { "order": "desc" } }
]
}
Elasticsearch는 토큰 분석 → 역인덱스 조회 → BM25 점수 계산을 거쳐 관련성이 높은 문서를 빠르게 반환한다.
근사 검색과 자동완성
역인덱스 기반 검색의 강점은 정확한 단어 매칭 외에 다양한 검색 방식을 지원한다는 점이다.
| 검색 유형 | 설명 | 예시 |
|---|---|---|
match | 기본 전문 검색, 분석기 적용 | “데이터 베이스” → 2개 토큰 OR 검색 |
match_phrase | 구문 검색, 순서 유지 | “인메모리 데이터베이스” 그대로 |
fuzzy | 오타 허용 검색 (Levenshtein 거리) | “Elasticsaerch” → Elasticsearch |
prefix | 접두사 검색 | “redi” → redis, redisson |
wildcard | 와일드카드 패턴 | “r*s” → redis, rabbitmq |
range | 범위 검색 | 날짜, 숫자 범위 |
Near Real-Time (NRT) 특성
Elasticsearch는 완전한 실시간이 아닌 Near Real-Time 검색을 지원한다.
문서 색인 흐름:
문서 쓰기
↓
In-memory Buffer (즉시)
↓ (Refresh, 기본 1초)
Segment (검색 가능 상태)
↓ (Flush, 기본 30분 or translog 512MB)
Disk (영구 저장)
- Refresh: 메모리 버퍼를 새 Segment로 만들어 검색 가능 상태로 전환 (기본 1초 간격)
- Flush: Segment를 디스크에 영구 기록, Translog 초기화
- 따라서 문서를 색인한 직후 최대 1초 후부터 검색 결과에 노출된다.
언제 Elasticsearch를 쓰는가
| 사용 적합 | 사용 부적합 |
|---|---|
| 대용량 텍스트 전문 검색 | 강한 트랜잭션/ACID 보장 필요 |
| 로그 분석 (ELK Stack) | 복잡한 관계형 조인 |
| 자동완성, 추천 검색어 | 단순 키-값 조회 (Redis가 더 적합) |
| 다국어 형태소 분석 검색 | 소규모 단순 검색 |
| 복잡한 집계 쿼리 (Aggregation) |
일반적으로 주 저장소는 RDBMS(MySQL, PostgreSQL), 검색/분석은 Elasticsearch로 분리하는 아키텍처를 자주 사용한다. RDBMS에 쓰기가 발생하면 CDC나 Logstash를 통해 Elasticsearch에 동기화한다.