LabHub
学习 学习路径 课程

调试实战

对方挂了 — 分清六张面孔,定位责任在谁

在 LabHub 中继续学习

한국어 원문으로 표시합니다.

목표

연결 거부·이름 해석 실패·연결 타임아웃·읽기 타임아웃·느린 응답·부분 응답을 각각 구분하는 판정기와 분류 표를 만든다. 타임아웃이 없을 때 시도가 끝나지 않는 것을 직접 재고, 마감을 지키는 재시도를 만들어 여섯 대상을 한꺼번에 점검한다.

왜 중요한가

"연동이 안 됩니다" 는 최소한 여섯 가지 서로 다른 사건이다. 우리 쪽에서는 장애인데 상대 대시보드는 초록색인 이유도 여기 있다 — 손을 못 잡은 연결은 상대 애플리케이션까지 가지도 않으므로 상대 로그에는 아무 기록이 없다. 가르는 일이 중요한 이유는 다음 행동이 달라지기 때문이다. 특히 재시도 가능 여부가 갈린다. 연결 전에 실패한 것은 상대가 요청을 받지 못했다는 뜻이라 다시 보내도 되지만, 읽기 타임아웃과 부분 응답은 요청이 이미 처리됐을 수 있다. 그 요청이 이체라면 재시도는 두 번 청구가 된다. 타임아웃을 아예 주지 않으면 더 나쁘다. 요청은 상대가 답할 때까지 기다리고, 그동안 작업 스레드는 돌아오지 않는다. 상대의 느림이 우리의 장애로 번지는 가장 흔한 경로가 이것이고, 거기에 재시도가 얹히면 기다림이 곱해집니다. 채점기는 여러분의 설명을 믿지 않는다. 채점기가 자기 포트에 여섯 얼굴을 세워 놓고 여러분의 판정기를 실제로 물려 분류를 대조한다. 포트는 실행마다 바뀌므로 값을 외워 넣을 수 없습니다.

단계

  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 에 네 절로 보고하세요.

참고

연동 상대의 여섯 얼굴 세우기

/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## 무엇이 안 됐나 ## 우리인가 그들인가 ## 재시도는 어떻게 했나 ## 남은 위험 네 절로 보고하세요.

상대에게 전화하기 전에 이 표를 만들면 통화가 짧아집니다. 연결 타임아웃을 '상대 장애' 로 적으면 상대는 자기 로그에서 아무것도 못 찾습니다 — 그 구분을 보고서에 남기세요. 재시도의 마감과 실제로 걸린 시간도 숫자로 적습니다.