リトライパターン

一時的な障害に対して自動的にリクエストを再試行し、指数バックオフで間隔を調整する設計パターン

耐障害性分散システム

リトライパターンとは

リトライパターンは、ネットワークタイムアウト、サービスの一時的な過負荷、データベースのロック競合など、一時的な障害 (Transient Fault) に対して自動的にリクエストを再試行する設計パターンである。分散システムでは一時的な障害は日常的に発生するため、リトライは必須の設計要素だ。

指数バックオフ + ジッター

リトライ間隔を固定 (1 秒ごと) にすると、障害中のサービスにリクエストが集中し、回復を妨げる (Thundering Herd)。指数バックオフではリトライごとに間隔を倍増させ、ジッターでランダムな揺らぎを加える。

async function withRetry<T>(
  fn: () => Promise<T>,
  maxRetries = 3,
  baseDelay = 1000,
): Promise<T> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (err) {
      if (attempt === maxRetries) throw err;
      if (!isRetryable(err)) throw err;

      const delay = baseDelay * 2 ** attempt;
      const jitter = Math.random() * delay * 0.5;
      await new Promise(r => setTimeout(r, delay + jitter));
    }
  }
  throw new Error('Unreachable');
}

function isRetryable(err: unknown): boolean {
  if (err instanceof Error && 'statusCode' in err) {
    const code = (err as any).statusCode;
    return code === 429 || code === 503 || code >= 500;
  }
  return true; // ネットワークエラーはリトライ
}
リトライ 1: 1 + ジッター (0〜0.5秒)
リトライ 2: 2 + ジッター (0〜1秒)
リトライ 3: 4 + ジッター (0〜2秒)

リトライすべきケースとすべきでないケース

リトライすべき (一時的) リトライすべきでない (恒久的)
HTTP 429 (Too Many Requests) HTTP 400 (Bad Request)
HTTP 503 (Service Unavailable) HTTP 401 (Unauthorized)
ネットワークタイムアウト HTTP 404 (Not Found)
DB のロック競合 バリデーションエラー
DynamoDB の ProvisionedThroughputExceededException ビジネスロジックエラー

恒久的なエラーをリトライしても成功しない。リソースを無駄に消費し、障害の回復を遅らせる。

AWS での実装パターン

SQS + Lambda

Lambda が例外をスローすると、SQS がメッセージを自動的にリトライする。maxReceiveCount でリトライ回数を制限し、上限に達したらデッドレターキュー (DLQ) に移動する。

Step Functions

Step Functions の Retry フィールドで、エラータイプごとにリトライ戦略を定義できる。

{
  "Retry": [{
    "ErrorEquals": ["States.TaskFailed"],
    "IntervalSeconds": 2,
    "MaxAttempts": 3,
    "BackoffRate": 2.0
  }]
}

リトライとべき等性

リトライが安全に動作するには、操作がべき等 (同じ操作を複数回実行しても結果が同じ) である必要がある。PUT /orders/123 は何度実行しても同じ結果だが、POST /orders は実行するたびに新しい注文が作成される。べき等でない操作にはべき等キーを使う。

より深く学ぶには関連書籍が役立つ。

関連用語