

# 耐久性のある Lambda 関数の呼び出し
<a name="durable-invoking"></a>

耐久性のある Lambda 関数では、標準の Lambda 関数と同じ呼び出し方法がサポートされます。耐久性のある関数は同期的に、非同期的に、イベントソースマッピングを通じて呼び出すことができます。呼び出しプロセスは標準関数と同じですが、耐久性のある関数は長時間の実行および自動状態管理には追加機能を提供します。

## 呼び出し方法
<a name="durable-invoking-methods"></a>

**同期呼び出し:** 耐久性のある関数を呼び出して、レスポンスを待ちます。同期呼び出しは、Lambda によって 15 分 (設定された関数および実行タイムアウトに応じて 15 分以下) に制限されます。結果が即時に必要な場合、またはレスポンスが予想される API やサービスと統合する場合、同期呼び出しを使用します。呼び出し元を中断せず、待機オペレーションを使用して効率的な計算を行うことができます。呼び出しは耐久性のある実行全体が完了するまで待機します。べき等実行を開始するには、[べき等性](durable-execution-idempotency.md)の説明に従って実行名パラメータを使用してください。

```
aws lambda invoke \
  --function-name my-durable-function:1 \
  --cli-binary-format raw-in-base64-out \
  --payload '{"orderId": "12345"}' \
  response.json
```

**非同期呼び出し:** レスポンスを待たずに処理するイベントをキューに入れます。Lambda によってイベントがキューに配置されて、すぐに返されます。非同期呼び出しは最大 1 年間の実行期間をサポートします。非同期呼び出しは「fire-and-forget」シナリオに使用するか、バックグラウンドで処理が発生する可能性がある場合に使用します。べき等実行を開始するには、[べき等性](durable-execution-idempotency.md)の説明に従って実行名パラメータを使用してください。

```
aws lambda invoke \
  --function-name my-durable-function:1 \
  --invocation-type Event \
  --cli-binary-format raw-in-base64-out \
  --payload '{"orderId": "12345"}' \
  response.json
```

**イベントソースマッピング:** Amazon SQS、Kinesis、DynamoDB などのストリームまたはキューベースのサービスでレコードが利用可能な場合、耐久性のある関数を自動的に呼び出すように Lambda を設定します。イベントソースマッピングではイベントソースがポーリングされ、レコードのバッチで関数が呼び出されます。実行期間の制限を含め、耐久性のある関数でイベントソースマッピングの使用に関する詳細については、「[耐久性のある関数でイベントソースマッピング](durable-invoking-esm.md)」を参照してください。

各呼び出し方法の詳細については、「[同期呼び出し](invocation-sync.md)」および「[非同期呼び出し](invocation-async.md)」を参照してください。

**注記**  
耐久性のある関数では、エラー処理用のデッドレターキュー (DLQ) がサポートされていますが、Lambda 送信先はサポートされていません。DLQ を設定し、失敗した呼び出しのレコードをキャプチャします。

## 修飾 ARN の要件
<a name="durable-invoking-qualified-arns"></a>

耐久性のある関数には、呼び出しをするために修飾識別子が必要です。バージョン番号、エイリアス、`$LATEST` を使用して耐久性のある関数を呼び出す必要があります。完全修飾 ARN またはバージョン/エイリアスサフィックス付きの関数名のいずれかを使用できます。非修飾識別子 (バージョンまたはエイリアスサフィックスなし) を使用することはできません。

**有効な呼び出し:**

```
# Using full ARN with version number
arn:aws:lambda:us-east-1:123456789012:function:my-durable-function:1

# Using full ARN with alias
arn:aws:lambda:us-east-1:123456789012:function:my-durable-function:prod

# Using full ARN with $LATEST
arn:aws:lambda:us-east-1:123456789012:function:my-durable-function:$LATEST

# Using function name with version number
my-durable-function:1

# Using function name with alias
my-durable-function:prod
```

**無効な呼び出し:**

```
# Unqualified ARN (not allowed)
arn:aws:lambda:us-east-1:123456789012:function:my-durable-function

# Unqualified function name (not allowed)
my-durable-function
```

この要件により、耐久性のある実行がライフサイクル中に一貫性が維持されます。耐久性のある実行が開始されると、特定の関数バージョンに固定されます。関数が数時間後または数日後に一時停止して再開した場合、Lambda によって実行を開始したバージョンが呼び出され、ワークフロー全体でコードの一貫性が確保されます。

