

# Lambda の耐久関数の再試行
<a name="durable-execution-sdk-retries"></a>

耐久関数は、アプリケーションの一時的な障害に対する回復力を高める自動再試行機能を提供します。SDK は、ビジネスロジックの障害に対するステップ再試行とインフラストラクチャの障害に対するバックエンド再試行の 2 つのレベルで再試行を処理します。

## ステップの再試行
<a name="durable-step-retries"></a>

ステップ内で捕捉されなかった例外が発生すると、SDK は設定された再試行戦略に基づいてステップを自動的に再試行します。ステップの再試行は、チェックポイントされた操作であり、SDK が実行を中断しても、進行状況を失うことなく後で再開できるようにします。

### ステップの再試行動作
<a name="durable-step-retry-behavior"></a>

次の表は、SDK がステップ内で例外を処理する方法を示しています。


| シナリオ | どうなるのか | 計測への影響 | 
| --- | --- | --- | 
| 再試行回数が残っているステップにおける例外 | SDK は再試行のチェックポイントを作成し、関数を停止します。次の呼び出しで、ステップは設定されたバックオフ遅延を伴って再試行します。 | 1 回のオペレーション \+ エラーペイロードサイズ | 
| 再試行回数が残っていないステップにおける例外 | ステップは失敗し、例外をスローします。ハンドラーコードがこの例外をキャッチしない場合、実行全体が失敗します。 | 1 回のオペレーション \+ エラーペイロードサイズ | 

ステップを再試行する必要があるとき、SDK は再試行状態をチェックポイントし、他の作業が実行されていない場合は Lambda 呼び出しを終了します。これにより、SDK はコンピュートリソースを消費することなくバックオフ遅延を実装できます。関数はバックオフ期間の後に自動的に再開されます。

### ステップ再試行戦略の設定
<a name="durable-step-retry-configuration"></a>

再試行戦略を設定して、ステップが失敗を処理する方法を制御します。最大試行回数、バックオフ間隔、再試行条件を指定できます。

**最大試行回数付きのエクスポネンシャルバックオフ:**

------
#### [ TypeScript ]

```
const result = await context.step('call-api', async () => {
  const response = await fetch('https://api.example.com/data');
  if (!response.ok) throw new Error(`API error: ${response.status}`);
  return await response.json();
}, {
  retryStrategy: (error, attemptCount) => {
    if (attemptCount >= 5) {
      return { shouldRetry: false };
    }
    // Exponential backoff: 2s, 4s, 8s, 16s, 32s (capped at 300s)
    const delay = Math.min(2 * Math.pow(2, attemptCount - 1), 300);
    return { shouldRetry: true, delay: { seconds: delay } };
  }
});
```

------
#### [ Python ]

```
def retry_strategy(error, attempt_count):
    if attempt_count >= 5:
        return RetryDecision(should_retry=False)
    # Exponential backoff: 2s, 4s, 8s, 16s, 32s (capped at 300s)
    delay = min(2 * (2 ** (attempt_count - 1)), 300)
    return RetryDecision(should_retry=True, delay=delay)

result = context.step(
    lambda _: call_external_api(),
    name='call-api',
    config=StepConfig(retry_strategy=retry_strategy)
)
```

------

**固定間隔バックオフ:**

------
#### [ TypeScript ]

```
const orders = await context.step('query-orders', async () => {
  return await queryDatabase(event.userId);
}, {
  retryStrategy: (error, attemptCount) => {
    if (attemptCount >= 3) {
      return { shouldRetry: false };
    }
    return { shouldRetry: true, delay: { seconds: 5 } };
  }
});
```

------
#### [ Python ]

```
def retry_strategy(error, attempt_count):
    if attempt_count >= 3:
        return RetryDecision(should_retry=False)
    return RetryDecision(should_retry=True, delay=5)

orders = context.step(
    lambda _: query_database(event['userId']),
    name='query-orders',
    config=StepConfig(retry_strategy=retry_strategy)
)
```

------

**条件付き再試行 (特定のエラーのみを再試行):**

------
#### [ TypeScript ]

```
const result = await context.step('call-rate-limited-api', async () => {
  const response = await fetch('https://api.example.com/data');
  
  if (response.status === 429) throw new Error('RATE_LIMIT');
  if (response.status === 504) throw new Error('TIMEOUT');
  if (!response.ok) throw new Error(`API_ERROR_${response.status}`);
  
  return await response.json();
}, {
  retryStrategy: (error, attemptCount) => {
    // Only retry rate limits and timeouts
    const isRetryable = error.message === 'RATE_LIMIT' || error.message === 'TIMEOUT';
    
    if (!isRetryable || attemptCount >= 3) {
      return { shouldRetry: false };
    }
    
    // Exponential backoff: 1s, 2s, 4s (capped at 30s)
    const delay = Math.min(Math.pow(2, attemptCount - 1), 30);
    return { shouldRetry: true, delay: { seconds: delay } };
  }
});
```

------
#### [ Python ]

