디버깅 실전 · 밖이 문제인가 우리가 문제인가 · 실습
저쪽이 죽었대요 — 여섯 얼굴을 갈라 책임을 가린다
목표
연결 거부·이름 해석 실패·연결 타임아웃·읽기 타임아웃·느린 응답·부분 응답을 각각 구분하는 판정기와 분류 표를 만든다. 타임아웃이 없을 때 시도가 끝나지 않는 것을 직접 재고, 마감을 지키는 재시도를 만들어 여섯 대상을 한꺼번에 점검한다.
왜 중요한가
"연동이 안 됩니다" 는 최소한 여섯 가지 서로 다른 사건이다. 우리 쪽에서는 장애인데 상대 대시보드는 초록색인 이유도 여기 있다 — 손을 못 잡은 연결은 상대 애플리케이션까지 가지도 않으므로 상대 로그에는 아무 기록이 없다.
가르는 일이 중요한 이유는 다음 행동이 달라지기 때문이다. 특히 재시도 가능 여부가 갈린다. 연결 전에 실패한 것은 상대가 요청을 받지 못했다는 뜻이라 다시 보내도 되지만, 읽기 타임아웃과 부분 응답은 요청이 이미 처리됐을 수 있다. 그 요청이 이체라면 재시도는 두 번 청구가 된다.
타임아웃을 아예 주지 않으면 더 나쁘다. 요청은 상대가 답할 때까지 기다리고, 그동안 작업 스레드는 돌아오지 않는다. 상대의 느림이 우리의 장애로 번지는 가장 흔한 경로가 이것이고, 거기에 재시도가 얹히면 기다림이 곱해집니다.
채점기는 여러분의 설명을 믿지 않는다. 채점기가 자기 포트에 여섯 얼굴을 세워 놓고 여러분의 판정기를 실제로 물려 분류를 대조한다. 포트는 실행마다 바뀌므로 값을 외워 넣을 수 없습니다.
단계
1. /root/dep/gen_dep.py 를 만들어 실행해 /root/dep/servers.py 를 만드세요.
2. /root/dep/classify.py 를 만들어 ok·connection_refused·dns_error 를 가르게 하세요.
3. 연결과 읽기의 타임아웃을 따로 주어 connect_timeout 과 read_timeout 을 갈라 보세요.
4. 부분 응답과 느린 응답을 가르고 /root/dep/timeout.json 에 타임아웃이 없을 때를 재세요.
5. /root/dep/classes.json 에 여섯 얼굴의 책임 위치와 재시도 가능 여부를 적으세요.
6. /root/dep/retry.py 로 마감을 지키는 재시도를 만들어 /root/dep/retry.json 에 적으세요.
7. 여섯 대상을 한꺼번에 점검해 /root/dep/verdict.json 에 표로 남기세요.
8. /root/dep/summary.json 과 /root/dep/dep_report.md 에 네 절로 보고하세요.
참고
- 서버 계약:
python3 /root/dep/servers.py --role <ok|slow|hang|partial|backlog> --port P [--delay-ms N] [--ready-file F]는 그 역할대로만 굴러갑니다. ready 파일이 생기면 준비된 것이고, 끝내려면 프로세스를 죽입니다. - 판정 계약:
python3 /root/dep/classify.py --url <주소> [--connect-timeout S] [--read-timeout S] [--slow-ms N] [--out <json>]은 url·class·elapsed_ms·evidence·connect_timeout·read_timeout 을 담은 JSON 한 덩어리를 냅니다. - 여섯 가지 class 이름은
dns_error·connection_refused·connect_timeout·read_timeout·partial_response·slow_response이고, 정상은ok입니다. - 느린 응답의 기준은
--slow-ms입니다. 성공했지만 그 시간을 넘겼으면slow_response로 적습니다. 200 이 왔다고 모두 성공으로 적으면 상대가 느려지는 추세가 보이지 않습니다. - 타임아웃: requests 는
timeout=(연결, 읽기)처럼 둘을 따로 받습니다. 한 값으로 주면 두 사건이 한 얼굴로 뭉개집니다.--connect-timeout 0이하를 주면 타임아웃 없이 기다리게 하세요. - 재시도 계약:
python3 /root/dep/retry.py --url <주소> --attempts N --deadline-s S [--connect-timeout S] [--read-timeout S] [--out <json>]은 attempts_allowed·attempts_made·deadline_s·elapsed_ms·final_class·tries·gave_up 을 냅니다. 마감을 넘기면 시도 횟수가 남아도 멈춰야 합니다. - 책임의 위치는
우리·상대·경로셋 중 하나로 적습니다. 재시도 가능 여부는 요청이 이미 처리됐을 수 있는지로 정합니다 — 읽기 타임아웃과 부분 응답은 위험합니다. - 재현 재료: 이름 해석 실패는 RFC 2606 이 예약한
.invalid이름으로, 연결 거부는 아무도 듣지 않는 포트로, 연결 타임아웃은 대기열을 채운 뒤 accept 하지 않는 소켓(backlog 역할)으로 만듭니다. - 흔한 실수: 타임아웃을 한 값으로 주기, 200 이면 모두 성공으로 적기, 부분 응답을 파싱 오류로 적기, 멱등이 아닌 요청을 읽기 타임아웃 뒤에 재시도하기.
- 부하 시험을 만들지 마세요. 채점 하나의 예산은 60초입니다. 서버는 다 쓰고 나면 꼭 죽이세요.
단계 8개
- 연동 상대의 여섯 얼굴 세우기
- 얼굴을 가르는 판정기 만들기
- 연결과 읽기를 갈라 보기
- 부분 응답과 느린 응답, 그리고 타임아웃 없음
- 여섯 얼굴의 책임과 재시도를 표로
- 마감을 지키는 재시도 만들기
- 여섯 대상을 한꺼번에 점검하기
- 책임의 위치를 나눠 보고하기