TT Lab
はじめる
学ぶ 学習パス コース

デバッグ実戦

向こうが落ちてる — 六つの顔を分けて責任の場所を決める

TT Labで続きを見る

目標

接続拒否・名前解決の失敗・接続タイムアウト・読み取りタイムアウト・遅い応答・部分応答をそれぞれ区別する判定器と分類表を作ります。タイムアウトがないときに試行が終わらないことを自分で測り、デッドラインを守るリトライを作って、6つの対象を一度に点検します。

なぜ重要なのか

「連携できません」は、少なくとも6つの異なる出来事です。こちら側では障害なのに、相手のダッシュボードが緑色である理由もここにあります。ハンドシェイクが終わらなかった接続は相手のアプリケーションまで行かないので、相手のログには何の記録もありません。 切り分けが重要な理由は、次の行動が変わるからです。特にリトライできるかどうかが分かれます。接続前に失敗したものは、相手がリクエストを受け取れていないという意味なので、もう一度送ってよいのですが、読み取りタイムアウトと部分応答は、リクエストがすでに処理されているかもしれません。そのリクエストが振込なら、リトライは二重請求になります。 タイムアウトをまったく与えないと、さらに悪くなります。リクエストは相手が応答するまで待ち、その間、作業スレッドは戻ってきません。相手の遅さがこちらの障害に広がる最もよくある経路がこれで、そこにリトライが加わると、待ち時間が掛け算になります。 採点ツールは、あなたの説明を信じません。採点ツールが自分のポートに6つの顔を立てて、あなたの判定器を実際に接続し、分類を照合します。ポートは実行のたびに変わるので、値を暗記して入れることはできません。

ステップ

  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つの顔の責任の所在とリトライの可否を書いてください。
  6. /root/dep/retry.pyでデッドラインを守るリトライを作り、/root/dep/retry.jsonに書いてください。
  7. 6つの対象を一度に点検して、/root/dep/verdict.jsonに表として残してください。
  8. /root/dep/summary.jsonを作り、/root/dep/dep_report.mdに4つの節で報告してください。

参考

連携相手の6つの顔を立てる

/root/dep/gen_dep.pyを作成して実行し、/root/dep/servers.pyを作ってください。ok・slow・hang・partial・backlogの5つの役割が、それぞれ自分のやり方どおりにだけ動く必要があります。

インターネットがなくても、6つの顔はすべてローカルソケットで再現できます。このスクリプトをそのまま保存して実行し、okの役割を1つ立てて、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つのタイムアウトは、別々に与える必要があります。

ステップ2の判定器は、2つのタイムアウトをtimeoutという1つの顔にまとめています。requestsは、接続段階の失敗をConnectTimeout、読み取り段階の失敗をReadTimeoutとして別々に投げるので、2つの例外を別々に捕まえれば分かれます。1つの顔のままだと、ファイアウォールの問題と相手の遅延を区別できません。

部分応答と遅い応答、そしてタイムアウトなし

partialの役割はpartial_response、slowの役割は--slow-msを超えたときにslow_responseとして区別してください。/root/dep/timeout.jsonには、with_timeoutとwithout_timeout(外部から5秒後に切ったときの終了コード)とnoteを書いてください。

部分応答は、本文を最後まで受け取って初めて表に出ます。ヘッダーだけを見て成功と書くと、見逃します。タイムアウトのないリクエストは自分では終わらないので、外から切る必要があります。timeout 5 python3 ...の終了コードが124だという事実そのものが証拠です。

6つの顔の責任とリトライを表にする

/root/dep/classes.jsonに、6つの顔(dns_error・connection_refused・connect_timeout・read_timeout・partial_response・slow_response)ごとに、whose(우리/상대/경로のいずれか。韓国語の値は、順に「こちら」「相手」「経路」という意味です)、retry_safe(trueかfalse)、next_step(10文字以上)を書いてください。リトライが危険な2つは、read_timeoutとpartial_responseです。

リトライできるかどうかは、「リクエストがすでに処理されているかもしれないか」で決めます。接続が確立する前に失敗したものは、相手がリクエストを受け取れていないという意味なので安全で、ハンドシェイクのあとの失敗は、すでに処理されているかもしれないので危険です。next_stepには、その顔に出会ったときに最初に見る場所を書いてください。

デッドラインを守るリトライを作る

/root/dep/retry.pyを作成して、--attemptsと--deadline-sの両方を守らせてください。hangの役割で実行した結果は、/root/dep/retry.jsonに残してください。デッドラインを超えたら、試行回数が残っていても止まる必要があります。

試行回数だけを決めたリトライは、最悪の場合どれだけ待つことになるのか約束できません。毎回の試行の前に残り時間を見て、残り時間がなければ止まってください。各試行にもタイムアウトがあって初めて、デッドラインが意味を持ちます。終わらない試行は、デッドラインでも救えません。

6つの対象を一度に点検する

6つの顔をすべて立てて1回ずつ判定し、/root/dep/verdict.jsonに、targets(name・role・class・whose・retry_safe)と、ours・theirs・pathの件数を書いてください。class6種類が、すべて1回ずつ出る必要があります。

点検表は、1行ずつ手で書くのではなく、判定器を実行して埋めるものです。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に## 무엇이 안 됐나、## 우리인가 그들인가、## 재시도는 어떻게 했나、## 남은 위험(韓国語の見出しは、順に「何が動かなかったか」「こちらか相手か」「リトライはどうしたか」「残るリスク」という意味です)の4つの節で報告してください。

相手に電話する前にこの表を作れば、通話が短くなります。接続タイムアウトを「相手の障害」と書くと、相手は自分のログで何も見つけられません。その区別を報告書に残してください。リトライのデッドラインと実際にかかった時間も、数字で書きます。