TT Lab
블로그 시작하기
배우기 러닝패스 코스

장애 지휘와 포스트모템

결제 사고 채널 — 누가 지휘했고, 무엇이 비었나

TT Lab 에서 이어서 보기

목표

사고 채널 기록(채팅·호출·주문)에서 역할의 흐름, 선언까지 걸린 시간, 주인 없는 결정을 데이터로 찾아내고, 계산한 숫자로 정해진 형식의 상황 공지를 씁니다. 마지막에는 호출이 사람에게 닿지 않은 사건을 설정에서 고칩니다.

왜 중요한가

사고 지휘는 「잘 하자」 는 다짐이 아니라 기록으로 확인할 수 있는 습관입니다. 선언까지 몇 분 걸렸는지, IC 가 손을 댔는지, 주인 없는 결정이 몇 개였는지는 채널 기록에 그대로 남고, 그것을 세어 보는 팀만 다음에 나아집니다. 상황 공지의 숫자는 고객과 경영진이 그대로 믿는 값이라 어림으로 쓰면 안 됩니다. 그리고 이 모든 것은 호출이 사람에게 닿아야 시작됩니다 — 호출 설정은 사고가 나기 전에 고쳐 둘 수 있는 거의 유일한 지휘 장치입니다.

단계

  1. python3 /opt/fixtures/ic/ickit.py open roles 로 결제 사고 재료를 펼치고 /root/ic/roles/README.txt 를 읽으세요. 사고 채널(chat.jsonl)에 글을 쓴 사람이 몇 명인지(봇 icbot 은 빼고, 같은 사람은 한 번) 세어 /root/ic/roles/answers/participants.txt 에 숫자 하나로 적습니다.
  2. 이 사고의 첫 경보(pages.csv 에서 이름이 pay- 로 시작하는 경보가 처음 triggered 된 시각)부터 (1) 첫 응답(pay- 경보의 첫 acked)까지, (2) 채널의 /declare 까지 몇 초가 걸렸는지 /root/ic/roles/answers/declare.txt 에 ack_delay_sec=<seconds>, declare_delay_sec=<seconds> 두 줄로 적습니다.
  3. 사고가 끝날 때(/resolve) IC·Comms·Ops 를 각각 누가 맡고 있었는지 /root/ic/roles/answers/roles.txt 에 ic: <name>, comms: <name>, ops: <name> 세 줄로 적습니다(이름은 @ 없이).
  4. IC 가 지휘를 맡은 그 순간에 직접 /doing 을 친 결정을 찾아 /root/ic/roles/answers/ic-hands.txt 에 결정 번호(D1 꼴)를 한 줄에 하나씩 적습니다(그런 결정이 없으면 none). IC 를 넘긴 뒤의 옛 IC 가 한 일은 여기에 들지 않습니다.
  5. /decide 로 기록된 결정 가운데 (1) /own 으로 주인이 정해지지 않은 것과 (2) 앞사람이 /done 하기 전에 다른 사람이 같은 결정을 /doing 한 것(동시에 손댄 것)을 찾아 /root/ic/roles/answers/work.txt 에 unowned: <decisions>, concurrent: <decisions> 두 줄로 적습니다(없으면 콜론 뒤를 비웁니다).
  6. Comms 가 /status 를 친 시각을 기준으로 첫 상황 공지를 /root/ic/roles/answers/status.txt 에 다섯 줄로 씁니다. 첫 줄은 [SEV<n>] <title> — status update(심각도와 /declare 의 제목), 이어서 time: <YYYY-MM-DD HH:MM> KST(공지 시각, 분까지), impact: failed_orders=<N> affected_customers=<M> since=<HH:MM> KST(실패한 주문 수·그 고객 수·첫 경보 시각), command: ic=@<name> comms=@<name>(공지 시각의 IC·Comms), next_update: <YYYY-MM-DD HH:MM> KST(README 의 심각도별 간격 안). 영향 숫자는 첫 경보 시각부터 공지 시각까지의 orders.csv 에서 셉니다. 키를 한국어(시각·영향·지휘·다음 공지)로 쓴 예전 형식도 받습니다.
  7. incident ic-slow-page 를 실행하면 사건이 납니다. 어젯밤 payments·search·identity 세 팀의 경보가 한 번씩 울렸는데 셋 다 처음 20분 동안 아무에게도 호출이 닿지 않았습니다(호출 기록 /var/log/pager/pages.log, 설정 /srv/pager/, 모의기 python3 /opt/fixtures/ic/pagersim.py <alert> <time>). 팀마다 원인을 찾아 언제 울려도 0분에 그 팀 당번에게 닿게 고치세요. 정책의 첫 단계는 그 팀 당번(oncall)을 0분에 부르는 것으로 두고, 한 사람에게 일주일 당번의 60% 넘게를 몰지 않으며, people.csv 의 사람은 지우지 않습니다. 당번은 사건 전 people.csv 에 그 팀으로 적혀 있던 사람 가운데서 맡깁니다(새 사람을 만들거나 퇴사자를 되살리지 않습니다). 연락처는 호출 서비스가 쓸 수 있는 sms:<number>·voice:<number>·push:<id>·email:<address> 꼴이어야 하고, 경로 규칙은 고칠 때 바꾸지 말고 더합니다(원래 이름의 경보도 계속 그 팀으로 가야 합니다). /root/incident/ic-slow-page/report.txt 에 payments: <code>, search: <code>, identity: <code> 세 줄과 evidence: <pages.log-line> 을 적습니다. 코드는 팀마다 다음 중 하나입니다: ack-timeout(호출은 닿았는데 확인이 늦음), contact-missing(당번의 연락 수단이 비었음), override-departed(당번 교대가 퇴사자를 가리킴), route-mismatch(경보 이름이 경로 규칙과 안 맞아 기본 정책으로 떨어짐), schedule-gap(당번표가 그 시각을 덮지 않음).

