장애를 만났을 때 추측하지 않고 순서대로 좁혀 가는 절차. 면접에서 "서버가 느려졌습니다. 어떻게 하시겠어요?"에 답하는 뼈대이기도 하다.
0단계 — 지혈이 먼저
원인 파악보다 서비스 복구가 우선인 경우가 많다
- 최근 배포가 있었나? → 즉시 롤백
- 특정 기능만 문제인가? → 기능 플래그로 차단
- 트래픽 급증인가? → 스케일 아웃, rate limit
"원인을 다 알아낸 뒤 고친다"가 아니라 "일단 멈추고 나서 분석한다." 다만 롤백 전에 로그·힙 덤프·스레드 덤프는 확보해 둔다 — 재시작하면 증거가 사라진다.
1단계 — 사실 확인
- 언제부터인가 — → 배포·설정 변경·트래픽 변화와 시간을 대조
- 전부인가 일부인가 — → 특정 API? 특정 사용자? 특정 지역?
- 얼마나 나쁜가 — → 평균이 아니라 p99. 에러율. 영향받는 사용자 수
- 재현되는가 — → 항상? 간헐적? 특정 조건?
"느리다"는 관찰이지 문제 정의가 아니다. "주문 목록 API의 p99가 어제 15시 배포 이후 300ms에서 3초로"까지 좁혀야 한다.
2단계 — 계층별로 좁힌다
클라이언트 → CDN → LB → 애플리케이션 → DB/캐시/외부 API
각 구간의 지표를 보며 어디서 시간이 소비되는지 찾는다.
| 계층 | 볼 것 |
|---|---|
| 인프라 | CPU, 메모리, 디스크 I/O, 네트워크 |
| JVM | GC 시간·빈도, 힙 사용률, 스레드 상태 |
| 애플리케이션 | 스레드 풀·커넥션 풀 사용률, 큐 대기 |
| DB | 슬로우 쿼리, 락 대기, 커넥션 수 |
| 외부 | 응답 시간, 에러율, 타임아웃 |
분산 추적(trace)이 있으면 이 단계가 훨씬 빠르다.
3단계 — 흔한 원인부터 의심한다
빈도순으로 보는 것이 효율적이다.
- ① 최근 변경 (배포·설정·데이터 마이그레이션) ← 압도적 1위
- ② 슬로우 쿼리 (인덱스 미사용, 데이터 증가)
- ③ 커넥션 풀·스레드 풀 고갈
- ④ GC (Full GC 빈발, 메모리 누수)
- ⑤ 외부 API 지연 → 연쇄 전파
- ⑥ 캐시 미스율 급증 (캐시 서버 장애·대량 만료)
- ⑦ 트래픽 급증 (이벤트, 봇, 크롤러)
- ⑧ 디스크 가득 참 / 파일 디스크립터 고갈
진단 명령 — 손에 익혀 둘 것
# 시스템
top / htop # CPU, 로드 애버리지
vmstat 1 # cs(컨텍스트 스위치), si/so(스왑) ← 스래싱 확인
iostat -x 1 # %util 100%면 디스크 병목
ss -s # 소켓 상태. TIME_WAIT 폭증 확인
# JVM
jstack <pid> # 스레드 덤프 — BLOCKED, 데드락 확인
jstat -gcutil <pid> 1s # GC 빈도·시간
jcmd <pid> GC.heap_info
# DB (MySQL)
SHOW PROCESSLIST; # 지금 무엇이 실행 중인가
SHOW ENGINE INNODB STATUS; # 락 대기, 데드락
SELECT * FROM sys.innodb_lock_waits; # 누가 누구를 막고 있나
스레드 덤프를 3~5초 간격으로 여러 번 뜨는 것이 요령이다. 같은 지점에 계속 멈춰 있으면 그곳이 병목이다.
4단계 — 가설과 검증
가설: "인덱스를 안 타서 느리다" 검증: EXPLAIN 으로 확인 → type=ALL 이면 가설 지지 조치: 인덱스 추가
- 확인: 조치 후 지표가 실제로 개선됐는가 — ★
★ 마지막이 중요하다. 여러 가지를 한꺼번에 바꾸면 무엇이 효과가 있었는지 알 수 없다. 한 번에 하나씩 바꾸고 확인한다.
5단계 — 재발 방지
- 근본 원인(root cause)을 문서로 남긴다
- 같은 문제를 사전에 잡을 지표·알림을 추가한다
- 재현 테스트를 자동화한다
- 프로세스를 고친다 (PR 체크리스트, 배포 절차)
"고쳤습니다"로 끝나면 절반이다. 면접에서도 이 단계를 말하는 사람이 훨씬 좋은 평가를 받는다.
면접 답변 골격
"먼저 영향 범위와 시작 시점을 확인하고, 최근 변경이 있었다면 롤백으로 지혈합니다. 그다음 계층별 지표로 병목 구간을 좁히고, 가설을 하나씩 검증합니다. 조치 후에는 지표로 효과를 확인하고, 같은 문제를 미리 잡을 알림을 추가합니다."