- Authors
- Name
- 왜 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)