온콜(On-call) 정리: 알림 라우팅부터 에스컬레이션, 알림 피로까지

온콜(On-call) 정리: 알림 라우팅부터 에스컬레이션, 알림 피로까지

알림 규칙을 공들여 만들어 놨다고 해 볼게요. 그런데 알림이 채팅 채널 하나에 전부 쏟아지고 있습니다. 낮에는 누군가 보고 반응하지만 새벽에는 아무도 안 봐요. 반대로 팀 전원에게 전화가 가도록 해 둔 팀도 있는데 다들 “누가 보겠지” 하고 다시 잠듭니다. 😅 규칙은 제대로 발동했는데 사람에게 닿지 않은 거죠.

알림이 발동한 순간부터 누군가 대응을 시작하는 순간까지, 그 사이의 경로를 설계하는 게 이 글의 주제입니다. 알림을 어디로 보낼지, 누가 받을지, 못 받으면 어떻게 할지, 그리고 알림이 너무 많아지면 무슨 일이 생기는지를 차례로 살펴보겠습니다.

알림과 통보 사이에 있는 것

앞 글에서 규칙이 발동한 사실을 알림(alert), 그걸 사람에게 전달하는 행위를 통보(notification)로 구분했는데요. 이 둘 사이에 계층이 하나 더 있습니다. 어느 알림을 어디로 보낼지 정하는 라우팅(routing), 비슷한 알림을 하나로 묶는 그룹핑(grouping), 큰 장애에 딸려 나오는 작은 알림을 눌러 두는 억제(inhibition)입니다.

왜 필요한지는 장애 하나를 상상해 보면 바로 보입니다. 데이터베이스가 죽으면 그걸 쓰는 서비스 열 개가 동시에 오류율 알림을 냅니다. 그 계층이 없으면 데이터베이스 알림 하나에 서비스 알림 열 개까지 열한 통이 같은 채널로 쏟아지죠. 그룹핑은 같은 원인으로 보이는 열 개를 “서비스 10개 오류율 상승” 한 통으로 묶고, 억제는 데이터베이스 알림이 발동 중인 동안 그 아래 알림을 아예 보내지 않습니다. 라우팅은 남은 알림을 심각도에 따라 다른 곳으로 보내고요.

Prometheus 생태계에서 이 역할을 맡는 Alertmanager의 설정으로 보면 다음과 같습니다.

alertmanager.yml
route:
  receiver: chat # 아무 규칙에도 안 걸리면 채팅 채널로
  group_by: [alertname, service]
  group_wait: 30s # 같은 그룹의 알림을 30초 모아서 한 통으로
  repeat_interval: 4h # 해소되지 않으면 4시간마다 다시 통보
  routes:
    - matchers: [severity="page"]
      receiver: oncall # 사람을 깨우는 경로
    - matchers: [severity="ticket"]
      receiver: tracker # 다음 근무 시간에 볼 경로

inhibit_rules:
  - source_matchers: [alertname="DatabaseDown"]
    target_matchers: [severity="page"]
    equal: [cluster] # 같은 클러스터에서 난 page 알림은 눌러 둠

앞 글의 예제에서 severity: page라는 꼬리표를 붙여 둔 이유가 여기서 드러납니다. 규칙 쪽은 “이 알림은 급하다”고 표시만 하고 그 표시를 보고 실제 경로를 정하는 건 이 계층의 일입니다. 규칙을 평가하는 쪽과 통보를 보내는 쪽이 분리되어 있으면 통보 경로를 바꿀 때 규칙을 하나도 건드리지 않아도 됩니다.

온콜: 정해진 사람이 받는다

oncall이라는 수신자에게 알림이 갔다고 해 볼게요. 그 수신자는 누구일까요? 온콜(on-call)은 정해진 기간 동안 알림을 받고 첫 대응을 책임지는 역할입니다. 보통 일주일 단위로 팀원이 돌아가며 맡는데 그 순서를 로테이션(rotation)이라고 부릅니다.

도입부의 “팀 전원에게 전화” 방식이 실패하는 이유는 책임이 나뉘기 때문입니다. 열 명이 같은 알림을 받으면 각자 “다른 아홉 명 중 누군가 보겠지”라고 생각하는 게 자연스러워요. 온콜은 이걸 뒤집어서 지금 이 시간에 알림을 받을 사람을 한 명으로 못 박습니다. 그 사람이 모든 걸 고쳐야 한다는 뜻은 아닙니다. 먼저 보고 상황을 파악한 다음 필요한 사람을 부르는 게 온콜의 일이에요. 그래서 1차(primary) 온콜 뒤에 2차(secondary) 온콜을 두는 팀이 많습니다. 1차가 못 받거나 혼자 감당이 안 될 때를 위해서요.

