对方挂了 — 分清六张面孔,定位责任在谁
한국어 원문으로 표시합니다.
목표
연결 거부·이름 해석 실패·연결 타임아웃·읽기 타임아웃·느린 응답·부분 응답을 각각 구분하는 판정기와 분류 표를 만든다. 타임아웃이 없을 때 시도가 끝나지 않는 것을 직접 재고, 마감을 지키는 재시도를 만들어 여섯 대상을 한꺼번에 점검한다.
왜 중요한가
"연동이 안 됩니다" 는 최소한 여섯 가지 서로 다른 사건이다. 우리 쪽에서는 장애인데 상대 대시보드는 초록색인 이유도 여기 있다 — 손을 못 잡은 연결은 상대 애플리케이션까지 가지도 않으므로 상대 로그에는 아무 기록이 없다. 가르는 일이 중요한 이유는 다음 행동이 달라지기 때문이다. 특히 재시도 가능 여부가 갈린다. 연결 전에 실패한 것은 상대가 요청을 받지 못했다는 뜻이라 다시 보내도 되지만, 읽기 타임아웃과 부분 응답은 요청이 이미 처리됐을 수 있다. 그 요청이 이체라면 재시도는 두 번 청구가 된다. 타임아웃을 아예 주지 않으면 더 나쁘다. 요청은 상대가 답할 때까지 기다리고, 그동안 작업 스레드는 돌아오지 않는다. 상대의 느림이 우리의 장애로 번지는 가장 흔한 경로가 이것이고, 거기에 재시도가 얹히면 기다림이 곱해집니다. 채점기는 여러분의 설명을 믿지 않는다. 채점기가 자기 포트에 여섯 얼굴을 세워 놓고 여러분의 판정기를 실제로 물려 분류를 대조한다. 포트는 실행마다 바뀌므로 값을 외워 넣을 수 없습니다.
단계
- /root/dep/gen_dep.py 를 만들어 실행해 /root/dep/servers.py 를 만드세요.
- /root/dep/classify.py 를 만들어 ok·connection_refused·dns_error 를 가르게 하세요.
- 연결과 읽기의 타임아웃을 따로 주어 connect_timeout 과 read_timeout 을 갈라 보세요.
- 부분 응답과 느린 응답을 가르고 /root/dep/timeout.json 에 타임아웃이 없을 때를 재세요.
- /root/dep/classes.json 에 여섯 얼굴의 책임 위치와 재시도 가능 여부를 적으세요.
- /root/dep/retry.py 로 마감을 지키는 재시도를 만들어 /root/dep/retry.json 에 적으세요.
- 여섯 대상을 한꺼번에 점검해 /root/dep/verdict.json 에 표로 남기세요.
- /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초입니다. 서버는 다 쓰고 나면 꼭 죽이세요.
연동 상대의 여섯 얼굴 세우기
/root/dep/gen_dep.py 를 만들어 실행해 /root/dep/servers.py 를 만드세요. ok·slow·hang·partial·backlog 다섯 역할이 각각 자기 방식대로만 굴러가야 합니다.
인터넷이 없어도 여섯 얼굴은 전부 로컬 소켓으로 재현됩니다. 이 스크립트를 그대로 저장해 실행하고, ok 역할을 하나 띄워 curl 로 한 번 불러 보세요. 다 쓰고 나면 프로세스를 죽이는 것을 잊지 마세요.
얼굴을 가르는 판정기 만들기
/root/dep/classify.py 를 만들어 정상은 ok, 아무도 듣지 않는 포트는 connection_refused, .invalid 이름은 dns_error 로 가르게 하세요. 출력에는 class·elapsed_ms·evidence 가 있어야 합니다.
requests 는 실패를 예외의 종류로 알려 줍니다. 다만 연결 거부와 이름 해석 실패는 둘 다 ConnectionError 로 오므로 메시지를 봐야 갈립니다. evidence 에는 판정의 근거가 된 메시지 꼬리를 그대로 남기세요 — 나중에 보고서에 붙일 것이 그것입니다.
연결과 읽기를 갈라 보기
connect_timeout 과 read_timeout 을 갈라 내세요. backlog 역할은 손잡기가 끝나지 않으므로 connect_timeout, hang 역할은 손은 잡고 답이 없으므로 read_timeout 입니다. 두 타임아웃은 따로 주어야 합니다.
2단계의 판정기는 두 타임아웃을 timeout 한 얼굴로 뭉쳐 놓았습니다. requests 는 연결 단계의 실패를 ConnectTimeout, 읽기 단계의 실패를 ReadTimeout 으로 따로 던지므로, 두 예외를 따로 잡으면 갈립니다. 한 얼굴로 두면 방화벽 문제와 상대 지연을 구별할 수 없습니다.
부분 응답과 느린 응답, 그리고 타임아웃 없음
partial 역할은 partial_response, slow 역할은 --slow-ms 를 넘겼을 때 slow_response 로 가르세요. 그리고 /root/dep/timeout.json 에 with_timeout 과 without_timeout(외부에서 5초 뒤 끊었을 때의 종료 코드)과 note 를 적으세요.
부분 응답은 본문을 끝까지 받아야 드러납니다 — 헤더만 보고 성공으로 적으면 놓칩니다. 타임아웃 없는 요청은 스스로 끝나지 않으므로 바깥에서 끊어야 합니다. timeout 5 python3 ... 의 종료 코드가 124 라는 사실 자체가 증거입니다.
여섯 얼굴의 책임과 재시도를 표로
/root/dep/classes.json 에 여섯 얼굴(dns_error·connection_refused·connect_timeout·read_timeout·partial_response·slow_response)마다 whose(우리/상대/경로)·retry_safe(참/거짓)·next_step(10자 이상)을 적으세요. 재시도가 위험한 둘은 read_timeout 과 partial_response 입니다.
재시도 가능 여부는 '요청이 이미 처리됐을 수 있는가' 로 정합니다. 연결이 이루어지기 전에 실패한 것은 상대가 요청을 받지 못했다는 뜻이라 안전하고, 손을 잡은 뒤의 실패는 이미 처리됐을 수 있어 위험합니다. next_step 에는 그 얼굴을 만났을 때 가장 먼저 볼 곳을 적으세요.
마감을 지키는 재시도 만들기
/root/dep/retry.py 를 만들어 --attempts 와 --deadline-s 를 함께 지키게 하고, hang 역할에 돌린 결과를 /root/dep/retry.json 에 남기세요. 마감을 넘기면 시도 횟수가 남아도 멈춰야 합니다.
시도 횟수만 정한 재시도는 최악의 경우 얼마나 기다릴지 약속하지 못합니다. 매 시도 전에 남은 시간을 보고, 남은 시간이 없으면 멈추세요. 각 시도에도 타임아웃이 있어야 마감이 뜻을 가집니다 — 끝나지 않는 시도는 마감으로도 구할 수 없습니다.
여섯 대상을 한꺼번에 점검하기
여섯 얼굴을 모두 세워 한 번씩 판정하고 /root/dep/verdict.json 에 targets(name·role·class·whose·retry_safe)와 ours·theirs·path 개수를 적으세요. class 여섯 가지가 모두 한 번씩 나와야 합니다.
점검표는 한 줄씩 손으로 적는 것이 아니라 판정기를 돌려 채우는 것입니다. whose 와 retry_safe 는 5단계의 표에서 가져오세요 — 표와 점검표가 어긋나면 둘 중 하나는 낡은 것입니다.
책임의 위치를 나눠 보고하기
/root/dep/summary.json 에 classes_covered·retry_elapsed_ms·retry_deadline_s·retry_attempts·theirs·path·no_timeout_exit_code 를 적고, /root/dep/dep_report.md 에 ## 무엇이 안 됐나 ## 우리인가 그들인가 ## 재시도는 어떻게 했나 ## 남은 위험 네 절로 보고하세요.
상대에게 전화하기 전에 이 표를 만들면 통화가 짧아집니다. 연결 타임아웃을 '상대 장애' 로 적으면 상대는 자기 로그에서 아무것도 못 찾습니다 — 그 구분을 보고서에 남기세요. 재시도의 마감과 실제로 걸린 시간도 숫자로 적습니다.