**ベストプラクティス**  
`$LATEST` ではなく、本番稼働の耐久性のある関数には番号付きのバージョンまたはエイリアスを使用します。番号付きバージョンは不変で、確定的な再生をサポートします。オプションとして、エイリアスは更新できる安定したリファレンスを提供し、呼び出しコードを変更せずに新しいバージョンを指します。エイリアスを更新すると、新しい実行では新しいバージョンが使用されますが、進行中の実行では元のバージョンが継続されます。`$LATEST` を使用してプロトタイプを作成するか、開発中のデプロイ時間を短縮することができます。実行中に基盤となるコードが変更された場合、実行が正しく再生されない (または失敗する) 可能性があることを理解してください。

## 実行ライフサイクルの概要
<a name="durable-invoking-execution-lifecycle"></a>

耐久性のある関数を呼び出すと、Lambda は複数の関数の呼び出しをまたぐ耐久性のある実行を作成します。

1. **初期呼び出し:** 呼び出しリクエストにより、新しい耐久性のある実行が作成されます。Lambda によって一意の実行 ID が割り当てられて、処理が開始されます。

1. **実行とチェックポイント:** 関数が耐久性のあるオペレーションを実行すると、SDK によって進行状況が追跡されるチェックポイントが作成されます。

1. **停止 (必要な場合):** 関数が `wait` や `waitForCallback` などの耐久性のある待機を使用した場合、または自動ステップ再試行を使用した場合、Lambda は実行を停止してコンピューティング時間の課金を停止します。

1. **再開:** 再開するとき (再試行後を含む)、Lambda によって関数が再度呼び出されます。SDK によってチェックポイントログが再生され、実行が一時停止した時点から続行されます。

1. **完了:** 関数が最終結果を返すか、未処理のエラーをスローすると、耐久性のある実行が完了します。

同期呼び出しの場合、呼び出し元は、耐久性のある実行全体が完了するまで待機します (待機操作を含む)。実行が呼び出しタイムアウト (15 分以下) を超えた場合、呼び出しはタイムアウトします。非同期呼び出しの場合、Lambda はすぐに返して実行は独立して続行されます。耐久性のある実行 API を使用して実行ステータスを追跡し、最終結果を取得します。

## アプリケーションコードからの呼び出し
<a name="durable-invoking-with-sdk"></a>

AWS SDK を使用して、アプリケーションコードから耐久性のある関数を呼び出します。呼び出しプロセスは標準関数と同じです。

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

```
import { LambdaClient, InvokeCommand } from '@aws-sdk/client-lambda';

const client = new LambdaClient({});

// Synchronous invocation
const response = await client.send(new InvokeCommand({
  FunctionName: 'arn:aws:lambda:us-east-1:123456789012:function:my-durable-function:1',
  Payload: JSON.stringify({ orderId: '12345' })
}));

const result = JSON.parse(Buffer.from(response.Payload!).toString());

// Asynchronous invocation
await client.send(new InvokeCommand({
  FunctionName: 'arn:aws:lambda:us-east-1:123456789012:function:my-durable-function:1',
  InvocationType: 'Event',
  Payload: JSON.stringify({ orderId: '12345' })
}));
```

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

```
import boto3
import json

client = boto3.client('lambda')

# Synchronous invocation
response = client.invoke(
    FunctionName='arn:aws:lambda:us-east-1:123456789012:function:my-durable-function:1',
    Payload=json.dumps({'orderId': '12345'})
)

result = json.loads(response['Payload'].read())

# Asynchronous invocation
client.invoke(
    FunctionName='arn:aws:lambda:us-east-1:123456789012:function:my-durable-function:1',
    InvocationType='Event',
    Payload=json.dumps({'orderId': '12345'})
)
```

------

## 連鎖呼び出し
<a name="durable-invoking-chained"></a>

耐久性のある関数によって `DurableContext` からの `invoke`オペレーションが使用されて、他の耐久性のある関数および耐久性のない関数を呼び出すことができます。呼び出される関数が完了するまで、呼び出し元の関数が待機 (一時停止) する連鎖呼び出しが作成されます。

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

