リトライパターン
一時的な障害に対して自動的にリクエストを再試行し、指数バックオフで間隔を調整する設計パターン
耐障害性分散システム
リトライパターンとは
リトライパターンは、ネットワークタイムアウト、サービスの一時的な過負荷、データベースのロック競合など、一時的な障害 (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 は実行するたびに新しい注文が作成される。べき等でない操作にはべき等キーを使う。
より深く学ぶには関連書籍が役立つ。