11장. 모니터링과 트러블슈팅 — 살아있는 클러스터 다루기
운영 중인 클러스터는 살아있는 시스템입니다. 한 노드의 heap 압박이 다른 노드의 증상으로 전파되죠. green이 yellow가 되는 순간 무슨 일이 벌어지는지, 어떤 명령을 어떤 순서로 쳐야 하는지를 시나리오로 익힙니다.
① green / yellow / red의 정확한 의미와 노드 장애 → 복구의 전 과정 ② _cat · _nodes/stats · hot_threads — 진단 도구 3종 세트 ③ thread pool 429와 디스크 watermark의 동작 ④ 증상별("검색이 느려요", "색인이 안 돼요") 진단 플로우
11.1 Cluster health — green, yellow, red의 진짜 의미
GET /_cluster/health
{
"status": "yellow",
"number_of_nodes": 6,
"active_primary_shards": 120,
"unassigned_shards": 5, ← 미할당 shard 수
"number_of_pending_tasks": 0
}
| 상태 | 의미 | 즉시 행동 |
|---|---|---|
| green | 모든 primary + replica 할당 완료 | 없음 |
| yellow | 모든 primary 할당, 일부 replica 미할당 | 5분 내 자동복구 안 되면 진단 |
| red | 일부 primary 미할당 | 즉시 대응 — 해당 데이터 색인·검색 불가 |
노드 하나가 죽었을 때 클러스터가 어떻게 반응하는지, 상태 전이 전체를 따라가 봅시다.
평화로운 green 상태. primary(P1~P3)와 replica(점선)가 세 노드에 분산 배치되어 있습니다.
node-3 다운! 거기 있던 primary P3와 replica R1이 사라졌습니다. 클러스터가 어떻게 반응할까요?
node-1에 있던 replica R3가 즉시 primary로 승격됩니다. 모든 primary가 살아 있으므로 상태는 yellow — red가 아닙니다. 서비스는 계속됩니다.
index.unassigned.node_left.delayed_timeout(기본 1분)을 기다려도 노드가 안 돌아오면, 남은 노드에 새 replica 복제(INITIALIZING)를 시작합니다. 데이터 크기에 따라 분~시간 단위.
복제가 끝나면 green 복귀. 여기까지 사람이 한 일은 없습니다 — 잘 설계된 클러스터는 스스로 치유합니다.
반전 시나리오: replica를 0으로 운영했다면 P3를 대체할 사본이 없어 red — 해당 shard의 데이터만 색인·검색 불가입니다. red는 "클러스터 다운"이 아니라 "일부 primary 미할당"이라는 정확한 의미로 읽어야 대응이 빨라집니다.
11.2 진단 도구 3종 세트 — _cat, _nodes/stats, hot_threads
_cat API는 사람이 읽기 좋은 표를 반환합니다. 운영자가 가장 자주 치는 명령부터.
# 노드별 핵심 지표 한 줄씩
GET /_cat/nodes?v&h=name,node.role,heap.percent,cpu,load_1m,disk.used_percent
name node.role heap.percent cpu load_1m disk.used_percent
es-01 cdfhirstw 68 45 2.10 67.3
es-03 cdfhirstw 85 62 3.01 71.8 ← heap 임계!
# shard 상태 (STARTED / RELOCATING / INITIALIZING / UNASSIGNED)
GET /_cat/shards?v&h=index,shard,prirep,state,docs,store,node
| 도구 | 언제 | 보는 것 |
|---|---|---|
_cat/nodes · indices · shards · allocation | 빠른 훑어보기 | heap/CPU/disk, 인덱스 크기, shard 상태·분포 |
_nodes/stats | 깊은 메트릭 | JVM heap·GC, indexing_pressure, thread_pool, breaker |
_nodes/hot_threads | "CPU 90%" 알람 직후 | 바쁜 thread의 stack — merge 폭주? 비싼 쿼리? GC? |
_cluster/pending_tasks | 클러스터가 굼뜰 때 | master의 state 변경 큐 (평소엔 비어 있어야 정상) |
Lucene Merge Thread가 상위 → 작은 segment 폭증 또는 활성 인덱스 force_merge 의심. [search] thread가 aggregation에 머무름 → 비싼 쿼리. [write] thread가 mapping update에 머무름 → dynamic mapping 폭주(10장). GC thread → heap 압박.
11.3 색인이 거부될 때 — thread pool과 429
"클라이언트에서 429 에러가 폭주해요"는 ES가 고장 난 것이 아니라 스스로를 보호하는 중이라는 신호입니다. 각 노드의 write thread pool에는 큐가 있고, 큐가 가득 차면 요청을 거부합니다.
정상 상태: bulk 요청이 write queue를 거쳐 워커 thread(코어 수만큼)로 흘러갑니다. 유입과 처리가 균형이면 queue는 거의 비어 있습니다.
색인 폭주 또는 처리 둔화(merge 폭주, heap 압박)로 유입이 처리를 앞지르면 queue가 차오릅니다. _cat/thread_pool/write?v의 queue 칼럼이 경고등입니다.
queue가 가득 차면 ES는 요청을 429(es_rejected_execution_exception)로 거부합니다. OOM으로 쓰러지는 대신 밀어내는 자기 보호 — bulk 응답 안의 항목별 429를 놓치면 데이터 유실입니다(9장).
올바른 대응: 클라이언트 exponential backoff 재시도 + 유입 조절. 그리고 근본 원인(bulk 크기, refresh_interval, merge, 노드 수)을 indexing_pressure·hot_threads로 진단합니다.
11.4 JVM heap과 circuit breaker
heap 진단의 위험 신호: heap_used_percent > 75% 지속, old GC가 분당 수 회 이상, breaker의 tripped 카운터 증가. circuit breaker는 OOM 직전에 ES가 요청을 스스로 거부한 흔적입니다.
GET /_nodes/stats/breaker
"breakers": {
"fielddata": { "tripped": 12 }, ← text 필드 fielddata 사용 의심 (10장)
"parent": { "tripped": 3 }
}
heap을 늘리기 전에 순서대로 점검하세요: ① 진짜 메모리 부족인가(cardinality 폭증, fielddata, 비싼 쿼리) ② cluster state가 큰가(shard 폭증 — 10장) ③ 쿼리가 비싼가(deep agg, deep paging). 해결 후에도 부족할 때만 RAM 추가 — 단, heap 31GB는 절대 넘기지 않습니다(compressed oops, 10장).
11.5 디스크 watermark — 85 / 90 / 95%의 3단 방어선
디스크가 차오르면 ES는 단계적으로 강하게 개입합니다. 마지막 단계는 꽤 충격적입니다: 모든 인덱스가 read-only가 됩니다.
디스크 사용률 70% — 아직 아무 일도 없습니다. shard 할당과 이동이 자유롭습니다.
85%(low) 돌파: 이 노드에는 새 shard를 할당하지 않습니다. 조용한 첫 번째 경고라 놓치기 쉽습니다.
90%(high) 돌파: 기존 shard를 다른 노드로 옮기기 시작합니다. _cat/shards에 RELOCATING이 보인다면 이 단계일 수 있습니다.
95%(flood_stage): 모든 인덱스가 read-only로 전환됩니다 — 색인이 전면 중단되는 운영 사고. 디스크를 확보한 뒤 read_only 블록을 수동 해제해야 하며, 디스크가 여전히 95% 위라면 다시 발동합니다. 근본 해결은 ILM 삭제 정책·노드 추가·티어 이전입니다.
11.6 shard가 UNASSIGNED일 때 — allocation/explain
미할당 shard의 원인은 추측하지 말고 물어보세요. 왜 할당이 안 되는지 ES가 직접 설명해 줍니다.
GET /_cluster/allocation/explain
{
"unassigned_info": { "reason": "NODE_LEFT" },
"node_allocation_decisions": [{
"node_name": "es-03",
"deciders": [{
"decider": "disk_threshold",
"explanation": "the node is above the high watermark..." ← 원인이 그대로
}]
}]
}
reason | 의미 | 처방 |
|---|---|---|
INDEX_CREATED | 신규 인덱스 할당 대기 | 대기, disk/awareness 확인 |
NODE_LEFT | 노드 이탈 | 노드 복귀 대기 또는 재할당 확인 |
ALLOCATION_FAILED | 5회 재시도 모두 실패 | POST /_cluster/reroute?retry_failed=true |
CLUSTER_RECOVERED | 재시작 후 복구 진행 | 자동 복구 대기 |
POST /_cluster/reroute의 allocate_empty_primary 명령은 빈 primary를 강제 할당합니다 — 그 shard의 기존 데이터는 사라집니다. 스냅샷 복원조차 불가능할 때의 마지막 수단으로만.
11.7 증상별 진단 플로우
# "검색이 갑자기 느려졌다"
1. _cluster/health → red? yellow? unassigned?
2. _cat/nodes?v → 어느 노드가 heap·CPU 높은가
3. _nodes/hot_threads → 지금 무슨 일을 하고 있나
4. _cat/thread_pool/search?v → queue·rejected?
5. slow log → 어떤 쿼리가 느린가 (query vs fetch phase 구분)
6. _nodes/stats/breaker → fielddata tripped?
# "색인이 느리다 / 429 폭주"
1. _cat/thread_pool/write?v → queue·rejected
2. _nodes/stats/indexing_pressure → 거부 카운트 (8.x)
3. _cat/allocation?v → 디스크 watermark 임박?
4. _cluster/pending_tasks → master 큐 적체 (mapping 폭증?)
5. _nodes/hot_threads → merge 폭주? mapping update?
운영 클러스터의 모니터링 데이터를 자기 자신에 저장하면, 클러스터가 죽는 순간 모니터링도 같이 죽습니다. Metricbeat/Elastic Agent로 별도 monitoring 클러스터에 보내는 것이 정석이며, Kibana Stack Monitoring이 이를 시각화해 줍니다.
✍️ 이해도 체크
이 장의 원문 문서: chapters/ch11_monitoring_troubleshooting.md — slow log 설정, OOM 진단 플로우, 진단 쿼리 15선이 담겨 있습니다.