| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 | 31 |
- 알고리즘사이트
- 부트캠프 강사 후기
- 스프링부트
- 알고리즘
- spring boot
- Java
- 몰입
- 초년생
- 회고
- 알고리즘초보
- 프로그래밍
- 퇴사 회고
- JMeter
- aws
- 서버 모니터링 가이드
- 성능테스트
- Spring
- 개발
- GraphQL 철학
- 퀴즈
- 알고리즘분류
- 알고리즘 추천
- 나를 만나는 글쓰기 챌린지
- 오늘의 읽기
- 자동화
- 글또
- DDD
- 코딩
- graphql
- 코드트리
- Today
- Total
영감을 (inspire) 주고픈 개발 블로그
오늘의 읽기: 서버 모니터링 분석 가이드 본문
서버 모니터링 분석 가이드 글 소개
원글: 서버 모니터링 분석 가이드
https://kciter.so/posts/server-monitoring-analysis-guide/
서버 개발자라면 한 번쯤 꼭 읽어볼 만한 글이라고 생각합니다.
모니터링 시스템을 구축하는 방법이나 대시보드를 만드는 방법에 대한 글은 꽤 많습니다. 그런데 막상 대시보드에 나타난 지표를 어떻게 읽어야 하는지, 어떤 지표들을 조합해서 문제의 원인을 좁혀야 하는지를 설명하는 글은 생각보다 많지 않습니다.
저도 이전에 블로그에 성능 모니터링 시스템을 구축하고 테스트한 내용을 적은 적이 있습니다. 하지만 돌이켜보면 시스템을 어떻게 구성했는지에 대해서는 적었어도, 실제 운영에서 그래프가 변했을 때 무엇을 의심해야 하는지는 충분히 다루지 못했던 것 같습니다.
원글을 읽으면서 가장 기억에 남았던 문장은 이것입니다.
리소스 지표는 혼자 읽으면 거짓말을 한다.
모니터링 지표는 문제의 원인을 바로 알려주는 정답이라기보다 서버에서 나타나고 있는 현상에 가깝습니다.
CPU가 올랐다거나 메모리가 늘었다는 사실만으로는 정확한 원인을 알 수 없습니다. 개발자는 여러 지표를 같이 보면서 현재 어떤 일이 벌어지고 있는지 추론해야 합니다.
그런 의미에서 이 글은 서버 지표를 해석할 때 사용할 수 있는 몇 가지 공식을 꽤 친절하게 정리하고 있습니다.
- 트래픽(Traffic): 얼마나 들어오고 있는가?
- 지연 시간(Latency): 얼마나 걸리는가?
- 에러(Errors): 얼마나 실패하는가?
- 포화도(Saturation): 얼마나 차 있는가?
CPU 사용률은 지연 시간과 같이 봐야 한다
CPU 사용률이 높다고 해서 무조건 문제가 있는 것은 아닙니다.
트래픽을 처리하면서 CPU를 충분히 사용하고 있다면 자연스러운 상황일 수도 있습니다. 반대로 CPU가 계속 놀고 있다면 필요 이상으로 비싼 인프라를 사용하고 있다고 볼 수도 있습니다.
중요한 것은 CPU 사용률 자체보다 응답 시간과 함께 어떻게 변하고 있는지를 보는 것입니다.
CPU가 포화 상태에 가까워지면서 응답 시간까지 증가한다면 연산 처리에서 병목이 생기고 있을 가능성이 있습니다.
반대로 CPU에는 여유가 있는데 응답 시간이 느려지고 있다면 DB, 외부 API, 네트워크처럼 I/O가 발생하는 영역을 먼저 의심해볼 수 있습니다.
기본적인 내용처럼 보이지만, 실제 장애 상황에서는 특정 지표 하나에 먼저 시선이 가는 경우가 많습니다. 원글에서는 이 부분을 그래프와 함께 설명하고 있어서 이해하기 좋았습니다.
메모리는 최고점보다 GC 이후 최저점을 봐야 한다
메모리 사용량이 계속 올라갔다 내려가는 그래프를 보면 처음에는 메모리 누수를 의심하기 쉽습니다.
하지만 GC가 있는 애플리케이션에서는 객체가 생성되면서 메모리가 증가하고, GC가 실행되면서 다시 감소하는 패턴이 자연스럽게 반복됩니다.
그래서 중요한 것은 메모리가 얼마나 높이 올라갔는지가 아니라, GC 이후에 어디까지 내려오는지를 보는 것입니다.
GC가 실행된 뒤에도 메모리 최저점이 조금씩 높아지고 있다면, 계속 살아남는 객체가 늘어나고 있다는 의미일 수 있습니다.
이 상태가 계속되면 힙 한계에 가까워지고, GC가 점점 더 자주 실행됩니다. 결국 CPU 사용량과 응답 시간이 먼저 나빠지고, 마지막에는 OOM으로 프로세스가 종료될 수 있습니다.
개인적으로는 메모리 누수를 발견했을 때 바로 원인을 찾으려고 붙잡고 있기보다, 힙 덤프를 남기고 우선 재시작해서 서비스를 복구하라는 부분도 인상 깊었습니다.
장애 상황에서는 원인을 바로 찾는 것보다 복구와 증거 보존이 먼저라는 점을 다시 생각하게 됐습니다.
처리 시간이 늘어나면 풀과 큐가 같이 무너진다
글에서 가장 좋았던 부분 중 하나는 처리 시간의 증가가 연쇄 장애로 이어지는 과정을 설명한 부분입니다.
트래픽이 늘어나지 않았더라도 요청 하나를 처리하는 시간이 길어지면 동시에 처리 중인 요청 수는 증가합니다.
예를 들어 DB에서 슬로 쿼리가 발생해 요청 처리 시간이 길어지면, 스레드나 DB 커넥션이 이전보다 오랫동안 반환되지 않습니다.
그러면 스레드 풀과 커넥션 풀이 점점 고갈되고, 처리되지 못한 요청은 대기 큐에 쌓이기 시작합니다.
큐에 요청이 쌓이면 응답 시간은 더 길어집니다. 클라이언트에서는 타임아웃이 발생하고, 재시도까지 붙으면 실제 트래픽보다 더 많은 요청이 서버로 들어올 수 있습니다.
상황이 더 나빠져 헬스 체크 요청까지 처리하지 못하면 로드 밸런서가 해당 서버를 제외합니다. 그러면 남은 서버들이 더 많은 트래픽을 받게 되고, 결국 다른 서버도 순서대로 무너질 수 있습니다.
트래픽은 그대로인데 하나의 느린 의존성 때문에 전체 클러스터가 영향을 받을 수 있다는 이 시나리오는 저도 실제로 경험한 적이 있습니다.
당시에는 서버가 호출하던 외부 AI API의 응답이 느려지면서 요청이 애플리케이션 내부에 계속 쌓였습니다. 외부 API를 기다리는 요청들이 오랫동안 자원을 점유했고, 시간이 지나면 일부 Pod가 새로운 요청과 헬스 체크까지 제대로 처리하지 못하는 상태가 됐습니다.
알림이 울릴 때마다 급하게 클러스터에 접속해 문제가 생긴 Pod를 수동으로 재시작했던 기억이 있습니다. Pod를 재시작하면 쌓여 있던 요청과 연결이 정리되면서 잠시 정상화됐지만, 외부 API의 지연이 계속되는 한 같은 문제는 다시 반복됐습니다.
돌이켜보면 Pod 재시작은 서비스를 임시로 복구하기 위한 대응이었을 뿐, 장애의 원인을 해결한 것은 아니었습니다.
이 부분을 읽으면서 풀 고갈이 항상 풀 크기가 작아서 발생하는 문제는 아니라는 점도 다시 생각하게 됐습니다.
DB나 외부 시스템이 느려져 자원의 반환이 늦어지고, 그 결과로 풀이 고갈되는 상황도 많습니다. 이런 상황에서 원인을 해결하지 않은 채 풀 크기만 늘리면 느린 하위 시스템에 더 많은 요청을 동시에 보내면서 오히려 문제를 키울 수도 있습니다.
결국 풀 고갈을 발견했을 때는 “풀의 크기가 충분한가?”만 물을 것이 아니라, “왜 자원이 제때 반환되지 않고 있는가?”를 함께 확인해야 합니다.
큐는 공짜가 아니다, 전체 처리량은 가장 좁은 단이 결정한다
대기 큐에 대한 내용도 기억에 남았습니다.
처리하지 못한 요청을 큐에 쌓아두면 당장은 요청을 거절하지 않아도 됩니다. 그래서 큐가 서버를 보호해주는 장치처럼 느껴질 수 있습니다.
하지만 유입 속도가 처리 속도보다 계속 빠르다면 큐는 문제를 해결하는 것이 아니라 문제 발생을 뒤로 미루는 역할만 하게 됩니다.
큐에 쌓인 요청도 메모리를 사용하고, 대기 시간이 길어질수록 사용자가 이미 연결을 끊은 요청을 뒤늦게 처리하는 상황도 발생할 수 있습니다.
결국 큐에도 최대 크기와 대기 시간 제한이 필요합니다.
서버가 감당할 수 없는 상황이라면 모든 요청을 받아서 늦게 실패시키는 것보다, 일부 요청을 빠르게 거절하는 것이 전체 시스템을 지키는 데 더 나을 수 있습니다.
모니터링을 바라보는 세 가지 시간
원글 후반부에서는 모니터링을 세 가지 시간으로 나눠 설명합니다.
- 장애가 발생하기 전, 미리 문제를 찾는 시간
- 장애가 발생한 동안, 응급 처치를 하는 시간
- 장애가 끝난 뒤, 같은 문제를 반복하지 않기 위한 시간
같은 대시보드를 보더라도 언제 보고 있느냐에 따라 던져야 하는 질문이 달라집니다.
이 세 가지 시간을 구분하지 않으면 장애가 발생한 상황에서 근본 원인을 찾느라 복구가 늦어지거나, 반대로 재시작만 반복하면서 같은 장애를 계속 겪게 될 수 있습니다.
미리 문제를 찾는 시간
평소에 대시보드를 보는 목적은 지금 장애가 발생했는지를 확인하는 것이 아니라, 앞으로 문제가 될 가능성이 있는 부분을 미리 찾는 것입니다.
원글에서는 하루 5분이라도 대시보드를 둘러보면서 서비스의 평소 모습을 익혀두라고 이야기합니다. 트래픽이 시간대별로 어떤 모양을 그리는지, 평소 CPU 사용률은 어느 정도인지, P99는 어느 범위에서 움직이는지 알아야 평소와 다른 변화도 발견할 수 있기 때문입니다.
이 부분을 읽으면서 모니터링에서도 결국 기준이 되는 것은 절대적인 숫자보다 서비스의 평소 상태라는 생각이 들었습니다.
CPU가 70%라는 숫자만 봐서는 높은지 낮은지 판단하기 어렵습니다. 평소 20%를 유지하던 서버가 갑자기 70%가 된 것과, 항상 60~70% 사이에서 안정적으로 동작하던 서버는 전혀 다른 상태이기 때문입니다.
또 알람은 순간적으로 발생하는 문제를 잡는 데는 효과적이지만, 매일 조금씩 느려지는 문제를 발견하기는 어렵습니다.
예를 들어 알람은 한 번도 울리지 않았지만 지난주 같은 요일과 비교했을 때 동일한 트래픽에서 P99가 계속 나빠지고 있을 수 있습니다. 이런 변화는 하루 단위 그래프만 보면 알아차리기 어렵기 때문에 지난주나 지난달의 같은 시간대와 비교하는 패널이 필요합니다.
메모리 누수나 DB 성능 저하, 캐시 히트율 감소처럼 서서히 진행되는 문제는 장애가 발생하기 전에 이런 추세 비교를 통해 먼저 발견할 수 있습니다.
가장 위험한 배포 직후
원글에서는 특히 배포 직후 30분을 가장 집중해서 그래프를 봐야 하는 시간으로 설명합니다.
서버에 발생하는 많은 장애가 갑작스러운 하드웨어 고장보다는 배포나 설정 변경처럼 사람이 시스템에 가한 변화에서 시작되기 때문입니다.
이때 가장 먼저 필요한 것은 그래프에 배포 시각을 표시하는 것입니다.
장애를 분석할 때 가장 먼저 묻게 되는 질문 중 하나가 “이 지표의 변화가 배포 전부터 있었는가, 배포 이후에 발생했는가?”이기 때문입니다. 배포 마커 하나만 있어도 원인을 좁히는 시간이 크게 줄어듭니다.
다만 배포 직후 P99나 CPU가 잠깐 증가했다고 해서 바로 문제가 생겼다고 판단해서는 안 됩니다.
새로 실행된 서버는 캐시가 비어 있고 DB 커넥션을 다시 맺어야 하며, JVM 환경에서는 JIT 컴파일도 충분히 진행되지 않은 상태일 수 있습니다. 그래서 배포 직후에는 응답 시간이 일시적으로 나빠질 수 있습니다.
중요한 것은 순간적인 값보다 방향입니다.
배포 직후 지표가 높아졌다가 몇 분 동안 다시 정상 범위로 내려온다면 워밍업 과정일 수 있습니다. 반대로 시간이 지나도 회복하지 않거나 계속 나빠진다면 문제가 있는 배포일 가능성이 높습니다.
원글에서 이야기한 것처럼 다음과 같은 기준을 미리 정해두는 것도 좋다고 생각합니다.
배포 이후 에러율이 일정 수준을 넘거나, P99가 10분 안에 회복되는 추세를 보이지 않는다면 별도의 토론 없이 롤백한다.
장애가 발생한 상태에서는 계속 지켜볼지, 롤백할지를 두고 판단이 늦어지기 쉽습니다. 그래서 판단 기준은 문제가 발생하기 전에 팀에서 합의해두는 것이 중요합니다.
응급 처치의 시간
장애가 실제로 발생했을 때는 원인 분석보다 먼저 영향 범위를 확인해야 합니다.
전체 사용자가 문제를 겪고 있는지, 특정 API만 느린지, 특정 지역이나 일부 사용자에게만 문제가 발생했는지를 먼저 파악해야 합니다.
원글에서는 이를 응급실의 환자 분류에 빗대어 트리아지라고 설명합니다. 전체 API가 느리다면 DB나 캐시처럼 여러 기능이 공유하는 자원을 의심할 수 있고, 특정 API만 느리다면 해당 API의 쿼리나 외부 호출로 범위를 좁힐 수 있습니다.
다음으로 확인할 것은 최근에 변경된 내용입니다.
- 애플리케이션 배포
- 인프라나 설정 변경
- 피처 플래그 변경
- 배치 작업 실행
- 마케팅 푸시로 인한 트래픽 변화
장애 직전에 변경된 내용이 있다면 그것이 원인일 가능성이 높기 때문에, 복잡한 분석을 시작하기 전에 되돌릴 수 있는지 먼저 확인해야 합니다.
그리고 장애 중에는 대부분 복구가 원인 규명보다 우선입니다.
롤백, 문제 기능 비활성화, 스케일 아웃, 프로세스 재시작 등을 통해 우선 사용자가 서비스를 다시 사용할 수 있도록 해야 합니다. 로그와 메트릭, 힙 덤프 등 분석에 필요한 증거가 남아 있다면 근본 원인은 서비스가 복구된 뒤에 찾아도 늦지 않습니다.
저도 앞에서 이야기한 외부 AI API 장애 당시에는 Pod를 재시작하면서 우선 서비스를 복구했습니다. 당시에는 반복되는 수동 재시작이 근본 해결책은 아니었지만, 장애가 진행 중인 상황에서 사용자의 영향을 줄이기 위한 응급 처치로는 필요했습니다.
다만 문제가 반복된다면 그다음부터는 재시작 자체를 대응 전략으로 삼기보다 타임아웃, 서킷 브레이커, 요청 수 제한, 재시도 정책 등을 통해 느린 외부 시스템의 문제가 전체 서버로 번지지 않도록 구조를 바꿔야 합니다.
장애가 끝난 뒤의 시간
서비스가 정상화되었다고 해서 장애 대응이 끝난 것은 아닙니다.
장애 중에는 “배포를 되돌렸더니 정상화됐다” 정도의 상관관계만 확인하고 넘어갈 수 있습니다. 하지만 장애가 끝난 뒤에는 왜 해당 배포가 문제를 일으켰는지, 왜 테스트나 리뷰 과정에서 발견하지 못했는지까지 확인해야 합니다.
원글에서는 장애 이후 그래프를 바탕으로 다음 네 시점을 정리하라고 이야기합니다.
- 문제의 첫 흔적이 나타난 시간
- 알람이 울린 시간
- 사람이 대응을 시작한 시간
- 서비스가 복구된 시간
첫 흔적부터 알람까지의 간격이 길었다면 감지하지 못한 전조가 있었다는 뜻입니다. 다음에는 그 전조를 알람이나 대시보드 패널로 만들 수 있는지 검토해야 합니다.
알람부터 복구까지 시간이 오래 걸렸다면 대응 절차에 문제가 있었을 수 있습니다. 배포 시각을 찾느라 시간을 보냈다면 배포 마커를 추가하고, 롤백 여부를 결정하지 못해 지연됐다면 롤백 기준을 미리 정하는 식입니다.
그래서 포스트모템의 결과는 단순한 장애 보고서나 반성문이 되어서는 안 됩니다.
- 새로 추가해야 할 알람
- 대시보드에 필요한 지표
- 자동화할 복구 절차
- 수정해야 할 코드와 설정
- 다음 장애에서 판단을 빠르게 할 기준
이처럼 다음 장애를 더 빠르게 발견하고, 더 짧게 끝낼 수 있도록 만드는 변경 목록이 남아야 합니다.
또 장애 원인을 사람에게 돌리지 않는다는 원칙도 중요합니다.
“누가 잘못 배포했다”에서 끝나면 같은 문제가 다시 발생할 수 있습니다. 그보다는 왜 해당 상황에서 그런 판단을 내릴 수밖에 없었는지, 검증 절차나 시스템에 어떤 안전장치가 없었는지를 확인해야 재발을 막을 수 있습니다.
결국 세 가지 시간은 다음과 같이 정리할 수 있습니다.
평소에는 이상을 알아차릴 수 있도록 정상 상태를 익히고, 장애 중에는 원인보다 복구를 우선하며, 장애 후에는 같은 문제를 반복하지 않도록 시스템을 바꾼다.
모니터링은 장애가 발생했을 때만 대시보드를 여는 일이 아니라는 점이 이 부분에서 가장 잘 드러났습니다.
마무리
모니터링은 만든느 것보다 대시보드에 나타나는 지표를 보고 현재 어떤 현상이 발생하고 있는지 파악하고, 여러 지표의 관계를 통해 가능한 원인을 하나씩 좁혀가는 과정이 더 중요합니다.
서버 장애 대응에는 경험이 중요하다는 이야기를 많이 합니다.
물론 직접 장애를 겪어본 경험은 분명히 중요합니다. 하지만 개발자 생활을 하다 보니 장애에도 반복해서 등장하는 패턴이 있고, 지표를 해석하는 데도 어느 정도 공식이 있다는 생각이 듭니다.
이 글은 그런 공식들을 시각적인 자료와 함께 잘 정리한 글입니다.
뭔가 제 8년 정도 일하면서 쌓은 노하우가 공개된 것 같은 느낌도 드네요.
운영 경험이 많지 않은 백엔드 개발자나, 모니터링 대시보드는 만들어두었지만 지표를 어떻게 읽어야 할지 막막한 개발자라면 꼭 한 번 읽어보기를 권합니다.
'후기 > 컴퓨터 관련 책' 카테고리의 다른 글
| 오늘의 읽기: 토스에서 일하며 깨달은 성장과 리더십 (0) | 2026.08.09 |
|---|---|
| 오늘의 읽기: 동일성은 왜 프로그래밍에서 가장 어려운 문제인가 (1) | 2026.08.05 |
| 개발 책 추천: 가상 면접 사례로 배우는 대규모 시스템 설계 기초2 (7) | 2024.10.03 |
| 코어 자바 9 (0) | 2020.09.03 |
| Node.js 마이크로 서비스 코딩공작소 (0) | 2018.04.30 |