알람 임계값을 다시 정한 이유
임계값은 한 번 정하면 끝이 아니다. 왜 다시 건드렸는지 기록.
알람이 너무 많았다
어느 시점부터 on-call 채널이 잡음 천지가 됐다. 새벽 3시에 울린 알람을 열어보면 CPU 스파이크가 90%를 찍었다가 2초 만에 내려간 것. 아무도 다치지 않았다. 근데 나는 깼다.
문제는 그 알람이 ‘한 번도 틀린 적 없다’는 점이었다. 임계값 기준으로는 진짜 발화했으니까. 그래서 묵묵히 넘겼는데, 어느 날 팀원이 “저 알람 진짜로 대응한 게 언제야?”라고 물었다. 3개월치 로그를 뒤졌더니 actionable한 케이스가 전체의 11%였다. 89%는 확인 후 닫기였다.
alert fatigue 가 수면 문제로 직결된다는 건 알고 있었다. 그게 신뢰 문제로도 번진다는 걸 뒤늦게 실감했다. 팀이 알람을 믿지 않기 시작하면, 진짜 사고가 났을 때도 채널을 늦게 확인한다. 실제로 올해 초 DB 커넥션 풀이 포화됐을 때 첫 확인까지 14분 걸렸다. 평소 같으면 3분 안에 봤을 거다.
무엇을 어떻게 바꿨나
먼저 알람을 세 종류로 나눴다.
- 즉시 대응 — 사람이 지금 당장 뭔가 해야 함. PagerDuty 호출.
- 확인 요망 — 오늘 안에 보면 됨. Slack 채널 메시지.
- 기록용 — 나중에 트렌드 볼 때 쓰는 것. 대시보드에만 찍음.
이 분류 자체는 새로운 개념이 아니다. Alerting on SLOs 책에서도 비슷한 얘기가 나온다. 막상 해보면 분류 기준이 흐릿해서 다들 첫 번째 버킷에 전부 때려 넣는다.
임계값 자체는 단순 퍼센트에서 지속 시간 + 비율 조합으로 바꿨다. 예를 들어 CPU 알람은 이전에 > 85% 였는데, 지금은 > 85% for 5m AND rate-of-change > 0 으로 바꿨다. 잠깐 튀었다 내려오는 스파이크는 걸리지 않는다. Prometheus 기준으로 표현하면 대략 이렇게 된다:
expr: |
(
avg by(instance) (rate(node_cpu_seconds_total{mode!="idle"}[5m])) * 100 > 85
)
and
(
deriv(node_cpu_seconds_total{mode!="idle"}[5m]) > 0
)
for: 5m
for: 5m 이 핵심이다. 이게 없으면 여전히 1초짜리 스파이크에도 울린다.
에러율 알람은 반대 방향에서 튜닝했다. absolute count 가 너무 낮을 때는 퍼센트 기반 알람이 오발화한다. 트래픽이 거의 없는 새벽 시간대에 요청 2건 중 1건이 실패하면 50% 에러율이 찍히는데, 이건 알람을 낼 상황이 아니다. rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.05 에 and rate(http_requests_total[5m]) > 1 조건을 붙였다. 초당 요청이 1건 미만이면 아예 무시.
이 작업을 하면서 제일 시간 걸린 건 코드가 아니었다. “이 알람이 울렸을 때 내가 진짜로 무엇을 하는가” 를 서술하는 runbook 초안 작성이었다. 대응 절차가 없는 알람은 결국 ‘일단 확인’ 으로 끝난다. 그게 피로의 원인이었다.
runbook 은 거창하게 만들지 않았다. Notion 페이지 하나에 알람 이름 / 먼저 볼 것 / 가장 흔한 원인 / 에스컬레이션 기준, 이 네 가지만 적었다. 2시간 만에 주요 알람 12개 커버.
다음 한 가지
한 달 뒤에 actionable rate 를 다시 세어서 — 이번엔 80% 이상 나오면 건드리지 않는다.
🛒 이 글과 어울리는 추천 상품
위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.