```
def retry_strategy(error, attempt_count):
    # Only retry rate limits and timeouts
    is_retryable = str(error) in ['RATE_LIMIT', 'TIMEOUT']
    
    if not is_retryable or attempt_count >= 3:
        return RetryDecision(should_retry=False)
    
    # Exponential backoff: 1s, 2s, 4s (capped at 30s)
    delay = min(2 ** (attempt_count - 1), 30)
    return RetryDecision(should_retry=True, delay=delay)

result = context.step(
    lambda _: call_rate_limited_api(),
    name='call-rate-limited-api',
    config=StepConfig(retry_strategy=retry_strategy)
)
```

------

**再試行を無効にする:**

------
#### [ TypeScript ]

```
const isDuplicate = await context.step('check-duplicate', async () => {
  return await checkIfOrderExists(event.orderId);
}, {
  retryStrategy: () => ({ shouldRetry: false })
});
```

------
#### [ Python ]

```
is_duplicate = context.step(
    lambda _: check_if_order_exists(event['orderId']),
    name='check-duplicate',
    config=StepConfig(
        retry_strategy=lambda error, attempt: {'should_retry': False}
    )
)
```

------

再試行戦略が `shouldRetry: false` を返すと、ステップは再試行せずにすぐに失敗します。これは、べき等性チェックや、安全に再実行できない副作用を伴う操作など、再試行すべきでない操作に使用します。

## ステップ外の例外
<a name="durable-handler-exceptions"></a>

ステップ外でハンドラーコードで捕捉されなかった例外が発生した場合、SDK は実行を失敗としてマークします。これにより、アプリケーションロジックのエラーが適切にキャプチャされ、報告されます。


| シナリオ | どうなるのか | 計測への影響 | 
| --- | --- | --- | 
| ステップ外のハンドラーコードの例外 | SDK は実行を FAILED としてマークし、エラーを返します。例外は自動的に再試行されません。 | エラーペイロードサイズ | 

エラーが発生しやすいコードの自動再試行を有効にするには、再試行戦略を使用してステップ内でラップします。ステップでは、設定可能なバックオフ付きの自動再試行が行われますが、ステップ外のコードは即座に失敗します。

## 呼び出しの再試行
<a name="durable-invocation-retries"></a>

呼び出しレベルの再試行は、Lambda の耐久性のある関数の呼び出し方法に応じて異なります。次の表は、さまざまな呼び出しタイプが呼び出しレベルの再試行にどのように影響するかを示しています。