```
export const handler = withDurableExecution(
  async (event: any, context: DurableContext) => {
    // Invoke another durable function and wait for result
    const result = await context.invoke(
      'process-order',
      'arn:aws:lambda:us-east-1:123456789012:function:order-processor:1',
      { orderId: event.orderId }
    );
    
    return { statusCode: 200, body: JSON.stringify(result) };
  }
);
```

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

```
@durable_execution
def handler(event, context: DurableContext):
    # Invoke another durable function and wait for result
    result = context.invoke(
        'arn:aws:lambda:us-east-1:123456789012:function:order-processor:1',
        {'orderId': event['orderId']},
        name='process-order'
    )
    
    return {'statusCode': 200, 'body': json.dumps(result)}
```

------

連鎖呼び出しは、呼び出し元の関数にチェックポイントを作成します。呼び出し元の関数が中断された場合、関数を再呼び出しせずに、呼び出される関数の結果でチェックポイントから再開されます。

**注記**  
クロスアカウントの連鎖呼び出しはサポートされていません。呼び出される関数は、呼び出し元の関数と同じ AWS アカウントに存在する必要があります。

# 耐久関数を使用したイベントソースマッピング
<a name="durable-invoking-esm"></a>

耐久関数は、すべての Lambda イベントソースマッピングで機能します。耐久関数のイベントソースマッピングは、標準関数の設定と同じ方法で設定します。イベントソースマッピングは、Amazon SQS、Kinesis、DynamoDB Streams などのイベントソースを自動的にポーリングし、レコードのバッチで関数を呼び出します。

イベントソースマッピングは、複雑なマルチステップワークフローでストリームまたはキューを処理する耐久関数に有用です。例えば、再試行、外部 API コール、人間の承認を使用して Amazon SQS メッセージを処理する耐久関数を作成できます。

## イベントソースマッピングが耐久関数を呼び出す方法
<a name="durable-esm-invocation-behavior"></a>

イベントソースマッピングは、耐久関数を同期的に呼び出し、耐久性のある実行が完全に終了するのを待ってから、次のバッチを処理したり、レコードを処理済みとしてマークします。耐久性のある実行の合計時間が 15 分を超えると、実行はタイムアウトして失敗します。イベントソースマッピングはタイムアウト例外を受け取り、再試行設定に従って処理します。

## 15 分間の実行制限
<a name="durable-esm-duration-limit"></a>

耐久関数がイベントソースマッピングによって呼び出される場合、耐久性のある実行の合計時間は 15 分を超えることはできません。この制限は、個々の関数呼び出しだけでなく、最初から最後までの耐久性のある実行全体に適用されます。

この 15 分の制限は、Lambda 関数のタイムアウト (最大 15 分) とは別です。関数タイムアウトは、個々の呼び出しを実行できる時間を制御し、耐久性のある実行のタイムアウトは、実行開始から完了までの合計経過時間を制御します。

**シナリオ例:**
+ **有効:** 耐久関数は、Amazon SQS メッセージを 3 つのステップで処理します。各ステップには 2 分かかり、最後のステップを完了するまで 5 分待機します。合計実行時間: 11 分。これは、合計が 15 分未満であるために機能します。
+ **無効:** 耐久関数は Amazon SQS メッセージを処理し、最初の処理を 2 分で完了してから、外部コールバックが完了するまで 20 分待機します。合計実行時間: 22 分。これは 15 分の制限を超え、失敗します。
+ **無効:** 耐久関数は、ステップ間に合計 30 分におよぶ複数の待機操作を伴いつつ、Kinesis レコードを処理します。個々の呼び出しは迅速に完了しますが、合計実行時間は 15 分を超えています。

**重要**  
イベントソースマッピングを使用する場合、耐久性のある実行のタイムアウトを 15 分以下に設定してください。そうしないと、イベントソースマッピングの作成は失敗します。ワークフローで実行時間が長い場合は、以下で説明する中間関数パターンを使用します。

## イベントソースマッピングの設定
<a name="durable-esm-configuration"></a>

Lambda コンソール、AWS CLI、または AWS SDK を使用して、耐久性のある関数のイベントソースマッピングを設定します。すべての標準イベントソースマッピングのプロパティは、耐久関数に適用されます。

```
aws lambda create-event-source-mapping \
  --function-name arn:aws:lambda:us-east-1:123456789012:function:my-durable-function:1 \
  --event-source-arn arn:aws:sqs:us-east-1:123456789012:my-queue \
  --batch-size 10 \
  --maximum-batching-window-in-seconds 5
```