참고

재료 펼치기 — 채널에 누가 있었나

python3 /opt/fixtures/ic/ickit.py open roles 로 결제 사고 재료를 펼치고 /root/ic/roles/README.txt 를 읽으세요. 사고 채널(chat.jsonl)에 글을 쓴 사람이 몇 명인지(봇 icbot 은 빼고, 같은 사람은 한 번) 세어 /root/ic/roles/answers/participants.txt 에 숫자 하나로 적습니다.

한 줄이 JSON 하나입니다. jq 로 user 칸만 뽑아 중복을 없애고 셉니다. 봇은 사람이 아닙니다.

첫 경보에서 응답·선언까지

이 사고의 첫 경보(pages.csv 에서 이름이 pay- 로 시작하는 경보가 처음 triggered 된 시각)부터 (1) 첫 응답(pay- 경보의 첫 acked)까지, (2) 채널의 /declare 까지 몇 초가 걸렸는지 /root/ic/roles/answers/declare.txt 에 ack_delay_sec=<seconds>, declare_delay_sec=<seconds> 두 줄로 적습니다.

두 파일의 시간대가 다릅니다(pages.csv 는 UTC, 채팅은 KST). 같은 순간 단위로 바꾼 뒤 빼세요. pages.csv 에는 이 사고와 무관한 경보도 먼저 있습니다. date -d 는 두 꼴을 모두 읽습니다.

사고가 끝날 때의 역할

사고가 끝날 때(/resolve) IC·Comms·Ops 를 각각 누가 맡고 있었는지 /root/ic/roles/answers/roles.txt 에 ic: <name>, comms: <name>, ops: <name> 세 줄로 적습니다(이름은 @ 없이).

README 의 명령 규칙대로 셉니다 — 메시지가 / 로 시작할 때만 명령이고, 같은 역할을 다시 맡기면 넘긴 것입니다. 봇의 되풀이, 문장 속에 적힌 명령, 봇이 거절한 역할은 역할 변경이 아닙니다.

IC 가 직접 손댄 결정

IC 가 지휘를 맡은 그 순간에 직접 /doing 을 친 결정을 찾아 /root/ic/roles/answers/ic-hands.txt 에 결정 번호(D1 꼴)를 한 줄에 하나씩 적습니다(그런 결정이 없으면 none). IC 를 넘긴 뒤의 옛 IC 가 한 일은 여기에 들지 않습니다.

채널은 시간순입니다. 위에서부터 읽으며 「지금 IC 가 누구인가」 를 변수 하나에 들고 있다가, /doing 을 친 사람과 비교하세요.

주인 없는 결정과 겹친 작업