| 呼び出しタイプ | どうなるのか | 
| --- | --- | 
| 同期呼び出し |  Lambda は、耐久性のある関数の実行中にエラーが発生しても呼び出しを自動的に再試行しません。呼び出しの失敗時の再試行は、同期呼び出しのソースに応じて異なります。例えば、AWS SDK を使用すると、デフォルトで InternalFailure と ThrottlingException は自動的に再試行されます。 | 
| 非同期呼び出し |  耐久性のある関数の実行が失敗した場合 (FAILED、STOPPED、または TIMED\_OUT ステータスになる場合など）、Lambda は実行を再試行しません。これは、Lambda が非同期呼び出しの失敗時に関数を再試行する標準の Lambda 関数とは異なります。非同期呼び出しの MaximumRetryAttempts 設定は、耐久性のある実行には適用されません。関数のデッドレターキュー (DLQ) を設定すると、Lambda はトリガーイベントを DLQ に送信します。 | 
| ESM (イベントソースマッピング) |  デフォルトでは、Lambda は成功するまでバッチ全体を再試行します。ストリームソース (DynamoDB と Kinesis) については、関数がエラーを返すときに Lambda が再試行する最大回数を設定できます。[イベントソースマッピングのバッチ処理](invocation-eventsourcemapping.md#invocation-eventsourcemapping-batching)を参照してください。Amazon SQS ESM の場合、元の Amazon SQS キュー上で DLQ から最大再試行回数を設定できます。[Amazon SQS ESM の設定](services-sqs-configure.md)を参照してください。関数レベルで DLQ を検討し、失敗したトリガーイベントを Lambda が DLQ に送信するようにすることもできます。[関数 DLQ](invocation-async-retain-records.md#invocation-dlq) を参照してください。すべての処理試行に失敗したイベント、または処理試行に成功したイベントのレコードを受信する場合は、ESM の送信先を設定できます。[非同期呼び出しイベントレコードの送信先](invocation-async-retain-records.md#invocation-async-destinations)を参照してください。 | 
| 直接トリガー |  これは「トリガー」に応じて異なります。例えば、Lambda は Amazon S3 イベント通知によって非同期でトリガーされる関数を処理します。「[Amazon SQS での Lambda の使用](with-sqs.md)」を参照してください。Lambda は Amazon SNS イベント通知によって非同期でトリガーされる関数を処理します。「[Amazon SNS 通知を使用した Lambda 関数の呼び出し](with-sns.md)」を参照してください。非同期呼び出しの再試行動作については、上の「非同期呼び出し」テーブルエントリを参照してください。Amazon SNS が Lambda に到達できない場合、またはメッセージが拒否される場合、Amazon SNS は、数時間にわたって間隔を増やして再試行します。詳細については、Amazon SNS のよくある質問の中の[信頼性](https://aws.amazon.com/sns/faqs/#Reliability)を参照してください。API Gateway は Lambda を同期的に呼び出し、真のエラーレスポンスをリクエスタに返します。[呼び出しの再試行](invocation-retries.md)を参照してください。非同期呼び出しの再試行動作については、上の「同期呼び出し」テーブルエントリを参照してください。詳細については、[さまざまな直接トリガー](invocation-eventsourcemapping.md#eventsourcemapping-trigger-difference)を参照してください。 | 

## バックエンドの再試行
<a name="durable-backend-retries"></a>

バックエンドの再試行は、Lambda でインフラストラクチャの障害やランタイムエラーが発生した場合、または SDK が耐久性のある実行サービスと通信できない場合に発生します。Lambda はこれらの障害に対して自動的に再試行して、耐久性のある関数が一時的なインフラストラクチャの問題から回復できるようにします。

### バックエンドの再試行シナリオ
<a name="durable-backend-retry-scenarios"></a>

Lambda は、次のシナリオが発生した場合に関数を自動的に再試行します。
+ **内部サービスエラー** – Lambda または耐久性のある実行サービスが 5xx エラー (一時的なサービスの問題を示す) を返す場合。
+ **スロットリング** – 同時実行制限またはサービスクォータが原因で関数がスロットリングされた場合。
+ **タイムアウト** – SDK がタイムアウト期間内に耐久性のある実行サービスに到達できない場合。
+ **サンドボックス初期化の失敗** – Lambda が実行環境を初期化できない場合。
+ **ランタイムエラー** – Lambda ランタイムで out-of-memory エラーやプロセスクラッシュなど、関数コード外のエラーが発生した場合。
+ **無効なチェックポイントトークンのエラー** – チェックポイントトークンが無効になった場合、通常はサービス側の状態の変化が原因です。

次の表で、SDK がこれらのシナリオを処理する方法について説明します。


| シナリオ | どうなるのか | 計測への影響 | 
| --- | --- | --- | 
| 耐久性のあるハンドラー外のランタイムエラー (OOM、タイムアウト、クラッシュ) | Lambda は呼び出しを自動的に再試行します。SDK は最後のチェックポイントからリプレイし、完了したステップをスキップします。 | エラーペイロードサイズ \+ 再試行ごとに 1 回のオペレーション | 
| CheckpointDurableExecution / GetDurableExecutionState APIs の呼び出し時のサービスエラー (5xx) または タイムアウト | Lambda は呼び出しを自動的に再試行します。SDK は最後のチェックポイントから再生されます。 | エラーペイロードサイズ \+ 再試行ごとに 1 回のオペレーション | 
| CheckpointDurableExecution / GetDurableExecutionState APIs の呼び出し時のスロットリング (429) または無効なチェックポイントトークン | Lambda はエクスポネンシャルバックオフを使用して呼び出しを自動的に再試行します。SDK は最後のチェックポイントから再生されます。 | エラーペイロードサイズ \+ 再試行ごとに 1 回のオペレーション | 
| CheckpointDurableExecution / GetDurableExecutionState APIs の場合のクライアントエラー (4xx、ただし 429 と無効なトークンを除く) | SDK は実行を FAILED としてマークします。エラーは永続的な問題を示しているため、自動再試行は行われません。 | エラーペイロードサイズ | 

バックエンドの再試行ではエクスポネンシャルバックオフを使用し、関数が成功するか、実行タイムアウトに達するまで続行します。リプレイ中、SDK は完了したチェックポイントをスキップし、最後に成功した操作の実行を続行し、関数が完了した作業を再実行しないようにします。

## 再試行のベストプラクティス
<a name="durable-retry-best-practices"></a>

再試行戦略を設定するときは、次のベストプラクティスに従ってください。
+ **明示的な再試行戦略を設定する** – 本番環境でデフォルトの再試行動作に依存しないでください。ユースケースに適した最大試行回数とバックオフ間隔を使用して、明示的な再試行戦略を設定します。
+ **条件付き再試行を使用する** – 一時的なエラー (レート制限、タイムアウト) のみを再試行し、永続的なエラー (検証エラー、未検出) ではフェイルファストする `shouldRetry` ロジックを実装します。
+ **適切な最大試行回数を設定する** – 回復性と実行時間のバランスを取ります。再試行回数が多すぎると障害検出が遅れる可能性がありますが、少なすぎると不要な障害が発生する可能性があります。
+ **エクスポネンシャルバックオフを使用する** – エクスポネンシャルバックオフは、ダウンストリームサービスの負荷を軽減し、一時的な障害から回復できる可能性を高めます。
+ **エラーが発生しやすいコードをステップでラップする** – ステップ外のコードを自動的に再試行することはできません。外部 API コール、データベースクエリ、その他のエラーが発生しやすい操作を再試行戦略でステップごとにラップします。
+ **再試行メトリクスをモニタリングする** – Amazon CloudWatch でステップ再試行操作と実行失敗を追跡して、パターンを特定し、再試行戦略を最適化します。