AWS SAM CLIのローカル実行でDurable Functionを使ってみました

AWS SAM CLIのローカル実行でDurable Functionを使ってみました

2025.12.05

初めに

先日AWS Lambdaで最大1年間実行可能となるDurable Functionがリリースされました。

https://aws.amazon.com/about-aws/whats-new/2025/12/lambda-durable-multi-step-applications-ai-workflows/

https://dev.classmethod.jp/articles/lambda-durable-function-25/

Durable Functionは各処理をステップに分割しチェックポイントを作成することで、「ステップ毎に最大15分」実行可能であり、「関数全体では中断を含めて最大1年間保持・継続が可能」な関数を実現することが可能となります。

既に当ブログでも別の方が記事にされている通りあくまでLambda関数の一回の実行の最大は15分であることは変わっていないため、ステップ単位での処理で15分を超過するようなケースには対応できないため注意しましょう。

https://dev.classmethod.jp/articles/about-durable-functions-timeout/

さて、Durable FunctionですがAWS SAM CLIでも先日リリースされたv1.150.0でローカルの実行にも対応しました。

https://github.com/aws/aws-sam-cli/releases/tag/v1.150.0

インターフェース付近ではなく裏で動かすコンテナの取り回し諸々全体への影響があるためかPRを見るとここしばらくみたことない量の変更・追加が入っています。

https://github.com/aws/aws-sam-cli/pull/8479/files

既に当ブログでいくつかの実行例も出てる中とはなりますがシンプルな処理でローカルで動作確認してみましょう。

セットアップ

テンプレート側(Lambda関数自体の設定)

関数側で有効化が必要となりますが、設定としてはAWS::Serverless::Function直下に存在するDurableConfigを設定すれば有効となります。

必須パラメーターは関数の実行最大時間(ExecutionTimeout)となっており、実行履歴の保持期間(RetentionPeriodInDays)を任意で設定可能です。

https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-function-durableconfig.html

Transform: AWS::Serverless-2016-10-31

Parameters:
  DurationTimeout:
    Type: Number
    Default: 1200

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.14
      MemorySize: 256
      Architectures:
        - arm64
      # 後述のコードで1ステップを60秒待機にするため収まりつつ、別ステップで落ちるほどの時間
      Timeout: 70
      DurableConfig:
        ExecutionTimeout: !Ref DurationTimeout

Lambda関数側

Durable Functionを利用するにあたり専用のライブラリが必要となるため追加でこちらをインストールするようにします。

https://github.com/aws/aws-durable-execution-sdk-python

https://pypi.org/project/aws-durable-execution-sdk-python/

echo "aws-durable-execution-sdk-python" > hello_world/requirements.txt

Pythonの場合ライブラリ側の事情としては3.11以上であれば対応していそうですが、Lambda側の機能的に3.14未満は対応しておらずデプロイ時にエラーとなるようですので実質的に3.14が最小バージョンになります。

UPDATE_FAILED                                              AWS::Lambda::Function                                      HelloWorldFunction                                         Resource handler returned message: "You cannot use a
                                                                                                                                                                                 managed runtime that does not support a durable
                                                                                                                                                                                 configuration (Service: Lambda, Status Code: 400,
                                                                                                                                                                                 Request ID: a97511b0-be02-4200-8daa-93db49b1d646) (SDK
                                                                                                                                                                                 Attempt Count: 1)" (RequestToken:
                                                                                                                                                                                 d8db558e-c13c-4867-2a17-a965f62fb9ba, HandlerErrorCode:
                                                                                                                                                                                 InvalidRequest)

コードは以下のような形で60秒待機するステップをループさせてみます。

from aws_durable_execution_sdk_python import (
    DurableContext,
    StepContext,
    durable_execution,
    durable_step,
)
from aws_durable_execution_sdk_python.config import Duration

@durable_step
def process_task(step_context: StepContext, loop_number: int):
    print(f">>> Loop {loop_number}: 処理 開始")
    step_context.logger.info(f"Loop {loop_number}: 処理を実行中")

    result = f"処理が完了しました"

    print(f"Loop {loop_number}: 処理 完了 - {result}")
    return result

@durable_execution
def lambda_handler(event, context: DurableContext):

    # ループで処理を実行
    for i in range(1, 4):
        # 処理実行
        result = process_task(i)

        # 待機(60秒)
        print(f">>> Loop {i}: 60秒待機 開始")
        context.wait(Duration.from_seconds(60))
        print(f"Loop {i}: 60秒待機 完了")

    return {
        "statusCode": 200,
        "body": "complete"
    }

実行

実行は通常通りlocal invokeでOKです。