/decide 로 기록된 결정 가운데 (1) /own 으로 주인이 정해지지 않은 것과 (2) 앞사람이 /done 하기 전에 다른 사람이 같은 결정을 /doing 한 것(동시에 손댄 것)을 찾아 /root/ic/roles/answers/work.txt 에 unowned: <decisions>, concurrent: <decisions> 두 줄로 적습니다(없으면 콜론 뒤를 비웁니다).

결정마다 「누가 지금 하고 있는가」 를 들고 있어야 합니다. 앞사람이 끝낸 뒤 확인차 다시 한 것은 겹친 것이 아닙니다. awk 의 연관 배열이나 python 의 dict 가 편합니다.

첫 상황 공지 쓰기

Comms 가 /status 를 친 시각을 기준으로 첫 상황 공지를 /root/ic/roles/answers/status.txt 에 다섯 줄로 씁니다. 첫 줄은 [SEV<n>] <title> — status update(심각도와 /declare 의 제목), 이어서 time: <YYYY-MM-DD HH:MM> KST(공지 시각, 분까지), impact: failed_orders=<N> affected_customers=<M> since=<HH:MM> KST(실패한 주문 수·그 고객 수·첫 경보 시각), command: ic=@<name> comms=@<name>(공지 시각의 IC·Comms), next_update: <YYYY-MM-DD HH:MM> KST(README 의 심각도별 간격 안). 영향 숫자는 첫 경보 시각부터 공지 시각까지의 orders.csv 에서 셉니다. 키를 한국어(시각·영향·지휘·다음 공지)로 쓴 예전 형식도 받습니다.

orders.csv 의 시각은 KST 인데 시간대 표시가 없습니다 — 첫 경보(UTC)와 비교하려면 같은 시간대로 맞춥니다. 고객 수는 중복 없이 셉니다. 역할은 공지 시각까지의 /role 만 반영합니다.

사건 — 경보 셋이 20분 동안 아무도 깨우지 못했다

incident ic-slow-page 를 실행하면 사건이 납니다. 어젯밤 payments·search·identity 세 팀의 경보가 한 번씩 울렸는데 셋 다 처음 20분 동안 아무에게도 호출이 닿지 않았습니다(호출 기록 /var/log/pager/pages.log, 설정 /srv/pager/, 모의기 python3 /opt/fixtures/ic/pagersim.py <alert> <time>). 팀마다 원인을 찾아 언제 울려도 0분에 그 팀 당번에게 닿게 고치세요. 정책의 첫 단계는 그 팀 당번(oncall)을 0분에 부르는 것으로 두고, 한 사람에게 일주일 당번의 60% 넘게를 몰지 않으며, people.csv 의 사람은 지우지 않습니다. 당번은 사건 전 people.csv 에 그 팀으로 적혀 있던 사람 가운데서 맡깁니다(새 사람을 만들거나 퇴사자를 되살리지 않습니다). 연락처는 호출 서비스가 쓸 수 있는 sms:<number>·voice:<number>·push:<id>·email:<address> 꼴이어야 하고, 경로 규칙은 고칠 때 바꾸지 말고 더합니다(원래 이름의 경보도 계속 그 팀으로 가야 합니다). /root/incident/ic-slow-page/report.txt 에 payments: <code>, search: <code>, identity: <code> 세 줄과 evidence: <pages.log-line> 을 적습니다. 코드는 팀마다 다음 중 하나입니다: ack-timeout(호출은 닿았는데 확인이 늦음), contact-missing(당번의 연락 수단이 비었음), override-departed(당번 교대가 퇴사자를 가리킴), route-mismatch(경보 이름이 경로 규칙과 안 맞아 기본 정책으로 떨어짐), schedule-gap(당번표가 그 시각을 덮지 않음).

방향: 호출이 닿기까지는 경로 → 팀 정책의 단계 → 그 시각의 당번(교대 표가 당번표보다 먼저) → 그 사람의 존재와 연락 수단 순서로 갑니다. 팀마다 어느 칸에서 끊겼는지가 다릅니다. 도구: pages.log 의 FAILED·route 줄, 모의기(아무 시각이나 넣어 미리 돌려 보기), grep, awk -F,. 구체: 팀마다 사건 시각의 경로 줄과 level=1 줄을 읽고, 그 줄이 가리키는 파일(routes·people·overrides·schedule)에서 그 시각·그 사람의 줄을 찾으세요. 고친 뒤에는 그 시각만이 아니라 일주일의 다른 시각으로도 모의기를 돌려 봅니다.