TL

タイムアウト設計とデッドライン伝播

各段に固定タイムアウトを置くと合計が積み上がり、一番外側が先に諦めて全段が無駄になります。残時間を引き継ぐデッドライン伝播なら、チェーン全体で「いつまで」を共有し、無駄な計算とリソースの拘束を断てます。

応用タイムアウトデッドラインキャンセル耐障害性マイクロサービス信頼性最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.段ごとに固定タイムアウトを置くと、内側の合計が外側の制限を超えうる。外側が先に諦めた後も内側は走り続け、誰も使わない応答のために計算とリソースを浪費する。
  • 2.デッドライン伝播は「絶対時刻の締め切り」をリクエストに乗せて各段へ引き継ぐ。各段は残時間を見て、足りなければ即座に打ち切り、下流へは残時間を渡す。
  • 3.締め切り超過やクライアント切断はキャンセルとして下流へ伝搬させ、進行中の処理を止めてコネクション・スレッド・ロックを解放する。これがリソースリークと作業の無駄を防ぐ。

なぜ「各段の固定タイムアウト」は破綻するのか

分散システム(/devops/microservices/)では、リクエストが API ゲートウェイ → サービス A → サービス B → データベースと段を重ねて伝わります。各段に素朴に固定タイムアウトを置くとどうなるか。たとえば外側のゲートウェイが 3 秒、A が B を呼ぶのに 3 秒、B が DB を引くのに 3 秒——一見どれも妥当に見えます。

問題は タイムアウトが加算される ことです。内側の各段が制限いっぱいまで粘ると、合計の待ち時間はゲートウェイの 3 秒を簡単に超えます。すると外側が先に諦めてクライアントへエラーを返すのに、内側の B や DB はまだ走り続けている。その応答は、待っていた呼び出し元がもういないので誰も受け取りません。計算リソース・コネクション・ロックは、捨てられる結果のために拘束され続けます。

固定タイムアウトの三つの破綻

(1) 段の合計が外側の制限を超えると、外側が先に諦め内側の作業が丸ごと無駄になる。(2) 逆に内側の制限が外側より長いと、外側のタイムアウトが実質的に無効化される。(3) 同じ固定値を全段でコピペすると、ネットワーク往復やキュー待ちの実時間が読めず、途中で時間切れになるのか最後までやり切れるのかを誰も把握できない。固定値は「呼び出しチェーンの全長」という情報を持てないのが根本欠陥です。

デッドライン:時間量ではなく絶対時刻で考える

破綻の原因は、各段が「自分は何秒待つか(相対的な時間量)」だけを知っていて、「このリクエスト全体がいつまでに終わるべきか」を知らない点にあります。これを反転させるのが デッドライン(deadline) です。

デッドラインは 絶対時刻の締め切り(例:13:00:05.200)です。最も外側の入口で「今 + 許容時間」として一度だけ決め、以降はリクエストに乗せて全段へ運びます。各段は自分のタイムアウト秒数を持つ代わりに、残時間 = デッドライン − 現在時刻 を都度計算して使います。

観点固定タイムアウト(相対)デッドライン(絶対時刻)
持つ情報自分が待つ秒数だけリクエスト全体の締め切り時刻
合計の振る舞い段ごとに加算され外側を超えうる全段で共有され決して超えない
下流へ渡す値また別の固定秒数残時間(デッドライン − 現在時刻)
時間切れの判定各段が独立に判断どの段でも同じ締め切りで一貫

絶対時刻にする利点は、段をまたいでも意味が保たれる ことです。相対秒数は「いつ測り始めたか」に依存しますが、絶対時刻はネットワークを越えても「13:00:05.200 まで」という一意の意味を持ちます(時刻同期の誤差は残りますが、ミリ秒〜秒スケールの締め切りでは実用上問題になりにくい)。

デッドライン伝播:残時間をチェーンで引き継ぐ

各段の処理は次の形になります。

各段の擬似コード
deadline = リクエストから取り出す       # 絶対時刻
remaining = deadline - now()
if remaining <= 0:
    return DEADLINE_EXCEEDED          # もう間に合わない。即打ち切り
# 下流を呼ぶときは「残時間」を渡す
result = call_downstream(req, deadline)  # デッドラインをそのまま伝播

ポイントは三つです。第一に、各段は処理に入る前に残時間を確認し、ゼロ以下なら何もせず即座に失敗を返す。間に合わないと分かっている作業を始めないことが、無駄な拘束を生まない最大の鍵です。第二に、下流を呼ぶときは 同じデッドラインをそのまま渡す。これにより DB の段も「13:00:05.200 まで」を知り、自分でクエリのタイムアウトをそこに合わせられます。第三に、自段の処理にも残時間を上限として課す——下流より先に自分が締め切りを越えてはいけません。

gRPC はこれを grpc-timeout ヘッダ(残時間を相対値でエンコード)として標準サポートし、受信側がローカルの絶対デッドラインへ復元します。一般的な実装では、トレースのコンテキスト伝播(/devops/tracing-context-propagation/)と同じく、リクエストのコンテキストにデッドラインを載せて運びます。Go の context.Context がその典型で、context.WithDeadline で作った値が呼び出しツリー全体に流れます。

末端に少しだけ余白を残す