耐久関数のイベントソースマッピングを設定するときは、必ず修飾 ARN (バージョン番号またはエイリアス) を使用してください。

## イベントソースマッピングによるエラー処理
<a name="durable-esm-error-handling"></a>

イベントソースマッピングは、耐久関数で動作する組み込みのエラー処理を提供します。
+ **再試行動作:** 最初の呼び出しが失敗した場合、イベントソースマッピングは再試行設定に従って再試行します。要件に基づいて最大再試行回数と再試行間隔を設定します。
+ **デッドレターキュー: **すべての再試行後に失敗したレコードをキャプチャするようにデッドレター キューを構成します。これにより、メッセージの損失を防ぎ、失敗したレコードを手動で検査できます。
+ **部分的なバッチ処理の失敗:** Amazon SQS および Kinesis では、部分的なバッチ処理の失敗レポートを使用して、レコードを個別に処理し、失敗したレコードのみを再試行します。
+ **Bisect on error:** Kinesis および DynamoDB Streams の場合、bisect on error 機能を有効にして、失敗したバッチを分割し、問題のあるレコードを分離します。

**注記**  
耐久性のある関数では、エラー処理用のデッドレターキュー (DLQ) がサポートされていますが、Lambda 送信先はサポートされていません。DLQ を設定し、失敗した呼び出しのレコードをキャプチャします。

イベントソースマッピングエラー処理の詳細については、[「イベントソースマッピング](invocation-eventsourcemapping.md)」を参照してください。

## 長時間実行されるワークフローに中間関数を使用する
<a name="durable-esm-intermediary-function"></a>

ワークフローの完了に 15 分以上かかる場合は、イベントソースマッピングと耐久関数の間に中間標準 Lambda 関数を使用します。中間関数は、イベントソースマッピングからイベントを受信し、耐久関数を非同期的に呼び出し、15 分間の実行制限を削除します。

このパターンは、イベントソースマッピングの同期呼び出しモデルを耐久関数の長時間かかる実行モデルから切り離します。イベントソースマッピングは中間関数を呼び出し、中間関数は耐久性のある実行を開始した後すぐに返されます。その後、耐久関数は、必要な期間 (最大 1 年間) 独立して実行されます。

### アーキテクチャ
<a name="durable-esm-intermediary-architecture"></a>

中間関数パターンでは、次の 3 つのコンポーネントを使用します。

1. **イベントソースマッピング:** イベントソース (Amazon SQS、Kinesis、DynamoDB Streams) をポーリングし、レコードのバッチと同期的に中間関数を呼び出します。

1. **中間関数:** イベントソースマッピングからイベントを受信し、必要に応じてデータを検証および変換し、耐久関数を非同期的に呼び出す標準の Lambda 関数。この関数は迅速に (通常は 1 秒未満) 完了し、イベントソースマッピングに制御を返します。

1. **耐久関数:** 長時間の実行が可能な複雑かつ多段階のロジックでイベントを処理します。非同期的に呼び出されるため、15 分の制限による制約はありません。

### 実装
<a name="durable-esm-intermediary-implementation"></a>

中間関数は、イベントソースマッピングからイベント全体を受信し、耐久関数を非同期的に呼び出します。実行名パラメータを使用してべき等実行が開始されるようにし、イベントソースマッピングが再試行された場合に重複処理を防止します。

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

```
import { LambdaClient, InvokeCommand } from '@aws-sdk/client-lambda';
import { SQSEvent } from 'aws-lambda';
import { createHash } from 'crypto';

const lambda = new LambdaClient({});

export const handler = async (event: SQSEvent) => {
  // Invoke durable function asynchronously with execution name
  await lambda.send(new InvokeCommand({
    FunctionName: 'arn:aws:lambda:us-east-1:123456789012:function:my-durable-function:1',
    InvocationType: 'Event',
    Payload: JSON.stringify({
      executionName: event.Name,
      event: event
    })
  }));
  
  return { statusCode: 200 };
};
```

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

```
import boto3
import json
import hashlib

lambda_client = boto3.client('lambda')

def handler(event, context):  
    # Invoke durable function asynchronously with execution name
    lambda_client.invoke(
        FunctionName='arn:aws:lambda:us-east-1:123456789012:function:my-durable-function:1',
        InvocationType='Event',
        Payload=json.dumps({
            'executionName': execution_name,
            'event': event["name"]
        })
    )
    
    return {'statusCode': 200}
```

