Split View: OpenSearch 운영·관리·인덱스 설계 실전 가이드 2026
OpenSearch 운영·관리·인덱스 설계 실전 가이드 2026
- 왜 OpenSearch 운영 역량이 중요한가
- 1. 프로덕션 클러스터 아키텍처
- 2. 인덱스 설계와 샤드 전략
- 3. ISM(Index State Management) 라이프사이클 관리
- 4. 모니터링과 알림 설정
- 5. 장애 시나리오와 복구 전략
- 6. AWS OpenSearch Service 운영 체크리스트
- 7. OpenSearch vs Elasticsearch 운영 관점 비교
- 8. Elasticsearch → OpenSearch 마이그레이션 체크리스트
- 9. 성능 튜닝 빠른 참조
- 10. 보안 강화 체크리스트
- 마무리: 운영자가 기억해야 할 7가지
- References
왜 OpenSearch 운영 역량이 중요한가
OpenSearch는 로그 분석, 전문 검색, 관측성(Observability) 파이프라인의 핵심 엔진이다. 클러스터를 띄우는 것은 쉽지만, 안정적으로 운영하면서 비용을 제어하는 것은 전혀 다른 문제다. 인덱스 하나의 샤드 수를 잘못 잡으면 클러스터 전체의 JVM 힙이 흔들리고, ISM 정책 하나가 빠지면 디스크가 차서 쓰기가 멈춘다.
이 글에서는 클러스터 아키텍처 → 인덱스 설계 → ISM 라이프사이클 → 모니터링 → 장애 대응 → 보안 → 마이그레이션 순서로, 운영자가 실제 프로덕션에서 마주치는 의사결정 포인트를 다룬다. 모든 API 예제는 OpenSearch 2.x 기준이며, AWS OpenSearch Service에서도 동일하게 사용할 수 있다.
1. 프로덕션 클러스터 아키텍처
1.1 노드 역할 분리
프로덕션 클러스터에서 각 노드 타입의 역할을 명확히 분리해야 안정성과 성능을 동시에 확보할 수 있다.
┌─────────────────────────────────────────────────────────────────┐
│ 클라이언트 / Data Prepper │
│ (Ingest Pipeline) │
└──────────────────────────┬──────────────────────────────────────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Master-1 │ │ Master-2 │ │ Master-3 │
│ (AZ-a) │ │ (AZ-b) │ │ (AZ-c) │
│ 전용 마스터│ │ 전용 마스터│ │ 전용 마스터│
└──────────┘ └──────────┘ └──────────┘
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Hot-1 │ │ Hot-2 │ │ Hot-3 │
│ (AZ-a) │ │ (AZ-b) │ │ (AZ-c) │
│ EBS gp3 │ │ EBS gp3 │ │ EBS gp3 │
└──────────┘ └──────────┘ └──────────┘
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Warm-1 │ │ Warm-2 │ │ Warm-3 │
│ UltraWarm│ │ UltraWarm│ │ UltraWarm│
│ (S3+캐시) │ │ (S3+캐시) │ │ (S3+캐시) │
└──────────┘ └──────────┘ └──────────┘
| 노드 타입 | 역할 | 권장 사양 | 배치 규칙 |
|---|---|---|---|
| 전용 마스터 | 클러스터 메타데이터 관리, 샤드 할당 | 최소 3대 (홀수), 최신 세대 인스턴스 | 반드시 3 AZ 분산 |
| Hot 데이터 노드 | 활성 읽기/쓰기 처리 | 높은 CPU + 빠른 스토리지 (gp3) | AZ 균등 분배 |
| Warm 데이터 노드 | 읽기 위주, 빈도 낮은 조회 | 높은 스토리지 밀도, 낮은 CPU | UltraWarm (S3 기반) |
| Coordinating 노드 | 요청 라우팅, 결과 집계 | 높은 CPU + 메모리, 스토리지 최소 | 검색 집약 워크로드에서만 |
1.2 용량 산정 공식
프로덕션 클러스터 사이징의 핵심 공식이다.
필요 스토리지 = 일일 원본 데이터
× (1 + 레플리카 수)
× 1.1 (인덱싱 오버헤드)
× 1.15 (OS 예약 + 내부 작업 여유)
× 보존 기간(일)
예시: 일별 로그 100 GiB, 레플리카 1, 30일 보존 → 100 × 2 × 1.1 × 1.15 × 30 = 7,590 GiB
디스크 사용률은 최대 75% 이내로 유지한다. 75%를 초과하면 OpenSearch가 새 샤드 할당을 거부하기 시작하고, 85%에서 read-only로 전환된다.
실제 프로비저닝 = 필요 스토리지 / 0.75 = 10,120 GiB
2. 인덱스 설계와 샤드 전략
2.1 샤드 사이징 기준
샤드 크기는 워크로드 유형에 따라 달라진다. OpenSearch 공식 문서와 AWS 가이드에서 권장하는 범위는 다음과 같다.
| 워크로드 | 권장 샤드 크기 | 근거 |
|---|---|---|
| 검색 지연 민감 (e-commerce, 자동완성) | 10–30 GiB | 지연 시간 최소화, 빠른 복구 |
| 쓰기 중심 / 로그 분석 | 30–50 GiB | 쓰기 처리량 극대화 |
| 최대 권장 한도 | 50 GiB | 이 이상은 복구·재배치 시간 급증 |
프라이머리 샤드 수 계산 공식:
프라이머리 샤드 수 ≈ (원본 데이터 크기 + 성장 여유) × 1.1 / 목표 샤드 크기
예시: 일별 로그 66 GiB, 향후 4배 성장 예상, 목표 샤드 크기 30 GiB
(66 + 198) × 1.1 / 30 ≈ 10 프라이머리 샤드
2.2 노드당 샤드 한도
| 제약 조건 | 한도 |
|---|---|
| JVM 힙 대비 | 힙 1 GiB당 25 샤드 |
| CPU 대비 | 샤드 1개당 vCPU 1.5개 (초기 규모) |
| OpenSearch ≤ 2.15 | 노드당 최대 1,000 샤드 |
| OpenSearch 2.17+ | JVM 힙 16 GiB당 1,000 샤드, 노드당 최대 4,000 |
핵심 원칙: 샤드 수를 데이터 노드 수의 배수로 설정해 균등 분배한다. 예를 들어 데이터 노드 3대라면 프라이머리 샤드 3, 6, 9, 12개 등으로 설정한다.
2.3 인덱스 템플릿과 매핑 설계
예제 1: Composable Index Template
인덱스 템플릿으로 모든 새 인덱스의 설정과 매핑을 일관되게 관리한다. dynamic: strict로 예상치 못한 필드 추가(매핑 폭발)를 방지하는 것이 핵심이다.
PUT _index_template/app-logs-template
{
"index_patterns": ["app-logs-*"],
"template": {
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"refresh_interval": "30s",
"index.translog.flush_threshold_size": "1024mb",
"index.codec": "zstd_no_dict",
"plugins.index_state_management.rollover_alias": "app-logs-write"
},
"mappings": {
"dynamic": "strict",
"properties": {
"@timestamp": { "type": "date" },
"level": { "type": "keyword" },
"message": { "type": "text", "analyzer": "standard" },
"service": { "type": "keyword" },
"trace_id": { "type": "keyword" },
"span_id": { "type": "keyword" },
"host": { "type": "keyword" },
"duration_ms": { "type": "float" },
"http_status": { "type": "short" },
"request_path":{ "type": "keyword" },
"user_id": { "type": "keyword" },
"metadata": {
"type": "object",
"enabled": false
}
}
}
},
"composed_of": ["common-settings"],
"priority": 200
}
설정 포인트 해설:
refresh_interval: 30s— 기본 1초에서 늘려 CPU/IO 부하를 줄인다. 실시간 검색이 불필요한 로그 워크로드에 적합하다.translog.flush_threshold_size: 1024mb— JVM 힙의 25% 수준으로 설정해 flush 빈도를 낮춘다.dynamic: strict— 정의하지 않은 필드가 들어오면 인덱싱을 거부한다. 매핑 폭발(mapping explosion) 방지의 핵심이다.index.codec: zstd_no_dict— OpenSearch 2.9+에서 사용 가능한 zstd 압축. 기본 LZ4 대비 25–30% 스토리지 절감.metadata.enabled: false— 검색하지 않는 중첩 객체는 인덱싱을 비활성화해 리소스를 절약한다.
2.4 레플리카 전략
| 구성 | 권장 레플리카 수 | 이유 |
|---|---|---|
| 단일 AZ | 1 | 노드 장애 시 데이터 보호 |
| Multi-AZ (2 AZ) | 1 | AZ 장애 시 다른 AZ에서 서비스 |
| Multi-AZ with Standby (3 AZ) | 2 | AZ 장애 시에도 100% 데이터 가용 |
| 대량 벌크 인덱싱 중 | 0 (임시) | 인덱싱 속도 극대화 후 복원 |
레플리카 0 작전 패턴: 초기 대량 로딩 시 레플리카를 0으로 설정하고, 완료 후 다시 올리면 인덱싱 속도가 최대 2배까지 향상된다.
// 인덱싱 전: 레플리카 비활성화
PUT my-bulk-index/_settings
{ "index.number_of_replicas": 0 }
// 인덱싱 완료 후: 레플리카 복원
PUT my-bulk-index/_settings
{ "index.number_of_replicas": 1 }
2.5 Data Stream vs 기존 인덱스 패턴
OpenSearch 2.6+에서는 Data Stream을 지원한다. 기존 Rollover + Alias 패턴과 비교하면 다음과 같다.
| 항목 | Rollover + Alias | Data Stream |
|---|---|---|
| 초기 설정 | 초기 인덱스 + alias 수동 생성 | 인덱스 템플릿만 등록 |
| 쓰기 대상 | write alias | data stream 이름 직접 사용 |
| 백킹 인덱스 이름 | app-logs-000001 | .ds-app-logs-000001 |
| 삭제 | 인덱스 단위 | DELETE _data_stream/app-logs |
| 추천 워크로드 | 범용 | 시계열(append-only) 전용 |
3. ISM(Index State Management) 라이프사이클 관리
ISM은 OpenSearch의 인덱스 수명주기 자동화 엔진이다. Elasticsearch의 ILM에 해당하며, Hot → Warm → Cold → Delete 흐름을 자동으로 처리한다.
3.1 Hot-Warm-Cold-Delete 전체 정책
예제 2: ISM 전체 라이프사이클 정책
PUT _plugins/_ism/policies/app-log-lifecycle
{
"policy": {
"description": "앱 로그 인덱스 라이프사이클: hot → warm → cold → delete",
"default_state": "hot",
"states": [
{
"name": "hot",
"actions": [
{
"rollover": {
"min_size": "30gb",
"min_index_age": "1d",
"min_doc_count": 10000000
}
}
],
"transitions": [
{ "state_name": "warm", "conditions": { "min_index_age": "3d" } }
]
},
{
"name": "warm",
"actions": [
{ "replica_count": { "number_of_replicas": 1 } },
{ "force_merge": { "max_num_segments": 1 } },
{ "allocation": {
"require": { "temp": "warm" },
"wait_for": true
}
}
],
"transitions": [
{ "state_name": "cold", "conditions": { "min_index_age": "30d" } }
]
},
{
"name": "cold",
"actions": [
{ "replica_count": { "number_of_replicas": 0 } },
{ "read_only": {} }
],
"transitions": [
{ "state_name": "delete", "conditions": { "min_index_age": "90d" } }
]
},
{
"name": "delete",
"actions": [
{
"notification": {
"destination": {
"slack": { "url": "https://hooks.slack.com/services/T.../B.../xxx" }
},
"message_template": {
"source": "인덱스 {{ctx.index}}가 보존 정책(90일)에 따라 삭제됩니다."
}
}
},
{ "delete": {} }
],
"transitions": []
}
],
"ism_template": [
{ "index_patterns": ["app-logs-*"], "priority": 100 }
]
}
}
운영 포인트:
- ISM 점검 주기 기본값은 5분이다.
plugins.index_state_management.job_interval로 조정할 수 있다. ism_template필드를 설정하면 패턴에 맞는 새 인덱스에 자동으로 정책이 붙는다.- rollover 조건(
min_size,min_index_age,min_doc_count)은 OR 조건이다 — 하나라도 충족하면 롤오버가 실행된다. allocation.require.temp: warm— 샤드를node.attr.temp=warm속성의 노드로 이동시킨다.- force_merge 후에는 해당 인덱스에 더 이상 쓰기를 하지 않아야 한다.
3.2 ISM 운영 명령어
# 정책 실행 상태 확인 (특정 인덱스)
GET _plugins/_ism/explain/app-logs-000001
# 기존 인덱스에 정책 부착
POST _plugins/_ism/add/old-logs-2025-*
{ "policy_id": "app-log-lifecycle" }
# 정책 버전 업데이트 (관리 중인 인덱스에 새 정책 적용)
POST _plugins/_ism/change_policy/app-logs-*
{
"policy_id": "app-log-lifecycle-v2",
"state": "warm"
}
# 실패한 ISM 작업 재시도
POST _plugins/_ism/retry/app-logs-000003
# 특정 인덱스에서 ISM 정책 제거
POST _plugins/_ism/remove/app-logs-000005
3.3 Rollover + Alias 패턴 실전 구성
예제 3: Rollover Alias 초기 설정과 운영 흐름
시계열 인덱스 운영의 표준 패턴이다. 쓰기 alias가 항상 최신 인덱스를 가리키고, ISM rollover가 새 인덱스를 생성·전환한다.
// 1단계: 초기 인덱스 생성 + alias 설정
PUT app-logs-000001
{
"aliases": {
"app-logs-write": { "is_write_index": true },
"app-logs-read": {}
}
}
// 2단계: ISM 정책의 rollover 액션이 자동으로 처리
// → app-logs-000002 생성
// → app-logs-write alias를 000002로 이동
// → 000001의 is_write_index를 false로 변경
// 읽기는 항상 read alias로 (모든 히스토리 대상)
GET app-logs-read/_search
{
"query": {
"bool": {
"must": [
{ "match": { "level": "ERROR" } },
{ "range": { "@timestamp": { "gte": "now-1h" } } }
]
}
},
"sort": [{ "@timestamp": "desc" }],
"size": 100
}
// 쓰기는 항상 write alias로
POST app-logs-write/_doc
{
"@timestamp": "2026-03-04T10:00:00Z",
"level": "ERROR",
"service": "payment-api",
"message": "결제 게이트웨이 타임아웃 발생",
"trace_id": "abc123",
"duration_ms": 30500,
"http_status": 504
}
alias 상태 확인:
# 현재 write index 확인
GET _alias/app-logs-write
# 전체 alias 구조 확인
GET _cat/aliases/app-logs-*?v&h=alias,index,is_write_index
4. 모니터링과 알림 설정
4.1 필수 모니터링 지표와 임계치
| 카테고리 | 지표 | 임계치 | 심각도 |
|---|---|---|---|
| 클러스터 상태 | ClusterStatus.red | ≥ 1 (1분, 1회) | Critical |
| 클러스터 상태 | ClusterStatus.yellow | ≥ 1 (1분, 5연속) | Warning |
| 쓰기 차단 | ClusterIndexWritesBlocked | ≥ 1 (5분, 1회) | Critical |
| 노드 수 | Nodes | < 예상 노드 수 (1일, 1회) | Critical |
| 디스크 여유 | FreeStorageSpace | ≤ 노드 스토리지의 25% | Warning |
| JVM 힙 | JVMMemoryPressure | ≥ 95% (1분, 3연속) | Critical |
| Old Gen JVM | OldGenJVMMemoryPressure | ≥ 80% (1분, 3연속) | Warning |
| CPU 사용률 | CPUUtilization | ≥ 80% (15분, 3연속) | Warning |
| 마스터 CPU | MasterCPUUtilization | ≥ 50% (15분, 3연속) | Warning |
| 쓰기 스레드풀 | ThreadpoolWriteRejected | ≥ 1 (SUM, 차분) | Warning |
| 검색 스레드풀 | ThreadpoolSearchRejected | ≥ 1 (SUM, 차분) | Warning |
| 스냅샷 실패 | AutomatedSnapshotFailure | ≥ 1 (1분, 1회) | Critical |
4.2 클러스터 상태 점검 API
예제 4: 종합 클러스터 헬스체크 명령어 모음
# 클러스터 전체 상태 (green/yellow/red)
GET _cluster/health
# 인덱스별 상태 확인
GET _cluster/health?level=indices
# 노드별 JVM, CPU, 디스크 상세
GET _nodes/stats/jvm,os,fs
# 핫 스레드 확인 (높은 CPU 원인 분석)
GET _nodes/hot_threads
# 스레드풀 상태 (대기, 거절 수 확인)
GET _cat/thread_pool?v&h=node_name,name,active,queue,rejected
# 샤드 배치 현황
GET _cat/shards?v&h=index,shard,prirep,state,docs,store,node&s=state
# 미할당 샤드 원인 분석
GET _cluster/allocation/explain
{
"index": "app-logs-000003",
"shard": 0,
"primary": true
}
# 인덱스별 크기 및 문서 수
GET _cat/indices?v&h=index,health,pri,rep,docs.count,store.size&s=store.size:desc
# 노드별 디스크 사용량
GET _cat/allocation?v&h=node,shards,disk.used,disk.avail,disk.percent
# 대기 중인 태스크 확인
GET _cat/pending_tasks?v
4.3 Slow Log로 병목 쿼리 잡기
PUT app-logs-*/_settings
{
"index.search.slowlog.threshold.query.warn": "10s",
"index.search.slowlog.threshold.query.info": "5s",
"index.search.slowlog.threshold.fetch.warn": "5s",
"index.indexing.slowlog.threshold.index.warn": "10s",
"index.indexing.slowlog.threshold.index.info": "5s"
}
warn 레벨을 10초, info 레벨을 5초로 설정하면, 10초 이상 걸리는 쿼리는 즉각 알림 대상이 된다. 이 로그를 OpenSearch Dashboards의 Discover에서 분석하거나, 별도 인덱스로 수집해 트렌드를 추적한다.
4.4 OpenSearch 알림(Alerting) 설정
예제 5: 클러스터 상태 모니터 + Slack 알림
POST _plugins/_alerting/monitors
{
"type": "monitor",
"name": "Cluster Red Status Alert",
"monitor_type": "query_level_monitor",
"enabled": true,
"schedule": {
"period": { "interval": 1, "unit": "MINUTES" }
},
"inputs": [
{
"search": {
"indices": [".opensearch-sap-log-types-config"],
"query": {
"size": 0,
"query": {
"match_all": {}
}
}
}
}
],
"triggers": [
{
"query_level_trigger": {
"name": "Cluster Status Red",
"severity": "1",
"condition": {
"script": {
"source": "ctx.results[0].hits.total.value >= 0",
"lang": "painless"
}
},
"actions": [
{
"name": "Notify Slack",
"destination_id": "slack-destination-id",
"message_template": {
"source": "클러스터 상태가 RED입니다.\n클러스터: {{ctx.monitor.name}}\n시각: {{ctx.periodEnd}}\n즉시 확인이 필요합니다."
},
"throttle_enabled": true,
"throttle": { "value": 10, "unit": "MINUTES" }
}
]
}
}
]
}
실전 팁: 실제 운영에서는 _cluster/health API를 주기적으로 호출하는 외부 모니터링(Prometheus + Grafana, Datadog 등)이 더 안정적이다. OpenSearch 자체 알림은 클러스터가 불안정할 때 함께 동작하지 않을 수 있다.
5. 장애 시나리오와 복구 전략
5.1 주요 장애 유형별 대응
| 장애 | 원인 | 영향 | 즉시 대응 |
|---|---|---|---|
| Red 클러스터 | 프라이머리 샤드 미할당 | 데이터 유실 위험, 해당 인덱스 쓰기 실패 | _cluster/allocation/explain으로 원인 분석 |
| Yellow 클러스터 | 레플리카 미할당 | 이중화 깨짐, 노드 장애 시 데이터 유실 | 노드 추가 또는 레플리카 수 조정 |
| 디스크 풀 | 스토리지 부족 | ClusterBlockException, 모든 쓰기 차단 | 오래된 인덱스 삭제, ISM 정책 점검 |
| JVM OOM | 힙 압력 > 95% | 노드 크래시, 연쇄 장애 | 무거운 쿼리 kill, 캐시 정리 |
| Split Brain | 마스터 노드 < 3대 + 네트워크 분리 | 데이터 불일치 | 전용 마스터 노드 3대 (3 AZ) 구성 필수 |
| AZ 장애 | AWS 인프라 문제 | 레플리카 부족 시 데이터 유실 | Multi-AZ + 레플리카 2 구성 |
| 매핑 폭발 | dynamic mapping + 비정형 필드 | 힙 급증, 인덱싱 지연 | dynamic: strict 또는 dynamic: false 전환 |
5.2 디스크 풀 긴급 복구
클러스터가 read-only로 전환된 경우 다음 순서로 복구한다.
# 1. 가장 큰 인덱스 확인
GET _cat/indices?v&h=index,store.size&s=store.size:desc&format=json
# 2. 오래된 불필요 인덱스 삭제
DELETE old-logs-2024-*
# 3. read-only 차단 해제 (모든 인덱스)
PUT _all/_settings
{
"index.blocks.read_only_allow_delete": null
}
# 4. 클러스터 레벨 차단 해제
PUT _cluster/settings
{
"persistent": {
"cluster.blocks.read_only": false
}
}
# 5. 클러스터 상태 확인
GET _cluster/health
디스크 사용률이 85%를 넘으면 OpenSearch는 자동으로 인덱스를 read-only로 전환한다. 이 임계치는
cluster.routing.allocation.disk.watermark.flood_stage설정으로 조정할 수 있지만, 근본적으로 디스크를 확보하는 것이 답이다.
5.3 스냅샷과 복구
예제 6: S3 스냅샷 리포지토리 등록 및 복원
// S3 리포지토리 등록
PUT _snapshot/s3-backup
{
"type": "s3",
"settings": {
"bucket": "my-opensearch-snapshots",
"base_path": "daily",
"region": "ap-northeast-2",
"server_side_encryption": true,
"role_arn": "arn:aws:iam::123456789012:role/OpenSearchSnapshotRole"
}
}
// 수동 스냅샷 생성
PUT _snapshot/s3-backup/snapshot-2026-03-04
{
"indices": "app-logs-*",
"ignore_unavailable": true,
"include_global_state": false
}
// 스냅샷 상태 확인
GET _snapshot/s3-backup/snapshot-2026-03-04/_status
// 특정 인덱스만 복원 (이름 변경하여 기존 인덱스와 충돌 방지)
POST _snapshot/s3-backup/snapshot-2026-03-04/_restore
{
"indices": "app-logs-000001,app-logs-000002",
"ignore_unavailable": true,
"include_global_state": false,
"rename_pattern": "(.+)",
"rename_replacement": "$1_restored",
"index_settings": {
"index.number_of_replicas": 0
}
}
// 복원 완료 후 레플리카 복원
PUT app-logs-000001_restored/_settings
{ "index.number_of_replicas": 1 }
include_global_state: false로 설정해야 보안 인덱스(.opendistro_security)와 충돌하지 않는다. 항상 이 옵션을 사용하라.
5.4 DR 전략 비교
| 전략 | RTO | RPO | 비용 | 복잡도 |
|---|---|---|---|---|
| 스냅샷/복원 | 수 시간 | 마지막 스냅샷 이후 데이터 | 낮음 | 낮음 |
| Cross-Cluster Replication | 수 분 | < 1분 지연 | 높음 (2× 클러스터) | 중간 |
| 이중 전송 (Active-Active) | 거의 0 | 거의 0 | 높음 (2× 클러스터 + 라우팅) | 높음 |
CCR 주의사항: 복제가 12시간 이상 일시 중지되면 resume이 불가능하다. 중지 후 처음부터 다시 시작해야 한다.
6. AWS OpenSearch Service 운영 체크리스트
6.1 인스턴스 타입 선택
| 용도 | 권장 인스턴스 | 비고 |
|---|---|---|
| 소규모 프로덕션 | r6g.large | 데이터 + 마스터 겸용 가능 |
| 범용 프로덕션 | r6g.xlarge ~ r6g.2xlarge | 가격 대비 성능 균형 |
| 로그 분석 (대용량) | im4gn.* | vCPU 대비 높은 스토리지 밀도 |
| 인덱싱 집약 분석 | OR1 인스턴스 | 가격 대비 성능 30% 향상 (2024+) |
| 전용 마스터 | 3대 × 3 AZ | 최신 세대, 항상 홀수 구성 |
| 프로덕션 금지 | t2.*, t3.small | 버스트 크레딧 소진 시 CPU 스로틀링 |
6.2 스토리지 티어 활용
| 티어 | 용도 | 백엔드 | 비용 (상대) |
|---|---|---|---|
| Hot | 활성 읽기/쓰기 | EBS (gp3 권장) | 100% |
| UltraWarm | 읽기 전용, 빈도 낮은 조회 | S3 + 캐시 | ~40% |
| Cold | 아카이브, 온디맨드 분석 | S3 | $0.024/GiB/월 |
- UltraWarm은 약 2.5 TiB 이상에서 비용 효율적이다.
- EBS는 gp3를 사용하라 — gp2 대비 9.6% 저렴하고 버스트 크레딧 걱정이 없다.
6.3 비용 최적화 전략
- ISM으로 자동 삭제 — 보존 기간이 지난 인덱스를 자동으로 정리한다.
- 인덱스 롤업 — 세분화된 데이터를 집계한 뒤 UltraWarm/Cold로 이동한다.
- 예약 인스턴스 — 14일 이상 안정 운영 후 구매. 1년 선결제 없음 ~30% 절감, 3년 전액 선결제 ~50% 절감.
- 미사용 인덱스 삭제 — 유지 보수 작업(스냅샷 등) 시에도 리소스를 소비한다.
- Auto-Tune 활성화 — JVM, 큐 크기, 캐시를 자동 최적화한다.
7. OpenSearch vs Elasticsearch 운영 관점 비교
| 항목 | OpenSearch | Elasticsearch |
|---|---|---|
| 라이선스 | Apache 2.0 (완전 오픈소스) | SSPL + Elastic License (7.11부터) |
| 보안 기능 | 무료 내장 (RBAC, FGAC, 감사 로그) | 유료 (Platinum+) |
| 알림 | 무료 플러그인 (Alerting) | Watcher (유료) |
| 이상 탐지 | 무료 플러그인 | ML (유료 Platinum+) |
| 수명주기 관리 | ISM (Index State Management) | ILM (Index Lifecycle Management) |
| SQL 지원 | 무료 플러그인 + PPL | 유료 기능 |
| 관리형 서비스 | AWS OpenSearch Service | Elastic Cloud (멀티 클라우드) |
| 벡터 검색 | k-NN 플러그인 (FAISS, nmslib, Lucene) | 네이티브 통합 (8.x HNSW) |
| 클라우드 | AWS 중심 | AWS, GCP, Azure 지원 |
| 릴리스 주기 | ~3개월 마이너 릴리스 | ~2주 패치, ~4개월 마이너 |
선택 기준 요약:
- OpenSearch: AWS 인프라 중심, 비용 민감, 오픈소스 라이선스 필수, 로그/관측성 워크로드
- Elasticsearch: Elastic APM/SIEM 필요, 멀티 클라우드 배포, 벡터 검색·RAG 집중, Elastic 에코시스템(Kibana, Beats, Logstash) 활용
8. Elasticsearch → OpenSearch 마이그레이션 체크리스트
8.1 마이그레이션 방법 비교
| 방법 | 다운타임 | 적합 상황 | 복잡도 |
|---|---|---|---|
| 스냅샷/복원 | 데이터 크기에 비례 | 단순 마이그레이션, 소규모 | 낮음 |
| 롤링 업그레이드 | 최소 | ES 6.8–7.10.2 → OS 1.x | 중간 |
| Remote Reindex | 없음 (라이브) | 버전 간 이동, 매핑 변경 필요 시 | 중간 |
| Migration Assistant | 거의 없음 | 대규모 클러스터, 다단계 버전 이동 | 낮음 (자동화) |
8.2 마이그레이션 전 체크리스트
- 버전 호환성 확인 — OpenSearch 1.x는 ES 7.10.2 기반. ES 7.11+ (SSPL)에서는 스냅샷/reindex 필요
- 플러그인 호환성 감사 — 서드파티 플러그인이 OpenSearch에서 지원되는지 확인
- API 경로 변경 —
_opendistro/→_plugins/(보안, 알림, ISM 등 모든 플러그인 API) - 클라이언트 라이브러리 교체 —
elasticsearch-py→opensearch-py,elasticsearch-js→opensearch-js - Kibana → OpenSearch Dashboards 참조 이름 전수 변경
- 전체 스냅샷 백업 — 마이그레이션 시작 전 필수
- 기능 동등성 검증 — 사용 중인 기능이 OpenSearch에 존재하는지 확인 (공식 호환성 매트릭스 참조)
- 성능 벤치마크 — 동일 쿼리 세트로 기존 환경과 신규 환경 비교 측정
- 롤백 계획 수립 — 마이그레이션 실패 시 원래 클러스터로 복귀하는 절차 문서화
- 보안 구성 마이그레이션 —
securityadmin.sh또는 API로 역할/사용자/테넌트 재생성
8.3 Remote Reindex 실전 예제
예제 7: 원격 클러스터에서 실시간 Reindex
POST _reindex
{
"source": {
"remote": {
"host": "https://old-es-cluster:9200",
"username": "admin",
"password": "********",
"socket_timeout": "60m",
"connect_timeout": "30s"
},
"index": "app-logs-2025-*",
"size": 5000,
"query": {
"range": {
"@timestamp": {
"gte": "2025-01-01",
"lt": "2026-01-01"
}
}
}
},
"dest": {
"index": "app-logs-migrated"
},
"conflicts": "proceed"
}
주의:
reindex.remote.allowlist설정에 원본 호스트를 등록해야 한다.- 대규모 마이그레이션은
wait_for_completion=false로 비동기 실행하고GET _tasks/{task_id}로 진행률을 모니터링한다. size: 5000은 한 번에 가져오는 scroll 크기다. 네트워크 대역폭에 따라 조정한다.
8.4 API 경로 변경표
| Elasticsearch / Open Distro | OpenSearch |
|---|---|
_opendistro/_security/ | _plugins/_security/ |
_opendistro/_alerting/ | _plugins/_alerting/ |
_opendistro/_ism/ | _plugins/_ism/ |
_opendistro/_anomaly_detection/ | _plugins/_anomaly_detection/ |
_opendistro/_sql/ | _plugins/_sql/ |
_opendistro/_ppl/ | _plugins/_ppl/ |
_opendistro/_knn/ | _plugins/_knn/ |
9. 성능 튜닝 빠른 참조
| 설정 | 기본값 | 권장값 | 효과 |
|---|---|---|---|
index.refresh_interval | 1s | 30s 이상 (로그) | CPU/IO 절감, 검색 가시성 지연 |
index.translog.flush_threshold_size | 512 MiB | JVM 힙의 25% | flush 빈도 감소 |
index.merge.policy | tiered | log_byte_size (시계열) | @timestamp 범위 쿼리 성능 개선 |
index.codec | LZ4 | zstd_no_dict | 디스크 25–30% 절감 |
| 벌크 요청 크기 | — | 5–15 MiB | 5 MiB부터 시작, 성능 정체 시점까지 증가 |
| 벌크 배치 수 | — | 5,000–10,000 docs/_bulk | 단건 호출 대비 극적인 처리량 향상 |
indices.memory.index_buffer_size | 10% | 10–15% | 인덱싱 집약 시 버퍼 확대 |
search.max_buckets | 65535 | 워크로드에 따라 | 집계 쿼리 안전 제한 |
벌크 인덱싱 최적화 스크립트:
from opensearchpy import OpenSearch, helpers
client = OpenSearch(
hosts=[{"host": "opensearch-node", "port": 9200}],
http_auth=("admin", "admin"),
use_ssl=True,
verify_certs=False,
)
def generate_actions(file_path):
"""로그 파일을 읽어 _bulk 액션으로 변환"""
import json
with open(file_path) as f:
for line in f:
doc = json.loads(line)
yield {
"_index": "app-logs-write",
"_source": doc,
}
success, errors = helpers.bulk(
client,
generate_actions("/var/log/app/events.jsonl"),
chunk_size=5000,
max_retries=3,
request_timeout=60,
)
print(f"Indexed: {success}, Errors: {len(errors)}")
10. 보안 강화 체크리스트
| 항목 | 설정 | 비고 |
|---|---|---|
| 전송 암호화 | TLS 1.2+ 필수 | 노드 간, 클라이언트 간 모두 |
| 인증 | SAML / OIDC / Internal DB | AWS는 Cognito 또는 SAML 연동 |
| FGAC | 인덱스·필드·문서 수준 접근 제어 | _plugins/_security/api/roles |
| 감사 로그 | 읽기/쓰기 접근 기록 | 컴플라이언스 요건 시 필수 |
| IP 기반 접근 제어 | VPC + Security Group | 퍼블릭 엔드포인트 사용 금지 |
| API 키 관리 | 주기적 로테이션 | 90일 주기 권장 |
마무리: 운영자가 기억해야 할 7가지
- 샤드 크기는 30–50 GiB를 기준으로 — 너무 작으면 메타데이터 오버헤드, 너무 크면 복구 지연.
- ISM은 선제적으로 설정 — 디스크가 차고 나서 만드는 것이 아니라 인덱스 생성과 동시에 적용한다.
- 모니터링은 계층적으로 — 클러스터 상태 → JVM/CPU → 스레드풀 거절 → 슬로우 로그 순서로 파악한다.
- 전용 마스터 노드 3대는 협상 불가 — Split Brain을 원천 차단하는 유일한 방법이다.
- 스냅샷은 매일, 복원 테스트는 분기별 — 복원을 실제로 해보지 않은 백업은 백업이 아니다.
- dynamic: strict는 기본값 — 매핑 폭발은 예방만이 답이다. 사후 대응은 reindex뿐이다.
- 디스크 워터마크를 항상 주시 — 75%에서 경고, 85%에서 read-only. ISM 자동 삭제가 유일한 안전망이다.
References
- OpenSearch - Choosing the number of shards (AWS)
- OpenSearch - Operational best practices (AWS)
- OpenSearch - Index State Management
- OpenSearch - ISM Policies
- OpenSearch - Index templates
- OpenSearch - Data streams
- OpenSearch - Tuning for indexing speed
- OpenSearch - Alerting
- OpenSearch - Security configuration
- AWS - Recommended CloudWatch alarms for OpenSearch
- AWS - Sizing OpenSearch Service domains
- AWS - Multi-tier storage for OpenSearch
- AWS - Cross-cluster replication
- AWS - Migrating to Amazon OpenSearch Service
- OpenSearch - Migrate or upgrade
- OpenSearch - Remote reindex
- Benchmarking OpenSearch and Elasticsearch - Trail of Bits (2025)
OpenSearch Operations, Management, and Index Design Practical Guide 2026
- Why OpenSearch Operations Expertise Matters
- 1. Production Cluster Architecture
- 2. Index Design and Shard Strategy
- 3. ISM (Index State Management) Lifecycle Management
- 4. Monitoring and Alert Configuration
- 5. Incident Scenarios and Recovery Strategies
- 6. AWS OpenSearch Service Operations Checklist
- 7. OpenSearch vs Elasticsearch Operations Perspective Comparison
- 8. Elasticsearch to OpenSearch Migration Checklist
- 9. Performance Tuning Quick Reference
- 10. Security Hardening Checklist
- Conclusion: 7 Things Every Operator Must Remember
- References
- Quiz
Why OpenSearch Operations Expertise Matters
OpenSearch is a core engine for log analytics, full-text search, and observability pipelines. Spinning up a cluster is easy, but operating it stably while controlling costs is an entirely different challenge. If the shard count for a single index is set incorrectly, the JVM heap of the entire cluster can become unstable. If a single ISM policy is missing, the disk fills up and writes stop.
This article covers the decision points that operators encounter in actual production, in the following order: cluster architecture, index design, ISM lifecycle, monitoring, incident response, security, and migration. All API examples are based on OpenSearch 2.x and can also be used on AWS OpenSearch Service.
1. Production Cluster Architecture
1.1 Node Role Separation
In production clusters, clearly separating each node type's role is essential for achieving both stability and performance.
+------------------------------------------------------------------+
| Client / Data Prepper |
| (Ingest Pipeline) |
+------------------------------+-----------------------------------+
|
+--------------------+--------------------+
v v v
+------------+ +------------+ +------------+
| Master-1 | | Master-2 | | Master-3 |
| (AZ-a) | | (AZ-b) | | (AZ-c) |
| Dedicated | | Dedicated | | Dedicated |
+------------+ +------------+ +------------+
| | |
v v v
+------------+ +------------+ +------------+
| Hot-1 | | Hot-2 | | Hot-3 |
| (AZ-a) | | (AZ-b) | | (AZ-c) |
| EBS gp3 | | EBS gp3 | | EBS gp3 |
+------------+ +------------+ +------------+
| | |
v v v
+------------+ +------------+ +------------+
| Warm-1 | | Warm-2 | | Warm-3 |
| UltraWarm | | UltraWarm | | UltraWarm |
| (S3+cache) | | (S3+cache) | | (S3+cache) |
+------------+ +------------+ +------------+
| Node Type | Role | Recommended Specs | Placement Rule |
|---|---|---|---|
| Dedicated Master | Cluster metadata management, shard allocation | Minimum 3 (odd number), latest gen instances | Must be distributed across 3 AZs |
| Hot Data Node | Active read/write processing | High CPU + fast storage (gp3) | Even distribution across AZs |
| Warm Data Node | Read-heavy, infrequent queries | High storage density, low CPU | UltraWarm (S3-based) |
| Coordinating Node | Request routing, result aggregation | High CPU + memory, minimal storage | Only for search-intensive workloads |
1.2 Capacity Sizing Formula
The core formula for production cluster sizing.
Required Storage = Daily Raw Data
x (1 + Number of Replicas)
x 1.1 (Indexing Overhead)
x 1.15 (OS Reserved + Internal Operations Margin)
x Retention Period (days)
Example: 100 GiB daily logs, 1 replica, 30-day retention: 100 x 2 x 1.1 x 1.15 x 30 = 7,590 GiB
Disk usage should be maintained at 75% maximum. Above 75%, OpenSearch begins rejecting new shard allocations, and at 85% it switches to read-only.
Actual Provisioning = Required Storage / 0.75 = 10,120 GiB
2. Index Design and Shard Strategy
2.1 Shard Sizing Standards
Shard size varies by workload type. The recommended ranges from OpenSearch official documentation and AWS guides are as follows.
| Workload | Recommended Shard Size | Rationale |
|---|---|---|
| Latency-sensitive search (e-commerce, autocomplete) | 10-30 GiB | Minimize latency, fast recovery |
| Write-heavy / log analytics | 30-50 GiB | Maximize write throughput |
| Maximum recommended limit | 50 GiB | Recovery/reallocation time spikes above this |
Primary shard count formula:
Primary Shard Count = (Raw Data Size + Growth Margin) x 1.1 / Target Shard Size
Example: 66 GiB daily logs, 4x growth expected, target shard size 30 GiB
(66 + 198) x 1.1 / 30 = approx. 10 primary shards
2.2 Per-Node Shard Limits
| Constraint | Limit |
|---|---|
| Per JVM heap | 25 shards per 1 GiB of heap |
| Per CPU | 1.5 vCPUs per shard (initial sizing) |
| OpenSearch 2.15 or earlier | Maximum 1,000 shards per node |
| OpenSearch 2.17+ | 1,000 shards per 16 GiB JVM heap, max 4,000 per node |
Key principle: Set the shard count as a multiple of data node count for even distribution. For example, with 3 data nodes, set primary shards to 3, 6, 9, 12, etc.
2.3 Index Templates and Mapping Design
Example 1: Composable Index Template
Index templates ensure consistent settings and mappings for all new indexes. Using dynamic: strict to prevent unexpected field additions (mapping explosion) is critical.
PUT _index_template/app-logs-template
{
"index_patterns": ["app-logs-*"],
"template": {
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"refresh_interval": "30s",
"index.translog.flush_threshold_size": "1024mb",
"index.codec": "zstd_no_dict",
"plugins.index_state_management.rollover_alias": "app-logs-write"
},
"mappings": {
"dynamic": "strict",
"properties": {
"@timestamp": { "type": "date" },
"level": { "type": "keyword" },
"message": { "type": "text", "analyzer": "standard" },
"service": { "type": "keyword" },
"trace_id": { "type": "keyword" },
"span_id": { "type": "keyword" },
"host": { "type": "keyword" },
"duration_ms": { "type": "float" },
"http_status": { "type": "short" },
"request_path":{ "type": "keyword" },
"user_id": { "type": "keyword" },
"metadata": {
"type": "object",
"enabled": false
}
}
}
},
"composed_of": ["common-settings"],
"priority": 200
}
Setting points explained:
refresh_interval: 30s-- Increased from the default 1 second to reduce CPU/IO load. Suitable for log workloads where real-time search is not required.translog.flush_threshold_size: 1024mb-- Set to 25% of JVM heap to reduce flush frequency.dynamic: strict-- Rejects indexing of undefined fields. This is the key to preventing mapping explosion.index.codec: zstd_no_dict-- zstd compression available in OpenSearch 2.9+. Achieves 25-30% storage savings compared to default LZ4.metadata.enabled: false-- Disables indexing for nested objects that are not searched, saving resources.
2.4 Replica Strategy
| Configuration | Recommended Replicas | Reason |
|---|---|---|
| Single AZ | 1 | Data protection against node failure |
| Multi-AZ (2 AZ) | 1 | Service from other AZ during AZ failure |
| Multi-AZ with Standby (3 AZ) | 2 | 100% data availability during AZ failure |
| During bulk indexing | 0 (temporary) | Maximize indexing speed, then restore |
Replica 0 operation pattern: Setting replicas to 0 during initial bulk loading and restoring afterward can improve indexing speed by up to 2x.
// Before indexing: disable replicas
PUT my-bulk-index/_settings
{ "index.number_of_replicas": 0 }
// After indexing complete: restore replicas
PUT my-bulk-index/_settings
{ "index.number_of_replicas": 1 }
2.5 Data Stream vs Traditional Index Pattern
OpenSearch 2.6+ supports Data Streams. Compared to the traditional Rollover + Alias pattern:
| Item | Rollover + Alias | Data Stream |
|---|---|---|
| Initial Setup | Manual initial index + alias creation | Only register index template |
| Write Target | Write alias | Use data stream name directly |
| Backing Index Name | app-logs-000001 | .ds-app-logs-000001 |
| Deletion | Per index | DELETE _data_stream/app-logs |
| Recommended Workload | General purpose | Time-series (append-only) only |
3. ISM (Index State Management) Lifecycle Management
ISM is OpenSearch's index lifecycle automation engine. It corresponds to Elasticsearch's ILM, automatically handling the Hot, Warm, Cold, Delete flow.
3.1 Hot-Warm-Cold-Delete Full Policy
Example 2: ISM Full Lifecycle Policy
PUT _plugins/_ism/policies/app-log-lifecycle
{
"policy": {
"description": "App log index lifecycle: hot -> warm -> cold -> delete",
"default_state": "hot",
"states": [
{
"name": "hot",
"actions": [
{
"rollover": {
"min_size": "30gb",
"min_index_age": "1d",
"min_doc_count": 10000000
}
}
],
"transitions": [
{ "state_name": "warm", "conditions": { "min_index_age": "3d" } }
]
},
{
"name": "warm",
"actions": [
{ "replica_count": { "number_of_replicas": 1 } },
{ "force_merge": { "max_num_segments": 1 } },
{ "allocation": {
"require": { "temp": "warm" },
"wait_for": true
}
}
],
"transitions": [
{ "state_name": "cold", "conditions": { "min_index_age": "30d" } }
]
},
{
"name": "cold",
"actions": [
{ "replica_count": { "number_of_replicas": 0 } },
{ "read_only": {} }
],
"transitions": [
{ "state_name": "delete", "conditions": { "min_index_age": "90d" } }
]
},
{
"name": "delete",
"actions": [
{
"notification": {
"destination": {
"slack": { "url": "https://hooks.slack.com/services/T.../B.../xxx" }
},
"message_template": {
"source": "Index {{ctx.index}} will be deleted according to retention policy (90 days)."
}
}
},
{ "delete": {} }
],
"transitions": []
}
],
"ism_template": [
{ "index_patterns": ["app-logs-*"], "priority": 100 }
]
}
}
Operational notes:
- The default ISM check interval is 5 minutes. It can be adjusted via
plugins.index_state_management.job_interval. - Setting the
ism_templatefield automatically attaches the policy to new indexes matching the pattern. - Rollover conditions (
min_size,min_index_age,min_doc_count) are OR conditions -- rollover executes when any one is met. allocation.require.temp: warm-- Moves shards to nodes with thenode.attr.temp=warmattribute.- After force_merge, no further writes should be made to that index.
3.2 ISM Operation Commands
# Check policy execution status (specific index)
GET _plugins/_ism/explain/app-logs-000001
# Attach policy to existing indexes
POST _plugins/_ism/add/old-logs-2025-*
{ "policy_id": "app-log-lifecycle" }
# Update policy version (apply new policy to managed indexes)
POST _plugins/_ism/change_policy/app-logs-*
{
"policy_id": "app-log-lifecycle-v2",
"state": "warm"
}
# Retry failed ISM operations
POST _plugins/_ism/retry/app-logs-000003
# Remove ISM policy from a specific index
POST _plugins/_ism/remove/app-logs-000005
3.3 Rollover + Alias Pattern Practical Setup
Example 3: Rollover Alias Initial Setup and Operation Flow
This is the standard pattern for time-series index operations. The write alias always points to the latest index, and ISM rollover creates and switches to new indexes.
// Step 1: Create initial index + alias setup
PUT app-logs-000001
{
"aliases": {
"app-logs-write": { "is_write_index": true },
"app-logs-read": {}
}
}
// Step 2: ISM policy's rollover action handles automatically
// -> Creates app-logs-000002
// -> Moves app-logs-write alias to 000002
// -> Sets 000001's is_write_index to false
// Reads always use the read alias (targets all history)
GET app-logs-read/_search
{
"query": {
"bool": {
"must": [
{ "match": { "level": "ERROR" } },
{ "range": { "@timestamp": { "gte": "now-1h" } } }
]
}
},
"sort": [{ "@timestamp": "desc" }],
"size": 100
}
// Writes always use the write alias
POST app-logs-write/_doc
{
"@timestamp": "2026-03-04T10:00:00Z",
"level": "ERROR",
"service": "payment-api",
"message": "Payment gateway timeout occurred",
"trace_id": "abc123",
"duration_ms": 30500,
"http_status": 504
}
Alias status check:
# Check current write index
GET _alias/app-logs-write
# Check full alias structure
GET _cat/aliases/app-logs-*?v&h=alias,index,is_write_index
4. Monitoring and Alert Configuration
4.1 Essential Monitoring Metrics and Thresholds
| Category | Metric | Threshold | Severity |
|---|---|---|---|
| Cluster Status | ClusterStatus.red | 1 or more (1 min, 1 occurrence) | Critical |
| Cluster Status | ClusterStatus.yellow | 1 or more (1 min, 5 consecutive) | Warning |
| Write Blocking | ClusterIndexWritesBlocked | 1 or more (5 min, 1 occurrence) | Critical |
| Node Count | Nodes | under expected count (1 day, 1 occurrence) | Critical |
| Free Disk | FreeStorageSpace | 25% or less of node storage | Warning |
| JVM Heap | JVMMemoryPressure | 95% or more (1 min, 3 consecutive) | Critical |
| Old Gen JVM | OldGenJVMMemoryPressure | 80% or more (1 min, 3 consecutive) | Warning |
| CPU Utilization | CPUUtilization | 80% or more (15 min, 3 consecutive) | Warning |
| Master CPU | MasterCPUUtilization | 50% or more (15 min, 3 consecutive) | Warning |
| Write Threadpool | ThreadpoolWriteRejected | 1 or more (SUM, delta) | Warning |
| Search Threadpool | ThreadpoolSearchRejected | 1 or more (SUM, delta) | Warning |
| Snapshot Failure | AutomatedSnapshotFailure | 1 or more (1 min, 1 occurrence) | Critical |
4.2 Cluster Status Check APIs
Example 4: Comprehensive Cluster Health Check Command Collection
# Overall cluster status (green/yellow/red)
GET _cluster/health
# Per-index status check
GET _cluster/health?level=indices
# Per-node JVM, CPU, disk details
GET _nodes/stats/jvm,os,fs
# Hot threads check (for high CPU root cause analysis)
GET _nodes/hot_threads
# Thread pool status (check pending and rejected counts)
GET _cat/thread_pool?v&h=node_name,name,active,queue,rejected
# Shard allocation status
GET _cat/shards?v&h=index,shard,prirep,state,docs,store,node&s=state
# Unassigned shard root cause analysis
GET _cluster/allocation/explain
{
"index": "app-logs-000003",
"shard": 0,
"primary": true
}
# Per-index size and document count
GET _cat/indices?v&h=index,health,pri,rep,docs.count,store.size&s=store.size:desc
# Per-node disk usage
GET _cat/allocation?v&h=node,shards,disk.used,disk.avail,disk.percent
# Check pending tasks
GET _cat/pending_tasks?v
4.3 Catching Bottleneck Queries with Slow Log
PUT app-logs-*/_settings
{
"index.search.slowlog.threshold.query.warn": "10s",
"index.search.slowlog.threshold.query.info": "5s",
"index.search.slowlog.threshold.fetch.warn": "5s",
"index.indexing.slowlog.threshold.index.warn": "10s",
"index.indexing.slowlog.threshold.index.info": "5s"
}
Setting the warn level to 10 seconds and info level to 5 seconds makes queries taking over 10 seconds immediate alerting targets. Analyze these logs in OpenSearch Dashboards' Discover or collect them into a separate index to track trends.
4.4 OpenSearch Alerting Configuration
Example 5: Cluster Status Monitor + Slack Alert
POST _plugins/_alerting/monitors
{
"type": "monitor",
"name": "Cluster Red Status Alert",
"monitor_type": "query_level_monitor",
"enabled": true,
"schedule": {
"period": { "interval": 1, "unit": "MINUTES" }
},
"inputs": [
{
"search": {
"indices": [".opensearch-sap-log-types-config"],
"query": {
"size": 0,
"query": {
"match_all": {}
}
}
}
}
],
"triggers": [
{
"query_level_trigger": {
"name": "Cluster Status Red",
"severity": "1",
"condition": {
"script": {
"source": "ctx.results[0].hits.total.value >= 0",
"lang": "painless"
}
},
"actions": [
{
"name": "Notify Slack",
"destination_id": "slack-destination-id",
"message_template": {
"source": "Cluster status is RED.\nCluster: {{ctx.monitor.name}}\nTime: {{ctx.periodEnd}}\nImmediate investigation required."
},
"throttle_enabled": true,
"throttle": { "value": 10, "unit": "MINUTES" }
}
]
}
}
]
}
Practical tip: In actual operations, external monitoring that periodically calls the _cluster/health API (Prometheus + Grafana, Datadog, etc.) is more reliable. OpenSearch's built-in alerting may not function when the cluster itself is unstable.
5. Incident Scenarios and Recovery Strategies
5.1 Response by Major Incident Type
| Incident | Cause | Impact | Immediate Response |
|---|---|---|---|
| Red Cluster | Unassigned primary shards | Data loss risk, write failure on affected index | Analyze cause with _cluster/allocation/explain |
| Yellow Cluster | Unassigned replicas | Redundancy broken, data loss risk on node failure | Add nodes or adjust replica count |
| Disk Full | Insufficient storage | ClusterBlockException, all writes blocked | Delete old indexes, review ISM policies |
| JVM OOM | Heap pressure over 95% | Node crash, cascading failures | Kill heavy queries, clear caches |
| Split Brain | Fewer than 3 master nodes + network partition | Data inconsistency | Dedicated master nodes (3 across 3 AZs) is mandatory |
| AZ Failure | AWS infrastructure issue | Data loss if replicas insufficient | Multi-AZ + 2 replicas configuration |
| Mapping Explosion | dynamic mapping + unstructured fields | Heap spike, indexing delays | Switch to dynamic: strict or dynamic: false |
5.2 Disk Full Emergency Recovery
When the cluster has switched to read-only, recover in the following order.
# 1. Identify the largest indexes
GET _cat/indices?v&h=index,store.size&s=store.size:desc&format=json
# 2. Delete old unnecessary indexes
DELETE old-logs-2024-*
# 3. Remove read-only block (all indexes)
PUT _all/_settings
{
"index.blocks.read_only_allow_delete": null
}
# 4. Remove cluster-level block
PUT _cluster/settings
{
"persistent": {
"cluster.blocks.read_only": false
}
}
# 5. Check cluster status
GET _cluster/health
When disk usage exceeds 85%, OpenSearch automatically switches indexes to read-only. This threshold can be adjusted via the
cluster.routing.allocation.disk.watermark.flood_stagesetting, but fundamentally freeing up disk space is the answer.
5.3 Snapshots and Recovery
Example 6: S3 Snapshot Repository Registration and Restoration
// Register S3 repository
PUT _snapshot/s3-backup
{
"type": "s3",
"settings": {
"bucket": "my-opensearch-snapshots",
"base_path": "daily",
"region": "ap-northeast-2",
"server_side_encryption": true,
"role_arn": "arn:aws:iam::123456789012:role/OpenSearchSnapshotRole"
}
}
// Create manual snapshot
PUT _snapshot/s3-backup/snapshot-2026-03-04
{
"indices": "app-logs-*",
"ignore_unavailable": true,
"include_global_state": false
}
// Check snapshot status
GET _snapshot/s3-backup/snapshot-2026-03-04/_status
// Restore specific indexes only (rename to avoid conflicts with existing indexes)
POST _snapshot/s3-backup/snapshot-2026-03-04/_restore
{
"indices": "app-logs-000001,app-logs-000002",
"ignore_unavailable": true,
"include_global_state": false,
"rename_pattern": "(.+)",
"rename_replacement": "$1_restored",
"index_settings": {
"index.number_of_replicas": 0
}
}
// Restore replicas after restoration is complete
PUT app-logs-000001_restored/_settings
{ "index.number_of_replicas": 1 }
Set
include_global_state: falseto avoid conflicts with security indexes (.opendistro_security). Always use this option.
5.4 DR Strategy Comparison
| Strategy | RTO | RPO | Cost | Complexity |
|---|---|---|---|---|
| Snapshot/Restore | Hours | Data since last snapshot | Low | Low |
| Cross-Cluster Replication | Minutes | Under 1 min lag | High (2x cluster) | Medium |
| Dual Ingestion (Active-Active) | Near zero | Near zero | High (2x cluster + routing) | High |
CCR caveat: If replication is paused for more than 12 hours, it cannot be resumed. It must be restarted from scratch.
6. AWS OpenSearch Service Operations Checklist
6.1 Instance Type Selection
| Use Case | Recommended Instance | Notes |
|---|---|---|
| Small production | r6g.large | Can serve as combined data + master |
| General production | r6g.xlarge to r6g.2xlarge | Price-performance balance |
| Log analytics (large) | im4gn.* | High storage density per vCPU |
| Indexing-intensive | OR1 instances | 30% better price-performance (2024+) |
| Dedicated Master | 3 instances across 3 AZs | Latest generation, always odd count |
| Not for production | t2.*, t3.small | CPU throttling when burst credits deplete |
6.2 Storage Tier Utilization
| Tier | Use Case | Backend | Cost (Relative) |
|---|---|---|---|
| Hot | Active read/write | EBS (gp3 recommended) | 100% |
| UltraWarm | Read-only, infrequent queries | S3 + cache | ~40% |
| Cold | Archive, on-demand analytics | S3 | $0.024/GiB/month |
- UltraWarm becomes cost-effective above approximately 2.5 TiB.
- Use gp3 for EBS -- 9.6% cheaper than gp2 with no burst credit concerns.
6.3 Cost Optimization Strategies
- Automatic deletion with ISM -- Automatically clean up indexes past their retention period.
- Index rollups -- Aggregate granular data then move to UltraWarm/Cold.
- Reserved Instances -- Purchase after 14+ days of stable operation. 1-year no upfront saves ~30%, 3-year all upfront saves ~50%.
- Delete unused indexes -- They consume resources even during maintenance tasks (snapshots, etc.).
- Enable Auto-Tune -- Automatically optimizes JVM, queue sizes, and caches.
7. OpenSearch vs Elasticsearch Operations Perspective Comparison
| Category | OpenSearch | Elasticsearch |
|---|---|---|
| License | Apache 2.0 (fully open source) | SSPL + Elastic License (from 7.11) |
| Security | Free built-in (RBAC, FGAC, audit log) | Paid (Platinum+) |
| Alerting | Free plugin (Alerting) | Watcher (paid) |
| Anomaly Detection | Free plugin | ML (paid Platinum+) |
| Lifecycle Mgmt | ISM (Index State Management) | ILM (Index Lifecycle Management) |
| SQL Support | Free plugin + PPL | Paid feature |
| Managed Service | AWS OpenSearch Service | Elastic Cloud (multi-cloud) |
| Vector Search | k-NN plugin (FAISS, nmslib, Lucene) | Native integration (8.x HNSW) |
| Cloud | AWS-centric | AWS, GCP, Azure support |
| Release Cycle | ~3 month minor releases | ~2 week patches, ~4 month minor |
Selection criteria summary:
- OpenSearch: AWS infrastructure-centric, cost-sensitive, open-source license required, log/observability workloads
- Elasticsearch: Elastic APM/SIEM needed, multi-cloud deployment, vector search/RAG focus, Elastic ecosystem (Kibana, Beats, Logstash) utilization
8. Elasticsearch to OpenSearch Migration Checklist
8.1 Migration Method Comparison
| Method | Downtime | Best For | Complexity |
|---|---|---|---|
| Snapshot/Restore | Proportional to data | Simple migration, small scale | Low |
| Rolling Upgrade | Minimal | ES 6.8-7.10.2 to OS 1.x | Medium |
| Remote Reindex | None (live) | Cross-version moves, mapping changes | Medium |
| Migration Assistant | Near zero | Large clusters, multi-step version moves | Low (automated) |
8.2 Pre-Migration Checklist
- Version compatibility check -- OpenSearch 1.x is based on ES 7.10.2. ES 7.11+ (SSPL) requires snapshot/reindex
- Plugin compatibility audit -- Verify third-party plugins are supported in OpenSearch
- API path changes --
_opendistro/to_plugins/(security, alerting, ISM, and all plugin APIs) - Client library replacement --
elasticsearch-pytoopensearch-py,elasticsearch-jstoopensearch-js - Kibana to OpenSearch Dashboards -- Complete all reference name changes
- Full snapshot backup -- Required before starting migration
- Feature parity verification -- Confirm features in use exist in OpenSearch (refer to official compatibility matrix)
- Performance benchmark -- Measure with identical query sets on old and new environments
- Rollback plan -- Document the procedure for reverting to the original cluster if migration fails
- Security configuration migration -- Recreate roles/users/tenants via
securityadmin.shor API
8.3 Remote Reindex Practical Example
Example 7: Live Reindex from Remote Cluster
POST _reindex
{
"source": {
"remote": {
"host": "https://old-es-cluster:9200",
"username": "admin",
"password": "********",
"socket_timeout": "60m",
"connect_timeout": "30s"
},
"index": "app-logs-2025-*",
"size": 5000,
"query": {
"range": {
"@timestamp": {
"gte": "2025-01-01",
"lt": "2026-01-01"
}
}
}
},
"dest": {
"index": "app-logs-migrated"
},
"conflicts": "proceed"
}
Caution:
- The source host must be registered in the
reindex.remote.allowlistsetting. - For large-scale migrations, run asynchronously with
wait_for_completion=falseand monitor progress withGET _tasks/{task_id}. size: 5000is the scroll size per fetch. Adjust based on network bandwidth.
8.4 API Path Change Reference
| Elasticsearch / Open Distro | OpenSearch |
|---|---|
_opendistro/_security/ | _plugins/_security/ |
_opendistro/_alerting/ | _plugins/_alerting/ |
_opendistro/_ism/ | _plugins/_ism/ |
_opendistro/_anomaly_detection/ | _plugins/_anomaly_detection/ |
_opendistro/_sql/ | _plugins/_sql/ |
_opendistro/_ppl/ | _plugins/_ppl/ |
_opendistro/_knn/ | _plugins/_knn/ |
9. Performance Tuning Quick Reference
| Setting | Default | Recommended | Effect |
|---|---|---|---|
index.refresh_interval | 1s | 30s or more (for logs) | CPU/IO savings, delayed search visibility |
index.translog.flush_threshold_size | 512 MiB | 25% of JVM heap | Reduced flush frequency |
index.merge.policy | tiered | log_byte_size (time-series) | Improved @timestamp range query performance |
index.codec | LZ4 | zstd_no_dict | 25-30% disk savings |
| Bulk request size | -- | 5-15 MiB | Start at 5 MiB, increase until performance plateaus |
| Bulk batch count | -- | 5,000-10,000 docs/_bulk | Dramatic throughput improvement over single calls |
indices.memory.index_buffer_size | 10% | 10-15% | Expanded buffer for indexing-intensive workloads |
search.max_buckets | 65535 | Depends on workload | Safety limit for aggregation queries |
Bulk indexing optimization script:
from opensearchpy import OpenSearch, helpers
client = OpenSearch(
hosts=[{"host": "opensearch-node", "port": 9200}],
http_auth=("admin", "admin"),
use_ssl=True,
verify_certs=False,
)
def generate_actions(file_path):
"""Reads a log file and converts to _bulk actions"""
import json
with open(file_path) as f:
for line in f:
doc = json.loads(line)
yield {
"_index": "app-logs-write",
"_source": doc,
}
success, errors = helpers.bulk(
client,
generate_actions("/var/log/app/events.jsonl"),
chunk_size=5000,
max_retries=3,
request_timeout=60,
)
print(f"Indexed: {success}, Errors: {len(errors)}")
10. Security Hardening Checklist
| Item | Setting | Notes |
|---|---|---|
| Transport Encryption | TLS 1.2+ required | Both inter-node and client |
| Authentication | SAML / OIDC / Internal DB | AWS uses Cognito or SAML |
| FGAC | Index/field/document level access | _plugins/_security/api/roles |
| Audit Log | Read/write access recording | Required for compliance |
| IP-Based Access | VPC + Security Group | Never use public endpoints |
| API Key Management | Periodic rotation | 90-day cycle recommended |
Conclusion: 7 Things Every Operator Must Remember
- Target 30-50 GiB shard size -- Too small means metadata overhead, too large means recovery delays.
- Set up ISM proactively -- Apply it when indexes are created, not after the disk fills up.
- Monitor in layers -- Cluster status, then JVM/CPU, then thread pool rejections, then slow logs.
- 3 dedicated master nodes are non-negotiable -- The only way to prevent split brain at its source.
- Snapshot daily, test restoration quarterly -- A backup that has never been restored is not a backup.
- dynamic: strict should be the default -- Prevention is the only answer for mapping explosion. The only remediation is reindex.
- Always watch disk watermarks -- Warning at 75%, read-only at 85%. ISM auto-deletion is the only safety net.
References
- OpenSearch - Choosing the number of shards (AWS)
- OpenSearch - Operational best practices (AWS)
- OpenSearch - Index State Management
- OpenSearch - ISM Policies
- OpenSearch - Index templates
- OpenSearch - Data streams
- OpenSearch - Tuning for indexing speed
- OpenSearch - Alerting
- OpenSearch - Security configuration
- AWS - Recommended CloudWatch alarms for OpenSearch
- AWS - Sizing OpenSearch Service domains
- AWS - Multi-tier storage for OpenSearch
- AWS - Cross-cluster replication
- AWS - Migrating to Amazon OpenSearch Service
- OpenSearch - Migrate or upgrade
- OpenSearch - Remote reindex
- Benchmarking OpenSearch and Elasticsearch - Trail of Bits (2025)
Quiz
Q1: What is the main topic covered in "OpenSearch Operations, Management, and Index Design
Practical Guide 2026"?
A comprehensive guide for OpenSearch cluster operators covering index design, shard/replica strategies, ISM rollover lifecycle, monitoring and alerting, incident response, security hardening, and migration with practical API examples.
Q2: Why OpenSearch Operations Expertise Matters?
OpenSearch is a core engine for log analytics, full-text search, and observability pipelines.
Spinning up a cluster is easy, but operating it stably while controlling costs is an entirely
different challenge.
Q3: Describe the Production Cluster Architecture.
1.1 Node Role Separation In production clusters, clearly separating each node type's role is
essential for achieving both stability and performance. 1.2 Capacity Sizing Formula The core
formula for production cluster sizing.
Q4: Describe the Index Design and Shard Strategy.
2.1 Shard Sizing Standards Shard size varies by workload type. The recommended ranges from
OpenSearch official documentation and AWS guides are as follows.
Q5: How does ISM (Index State Management) Lifecycle Management work?
ISM is OpenSearch's index lifecycle automation engine. It corresponds to Elasticsearch's ILM,
automatically handling the Hot, Warm, Cold, Delete flow. 3.1 Hot-Warm-Cold-Delete Full Policy
Operational notes: The default ISM check interval is 5 minutes.