残時間をそのまま下流に渡すと、ネットワーク往復やレスポンスの送り返しに使う時間がなくなり、応答を作れても運ぶ前に締め切ってしまいます。実務では「残時間からネットワーク往復見積もりを差し引いた値」を下流へ渡す、あるいは末端の DB クエリには残時間の一定割合だけを与える、といった微調整を入れます。最外段の予算配分(ゲートウェイで全体予算を決め、各段に割り当てる)も有効です。

キャンセルの伝搬:止めなければ意味がない

デッドライン超過を検知して呼び出し元が諦めても、下流が走り続けていれば被害は消えません。締め切りを越えた/クライアントが切断した——これらは下流へ キャンセル信号 として伝えなければなりません。キャンセルが伝搬して初めて、進行中の処理を止め、占有していたリソースを解放できます。

キャンセルが伝搬しないと
クライアント切断 ──▶ ゲートウェイ即終了
                       │(信号が下へ流れない)
                       ▼
          A・B・DB は処理を継続 ──▶ コネクション/スレッド/ロックを保持
                                    ──▶ 結果は誰も受け取らない(純粋な浪費)

解放すべきリソースは具体的には、下流への TCP コネクションやプール枠、処理スレッドやコルーチン、DB のトランザクションが握るロックや一時メモリ、そしてキューに積んだ未処理の作業です。これらを締め切りと同時に手放さないと、過負荷時に 使われない作業のためのリソース が積み上がり、待ち行列が膨らんで系全体の遅延が発散します(/devops/queueing-theory-tail-latency/)。

キャンセルの三つの落とし穴

(1) ブロッキング I/O がキャンセル不能だと、信号を送っても相手の応答を待ち続けてスレッドが解放されない——コネクションを物理的に閉じる手段が要る。(2) DB のトランザクションをキャンセルしても、サーバー側のクエリは止まらず実行され続ける実装がある(クライアントが結果を捨てるだけ)。(3) 「途中までの副作用」が残る——書き込み系の処理をキャンセルすると、中途半端な状態が残りうる。冪等性やサーガ的な補償を併せて設計しないと、キャンセルが新たな不整合を生む。

タイムアウト・再試行・ブレーカの関係

タイムアウトは単体では完結せず、他の耐障害パターンと噛み合って働きます。

  • 再試行との関係:タイムアウトで失敗した呼び出しを再試行(/devops/retry-backoff-jitter/)するとき、デッドラインは再試行を含めた 総予算 として効きます。残時間がゼロなら再試行してはいけません——間に合わないからです。デッドラインがあると「あと何回再試行する余地があるか」を時間で判断でき、無限リトライによる負荷増幅を自然に止められます。
  • サーキットブレーカとの関係:固定タイムアウト直前まで待たされる呼び出しが大量に滞留するのが最も危険です(/devops/circuit-breaker-bulkhead/)。デッドラインで早期に打ち切れば、弱った依存先への拘束時間そのものが短くなり、ブレーカが開くまでの被害も小さくなります。
  • 適用層:これらをアプリ側に都度書くと抜けが出ます。サービスメッシュ(/devops/service-mesh/)のサイドカーでデッドラインとキャンセルを横断的に適用すれば、言語非依存で一貫したポリシーを敷けます。
設計レビューの定番論点

「各段にタイムアウトを入れた」は不合格回答です。問われるのは——(1) 相対秒数ではなく 絶対時刻のデッドライン を最外段で決めて伝播しているか、(2) 各段が処理前に残時間を確認し間に合わなければ即打ち切るか、(3) 締め切り超過・切断を キャンセルとして下流へ伝搬 し、コネクション・スレッド・ロックを解放するか、(4) 再試行をデッドライン内の総予算として制御しているか。固定タイムアウトの加算問題を説明できるかが上級者の分かれ目です。

まとめ

  • 各段に固定タイムアウトを置くと相対秒数が加算され、外側が先に諦めて内側の作業が丸ごと無駄になる。原因は各段が「チェーン全体の締め切り」を知らないこと。
  • デッドライン伝播 は絶対時刻の締め切りを最外段で決め、全段へ引き継ぐ。各段は残時間を計算し、足りなければ即打ち切り、下流には残時間を渡す。これで合計が外側を超えない。
  • 締め切り超過・クライアント切断は キャンセル として下流へ伝搬し、進行中の処理を止めてコネクション・スレッド・ロック・メモリを解放する。止めなければ純粋な浪費が残る。
  • タイムアウトは再試行の総予算・サーキットブレーカ・サービスメッシュと層をなして働く。鍵は一貫して「いつまで」を共有し、間に合わない作業を始めない・続けないこと。

DevOps/インフラ Article

タイムアウト設計とデッドライン伝播を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

タイムアウト

比較で見る軸

難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 6

導入後に効く点

デッドライン伝播は「絶対時刻の締め切り」をリクエストに乗せて各段へ引き継ぐ。各段は残時間を見て、足りなければ即座に打ち切り、下流へは残時間を渡す。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
DevOps/インフラ
タグ数
6

判断チェックリスト

  • 自社の用途が「タイムアウト / デッドライン」に近いか確認する。
  • 強みである「段ごとに固定タイムアウトを置くと、内側の合計が外側の制限を超えうる。外側が先に諦めた後も内側は走り続け、誰も使わない応答のために計算とリソースを浪費する。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

タイムアウトデッドラインキャンセル耐障害性マイクロサービスタイムアウトデッドラインキャンセル