【テクニカル・上級編】 APIゲートウェイでのリクエストタイムアウトと再試行(Retry)戦略 – Web APIアーキテクチャ・データ連携実践ガイド

泥沼のAPIタイムアウトと再試行戦略:パケットが語る「正解」への道のり

システムアーキテクトとして現場に立つと、たかだか「タイムアウト」と「リトライ」という二つの言葉が、どれほどまでにシステムを破壊しうるかを嫌というほど思い知らされる。

REST APIの設計において、エンドポイントをどれほど美しく整えたとしても、その下を流れるトランスポート層がガタガタであれば、それは砂上の楼閣に過ぎない。今回は、APIゲートウェイにおけるタイムアウト設定と再試行戦略を、パケットレベルの挙動から紐解いていこう。

—

1. タイムアウトの深淵:なぜ「魔法の数字」は存在しないのか

多くのエンジニアは、ゲートウェイのタイムアウトを「適当に60秒」と設定して安心する。だが、パケットの視点では、この60秒は「TCP接続の確立(3-way handshake)」から「TLSネゴシエーション」、「アプリケーション層の処理」、そして「応答の帰還」までを含めた総和であることに注意しなければならない。

トランスポート層の最適化

もしクライアントが広域ネットワークを跨ぐ場合、RTT(Round Trip Time)が100ms増えるだけで、TLS 1.3のハンドシェイクすら遅延の要因となる。TCP_NODELAYを有効にし、Nagleアルゴリズムによるパケット結合の待ち時間を排除することは基本中の基本だ。

さらに、Linuxカーネルのtcp_rmemやtcp_wmemといったバッファチューニングを怠ると、高負荷時にウィンドウサイズが適切に拡大せず、スループットが頭打ちになる。

# sysctlでのバッファチューニング例
# 高帯域・長距離通信でのスループット低下を防ぐための最小値・デフォルト値・最大値の設定
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

—

2. 冪等性の呪縛と再試行戦略の設計

リトライを安易に実装してはいけない。特にPOSTメソッドのように冪等性が保証されないリクエストに対して自動リトライをかけることは、データベースの二重登録を引き起こし、障害を連鎖させる「自己増殖型DoS」になりかねない。

指数バックオフとジッター(Jitter)

再試行を行う場合、必ず「指数バックオフ」を採用し、かつ「ジッター」を付与せよ。全クライアントが一斉にリトライを試みると、バックエンドは再起不能なダメージを受ける。

import random
import time

def execute_with_retry(attempt, max_attempts=3):
    """
    指数バックオフ + フルジッターによる再試行制御
    """
    base_delay = 0.1  # 100ms
    delay = min(10, base_delay * (2 ** attempt)) # 最大10秒に制限
    jitter = random.uniform(0, delay)
    
    time.sleep(jitter)
    print(f"再試行待機時間: {jitter:.4f}秒")

この「ジッター」の有無が、大規模障害からの復旧時間を劇的に変える。パケットが輻輳した際、同期的な再試行はネットワークの帯域を完全に枯渇させるからだ。

—

3. ヘッダー圧縮とセキュリティのトレードオフ

HTTP/2やHTTP/3 (QUIC) を利用する場合、HPACKやQPACKといったヘッダー圧縮が効く。しかし、ここで注意すべきは「脆弱性」だ。CRIMEやBREACH攻撃のように、圧縮を利用したサイドチャネル攻撃は今も存在する。

APIゲートウェイでリクエストを中継する際、Authorizationヘッダーなどの機密情報を含むヘッダーを動的に圧縮対象から外す、あるいは暗号化と圧縮の順序を意識した設計が必要だ。

—

4. 現場で見極める「正しいタイムアウト」の境界線

最後に、アーキテクトとして推奨する「ゲートウェイ設定の考え方」をまとめる。

1. 接続タイムアウト(Connect Timeout): 非常に短く設定せよ。TCPのSYN再送タイマーを考慮し、通常は2〜5秒が適切だ。これを超えて接続できない場合、バックエンドは既に死んでいる。
2. 読み取りタイムアウト(Read Timeout): バックエンドのP99レスポンス時間に基づき、余裕を持たせた値を設定する。ただし、ストリーミングAPIでない限り、極端に長いタイムアウトはコネクションの枯渇を招く。
3. HTTP 429 Too Many Requestsの尊重: 再試行戦略において、バックエンドが返す Retry-After ヘッダーを解析し、ゲートウェイ側でその時間までリクエストを遮断するロジックを実装するのが、真に堅牢な設計だ。

ゲートウェイ設定(Nginxの例)

# upstream設定でのタイムアウト例
upstream backend_api {
    server api.internal.local;
    keepalive 32; # コネクション再利用によるハンドシェイク削減
}

location / {
    proxy_connect_timeout 2s; # 接続失敗を素早く検知
    proxy_read_timeout 5s;    # 応答待ちを適切に制限
    proxy_next_upstream error timeout http_500; # 冪等性がある場合のみリトライ
}

結びに代えて

ネットワークは常に嘘をつく。パケットロスも、ルーティングの揺らぎも、バックエンドの予期せぬGC(ガベージコレクション)も、全ては統計的な確率の中で起きている。

だからこそ、我々アーキテクトは「失敗することを前提」とした設計をしなければならない。タイムアウトをただの待ち時間と捉えるのではなく、システム全体のリソースを守るための「遮断弁」として定義し、再試行をネットワークの調和を乱さないための「知的な振る舞い」として実装すること。

プロトコルの深淵を覗くことは、システムの生存戦略を練ることに他ならない。あなたのAPIゲートウェイが、今日のトラフィックを適切に捌ききれることを願っている。

コメント

タイトルとURLをコピーしました