온콜 일정은 도구가 관리합니다. 누가 언제 온콜이고 휴가 중이면 누가 대신하는지를 달력처럼 두고 통보 계층이 그걸 조회해서 지금 이 순간의 온콜에게만 연락합니다.

확인과 에스컬레이션

통보를 받은 온콜은 먼저 확인(acknowledge)을 누릅니다. 흔히 ack라고 줄여 부르는데 “내가 봤고 내가 맡는다”는 신호예요. 아직 고친 건 아닙니다. 고쳐서 조건에서 벗어나는 건 해소(resolve)고 확인은 그 전 단계입니다. 이 둘을 구분해야 “누군가 보고 있다”와 “아무도 안 보고 있다”를 시스템이 알 수 있습니다.

확인이 안 오면 어떻게 될까요? 여기서 에스컬레이션(escalation)이 나옵니다. 정해진 시간 안에 확인이 없으면 다음 사람에게 넘기는 규칙이에요. 5분 안에 1차 온콜이 확인하지 않으면 2차 온콜에게, 거기서도 5분이 지나면 팀 리드에게 가는 식입니다. 사슬 전체를 에스컬레이션 정책(escalation policy)이라고 합니다.

flowchart TB
    accTitle: 에스컬레이션 정책의 흐름
    accDescr: 알림이 통보되면 1차 온콜이 확인했는지 판단한다. 확인했으면 대응을 시작하고, 5분 안에 확인이 없으면 2차 온콜에게 넘긴다. 2차도 확인하지 않으면 팀 리드에게 넘어간다.

    notify["통보 발송"] --> primary{"1차 온콜이<br/>5분 안에 확인?"}
    primary -- "예" --> respond(["대응 시작"])
    primary -- "아니요" --> secondary{"2차 온콜이<br/>5분 안에 확인?"}
    secondary -- "예" --> respond
    secondary -- "아니요" --> lead["팀 리드에게 통보"]
    lead --> respond

에스컬레이션은 사람을 벌주는 장치가 아니라 안전망입니다. 휴대폰이 무음이었을 수도 있고, 지하철 안이었을 수도 있어요. 한 사람이 못 받았다고 장애가 방치되는 일을 막는 게 목적이지, 못 받은 사람을 추궁하는 게 목적이 아닙니다. 그래서 타임아웃도 심각도에 맞춰 정합니다. 사람을 깨우는 알림이면 5분, 아침에 봐도 되는 알림이면 몇 시간이어도 괜찮습니다.

확인도 해소도 없이 시간이 흐르면 어떻게 될까요? 앞의 설정에서 repeat_interval: 4h가 그 경우를 다룹니다. 알림이 살아 있는 한 4시간마다 다시 통보해서 확인은 했는데 잊어버린 알림이 조용히 묻히지 않게 합니다.

심각도가 경로를 정한다

앞 글에서 사람을 깨우는 알림과 알려만 두는 알림을 구분하자고 했는데 그 구분이 통보 경로에서 실체가 됩니다. 심각도(severity)는 알림의 속성이라기보다 “이 알림에 어떻게 대응하기로 약속했는가”를 적어 둔 꼬리표예요.

처음에는 세 단계면 충분합니다. 지금 당장 사람을 깨우는 page, 다음 근무 시간에 보면 되는 ticket, 참고만 하면 되는 info. page는 전화나 푸시 알림처럼 사람을 실제로 깨울 수 있는 채널로 갑니다. ticket은 이슈 트래커에 등록되고 info는 채팅 채널에 남고요. 단계마다 채널과 기대 응답 시간이 짝으로 붙어 있는 셈이죠. 단계를 다섯 개, 여섯 개로 늘리면 오히려 경계가 흐려져서 “이건 P3인가 P4인가”를 놓고 시간을 씁니다.

page라는 말은 호출기(pager)에서 왔습니다. 휴대폰 이전에 의사나 엔지니어가 허리에 차던 그 기계요. 지금은 “누군가를 page한다”가 “깨워서 부른다”는 동사로 굳어졌습니다. 참고로 이 단계 이름은 도구마다 severity, urgency, priority로 제각각이라 도구 문서를 읽을 때 같은 개념인지 확인해야 합니다.

알림 피로: 많은 알림은 없는 것보다 나쁘다

알림이 너무 많으면 사람은 둔감해집니다. 이걸 알림 피로(alert fatigue)라고 부르는데 늑대가 나타났다고 계속 외치던 양치기 소년 이야기 그대로예요. 알림이 아예 없는 것보다 나쁩니다. 알림이 없으면 대시보드라도 보는데 알림에 지친 사람은 진짜 장애 알림도 “또 그거겠지” 하고 넘기니까요.

알림 피로의 원인은 대개 규칙 쪽에 있습니다. 받아도 할 게 없는 알림, 사용자 영향이 없는 원인 기반 알림, 지속 시간이 없어서 순간마다 튀는 규칙, 그룹핑이 안 돼서 장애 하나에 스무 통씩 오는 중복. 앞 글에서 다룬 내용이 전부 여기로 돌아옵니다.

