모니터링 알림 규칙 정리: 대시보드는 보는 것, 알림은 알려주는 것

새벽 3시에 결제 API가 죽었는데 아침 9시에 출근해서 대시보드를 열어 보고서야 알았던 경험, 혹시 있으신가요? 😅 대시보드는 있었습니다. 그래프도 정확했고요. 오류율이 치솟은 시각까지 그대로 그려져 있었죠. 문제는 그 여섯 시간 동안 아무도 그 화면을 보고 있지 않았다는 겁니다.
이런 일을 겪고 나면 “대시보드를 더 잘 만들어야겠다”는 결론으로 가기 쉬운데요. 사실 대시보드가 아무리 좋아져도 문제는 해결되지 않습니다. 대시보드와 알림은 하는 일이 다르기 때문입니다. 이 글에서는 둘의 역할이 어떻게 나뉘는지, 알림 규칙이 무엇으로 이루어지는지, 그리고 무엇에 알림을 걸어야 하는지를 정리해 보겠습니다.
대시보드가 못 하는 것
대시보드(dashboard)는 사람이 질문하는 도구입니다. “지금 트래픽이 어느 정도지?”, “배포 이후 응답 시간이 달라졌나?” 같은 질문을 들고 화면을 열면 그래프가 답을 줍니다. 질문이 먼저고 답이 나중이죠. 그래서 대시보드는 사람이 보고 있을 때만 의미가 있습니다.
그런데 사람은 24시간 화면을 볼 수 없습니다. 화면 여러 개를 벽에 붙여 놓는 상황실을 만들어도 결국 병목은 사람의 주의력입니다. 퇴근하면 아무도 없고 회의 중이면 아무도 안 봅니다. 보고 있다 해도 그래프 하나가 슬쩍 올라가는 걸 놓치기 쉽고요.
그래서 방향을 뒤집어야 합니다. 사람이 시스템에게 묻는 게 아니라 시스템이 사람에게 말을 걸게 하는 거죠. 이게 알림(alert)입니다. 대시보드는 “무슨 일이 일어나고 있나”를 조사할 때 쓰고, 알림은 “일이 났다”는 사실을 전달할 때 씁니다. 둘은 경쟁 관계가 아니라 역할 분담이에요. 알림을 받고 대시보드를 열어 조사하는 흐름이 자연스러운 조합입니다.
알림 규칙을 이루는 네 가지
알림 규칙(alert rule)은 시스템이 언제 사람을 부를지 미리 적어 둔 문장입니다. 도구마다 문법은 다르지만 뜯어보면 항상 네 부분으로 이루어집니다. 무엇을 볼 것인가 하는 대상, 어떻게 비교할 것인가 하는 조건, 비교 기준이 되는 임계값(threshold), 그리고 그 상태가 얼마나 이어져야 하는가 하는 지속 시간(duration)입니다.
말로 풀면 이렇게 됩니다. “결제 API의 5xx 응답 비율이 5%를 넘는 상태가 10분 이상 이어지면 알린다.” 여기서 대상은 5xx 응답 비율, 조건은 넘는다, 임계값은 5%, 지속 시간은 10분이죠. HTTP 상태 코드 중 5xx는 서버 쪽 오류라 사용자가 실제로 실패를 겪었다는 신호입니다.
같은 규칙을 Prometheus 식으로 쓰면 다음과 같습니다.
groups:
- name: payment
rules:
- alert: PaymentHighErrorRate
# 대상 + 조건 + 임계값: 최근 5분간 5xx 비율이 5%를 넘는가
expr: |
sum(rate(http_requests_total{service="payment", status=~"5.."}[5m]))
/ sum(rate(http_requests_total{service="payment"}[5m])) > 0.05
# 지속 시간: 이 상태가 10분 이상 이어져야 발동
for: 10m
labels:
severity: page
annotations:
summary: "결제 API 5xx 비율이 10분째 5%를 넘고 있습니다"
expr에 대상과 조건과 임계값이 한 줄로 들어가 있고 for가 지속 시간입니다. labels의 severity는 이 알림이 얼마나 급한지를 나타내는 꼬리표이고, annotations는 알림을 받은 사람이 읽을 설명이에요. 대상은 지표(metric)에서 뽑는 질의라는 점도 눈여겨볼 만합니다. 알림 규칙은 결국 지표 위에 얹히는 조건문입니다.
여기에 숨어 있는 시간 개념이 하나 더 있습니다. 평가 주기(evaluation interval)인데요. 알림 규칙은 연속으로 감시하는 게 아니라 정해진 주기, 보통 1분마다 한 번씩 조건을 확인합니다. 그러니까 for: 10m은 “10번 연속으로 확인했을 때 매번 조건이 참이었다”는 뜻에 가깝습니다.
지속 시간이 있는 이유
네 요소 중에서 처음 알림을 만들 때 가장 자주 빠뜨리는 게 지속 시간입니다. “5%를 넘으면 알린다”까지만 적고 끝내는 거죠. 그러면 어떻게 될까요?
실제 시스템의 지표는 잔잔하지 않습니다. 배포 직후 몇 초나 외부 API가 잠깐 느려진 틈에 오류율은 얼마든지 튀어 오릅니다. 배치 작업이 도는 시간대도 마찬가지고요. 지속 시간이 없으면 이런 순간적인 튐(spike)마다 알림이 울립니다. 확인해 보면 이미 정상으로 돌아와 있고요. 이런 알림이 몇 번 반복되면 사람은 알림을 믿지 않게 됩니다. 정작 진짜 장애가 왔을 때 “또 그거겠지” 하고 넘기게 되는 거죠.
지속 시간은 “잠깐 튄 것”과 “정말 문제가 생긴 것”을 가르는 장치입니다. 10분이라는 값은 결제 API처럼 중요한 서비스에는 길 수도 있고, 야간 배치처럼 느긋한 작업에는 짧을 수도 있어요. 정답이 있는 게 아니라 “이 상태가 얼마나 이어지면 사람이 개입해야 하는가”를 서비스마다 정하는 겁니다.
임계값은 어디서 오는가
숫자를 고르는 건 알림 규칙에서 가장 어려운 부분입니다. 5%는 어디서 나온 걸까요? 처음에는 아무 근거가 없습니다. 그래서 몇 가지 원칙을 두고 시작하는 게 좋습니다.
우선 건수보다 비율이 안정적입니다. “5xx가 500건을 넘으면”은 트래픽이 많은 낮과 적은 새벽에 의미가 완전히 다릅니다. 낮에는 정상 범위인 숫자가 새벽에는 재앙일 수 있죠. “5xx 비율이 5%를 넘으면”은 트래픽 규모와 무관하게 같은 뜻을 유지합니다.
또한 평균은 문제를 숨깁니다. 평균 응답 시간이 200ms라도 상위 1% 요청은 5초가 걸리고 있을 수 있어요. 그 1%가 결제 완료 화면을 기다리는 사용자라면 평균은 아무것도 말해 주지 않습니다. 그래서 응답 시간에는 백분위(percentile)를 씁니다. p99가 1초를 넘는지를 보면 “100명 중 가장 느린 한 명도 1초 안에는 응답을 받는가”를 묻는 셈이 됩니다.
그리고 임계값은 정답이 아니라 가설입니다. 과거 데이터를 며칠 들여다보면 정상 범위가 보이는데, 처음 임계값은 그 범위 바깥에 넉넉하게 두고 시작합니다. 알림이 너무 자주 울리면 올리고, 장애를 놓쳤으면 내리고요. 몇 주를 거치며 조정하는 게 정상적인 과정이지 처음부터 맞추려고 애쓸 필요가 없습니다. 이 가설에 근거를 주는 게 SLO인데, SLI, SLO, SLA 정리에서 다룹니다.
증상에 알릴까, 원인에 알릴까
“CPU 사용률이 90%를 넘으면 알린다”는 규칙이 있다고 해 볼게요. 새벽에 이 알림을 받고 일어났는데 확인해 보니 CPU는 높지만 응답 시간도 오류율도 멀쩡합니다. 배치 작업이 돌고 있었을 뿐이죠. 할 일이 없어서 다시 잡니다. 이런 알림은 원인 기반(cause-based) 알림입니다. 문제를 일으킬 수도 있는 조건에 반응하는 거예요.
반대로 “오류율이 5%를 넘으면” 또는 “p99 응답 시간이 1초를 넘으면”은 증상 기반(symptom-based) 알림입니다. 사용자가 실제로 겪고 있는 문제에 반응하죠. 이 알림을 받고 일어나면 확인할 게 반드시 있습니다. 누군가 지금 실패를 겪고 있으니까요.
여기서 나오는 원칙이 있습니다. 사람을 깨우는 알림은 증상에 걸어야 한다는 겁니다. 원인은 수십 가지지만 증상은 몇 가지로 수렴합니다. 원인마다 알림을 걸면 규칙이 수십 개가 되고 그중 절반은 사용자 영향이 없는 상황에서 울립니다. 증상에 걸면 규칙은 몇 개로 줄고 울렸을 때는 항상 의미가 있습니다. 이 원칙은 Google의 Rob Ewaschuk가 “My Philosophy on Alerting”에서 정리했고 나중에 Site Reliability Engineering 책의 부록으로 실리면서 널리 알려졌습니다.
그렇다고 원인 알림이 쓸모없는 건 아닙니다. “디스크 사용률이 80%를 넘었다”는 지금 당장 사용자 영향은 없지만 며칠 뒤 장애의 예고죠. 이런 알림은 사람을 깨우는 대신 아침에 확인할 목록에 넣어 두는 게 맞습니다. 사람을 깨우는 알림과 알려만 두는 알림을 구분하는 것, 이게 앞의 예제에서 severity: page라는 꼬리표가 하는 일입니다. 어떤 심각도(severity)의 알림을 누구에게 어떤 경로로 보낼지는 그 자체로 큰 주제라 온콜 정리에서 따로 다룹니다.
알림의 상태: 대기, 발동, 해소
알림 규칙은 켜짐과 꺼짐 두 가지가 아니라 세 가지 상태를 오갑니다. 조건은 충족했지만 아직 지속 시간을 채우지 못한 대기(pending) 상태, 지속 시간까지 채워서 실제로 알림이 나간 발동(firing) 상태, 그리고 조건에서 벗어나 정상으로 돌아온 해소(resolved) 상태입니다.
flowchart TB
accTitle: 알림 규칙의 세 가지 상태
accDescr: 정상 상태에서 조건이 충족되면 대기 상태로 들어가고, 지속 시간을 채우면 발동 상태가 되어 알림이 나간다. 어느 단계에서든 조건에서 벗어나면 해소되어 정상으로 돌아간다.
normal(["정상"]) -- "조건 충족" --> pending["대기<br/>(pending)"]
pending -- "지속 시간 채움" --> firing["발동<br/>(firing)"]
pending -- "조건 벗어남" --> normal
firing -- "조건 벗어남" --> resolved["해소<br/>(resolved)"]
resolved --> normal
해소 상태가 따로 있는 이유는 “끝났다”는 것도 알려야 하기 때문입니다. 새벽에 알림을 받고 확인하러 들어갔는데 이미 정상이라면, 해소 통보가 있었으면 침대에서 나오지 않아도 됐겠죠. 그리고 발동과 해소가 짧은 간격으로 반복되는 요동(flapping)을 잡아내는 데도 상태 구분이 필요합니다. 임계값 근처에서 지표가 오르내리면 알림이 울렸다 꺼졌다를 반복하는데 지속 시간을 두는 것만으로도 상당 부분 걸러집니다.
한 가지 더 구분해 둘 게 있습니다. 규칙이 발동했다는 사실이 알림이고 그걸 사람에게 전달하는 행위는 통보(notification)입니다. 알림 하나가 여러 채널로 통보될 수도 있고, 여러 알림이 하나로 묶여 통보될 수도 있어요. 규칙을 평가하는 쪽과 통보를 보내는 쪽이 분리되어 있는 도구가 많은 것도 이 때문입니다.
같은 말, 다른 이름
이 글에서는 알림 규칙이라고 불렀지만 도구마다 부르는 이름이 다릅니다. Prometheus는 alerting rule, Grafana는 4.0부터 alert이라고 부르고, Datadog은 monitor, AWS CloudWatch는 alarm이라고 합니다. Nagios 시절에는 서비스가 살았는지 죽었는지 묻는 검사(check)가 곧 알림이었는데, 이건 로드 밸런서의 건강 검사와 같은 발상이죠.
이름은 달라도 안을 열어 보면 대상, 조건, 임계값, 지속 시간 네 요소는 그대로입니다. 그러니 도구를 바꿀 때 문법을 새로 배우더라도 규칙을 설계하는 방식은 그대로 가져갈 수 있습니다.
마치며
대시보드는 사람이 묻고 시스템이 답하는 도구이고, 알림은 시스템이 먼저 말을 거는 도구입니다. 알림 규칙은 대상, 조건, 임계값, 지속 시간 네 요소로 이루어지고, 이 중 지속 시간이 잡음을 거르는 핵심이었죠. 무엇에 알림을 걸지는 원인보다 증상을 기준으로 정하고, 사람을 깨우는 알림과 알려만 두는 알림을 구분합니다.
알림이 발동한 다음에는 누가 받을지, 받은 사람이 못 보면 어떻게 할지 하는 문제가 이어집니다. 알림이 너무 많아지면 무슨 일이 생기는지도요. 온콜과 알림 피로에 관한 이야기는 다음 글에서 이어 갑니다.
알림 규칙의 문법과 옵션은 Prometheus 공식 문서의 Alerting rules를 참고하세요.
This work is licensed under CC BY 4.0