결제 사고 채널 — 누가 지휘했고, 무엇이 비었나
목표
사고 채널 기록(채팅·호출·주문)에서 역할의 흐름, 선언까지 걸린 시간, 주인 없는 결정을 데이터로 찾아내고, 계산한 숫자로 정해진 형식의 상황 공지를 씁니다. 마지막에는 호출이 사람에게 닿지 않은 사건을 설정에서 고칩니다.
왜 중요한가
사고 지휘는 「잘 하자」 는 다짐이 아니라 기록으로 확인할 수 있는 습관입니다. 선언까지 몇 분 걸렸는지, IC 가 손을 댔는지, 주인 없는 결정이 몇 개였는지는 채널 기록에 그대로 남고, 그것을 세어 보는 팀만 다음에 나아집니다. 상황 공지의 숫자는 고객과 경영진이 그대로 믿는 값이라 어림으로 쓰면 안 됩니다. 그리고 이 모든 것은 호출이 사람에게 닿아야 시작됩니다 — 호출 설정은 사고가 나기 전에 고쳐 둘 수 있는 거의 유일한 지휘 장치입니다.
단계
python3 /opt/fixtures/ic/ickit.py open roles로 결제 사고 재료를 펼치고/root/ic/roles/README.txt를 읽으세요. 사고 채널(chat.jsonl)에 글을 쓴 사람이 몇 명인지(봇icbot은 빼고, 같은 사람은 한 번) 세어/root/ic/roles/answers/participants.txt에 숫자 하나로 적습니다.- 이 사고의 첫 경보(
pages.csv에서 이름이pay-로 시작하는 경보가 처음 triggered 된 시각)부터 (1) 첫 응답(pay- 경보의 첫 acked)까지, (2) 채널의/declare까지 몇 초가 걸렸는지/root/ic/roles/answers/declare.txt에ack_delay_sec=<seconds>,declare_delay_sec=<seconds>두 줄로 적습니다. - 사고가 끝날 때(
/resolve) IC·Comms·Ops 를 각각 누가 맡고 있었는지/root/ic/roles/answers/roles.txt에ic: <name>,comms: <name>,ops: <name>세 줄로 적습니다(이름은 @ 없이). - IC 가 지휘를 맡은 그 순간에 직접
/doing을 친 결정을 찾아/root/ic/roles/answers/ic-hands.txt에 결정 번호(D1꼴)를 한 줄에 하나씩 적습니다(그런 결정이 없으면none). IC 를 넘긴 뒤의 옛 IC 가 한 일은 여기에 들지 않습니다. /decide로 기록된 결정 가운데 (1)/own으로 주인이 정해지지 않은 것과 (2) 앞사람이/done하기 전에 다른 사람이 같은 결정을/doing한 것(동시에 손댄 것)을 찾아/root/ic/roles/answers/work.txt에unowned: <decisions>,concurrent: <decisions>두 줄로 적습니다(없으면 콜론 뒤를 비웁니다).- 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에서 셉니다. 키를 한국어(시각·영향·지휘·다음 공지)로 쓴 예전 형식도 받습니다. 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 <name>이 재료를/root/ic/<name>/에 펼칩니다. 재료는 사람마다 다른 무작위 씨앗으로 만들어지고, 채점기는 같은 씨앗으로 정답을 다시 계산합니다 — 남의 숫자는 맞지 않습니다. 다시 펼쳐도 같은 재료이고,--fresh를 붙이면 새 재료로 처음부터입니다. - 답은
/root/ic/<name>/answers/아래에 씁니다. 세션이 끝나면 사라지니 필요하면 따로 보관하세요. - 사건 도구:
incident list로 낼 수 있는 사건과 한 줄 증상을,incident show <name>으로 티켓을 다시 봅니다. 같은 사건을 다시 내면 새 무작위 재료로 처음 고장 상태가 됩니다(앞서 적은 답은 맞지 않게 됩니다). - 보고서는
/root/incident/<name>/report.txt입니다. 대상마다<target>: <cause-code>한 줄씩, 마지막에evidence: <line>를 그대로 붙입니다. 증거는 재료나 관찰 기록에 그대로 있는 한 줄(20자 이상)이어야 합니다. 채점기는 고친 뒤의 실제 상태를 먼저 보고, 보고서는 판단을 확인하는 데 씁니다. - 재료의 시간대:
pages.csv는 UTC,chat.jsonl은 KST(+09:00),orders.csv는 KST 인데 표시가 없습니다. jq -r 'select(.user != "icbot") | .text' chat.jsonl처럼 사람의 메시지만 걸러 읽으면 흐름이 잘 보입니다.- 흔한 실수 둘 — 봇의 되풀이(icbot)를 사람의 명령으로 세는 것, 그리고 문장 속에 백틱으로 적힌 명령을 진짜 명령으로 읽는 것.
재료 펼치기 — 채널에 누가 있었나
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)에서 그 시각·그 사람의 줄을 찾으세요. 고친 뒤에는 그 시각만이 아니라 일주일의 다른 시각으로도 모의기를 돌려 봅니다.