Mounting /xxx/sam-app/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated, inside runtime container
SAM_CONTAINER_ID: e0d1b107ca055bee14e2890acc090706d6c053b647c3f51eda9db65db6358588
START RequestId: d3fd4ef7-3f2e-45a8-a1c3-719ceec231e7 Version: $LATEST
>>> Loop 1: 60秒待機 開始
END RequestId: 7133d1c4-379c-4034-b365-058c486d9526
REPORT RequestId: 7133d1c4-379c-4034-b365-058c486d9526	Init Duration: 0.05 ms	Duration: 2828.89 ms	Billed Duration: 2829 ms	Memory Size: 256 MB	Max Memory Used: 256 MB
START RequestId: 2daf3bae-7583-40a4-ae13-44a2220753cb Version: $LATEST
>>> Loop 1: 60秒待機 開始
Loop 1: 60秒待機 完了
>>> Loop 2: 60秒待機 開始
END RequestId: 397b8e14-9a32-40c1-b325-78c5b7a4a0ea
REPORT RequestId: 397b8e14-9a32-40c1-b325-78c5b7a4a0ea	Duration: 249.17 ms	Billed Duration: 250 ms	Memory Size: 256 MB	Max Memory Used: 256 MB
START RequestId: 00802c78-17e8-4547-b369-d502370508c7 Version: $LATEST
>>> Loop 1: 60秒待機 開始
Loop 1: 60秒待機 完了
>>> Loop 2: 60秒待機 開始
Loop 2: 60秒待機 完了
>>> Loop 3: 60秒待機 開始
END RequestId: d0801013-5773-4d53-94d2-097036d4e99e
REPORT RequestId: d0801013-5773-4d53-94d2-097036d4e99e	Duration: 281.19 ms	Billed Duration: 282 ms	Memory Size: 256 MB	Max Memory Used: 256 MB
START RequestId: 7e5f5498-2249-475d-9c7d-8201966990ea Version: $LATEST
>>> Loop 1: 60秒待機 開始
Loop 1: 60秒待機 完了
>>> Loop 2: 60秒待機 開始
Loop 2: 60秒待機 完了
>>> Loop 3: 60秒待機 開始
Loop 3: 60秒待機 完了
END RequestId: 221439fd-896e-4cd1-86f1-54c16a16bbec
REPORT RequestId: 221439fd-896e-4cd1-86f1-54c16a16bbec	Duration: 131.07 ms	Billed Duration: 132 ms	Memory Size: 256 MB	Max Memory Used: 256 MB

Execution Summary:
=========================
ARN:      067ac80a-84c8-4787-9ccd-a2f56cb9d45a
Name:     N/A
Duration: 183.62s
Status:   SUCCEEDED ✅
Input:    {}
Result:   {"statusCode": 200, "body": "complete"}

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/durable-functions.html
When a durable function resumes from a wait point or interruption like retries, the system performs replay. During replay, your code runs from the beginning but skips over completed checkpoints, using stored results instead of re-executing completed operations.

あれ...ログが累積する?と思ったのですが、再実行の際は「以前のチェックポイントから再開」ではなく「最初から実行した上で結果を再利用する(キャッシュのイメージに近い?)」というもののようです。

ログもループごと(正確には@durable_step毎)にまとめて出力されたのでおや?と思ったのですが、実行としてはこんな感じで親に管理元となるエミュレーターのコンテナが動きつつ(こちらの方が開始がやや早い)、それとは別のコンテナで実際のコードが動くようです。

 docker ps
CONTAINER ID   IMAGE                                                                    COMMAND                   CREATED          STATUS          PORTS                                       NAMES
795d2ccfae60   public.ecr.aws/lambda/python:3.14-rapid-arm64                            "/var/rapid/aws-lamb…"   13 seconds ago   Up 11 seconds   0.0.0.0:5621->8080/tcp                      sharp_pascal
a9a92c67baab   samcli/durable-execution-emulator:aws-durable-execution-emulator-arm64   "/usr/local/bin/aws-…"   37 seconds ago   Up 35 seconds   0.0.0.0:9014->9014/tcp, :::9014->9014/tcp   sam-durable-execution-emulator

実行環境と管理用のコンテナが別になるためか、実行中はこんな感じでSAM側からは定期的にポーリングして実行状態を監視しており、

2025-12-05 14:07:46,785 | Polling execution status for ARN: e0b7d53a-3801-4fde-bb1b-3565e0e224fc
2025-12-05 14:07:46,928 | Checking for pending callbacks in execution: e0b7d53a-3801-4fde-bb1b-3565e0e224fc
2025-12-05 14:07:46,929 | Getting durable execution history for ARN 'e0b7d53a-3801-4fde-bb1b-3565e0e224fc' with include_execution_data=True
2025-12-05 14:07:48,155 | Polling execution status for ARN: e0b7d53a-3801-4fde-bb1b-3565e0e224fc
2025-12-05 14:07:48,177 | Checking for pending callbacks in execution: e0b7d53a-3801-4fde-bb1b-3565e0e224fc
2025-12-05 14:07:48,178 | Getting durable execution history for ARN 'e0b7d53a-3801-4fde-bb1b-3565e0e224fc' with include_execution_data=True
2025-12-05 14:07:49,203 | Polling execution status for ARN: e0b7d53a-3801-4fde-bb1b-3565e0e224fc
2025-12-05 14:07:49,218 | Checking for pending callbacks in execution: e0b7d53a-3801-4fde-bb1b-3565e0e224fc
2025-12-05 14:07:49,219 | Getting durable execution history for ARN 'e0b7d53a-3801-4fde-bb1b-3565e0e224fc' with include_execution_data=True