------

中間関数自体のべき等性については、[Powertools for AWS Lambda](https://docs.aws.amazon.com//powertools/) を使用して、イベントソースマッピングが中間関数を再試行した場合に、耐久関数の重複呼び出しを防止します。

耐久関数は、実行名を持つペイロードを受信し、長時間実行されるロジックを持つすべてのレコードを処理します。

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

```
import { withDurableExecution, DurableContext } from '@aws/durable-execution-sdk-js';

export const handler = withDurableExecution(
  async (payload: any, context: DurableContext) => {
    const sqsEvent = payload.event;
    
    // Process each record with complex, multi-step logic
    const results = await context.map(
      sqsEvent.Records,
      async (ctx, record) => {
        const validated = await ctx.step('validate', async () => {
          return validateOrder(JSON.parse(record.body));
        });
        
        // Wait for external approval (could take hours or days)
        const approval = await ctx.waitForCallback(
          'approval',
          async (callbackId) => {
            await requestApproval(callbackId, validated);
          },
          { timeout: { hours: 48 } }
        );
        
        // Complete processing
        return await ctx.step('complete', async () => {
          return completeOrder(validated, approval);
        });
      }
    );
    
    return { statusCode: 200, processed: results.getResults().length };
  }
);
```

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

```
from aws_durable_execution_sdk_python import durable_execution, DurableContext
from aws_durable_execution_sdk_python.config import Duration, WaitForCallbackConfig
from collections.abc import Sequence
import json

def validate_order(order_data: dict) -> dict:
    """Validate order data - always passes."""
    return order_data

def request_approval(callback_id: str, validated_order: dict) -> None:
    """Request approval for the order - always passes."""
    pass

def complete_order(validated_order: dict, approval_result: str) -> dict:
    """Complete the order processing - always passes."""
    return validated_order

@durable_execution
def lambda_handler(payload, context: DurableContext):
    sqs_event = payload['event']

    def process_record(
        ctx: DurableContext, 
        record: dict, 
        index: int, 
        items: Sequence[dict]
    ) -> dict:
        validated = ctx.step(
            lambda _: validate_order(json.loads(record['body'])),
            name=f'validate-{index}'
        )

        approval = ctx.wait_for_callback(
            submitter=lambda callback_id, wait_ctx: request_approval(callback_id, validated),
            name=f'approval-{index}',
            config=WaitForCallbackConfig(timeout=Duration.from_seconds(172800))
        )

        return ctx.step(
            lambda _: complete_order(validated, approval),
            name=f'complete-{index}'
        )

    results = context.map(
        inputs=sqs_event['Records'],
        func=process_record,
        name='process-records'
    )

    return {
        'statusCode': 200, 
        'started': results.started_count,
        'completed': results.success_count,
        'failed': results.failure_count,
        'total': results.total_count
    }
```

------

### 主な考慮事項
<a name="durable-esm-intermediary-tradeoffs"></a>

このパターンは、耐久性のある実行からイベントソースマッピングを切り離すことで、15 分間の実行制限を削除します。中間関数は、耐久性のある実行を開始した直後に返り、イベントソースマッピングの処理を続行できるようにします。その後、耐久関数は、必要な期間だけ独立して実行されます。

中間関数は、耐久性のある実行が完了したときではなく、耐久関数を呼び出すときに成功します。耐久性のある実行が後で失敗した場合、イベントソースマッピングはすでにバッチを正常に処理しているため、再試行されません。耐久関数にエラー処理を実装し、失敗した実行に対してデッドレターキューを設定します。

実行名パラメータを使用して、べき等実行が開始されるようにします。イベントソースマッピングが中間関数を再試行する場合、実行名がすでに存在するため、耐久関数は重複した実行を開始しません。

## サポートされているイベントソース
<a name="durable-esm-supported-sources"></a>

耐久関数は、イベントソースマッピングを使用するすべての Lambda イベントソースをサポートします。
+ Amazon SQS キュー (スタンダードおよび FIFO)
+ Kinesis Streams
+ DynamoDB Streams
+ Amazon Managed Streaming for Apache Kafka (Amazon MSK)
+ セルフマネージド Apache Kafka
+ Amazon MQ (ActiveMQ および RabbitMQ)
+ Amazon DocumentDB 変更ストリーム

耐久関数を呼び出す場合、すべてのイベントソースタイプには 15 分間の耐久性のある実行制限が適用されます。

# 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 エラーペイロードサイズ | 
| 再試行回数が残っていないステップにおける例外 | ステップは失敗し、例外をスローします。ハンドラーコードがこの例外をキャッチしない場合、実行全体が失敗します。 | 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 {'should_retry': False}
    # Exponential backoff: 2s, 4s, 8s, 16s, 32s (capped at 300s)
    delay = min(2 * (2 ** (attempt_count - 1)), 300)
    return {'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 {'should_retry': False}
    return {'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 {'should_retry': False}
    
    # Exponential backoff: 1s, 2s, 4s (capped at 30s)
    delay = min(2 ** (attempt_count - 1), 30)
    return {'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-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 再試行ごとに 1 回のオペレーション | 
| CheckpointDurableExecution / GetDurableExecutionState APIs の呼び出し時のサービスエラー (5xx) または タイムアウト | Lambda は呼び出しを自動的に再試行します。SDK は最後のチェックポイントから再生されます。 | エラーペイロードサイズ \$1 再試行ごとに 1 回のオペレーション | 
| CheckpointDurableExecution / GetDurableExecutionState APIs の呼び出し時のスロットリング (429) または無効なチェックポイントトークン | Lambda はエクスポネンシャルバックオフを使用して呼び出しを自動的に再試行します。SDK は最後のチェックポイントから再生されます。 | エラーペイロードサイズ \$1 再試行ごとに 1 回のオペレーション | 
| CheckpointDurableExecution / GetDurableExecutionState APIs の場合のクライアントエラー (4xx、ただし 429 と無効なトークンを除く) | SDK は実行を FAILED としてマークします。エラーは永続的な問題を示しているため、自動再試行は行われません。 | エラーペイロードサイズ | 

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

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

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

# べき等性
<a name="durable-execution-idempotency"></a>

耐久関数は、実行名から実行を開始するための組み込みのべき等性を提供します。実行名を指定すると、Lambda はそれを使用して重複する実行を防ぎ、呼び出しリクエストの安全な再試行を可能にします。ステップにはデフォルトで at-least-once の実行セマンティクスがあります。リプレイ中、SDK は完了したステップを再実行せずにチェックポイントされた結果を返しますが、完了前に発生する可能性がある再試行を処理できるように、ビジネスロジックはべき等性である必要があります。

**注記**  
Lambda イベントソースマッピング (ESM) は、起動時のべき等性をサポートしていません。したがって、各呼び出し (再試行を含む) は新しい耐久性のある実行を開始します。イベントソースマッピングを使用してべき等性の実行を確実に行うには、[の Powertools for AWS Lambda](https://docs.aws.amazon.com//powertools/) などの関数コードにべき等性ロジックを実装するか、通常の Lambda 関数をプロキシ (ディスパッチャー) として使用して、べき等性キー (実行名パラメータ) を使用して耐久性関数を呼び出します。

## 実行名
<a name="durable-idempotency-execution-names"></a>

耐久関数を呼び出すときに実行名を指定できます。実行名はべき等性キーとして機能し、重複した実行を作成せずに呼び出しリクエストを安全に再試行できます。名前を指定しない場合、Lambda は一意の実行 ID を自動的に生成します。

この名前は、ユーザーのアカウント内とリージョン内で一意でなければなりません。すでに存在する実行名で関数を呼び出す場合、Lambda の動作は既存の実行の状態とペイロードが一致するかどうかによって異なります。

## べき等性の動作
<a name="durable-idempotency-behavior"></a>

次の表は、実行名を指定したかどうか、既存の実行状態、ペイロードが一致するかどうかに基づいて、Lambda が呼び出しリクエストを処理する方法を示しています。


| シナリオ | 名前が指定されていますか? | 既存の実行状態 | ペイロードは同一ですか? | 行動 | 
| --- | --- | --- | --- | --- | 
| 1 | 不可 | 該当なし | 該当なし | 新しい実行が開始される: Lambda は一意の実行 ID を生成し、新しい実行を開始する | 
| 2 | はい | 存在したことがない、または保持の有効期限が切れている | 該当なし | 新しい実行が開始される: Lambda は指定された名前で新しい実行を開始する | 
| 3 | はい | 実行中 | はい | 冪等性の開始: Lambda は、重複を開始せずに既存の実行情報を返します。同期呼び出しの場合、これは実行中の実行への再アタッチとして動作します。 | 
| 4 | はい | 実行中 | 不可 | エラー: この名前の実行はすでに異なるペイロードで実行されているため、Lambda は DurableExecutionAlreadyExists エラーを返す | 
| 5 | はい | クローズ済み (成功、失敗、停止、またはタイムアウト) | はい | べき等性の開始: Lambda は、新しい実行を開始せずに既存の実行情報を返します。クローズされた実行結果が返されます | 
| 6 | はい | クローズ済み (成功、失敗、停止、またはタイムアウト) | 不可 | エラー: この名前の実行は別のペイロードですでに完了しているため、Lambda は DurableExecutionAlreadyExists エラーを返します | 

**注記**  
シナリオ 3 と 5 は、重複を作成するのではなく、既存の実行情報を返すことで、Lambda が重複した呼び出しリクエストを安全に処理する、べき等性の動作を示しています。

## ステップのべき等性
<a name="durable-idempotency-steps"></a>

ステップには、デフォルトで at-least-once 実行セマンティクスがあります。待機、コールバック、または失敗後に関数が再生されると、SDK はチェックポイントログに対して各ステップをチェックします。すでに完了したステップの場合、SDK はステップロジックを再実行せずにチェックポイントされた結果を返します。ただし、ステップが失敗した場合、またはステップが完了する前に関数が中断された場合、ステップは複数回実行される可能性があります。

ステップでラップされたビジネスロジックは、発生する可能性のある再試行を処理するように、べき等性である必要があります。べき等性キーを使用して、ステップが再試行された場合でも、支払いやデータベース書き込みなどのオペレーションが 1 回だけ実行されるようにします。

**例: ステップでのべき等性キーの使用**

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

```
import { withDurableExecution, DurableContext } from '@aws/durable-execution-sdk-js';
import { randomUUID } from 'crypto';

export const handler = withDurableExecution(
  async (event: any, context: DurableContext) => {
    // Generate idempotency key once
    const idempotencyKey = await context.step('generate-key', async () => {
      return randomUUID();
    });
    
    // Use idempotency key in payment API to prevent duplicate charges
    const payment = await context.step('process-payment', async () => {
      return paymentAPI.charge({
        amount: event.amount,
        idempotencyKey: idempotencyKey
      });
    });
    
    return { statusCode: 200, payment };
  }
);
```

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

```
from aws_durable_execution_sdk_python import durable_execution, DurableContext
import uuid

@durable_execution
def handler(event, context: DurableContext):
    # Generate idempotency key once
    idempotency_key = context.step(
        lambda _: str(uuid.uuid4()),
        name='generate-key'
    )
    
    # Use idempotency key in payment API to prevent duplicate charges
    payment = context.step(
        lambda _: payment_api.charge(
            amount=event['amount'],
            idempotency_key=idempotency_key
        ),
        name='process-payment'
    )
    
    return {'statusCode': 200, 'payment': payment}
```

------

実行モードを `AT_MOST_ONCE_PER_RETRY` に設定することで、at-most-once の実行セマンティクスを使用するようにステップを設定できます。これにより、ステップは再試行ごとに最大 1 回実行されますが、ステップが完了する前に関数が中断された場合はまったく実行されない可能性があります。

SDK は、リプレイ時にステップ名と実行順序がチェックポイントログと一致していることを検証することで、決定的なリプレイを実行します。コードが別の順序または名前でステップを実行しようとすると、SDK は `NonDeterministicExecutionError` をスローします。

**完了したステップでのリプレイの仕組み:**

1. 最初の呼び出し: 関数はステップ A を実行し、チェックポイントを作成してから待機する

1. 2 回目の呼び出し (待機後): 関数は最初から再生されますが、ステップ A はチェックポイントされた結果をすぐに返すため、再実行されず、そのままステップ B に進みます。

1. 3 回目の呼び出し (別の待機後): 関数は最初から再生されますが、ステップ A とステップ B はチェックポイントされた結果をすぐに返すため、そのままステップ C に進みます。

このリプレイメカニズムにより、完了したステップが再実行されることはありませんが、ビジネスロジックは完了前に再試行を処理するべき等性である必要があります。