그래서 기준을 하나 세워 두면 좋습니다. 사람을 깨우는 알림은 받은 사람이 지금 해야 할 행동이 있어야 한다는 겁니다. 알림을 받고 확인해 보니 할 일이 없었다면 그 규칙은 page가 아니라 ticket으로 내리거나 아예 지워야 합니다. “혹시 몰라서” 남겨 두는 알림이 쌓이면 그게 알림 피로의 재료가 됩니다.

부담은 측정할 수 있습니다. 교대 한 번에 page가 몇 건 오는지 세는 거예요. Google의 Site Reliability Engineering 책은 장애 하나를 제대로 처리하는 데 원인 분석부터 복구, 사후 기록까지 평균 6시간이 든다고 계산하고, 12시간 교대에 page 2건을 넘기면 온콜이 다른 일을 전혀 할 수 없는 상태라고 봅니다. 그 숫자를 넘기고 있다면 규칙을 고치는 게 새 기능보다 먼저입니다. 온콜 부담이 쌓이면 개인의 문제로 끝나지 않고 번아웃으로 이어지고, 팀에서 온콜을 기피하는 사람이 늘어나면 남은 사람의 부담이 다시 커지는 악순환이 됩니다.

알림을 잠시 끄기

배포 중이거나 점검 중이거나 이미 알고 있는 장애를 고치는 중이면 알림이 계속 울리는 게 방해가 됩니다. 이럴 때 조건과 만료 시간을 정해 통보를 잠시 막는 걸 사일런스(silence)라고 합니다. “다음 두 시간 동안 service=payment인 알림은 통보하지 않는다” 같은 식이죠.

만료 시간이 핵심입니다. 끄고 잊어버리면 그 규칙은 영원히 없는 것과 같아져서 몇 달 뒤 장애가 나도 아무도 모릅니다. 그래서 사일런스는 언제나 끝나는 시각과 함께 만들고 끝나기 전에 연장할지 말지를 다시 판단하게 합니다. 또 사일런스는 알림 자체를 멈추는 게 아니라 통보만 막습니다. 규칙은 계속 평가되고 발동 상태도 대시보드에 그대로 보여요. 그래서 사일런스 중에 무슨 일이 있었는지 나중에 되짚어 볼 수 있습니다.

사일런스도 도구마다 사일런스, 유지보수 창(maintenance window), 다운타임(downtime)으로 이름이 다르지만 하는 일은 같습니다.

온콜이 끝난 뒤

장애가 해소되면 끝이 아닙니다. 무슨 일이 있었는지, 왜 일어났는지, 어떻게 알아챘는지, 다음에는 무엇을 바꿀지를 적는 사후 검토(postmortem)가 이어집니다. 여기서 “누구 잘못인가”를 따지지 않는 게 원칙이라 비난 없는(blameless) 사후 검토라고 부르기도 하고요.

사후 검토에서 알림에 관한 질문은 늘 두 가지입니다. 알림이 제때 울렸는가, 그리고 울린 알림이 도움이 됐는가. 늦게 울렸으면 임계값이나 지속 시간을 조정하고, 쓸데없이 울렸으면 심각도를 내리거나 규칙을 지웁니다. 앞 글에서 임계값은 정답이 아니라 가설이라고 했는데 그 가설을 검증하는 자리가 바로 온콜입니다. 온콜에서 겪은 게 규칙으로 돌아가고 고친 규칙이 다음 온콜을 편하게 만드는 회로가 도는 팀이 알림을 잘 쓰는 팀이에요.

마치며

알림은 규칙이 발동한 사실이고, 그 사실이 사람에게 닿으려면 라우팅과 그룹핑을 거쳐 온콜에게 통보되어야 합니다. 온콜은 정해진 한 사람이 받고 확인이 없으면 에스컬레이션으로 다음 사람에게 넘어갑니다. 어느 채널로 갈지는 심각도가 정하고요. 알림이 너무 많으면 사람이 둔감해지니 사람을 깨우는 알림은 반드시 행동이 따라야 하고, 교대당 page 수로 부담을 재서 규칙을 고치는 쪽으로 되돌립니다.

그런데 아직 남은 질문이 있습니다. 오류율 임계값을 5%로 할지 1%로 할지를 놓고 팀마다 의견이 갈리는데, 이 논쟁은 “우리 서비스는 얼마나 실패해도 괜찮은가”를 먼저 정하지 않으면 끝나지 않습니다. 그 기준을 세우는 SLI와 SLO 이야기는 다음 글에서 이어 갑니다.

라우팅, 그룹핑, 억제, 사일런스의 설정 문법은 Alertmanager 공식 문서를 참고하세요.

This work is licensed under CC BY 4.0CCBY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord