APIゲートウェイの「死の淵」を歩く:タイムアウトとリトライ戦略の流儀
インフラエンジニアとして現場に立っていると、必ずと言っていいほど「APIがたまにタイムアウトする」「リクエストが重複してデータが壊れた」という怪奇現象に遭遇します。
特にマイクロサービス化が進んだ現代のアーキテクチャにおいて、APIゲートウェイは「信頼の最後の砦」です。しかし、ここの設定を甘く見ると、バックエンドが遅延した瞬間にゲートウェイ自体がリソース枯渇を起こし、連鎖的にシステム全体が共倒れする「カスケード故障」を招きます。
今日は、RFC 7231やHTTPのセマンティクスを紐解きつつ、現場で泥臭く戦うための「タイムアウトとリトライ」の設計術を伝授しましょう。
—
1. なぜ「タイムアウト設定」は複雑なのか
REST APIにおいて、クライアントとサーバーの間にゲートウェイが介在する場合、タイムアウト設定には大きく分けて2つの視点が必要です。
- Connect Timeout: TCPハンドシェイクが完了するまでの時間。
- Read/Write Timeout: 接続後、サーバーがヘッダーを返し、ボディを全て送信し終えるまでの時間。
多くのエンジニアがやりがちなミスは、これらを一律に「5秒」といった固定値にすることです。実際には、サービスごとに「どの程度のレスポンス速度を期待するか」というSLA(Service Level Agreement)に基づき、ゲートウェイのタイムアウト値を設定しなければなりません。
設定の勘所
ゲートウェイの設定(例えばNginxの proxy_read_timeout など)は、「最長の期待レスポンス時間+バッファ」にするのが鉄則です。
# Nginx設定例:バックエンドの遅延に対する防御
location /api/v1/ {
# TCP接続確立までの許容時間
proxy_connect_timeout 2s;
# レスポンスヘッダー受信までの許容時間
proxy_read_timeout 5s;
# データを書き込むまでの許容時間
proxy_send_timeout 5s;
# タイムアウト時に別サーバーへフェイルオーバーさせる(冪等性に注意!)
proxy_next_upstream error timeout http_500 http_502 http_503;
}
—
2. リトライの地獄と「冪等性(Idempotency)」の絶対法則
さて、タイムアウトしたリクエストをどう再送するか。ここでエンジニアの知識が試されます。最も重要なルールは、「冪等性(Idempotency)が保証されないリクエストは、自動リトライしてはならない」ということです。
HTTPメソッドにおける冪等性の定義(RFC 7231)を再確認しましょう。
- 冪等である (
GET,PUT,DELETE,HEAD,OPTIONS): 何度実行してもサーバー側の状態が変わらないもの。 - 冪等でない (
POST): 実行するたびにリソースが新規作成される可能性があるもの。
POST リクエストを安易にリトライすると、サーバー側では処理が成功しているのに、ゲートウェイ側でタイムアウトして「失敗」と判定された場合、「二重決済」や「二重投稿」が発生します。
実装のヒント:Idempotency-Key
これを解決するのが Idempotency-Key ヘッダーです。クライアント側でユニークなUUIDを生成し、ヘッダーに乗せて送信します。サーバー側ではこのキーをRedis等でチェックし、既知のキーであれば「前回の結果」を返します。
—
3. 指数バックオフ(Exponential Backoff)の適用
APIが過負荷状態のときに、追い打ちをかけるようにリトライを繰り返すのは「DDOS攻撃」と変わりません。ここで導入すべきが「指数バックオフ」と「ジッター(揺らぎ)」です。
リトライ間隔を 2^n * base_delay のように増やし、さらにランダムな数値を加算することで、リクエストのタイミングを分散させます。
Pythonでの実装例
import time
import random
def execute_request_with_retry(api_call, max_retries=3):
base_delay = 1.0 # 基本待機時間
for attempt in range(max_retries):
try:
return api_call()
except TimeoutError:
# 2の乗数で待機し、ジッター(0.1秒)を加える
delay = (base_delay * (2 ** attempt)) + random.uniform(0, 0.1)
print(f"Retrying in {delay:.2f} seconds...")
time.sleep(delay)
raise Exception("Max retries exceeded")
—
4. 現場で生き残るための運用Tips
最後にもう一つ。デバッグの際に最も重要なのは、「ゲートウェイが出したタイムアウトなのか、バックエンドが出したタイムアウトなのか」をログで識別することです。
- ゲートウェイ側のエラー:
504 Gateway Timeout - バックエンド側のエラー:
500 Internal Server Errorや502 Bad Gateway
通信フローを確認する際は、必ず X-Request-ID ヘッダーを全システムで付与してください。これがないと、巨大なログの海の中から、特定のタイムアウトしたパケットを追いかけることは不可能です。
まとめ:美しいAPI運用のために
1. タイムアウトはサービスごとに最適化せよ: 全体一律は悪手。
2. 冪等性のないリクエストを自動リトライするな: POST には Idempotency-Key を。
3. 指数バックオフでサーバーを休ませろ: 攻撃者になってはいけない。
ネットワークの深淵は常に「想定外」に満ちています。しかし、こうした理論に基づいた堅牢な設計こそが、障害発生時にエンジニアを「夜中の呼び出し」から解放してくれる唯一の手段なのです。
次に皆さんが設計するAPIエンドポイントが、どんな状況下でも健やかに振る舞えることを祈っています。それでは、良いエンジニアリングを!
コメント