왜 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 비용 최적화 전략
1. **ISM으로 자동 삭제** — 보존 기간이 지난 인덱스를 자동으로 정리한다.
2. **인덱스 롤업** — 세분화된 데이터를 집계한 뒤 UltraWarm/Cold로 이동한다.
3. **예약 인스턴스** — 14일 이상 안정 운영 후 구매. 1년 선결제 없음 ~30% 절감, 3년 전액 선결제 ~50% 절감.
4. **미사용 인덱스 삭제** — 유지 보수 작업(스냅샷 등) 시에도 리소스를 소비한다.
5. **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 액션으로 변환"""
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가지
1. **샤드 크기는 30–50 GiB를 기준으로** — 너무 작으면 메타데이터 오버헤드, 너무 크면 복구 지연.
2. **ISM은 선제적으로 설정** — 디스크가 차고 나서 만드는 것이 아니라 인덱스 생성과 동시에 적용한다.
3. **모니터링은 계층적으로** — 클러스터 상태 → JVM/CPU → 스레드풀 거절 → 슬로우 로그 순서로 파악한다.
4. **전용 마스터 노드 3대는 협상 불가** — Split Brain을 원천 차단하는 유일한 방법이다.
5. **스냅샷은 매일, 복원 테스트는 분기별** — 복원을 실제로 해보지 않은 백업은 백업이 아니다.
6. **dynamic: strict는 기본값** — 매핑 폭발은 예방만이 답이다. 사후 대응은 reindex뿐이다.
7. **디스크 워터마크를 항상 주시** — 75%에서 경고, 85%에서 read-only. ISM 자동 삭제가 유일한 안전망이다.
References
- [OpenSearch - Choosing the number of shards (AWS)](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/bp-sharding.html)
- [OpenSearch - Operational best practices (AWS)](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/bp.html)
- [OpenSearch - Index State Management](https://docs.opensearch.org/latest/im-plugin/ism/index/)
- [OpenSearch - ISM Policies](https://docs.opensearch.org/latest/im-plugin/ism/policies/)
- [OpenSearch - Index templates](https://docs.opensearch.org/latest/im-plugin/index-templates/)
- [OpenSearch - Data streams](https://docs.opensearch.org/latest/im-plugin/data-streams/)
- [OpenSearch - Tuning for indexing speed](https://docs.opensearch.org/latest/tuning-your-cluster/performance/)
- [OpenSearch - Alerting](https://docs.opensearch.org/latest/observing-your-data/alerting/index/)
- [OpenSearch - Security configuration](https://docs.opensearch.org/latest/security/configuration/index/)
- [AWS - Recommended CloudWatch alarms for OpenSearch](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/cloudwatch-alarms.html)
- [AWS - Sizing OpenSearch Service domains](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/sizing-domains.html)
- [AWS - Multi-tier storage for OpenSearch](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/multi-tier-storage.html)
- [AWS - Cross-cluster replication](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/replication.html)
- [AWS - Migrating to Amazon OpenSearch Service](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/migration.html)
- [OpenSearch - Migrate or upgrade](https://docs.opensearch.org/latest/upgrade-or-migrate/)
- [OpenSearch - Remote reindex](https://docs.opensearch.org/latest/im-plugin/remote-reindex/)
- [Benchmarking OpenSearch and Elasticsearch - Trail of Bits (2025)](https://blog.trailofbits.com/2025/03/06/benchmarking-opensearch-and-elasticsearch/)
현재 단락 (1/561)
OpenSearch는 로그 분석, 전문 검색, 관측성(Observability) 파이프라인의 핵심 엔진이다. 클러스터를 띄우는 것은 쉽지만, **안정적으로 운영하면서 비용을 제어*...