같은 5xx, 다른 범인 — 시계를 맞추고 타임라인 세우기
목표
시간대와 시계가 서로 다른 기록들(서버 로그 둘, 로드밸런서 표본, git 배포 기록, 플래그 기록, 경보·지표)을 하나의 기준 시계로 맞춰 타임라인을 세우고, 첫 오류·용의 변경·탐지 지연·완화 시각을 데이터로 정합니다. 마지막에는 같은 증상의 세 서비스를 원인별로 가르고 되돌릴 것만 되돌립니다.
왜 중요한가
포스트모템의 타임라인이 틀리면 그 위에 쌓은 원인과 조치가 모두 틀립니다. 그리고 타임라인을 틀리게 만드는 것은 대개 큰 실수가 아니라 시간대 표시 하나, 몇 분 어긋난 시계 하나입니다. 사고 중에는 같은 착시가 「전부 롤백하자」 는 결정으로 이어집니다 — 멀쩡한 변경을 되돌리는 것은 새 변경을 하나 더 하는 것이고, 진짜 원인은 그대로 남습니다.
단계
python3 /opt/fixtures/ic/ickit.py open timeline으로 재료를 펼치고/root/ic/timeline/README.txt를 읽으세요.web-1.log와web-2.log에서 상태 코드가 5xx 인 줄 수를 합쳐/root/ic/timeline/answers/errors.txt에 숫자 하나로 적습니다.deploys.log(git log)의 배포와flags.log의 플래그 변경을 모두 UTC 로 바꿔 오래된 것부터/root/ic/timeline/answers/changes.tsv에<utc-time>\t<change-id>한 줄씩 적습니다. 시각은2026-11-04T05:12:03Z꼴, 변경 id 는 배포면 커밋 sha 앞 7자, 플래그면flag:<name>입니다.lb-sample.log(기준 시계)와 두 서버 로그에 같은 req id 로 찍힌 요청을 짝지어, 서버마다server_time − reference_time을 정수 초로 재서/root/ic/timeline/answers/skew.txt에web-1=<seconds>,web-2=<seconds>두 줄로 적습니다(앞선 시계는 +, 뒤처진 시계는 −).- 앞 단계의 어긋남을 빼서 두 서버의 5xx 를 기준 시계로 맞추고, 가장 이른 것의 시각(UTC, 초까지)과 서버를
/root/ic/timeline/answers/first-error.txt에first_error_utc=<time>,host=<server>두 줄로 적습니다. - 기준 시계의 첫 오류 전 30분(첫 오류 시각 포함) 안에 들어간 변경의 id 를
/root/ic/timeline/answers/suspects.txt에 한 줄에 하나씩 적습니다. - 기준 시계의 첫 오류부터 이 사고의 경보(
alerts.csv의api-5xx-ratefired)까지 걸린 초와, README 에 정의된 완화 시각(분의 시작, UTC)을/root/ic/timeline/answers/phases.txt에detect_delay_sec=<seconds>,mitigated_utc=<time>두 줄로 적습니다. incident ic-error-spike를 실행하면 사건이 납니다. cart·search·pay 세 서비스가 오늘 차례로 배포됐고 셋 다 그 무렵부터 5xx 가 이어집니다(기록/srv/spike/, README 먼저). 서비스마다 오류를 시작시킨 것을 찾아, 되돌릴 것만/srv/spike/rollback.conf에<service> app=<previous-version>또는<service> config=<previous-revision>으로 적습니다(되돌릴 필요가 없는 서비스는 적지 않습니다)./root/incident/ic-error-spike/report.txt에cart: <code>,search: <code>,pay: <code>세 줄과, 서비스마다 그 원인을 보여 주는 재료의 한 줄을evidence-cart: <line>,evidence-search: <line>,evidence-pay: <line>로 적습니다(원인이 된 변경의 줄, 또는 의존 서비스 지표·시간 초과 줄). 그리고 서비스마다 기준 시계(로드밸런서)로 맞춘 첫 5xx 시각을start-cart: <utc-time>,start-search: <utc-time>,start-pay: <utc-time>로 적습니다(두 서버 중 더 이른 것, ±10초까지 받습니다). 원인은 서비스마다 따로 정해져 있어 같은 원인이 여럿일 수도 있습니다. 코드는 서비스마다 다음 중 하나입니다:bad-deploy(새 앱 판의 결함),clock-skew(시계 어긋남이 만든 착시),config-push(설정 배포),traffic-surge(요청 급증),upstream-timeout(하위 의존 서비스 지연).
참고
- 재료 도구:
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자 이상)이어야 합니다. 채점기는 고친 뒤의 실제 상태를 먼저 보고, 보고서는 판단을 확인하는 데 씁니다. - 로그 한 줄:
<time> <server> req=<id> GET <path> <status> <latency>ms. 다섯째 칸이 경로, 여섯째 칸이 상태입니다. - git log 의 Date 꼴은 python
datetime.strptime(s, "%a %b %d %H:%M:%S %Y %z")로 읽습니다. ISO 꼴은datetime.fromisoformat이 읽습니다 (끝의 Z 는+00:00으로 바꿔 넣으세요). - 흔한 실수 둘 — 시간대(+09:00)를 시계 어긋남으로 읽는 것, 그리고 git log 의 날짜 글자(Nov 3)를 순간처럼 정렬하는 것.
재료 펼치기 — 5xx 는 몇 줄인가
python3 /opt/fixtures/ic/ickit.py open timeline 으로 재료를 펼치고 /root/ic/timeline/README.txt 를 읽으세요. web-1.log 와 web-2.log 에서 상태 코드가 5xx 인 줄 수를 합쳐 /root/ic/timeline/answers/errors.txt 에 숫자 하나로 적습니다.
상태 코드는 줄의 여섯째 칸입니다. 4xx 는 서버 잘못이 아니라 세지 않습니다.
변경을 UTC 한 줄로
deploys.log(git log)의 배포와 flags.log 의 플래그 변경을 모두 UTC 로 바꿔 오래된 것부터 /root/ic/timeline/answers/changes.tsv 에 <utc-time>\t<change-id> 한 줄씩 적습니다. 시각은 2026-11-04T05:12:03Z 꼴, 변경 id 는 배포면 커밋 sha 앞 7자, 플래그면 flag:<name> 입니다.
git 의 Date 는 커밋한 사람의 시간대(+0900·-0800)로 찍히고 최신이 위입니다. 그 꼴은 GNU date 가 못 읽으니 python 의 strptime("%a %b %d %H:%M:%S %Y %z")를 쓰세요. 날짜 글자가 아니라 순간으로 정렬해야 합니다.
서버 시계는 몇 초 어긋났나
lb-sample.log(기준 시계)와 두 서버 로그에 같은 req id 로 찍힌 요청을 짝지어, 서버마다 server_time − reference_time 을 정수 초로 재서 /root/ic/timeline/answers/skew.txt 에 web-1=<seconds>, web-2=<seconds> 두 줄로 적습니다(앞선 시계는 +, 뒤처진 시계는 −).
시간대 표시(+09:00, Z)를 먼저 읽어 같은 순간(에포크 초)으로 바꾼 뒤 뺍니다. 요청은 로드밸런서를 지난 뒤 1초 안에 서버에 닿으므로 여러 쌍의 가운데 값(median)을 반올림하면 됩니다.
기준 시계의 첫 오류
앞 단계의 어긋남을 빼서 두 서버의 5xx 를 기준 시계로 맞추고, 가장 이른 것의 시각(UTC, 초까지)과 서버를 /root/ic/timeline/answers/first-error.txt 에 first_error_utc=<time>, host=<server> 두 줄로 적습니다.
맞춘 시각 = 서버가 찍은 시각 − 그 서버의 어긋남. 시계가 뒤처진 서버의 줄을 그대로 읽으면 배포보다 먼저 오류가 난 것처럼 보입니다.
용의 변경 좁히기
기준 시계의 첫 오류 전 30분(첫 오류 시각 포함) 안에 들어간 변경의 id 를 /root/ic/timeline/answers/suspects.txt 에 한 줄에 하나씩 적습니다.
앞의 두 산출물(changes.tsv, first-error.txt)을 맞대면 됩니다. 맞추지 않은 시계의 첫 오류로 창을 잡으면 촉발 변경이 창 밖으로 밀립니다.
탐지 지연과 완화 시각
기준 시계의 첫 오류부터 이 사고의 경보(alerts.csv 의 api-5xx-rate fired)까지 걸린 초와, README 에 정의된 완화 시각(분의 시작, UTC)을 /root/ic/timeline/answers/phases.txt 에 detect_delay_sec=<seconds>, mitigated_utc=<time> 두 줄로 적습니다.
완화 시각은 metrics.csv 의 분당 오류율로 정의대로 찾습니다 — 처음 5% 이상이 된 분 뒤에서, 1% 미만이 5분 연속 이어지는 첫 분입니다. 사고 전의 조용한 분을 고르지 않게 주의하세요.
사건 — 세 서비스 모두 배포 직후 오류, 무엇을 되돌리나
incident ic-error-spike 를 실행하면 사건이 납니다. cart·search·pay 세 서비스가 오늘 차례로 배포됐고 셋 다 그 무렵부터 5xx 가 이어집니다(기록 /srv/spike/, README 먼저). 서비스마다 오류를 시작시킨 것을 찾아, 되돌릴 것만 /srv/spike/rollback.conf 에 <service> app=<previous-version> 또는 <service> config=<previous-revision> 으로 적습니다(되돌릴 필요가 없는 서비스는 적지 않습니다). /root/incident/ic-error-spike/report.txt 에 cart: <code>, search: <code>, pay: <code> 세 줄과, 서비스마다 그 원인을 보여 주는 재료의 한 줄을 evidence-cart: <line>, evidence-search: <line>, evidence-pay: <line> 로 적습니다(원인이 된 변경의 줄, 또는 의존 서비스 지표·시간 초과 줄). 그리고 서비스마다 기준 시계(로드밸런서)로 맞춘 첫 5xx 시각을 start-cart: <utc-time>, start-search: <utc-time>, start-pay: <utc-time> 로 적습니다(두 서버 중 더 이른 것, ±10초까지 받습니다). 원인은 서비스마다 따로 정해져 있어 같은 원인이 여럿일 수도 있습니다. 코드는 서비스마다 다음 중 하나입니다: bad-deploy(새 앱 판의 결함), clock-skew(시계 어긋남이 만든 착시), config-push(설정 배포), traffic-surge(요청 급증), upstream-timeout(하위 의존 서비스 지연).
방향: 같은 증상이라도 시작 패턴이 다릅니다 — 새 판이 들어간 서버부터 차례로인가, 모든 서버가 동시에인가, 의존 서비스 지연이 먼저 솟았는가. 시계를 맞추기 전에는 순서를 믿지 마세요. 도구: lb-sample.log 로 서버마다 어긋남 재기, git log 의 Date 를 strptime 으로, metrics 의 dep_p99_ms, 오류 줄의 상태 코드와 응답 시간. 구체: 서비스마다 두 서버의 첫 5xx 를 기준 시계로 맞추고, 그 시각을 그 서비스의 앱 배포·설정 배포 시각과 나란히 놓으세요. 되돌린다면 오류를 시작시킨 변경의 바로 앞 판(리비전)입니다.