少なくとも標準出力は完了したタイミングでまとめて返ってくるようです。

2025-12-05 15:54:37,817 | Polling execution status for ARN: 9a16d7f4-ee40-4c64-9129-3375771c04eb
2025-12-05 15:54:38,001 | Checking for pending callbacks in execution: 9a16d7f4-ee40-4c64-9129-3375771c04eb
2025-12-05 15:54:38,002 | Getting durable execution history for ARN '9a16d7f4-ee40-4c64-9129-3375771c04eb' with include_execution_data=True
START RequestId: 70089d87-64c1-496d-ac83-fd7e4e979828 Version: $LATEST
>>> Loop 1: 60秒待機 開始
Loop 1: 60秒待機 完了
>>> Loop 2: 60秒待機 開始
Loop 2: 60秒待機 完了
>>> Loop 3: 60秒待機 開始
END RequestId: 041631f5-c8cd-4150-bbcf-c5ba83b24fc3
REPORT RequestId: 041631f5-c8cd-4150-bbcf-c5ba83b24fc3	Duration: 241.83 ms	Billed Duration: 242 ms	Memory Size: 256 MB	Max Memory Used: 256 MB
2025-12-05 15:54:39,022 | Polling execution status for ARN: 9a16d7f4-ee40-4c64-9129-3375771c04eb
2025-12-05 15:54:39,035 | Checking for pending callbacks in execution: 9a16d7f4-ee40-4c64-9129-3375771c04eb
2025-12-05 15:54:39,036 | Getting durable execution history for ARN '9a16d7f4-ee40-4c64-9129-3375771c04eb' with include_execution_data=True

ExecutionTimeoutの値を短くしステップ実行中にダウンした場合でも実行されたところまでは出力してくれそうです。

Mounting /xxx/sam-app/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated, inside runtime container
SAM_CONTAINER_ID: 6039af1fe94b018fb1b0ae469e73e191bf7e1e72a0a4299e7e0d65355e1b91c0
START RequestId: 1c8fb107-4510-4a03-8a3b-61f4c473d730 Version: $LATEST
>>> Loop 1: 60秒待機 開始
END RequestId: 917c0cf0-e5da-431e-8f57-dd5b527a300e
REPORT RequestId: 917c0cf0-e5da-431e-8f57-dd5b527a300e	Init Duration: 0.02 ms	Duration: 2325.34 ms	Billed Duration: 2326 ms	Memory Size: 256 MB	Max Memory Used: 256 MB
START RequestId: 71836912-bb78-4844-b9c1-f577ec7de7b5 Version: $LATEST
>>> Loop 1: 60秒待機 開始
Loop 1: 60秒待機 完了
>>> Loop 2: 60秒待機 開始
END RequestId: 7542251b-3db4-4b97-9518-02b83437b2f4
REPORT RequestId: 7542251b-3db4-4b97-9518-02b83437b2f4	Duration: 255.99 ms	Billed Duration: 256 ms	Memory Size: 256 MB	Max Memory Used: 256 MB

Execution Summary:
=========================
ARN:      30b85410-e184-4c31-9114-155e90c9b1f8
Name:     N/A
Duration: 120.11s
Status:   TIMED_OUT ⚠️
Input:    {}
Error:    Unknown: Execution timed out after 120 seconds.

Commands you can use next
=========================
[*] Get execution details: sam local execution get 30b85410-e184-4c31-9114-155e90c9b1f8
[*] View execution history: sam local execution history 30b85410-e184-4c31-9114-155e90c9b1f8

あくまでSAMのローカル実行はエミュレーターですので、この辺りの取り回しについてはAWS上のLambda関数としてのDurable Functionの実際の挙動ではなく、SAMのエミュレーター側特有の取り回しの可能性もありますのでご注意ください。

終わりに

AWS SAM CLIをDurable Functionのローカル実行を試してみました。

ある程度ステップを切った上で結果再利用で対応できるケースや、外部の処理待ちが長いものに関して外部に情報を保持するのではなくLambdaに情報を保持しつつ長期中断ができるとても良いアップデートです(早く東京リージョンにもきて欲しいですね)。

中断・再開等を含めれば長時間実行できてしまうのとLambdaの実行は従量課金という関係上、コード側で意図しないバグを引くと長時間実行され想定以上の課金が発生させてしまうリスクはありますが、ある程度テストケースがしっかりしているのであれば事前に開発端末等でその辺りを潰せるので本機能のローカル実行の対応はとてもありがたそうです。

この記事をシェアする

FacebookHatena blogX

関連記事