OpenSearch 완전 가이드: Reindex(무중단)부터 Analyzer/Tokenizer/Normalizer, 인덱스 설계, Keyword·Term 개념까지
OpenSearch를 운영하다 보면 결국 Reindex로 돌아옵니다.
- 매핑을 바꿔야 한다(필드 타입 변경/추가)
- analyzer를 바꿔야 한다(토큰화/동의어/정규화)
- 정렬/집계를 더 빠르게 해야 한다(doc_values, keyword)
- 검색 품질을 올려야 한다(BM25 튜닝, ngram, synonym)
- 벡터/하이브리드를 붙이고 싶다(k-NN, RRF)
문제는 OpenSearch에서 **많은 변경이 "기존 인덱스에 바로 적용되지 않는다"**는 점입니다.
그래서 운영에서는 "수정"이 아니라 새 인덱스를 만들고 Reindex한 뒤 Alias를 스왑하는 방식(블루/그린)이 표준입니다.
이 글은 다음을 예시와 함께 한 번에 정리합니다.
- Reindex가 왜 필요한지 / 언제 필요한지
- 무중단 Reindex(blue/green + alias) 운영 절차
- analyzer / tokenizer / token filter / char filter / normalizer 차이와 예시
- 인덱스 설계 전략(샤드/레플리카/refresh/merge/ILM/alias)
- keyword vs text, term의 의미, query 유형별 올바른 사용법
- 실전에서 자주 터지는 함정과 체크리스트
참고: OpenSearch는 Elasticsearch 계열이므로 개념/DSL이 매우 유사합니다. (버전/플러그인 차이는 운영 환경에 맞게 확인)
TL;DR
- 매핑/분석기(analyzer)는 "데이터가 인덱싱되는 방식" 이라서, 바꾸려면 대부분 Reindex가 필요합니다.
- 운영 표준은
index_vN새로 만들고 -> Reindex -> 검증 -> alias swap 입니다. text는 "검색용(분석됨)",keyword는 "정확일치/정렬/집계용(분석 안 됨)"입니다.term query는 분석을 안 하는 정확일치입니다.text필드에 쓰면 흔히 "왜 검색이 안 되지?"가 발생합니다.- analyzer/normalizer는 품질에 직결되지만, 바꾸는 순간 Reindex 비용이 생기므로 버저닝/alias 전략이 필수입니다.
1) Reindex가 필요한 이유: "인덱싱은 과거를 바꾸지 않는다"
OpenSearch에서 검색은 크게 두 단계를 가집니다.
- Index-time(색인 시점): 문서를 저장하면서 텍스트를 토큰화/정규화하고 역색인을 만든다
- Search-time(검색 시점): 쿼리를 분석해서 역색인에서 매칭한다
여기서 중요한 점:
- analyzer/tokenizer/필드 타입 같은 설정은 Index-time에 적용됩니다.
- 이미 인덱싱된 문서의 토큰/필드 구조는 자동으로 다시 만들어지지 않습니다.
즉, 아래 변경은 대부분 "기존 인덱스에 적용 불가" -> 새 인덱스 + Reindex로 갑니다.
Reindex가 거의 필수인 변경들
- 필드 타입 변경:
text -> keyword,integer -> long,date format 변경등 - analyzer 변경(동의어/토크나이저/필터 변경)
- normalizer 변경(keyword 정규화 방식 변경)
index_options,norms,doc_values,fielddata같은 인덱싱/저장 방식 변경copy_to전략 변경, multi-field 설계 변경
Reindex 없이 가능한 변경(대표)
- 새 필드 추가(단, 기존 문서에 값이 없을 뿐)
- 일부 settings(예:
number_of_replicas) 조정 - alias 추가/변경(인덱스 데이터 자체는 그대로)
2) Reindex의 3가지 방식: API / Update-by-query / 재색인 파이프라인
2.1 _reindex API (가장 표준)
- 소스 인덱스 -> 목적 인덱스로 문서 복사
- (옵션) 스크립트로 문서 변형 가능
2.2 _update_by_query
- 같은 인덱스 내에서 문서를 다시 써서(업데이트) 일부 필드를 재계산
- analyzer 변경 같은 "토큰 재생성"에는 한계가 큼(근본적 구조는 안 바뀜)
2.3 애플리케이션/ETL 기반 재색인
- DB/원천에서 다시 읽어서 새 인덱스에 인덱싱
- 가장 통제 가능하지만 구현 비용이 큼
- 대규모 운영에선 결국 이 방식이 더 안정적인 경우도 많습니다(정합성/검증/재시도).
3) 무중단 Reindex 표준: Blue/Green + Alias Swap
운영에서 가장 안전한 패턴은 다음입니다.
- 읽기 alias:
books_read - 쓰기 alias:
books_write - 실제 인덱스:
books_v1,books_v2, ...
3.1 목표 상태
- 검색 서비스는 항상
books_read만 조회 - 인덱싱 파이프라인은 항상
books_write만 인덱싱 - reindex 완료 후
books_read/books_write가 새로운 인덱스를 가리키도록 스왑
3.2 예시: v1 -> v2 무중단 절차
(1) 새 인덱스 생성: books_v2
PUT books_v2
{
"settings": {
"number_of_shards": 6,
"number_of_replicas": 1,
"analysis": {
"char_filter": {
"strip_html": { "type": "html_strip" }
},
"filter": {
"ko_syn": {
"type": "synonym_graph",
"synonyms": [
"해리포터, harry potter",
"자바, java"
]
}
},
"tokenizer": {
"edge_ngram_tok": {
"type": "edge_ngram",
"min_gram": 1,
"max_gram": 20,
"token_chars": [ "letter", "digit" ]
}
},
"analyzer": {
"ko_search": {
"type": "custom",
"char_filter": [ "strip_html" ],
"tokenizer": "standard",
"filter": [ "lowercase", "ko_syn" ]
},
"ac_index": {
"type": "custom",
"tokenizer": "edge_ngram_tok",
"filter": [ "lowercase" ]
}
},
"normalizer": {
"kw_norm": {
"type": "custom",
"filter": [ "lowercase", "asciifolding" ]
}
}
}
},
"mappings": {
"properties": {
"book_id": { "type": "keyword" },
"title": {
"type": "text",
"analyzer": "ko_search",
"fields": {
"raw": { "type": "keyword", "normalizer": "kw_norm" }
}
},
"author": {
"type": "text",
"analyzer": "ko_search",
"fields": {
"raw": { "type": "keyword", "normalizer": "kw_norm" }
}
},
"published_at": { "type": "date" },
"price": { "type": "integer" }
}
}
}
(2) Reindex 실행: v1 -> v2
POST _reindex?wait_for_completion=false
{
"source": { "index": "books_v1" },
"dest": { "index": "books_v2", "op_type": "create" }
}
wait_for_completion=false로 비동기 실행 후 task를 추적합니다.op_type=create는 "이미 있는 문서는 덮어쓰지 않음"이라 재실행에 조금 더 안전합니다.
(3) Reindex 중 성능/부하 제어
POST _reindex?wait_for_completion=false
{
"source": {
"index": "books_v1",
"size": 1000
},
"dest": { "index": "books_v2" },
"conflicts": "proceed"
}
size로 배치 크기 조절requests_per_second(지원 시)로 스로틀링- 운영에서는 리프레시/레플리카를 조절해 속도를 올렸다가 끝나면 원복하는 패턴을 씁니다.
(4) 검증(필수)
- 문서 수 비교
- 샘플 쿼리 결과 비교(스냅샷 테스트)
- 집계/정렬 결과 비교
- 특정 키워드(동의어/정규화) 케이스 회귀 테스트
예: 문서 수
GET books_v1/_count
GET books_v2/_count
예: 샘플 검색 품질 비교
GET books_v2/_search
{
"query": { "match": { "title": "해리 포터" } },
"size": 5
}
(5) Alias Swap (원자적으로)
POST _aliases
{
"actions": [
{ "remove": { "alias": "books_read", "index": "books_v1" } },
{ "add": { "alias": "books_read", "index": "books_v2" } },
{ "remove": { "alias": "books_write", "index": "books_v1" } },
{ "add": { "alias": "books_write", "index": "books_v2", "is_write_index": true } }
]
}
_aliases는 액션을 묶어 "사실상 원자적"으로 바꿉니다.- 이 순간부터 서비스는 무중단으로 v2를 사용합니다.
(6) 구 인덱스 보관/삭제
- 즉시 삭제하지 말고 일정 기간 보관(롤백/감사/회귀 대응)
- 보관 기간이 끝나면 삭제
4) Analyzer / Tokenizer / Normalizer: 무엇이 어떻게 다른가?
4.1 Tokenizer
- 텍스트를 토큰으로 쪼개는 규칙
- 예: 공백/구두점 기준, ngram, edge_ngram 등
예: "Harry Potter" -> ["harry", "potter"] (standard tokenizer)
4.2 Analyzer
- (char_filter) -> tokenizer -> token_filter
text필드에 적용되는 "검색 가능한 형태로 만드는 파이프라인"
예시:
- 소문자화, 동의어 확장, stopword 제거, stemming 등
4.3 Normalizer
keyword필드용 "분석기"- 토큰화(tokenizer)가 없다 (keyword는 쪼개면 안 되니까)
- 주로
lowercase,asciifolding같은 정규화만 수행
예: Keyword: "Kim"을 "kim"으로 통일 -> 집계/정렬/정확일치 안정화
5) 인덱스 설계 전략(운영 관점): 샤드/레플리카/refresh/alias
5.1 샤드 수
- 샤드는 "병렬 처리 + 분산"의 단위
- 너무 많으면 오버헤드(파일/세그먼트/메모리) 증가
- 너무 적으면 확장성/처리량 제한
원칙
- "지금 데이터 크기 + 성장률 + 쿼리 패턴"으로 결정
- reindex로만 바꿀 수 있는 경우가 많으니(버전/환경에 따라) 초기부터 과도한 샤드는 피하는 편이 낫습니다.
5.2 레플리카 수
- 검색 가용성/성능(읽기 분산) vs 인덱싱 비용(쓰기 2배)
- ingest/reindex 동안
replicas=0으로 속도 올리고 끝나면 1로 올리는 패턴이 흔함(운영 정책에 따라)
5.3 refresh_interval
- refresh는 "검색 가능해지는 주기"
- 짧으면 실시간성이 좋지만 인덱싱 비용 증가
- 대량 적재/리인덱스 동안 refresh를 늘리거나 끄고, 끝나면 원복하면 성능이 크게 좋아집니다.
5.4 alias는 운영의 핵심
- 인덱스 이름은 버전으로 고정(
_vN) - 서비스는 alias만 바라본다(
_read,_write) - 무중단 전환/롤백/실험이 쉬워집니다.
6) text vs keyword: 검색/집계/정렬의 분기점
6.1 text
- analyzer로 분석됨
match,multi_match같은 "풀텍스트 검색"에 적합
6.2 keyword
- 분석하지 않는 "그대로의 값"
term,terms,prefix,wildcard(주의), 정렬, 집계에 적합
6.3 실무 표준: multi-field
같은 원문을 두 관점으로 쓰고 싶으면:
title은text(검색)title.raw는keyword(정렬/집계/정확일치)
이게 가장 흔한 패턴입니다.
7) "term"은 무엇인가? (그리고 왜 term query가 자주 실패하나)
7.1 term(개념)
- 역색인에 들어가는 토큰(또는 keyword 값) 을 term이라고 생각하면 됩니다.
keyword필드의 term은 "원문(정규화 후)" 그 자체text필드의 term은 analyzer를 거쳐 쪼개진 토큰들
7.2 term query는 "분석을 안 하는 정확일치"
그래서 아래 실수가 매우 흔합니다.
title이text인데term으로"Harry Potter"를 찾으려 한다- 하지만
title의 term은 보통"harry","potter"로 나뉘어 있음 - 결과: 매칭이 안 됨
정확일치는 보통 title.raw(keyword)에 term을 씁니다.
예시:
GET books_v2/_search
{
"query": {
"term": { "title.raw": "harry potter" }
}
}
반대로 text에는 match:
GET books_v2/_search
{
"query": {
"match": { "title": "Harry Potter" }
}
}
8) 쿼리 유형별 올바른 사용 가이드(짧고 실전적으로)
- 정확일치(필터):
term/terms+keyword - 범위 필터:
range+ numeric/date - 풀텍스트 검색:
match/multi_match+text - 부분일치(자동완성):
edge_ngram(index analyzer) +match(search analyzer) 또는 completion 설계 - 정렬:
keyword(또는 numeric/date),text정렬은 거의 금지(필요하면.raw) - 집계(aggregation):
keyword또는 numeric/date (text는 fielddata 필요 -> 메모리 위험)
9) Analyzer 설계 예시 3종: 검색/정렬/자동완성
9.1 검색용 analyzer(동의어 + 소문자)
match품질 개선- 동의어는 운영에서 변경이 잦아 "reindex 비용"과 직결됩니다.
- 파일 기반 synonyms/검색 시 확장 등 전략을 분리할 가치가 큼
9.2 정렬/집계용 normalizer(keyword)
"Kim","KIM","kim"을 같은 버킷으로 묶고 싶을 때lowercase+asciifolding이 흔한 기본값
9.3 자동완성(edge_ngram)
- 인덱싱 시
edge_ngram으로 접두어 토큰을 만들어 두면 빠른 prefix 매칭이 됩니다. - 단점: 인덱스 크기 증가, 품질(노이즈) 튜닝 필요
10) Reindex 운영에서 자주 터지는 함정(그리고 피하는 법)
함정 A) alias 없이 인덱스 이름을 서비스에 박아둠
- 리인덱스/롤백이 "배포"가 되어버림 -> 장애 시 복구가 느림 -> 처음부터 read/write alias 고정
함정 B) analyzer 변경을 가볍게 봄
- analyzer 변경 = 대부분 재색인
- 동의어/토큰화는 품질에 큰 영향 + 회귀가 잦음 -> "동의어 버전/롤백/검증 세트"를 운영 도구로 갖추는 게 좋습니다.
함정 C) text에 집계/정렬 걸어서 메모리 폭발
- text는 doc_values가 아니라 fielddata로 올라가서 무거움
-> multi-field로
.raw키워드를 항상 같이 둔다
함정 D) reindex 중 클러스터가 느려짐
- reindex는 대규모 I/O + merge를 유발 -> 스로틀링, refresh/replica 조절, 운영 윈도우 분리
11) "운영형 Reindex" 체크리스트(실무용)
- 새 인덱스 이름은 버전으로(
index_vN) - read/write alias 분리(
_read,_write) - 매핑/analysis는 템플릿화(복붙 금지)
- reindex 중 부하 제어(스로틀/배치/refresh/replica)
- 검증: count / 샘플 쿼리 / 집계 / 정렬 / 회귀 키워드 세트
- alias swap은
_aliases로 묶어서 실행 - 롤백 플랜: alias를 v1로 되돌릴 수 있는가?
- 구 인덱스 삭제는 "검증 후 + 보관 기간 후"
12) 마무리: OpenSearch 운영의 핵심은 "Reindex를 전제로 한 설계"다
OpenSearch에서 성능/품질을 개선하는 대부분의 작업은 결국:
- 분석기 변경
- 매핑 개선
- 인덱스 구조 튜닝
으로 이어지고, 이는 Reindex를 요구합니다.
그래서 운영에서 중요한 건 "Reindex를 어떻게 하는가?"가 아니라:
Reindex가 언제든 가능하게 인덱스를 설계했는가?