본문
[OpenSearch 트러블슈팅] 대용량 단일 인덱스 병목 현상과 시계열 데이터 최적화
Cloud & Infrastructure/Data Engineering 2025. 12. 24. 16:13
운영 중인 OpenSearch 클러스터에서 간헐적인 검색 지연과 쓰기 차단 현상이 발생했습니다.
모니터링 지표와 인덱스 구조를 분석하여 근본적인 원인을 도출하고 아키텍처 레벨의 해결책을 적용하는 과정을 정리합니다.
1. 클러스터 주요 증상 및 지표 분석
CloudWatch 대시보드와 클러스터 상태를 확인한 결과, 전형적인 리소스 병목 현상이 관찰되었습니다.
- JVM 메모리 압력 폭주: Data Node의 JVM 압력이 20%~90% 사이를 오가며 급격한 변동폭을 보였습니다.
- 빈번한 쓰기 차단(Writes Blocked): ClusterIndexWritesBlocked 이벤트가 다수 발생하여 붉은색 스파이크가 대시보드를 덮었습니다.
- 저조한 Query Cache 적중률: 총 206만 건의 쿼리 중 적중률(Hit)은 21.7%에 불과하고, Miss 비율이 78.3%에 달했습니다.
- 세그먼트 병합 지연: 전체 문서 대비 삭제 문서 비율이 11.2%(약 15만 건)로 누적되어 있었습니다.
- 지연 시간 스파이크: 인덱싱 및 검색 지연 시간(Latency) 그래프에서 주기적인 튀어오름 현상이 확인되었습니다.




2. 근본 원인 파악
가장 큰 원인은 28.8GB에 달하는 거대한 단일 인덱스(xxx_xxx_info)에 있었습니다.
# 샤드 상태 확인
GET /_cat/shards?v&s=store:desc
index shard prirep state docs store ip node
xxx_xxx_info 1 p STARTED 100744 2.9gb x.x.x.x 846647e...
xxx_xxx_info 3 p STARTED 101023 2.8gb x.x.x.x 5bd396f...
xxx_xxx_info 3 r STARTED 101023 2.8gb x.x.x.x 72bdd59...
...
🤔 샤드가 나뉘어 있는데도 왜 문제가 될까?
물리적으로 파티셔닝(샤딩)이 되어 있다 하더라도, 논리적으로 하나의 거대한 단일 인덱스로 묶여 있으면 다음과 같은 악순환이 발생합니다.
1. 동시다발적인 캐시 무효화(Invalidation): 샤드 중 어느 한 곳에라도 새로운 쓰기가 발생하면 전체 인덱스의 쿼리 캐시가 무효화되어 효율이 급감합니다.
2. 과도한 GC 및 JVM 부하: 과거와 최신 데이터가 무작위로 섞여 있어, 데이터 업데이트 시 모든 샤드에서 끊임없이 세그먼트 병합(Segment Merge)이 발생합니다.
3. 비효율적인 검색 (Scatter and Gather): 최신 데이터만 검색하려 해도 5개 샤드 전체를 항상 뒤져야 하는 비효율이 발생합니다.
3. 핵심 해결 방안: ISM Rollover 적용
하드웨어 스케일업은 임시방편일 뿐입니다. 아키텍처 개선을 위해 시계열 인덱스 구조로 전환했습니다.
- ISM(Index State Management) 도입: 단일 인덱스를 용량별(예: 5GB) 또는 기간별로 자동 롤오버되도록 설정합니다.
- 쓰기와 읽기 부하 격리: 쓰기는 최신(Hot) 인덱스에만 집중시키고, 과거 인덱스는 Read-Only 상태로 전환하여 캐시 효율을 극대화합니다.
- 안전한 Force Merge: 쓰기가 멈춘 과거 인덱스에만 비피크 시간대에 Force Merge를 실행하여 삭제 문서를 정리하고 세그먼트를 압축합니다.
4. OpenSearch ISM 정책 구성 예시
1) ISM Policy 정의
PUT _plugins/_ism/policies/xxx_time_series_policy
{
"policy": {
"description": "데이터(xxx_xxx_info)를 위한 시계열 롤오버 및 비용 최적화 정책",
"default_state": "hot",
"states": [
{
"name": "hot",
"actions": [
{
"rollover": {
"min_size": "5gb",
"min_index_age": "30d"
}
}
],
"transitions": [{ "state_name": "warm" }]
},
{
"name": "warm",
"actions": [
{ "read_only": {} },
{ "force_merge": { "max_num_segments": 1 } }
],
"transitions": [
{
"state_name": "delete",
"conditions": { "min_index_age": "365d" }
}
]
},
{
"name": "delete",
"actions": [{ "delete": {} }],
"transitions": []
}
],
"ism_template": [
{
"index_patterns": ["xxx_xxx_info-*"],
"priority": 100
}
]
}
}
2) 논리적 단일 진입점 유지 (Alias)
애플리케이션 수정 없이 그대로 xxx_xxx_info라는 이름을 사용할 수 있도록 Alias를 설정합니다.
POST /_aliases
{
"actions": [
{
"add": {
"index": "xxx_xxx_info-000001",
"alias": "xxx_xxx_info",
"is_write_index": true
}
}
]
}
5. 마무리 및 인사이트
| 비교 항목 | Alias + ISM Rollover | Data Stream |
| 동작 방식 | 수동으로 Alias(별칭)를 생성하고 ISM으로 롤오버 제어 | OpenSearch가 백그라운드 인덱스(Backing Indices)를 완전 자동 관리 |
| 수정/삭제 | 자유롭게 가능 (O) | 원칙적으로 불가 (Append-Only) |
| 초기 세팅 | 첫 인덱스 및 별칭(Alias) 부트스트랩 수동 진행 필요 | 템플릿 설정만으로 완전 자동화 (매우 간편) |
| 필수 필드 | 제한 없음 | @timestamp 필드 강제 |
| 적합한 케이스 | 내용 업데이트가 빈번한 정보성 데이터 | 서버 로그, 시스템 메트릭 |
결론: 데이터가 한 번 쌓인 후 절대 변하지 않는다면 Data Stream이 유리하지만, 본 사례의 xxx_xxx_info 데이터처럼 사후 수정이나 삭제가 발생할 가능성이 있다면 Alias + ISM Rollover 방식이 훨씬 유연하고 안전한 선택입니다.
댓글