はじめに:Lambda の歴史が動いた re:Invent 2025
2014 年の登場以来、AWS Lambda は「ステートレス」であることをその美学としてきました。しかし、現実のビジネスロジックは往々にして「状態(ステート)」を必要とします。
re:Invent 2025 で発表された AWS Lambda Durable Functions は、この 10 年の常識を覆すアップデートです。Lambda に step や wait といった操作を導入することで、処理途中の チェックポイント(状態) を保存しながら実行を継続できるようになりました。
本記事では、この新機能の背景から、アーキテクチャ、Step Functions との使い分けまでを解説します。
開発の背景:モノリスの書き心地をマイクロサービスへ
「Build like Monoliths, Deploy like Microservices」
開発者にとって、モノリス(単一のコードベース)は理解しやすく、ローカルでのデバッグも容易です。一方で、運用フェーズではスケーラビリティや独立デプロイが可能なマイクロサービスが理想です。
AWS はこれまで、Lambda、Step Functions (2016)、EventBridge (2019) を通じてこのギャップを埋めてきました。しかし、開発者は依然として以下の不満を抱えていました。
- ASL (Amazon States Language) の学習コスト: ワークフローを記述するために独自の JSON 構造を書く必要がある。
- ロジックの断片化: 本来 1 つのビジネスドメインであるはずの処理が、複数の Lambda 関数に分割され、管理が複雑になる。
- 長時間の待機: Lambda 単体では最大 15 分という制限があり、数日間の待機が必要な場合は Step Functions が必須だった。
AWS Lambda Durable Functions は、「慣れ親しんだプログラミング言語のまま、長時間のワークフローを Lambda 1 つで完結させる」 ことを目指して誕生しました。
Durable Execution の仕組み:チェックポイントとリプレイ
Durable Functions の核となる概念は Durable Execution(永続実行) です。これは、SDK が提供するプリミティブを使用して、進捗をチェックポイントし、障害や待機から回復する仕組みです。
チェックポイント (Checkpointing)
関数が context.step() を実行するたびに、Lambda はその結果を自動的に保存します。これが「チェックポイント」です。
リプレイ (Replay) のメカニズム
これが最も重要なポイントです。関数が待機(wait)から復帰したり、エラーからリトライしたりする際、Lambda は関数を最初から(ハンドラの先頭から)再実行します。
- 再開: Lambda が再度インボークされる。
- ログの確認: SDK がこれまでの「チェックポイントログ」を確認。
- 値の置換: すでに完了している
stepに到達すると、実際の処理は行わず、保存されている結果を即座に返します。 - 継続: 未完了のステップから実際の処理を再開します。
[!CAUTION]非決定論的(Non-deterministic)な操作の禁止
リプレイの特性上、コードは「同じ入力に対して常に同じ結果」を返す必要があります。random() や datetime.now() を step の外で直接使うと、リプレイ時に値が変わり、不整合(Non-deterministic error)を引き起こします。これらは必ず step 内で実行するか、SDK の提供するメソッドを使用してください。
実装ガイド:SDK と DurableContext
現在、JavaScript/TypeScript および Python 用の SDK が提供されています。
Python での実装例
Python SDK は同期的な記述スタイルを採用しており、await を使用しません。
Python
from aws_durable_execution_sdk_python import (
DurableContext,
durable_execution,
durable_step,
)
@durable_step
def validate_order(step_context, order_id):
step_context.logger.info(f"Validating order {order_id}")
return {"order_id": order_id, "status": "validated"}
@durable_step
def process_payment(step_context, order_data):
# 外部API連携などのビジネスロジック
return {"status": "success", "transaction_id": "tx123"}
@durable_execution
def handler(event, context: DurableContext):
# 1. 注文チェック
order = context.step(lambda: validate_order(event))
# 2. 決済実行(自動的にチェックポイントされる)
payment = context.step(process_payment(order))
# 3. 1時間の待機(リソース消費ゼロ)
context.wait(3600)
# 4. 完了通知
result = context.step(lambda: send_notification(payment))
return result
3 種類の待機操作(Wait States)
Durable Functions では、待機中に Lambda の実行(課金)を停止できます。
- Timer Wait:
context.wait(seconds)。指定時間後に自動再開。 - Callback Wait:
context.wait_for_callback()。外部システムからの承認などを待つ。 - Condition Wait (Polling):
context.wait_for_condition()。特定の条件が True になるまで定期的にチェックする。
比較:Step Functions vs Durable Functions
「どちらを使うべきか?」という問いに対し、AWS は以下の基準を提示しています。
| 項目 | AWS Step Functions | AWS Lambda Durable Functions |
| 主な目的 | AWS 全体のオーケストレーション | Lambda 内のアプリ開発 |
| 記述方法 | ASL (JSON/YAML) / Visual Studio | プログラミング言語 (TS, Python) |
| デバッグ | ビジュアルグラフでの追跡 | コードレベルのデバッグ / ユニットテスト |
| 統合 | 220 以上の AWS サービスと直接連携 | SDK/コードによる柔軟な統合 |
| 最大実行期間 | 1 年 | 1 年 |
使い分けの指針:
- Step Functions: 複数の AWS サービス(Sagemaker, Glue, ECS 等)を組み合わせる場合や、ワークフローを視覚的に可視化・管理したい場合。
- Durable Functions: 複雑なループ、条件分岐、外部 API 連携など、「コードとして記述した方が自然な」 ロジックをステートフルに実行したい場合。
運用上の重要事項:呼び出しと設定
実行タイムアウトと保持期間
Durable Functions には 2 つの新しいタイムアウト概念があります。
- Lambda Function Timeout (最大 15 分): 1 回のインボーク(実行)の制限。
- Execution Timeout (最大 1 年): ワークフロー全体(開始から完了まで)の制限。
また、Retention Period(保持期間) は実行完了後に履歴データを保存する期間(デフォルト 14 日)を指します。
呼び出しの制約(Qualified ARNs)
Durable Functions を呼び出す際は、必ず バージョン または エイリアス を指定(Qualified ARN)する必要があります。
- OK:
my-function:1,my-function:prod,my-function:$LATEST - NG:
my-function(アンクォリファイド)
これは、リプレイ中にコードが書き換わって非決定論的な動作になるのを防ぐため、特定のバージョンに実行を固定(ピン留め)するためです。
コスト構造
通常の Lambda 料金に加えて、以下の Durable 料金が発生します。(ap-northeast-1 の2025年12月時点の料金です)
- Durable Operations: $10.60 / 100 万回(step, wait 等)
- Data Written: $0.33 / GB(状態の保存)
- Data Retained: $0.20 / GB-month(履歴の保持)
Step Functions (Standard) の状態遷移課金と比較して、ステップ数が多い複雑なロジックでは Durable Functions の方がコスト効率が高くなる可能性があります。
ユースケース:作業申請承認ワークフロー
(申請 → 承認待ち → 承認されたら作業実行 → 通知)
サンプルコード (Python)
from aws_durable_execution_sdk_python import (
DurableContext,
durable_execution,
durable_step,
)
import json
# 1. 実際のビジネスロジックを step として定義
@durable_step
def validate_request(step_context, data):
print(f"Validating request: {data['request_id']}")
# 申請内容の正当性チェック
return True
@durable_step
def execute_task(step_context, data):
print(f"Executing task for: {data['request_id']}")
# ここで実際の作業(リソース作成や設定変更など)を行う
return {"status": "completed", "resource_id": "res-12345"}
@durable_step
def notify_user(step_context, message):
print(f"Notification sent: {message}")
# SNSやSES、Slackなどへの通知
return "sent"
# 2. メインの Durable 実行ロジック
@durable_execution
def handler(event, context: DurableContext):
"""
作業申請から承認、実行までを一元管理するハンドラ
"""
request_data = event.get("data")
# --- ステップ 1: 申請内容のチェック ---
is_valid = context.step(lambda: validate_request(request_data))
if not is_valid:
return {"status": "rejected", "reason": "Invalid request format"}
# --- ステップ 2: 外部の承認待ち (Wait for Callback) ---
# ここで実行は一時停止し、課金も止まります。
# callback_id を外部(Slackや管理画面)に送り、承認ボタンが押されるのを待ちます。
approval_result = context.wait_for_callback(
lambda callback_id: context.step(
lambda: notify_user(f"承認が必要です。ID: {callback_id}")
)
)
# 承認結果の判定
if approval_result.get("action") != "APPROVE":
context.step(lambda: notify_user("申請が却下されました。"))
return {"status": "terminated", "reason": "User rejected"}
# --- ステップ 3: 承認されたので作業実行 ---
# 前のステップが完了していれば、リプレイ時はここから再開されます。
task_result = context.step(lambda: execute_task(request_data))
# --- ステップ 4: 最終通知 ---
context.step(lambda: notify_user(f"作業が完了しました: {task_result['resource_id']}"))
return {
"status": "success",
"execution_id": context.execution_id
}このコードをベースに、Durable Functions を導入する際のメリットを以下のように提案します。
1. 「やり直し」に強い設計
従来の Lambda では、途中でタイムアウトやエラーが起きると最初からやり直し(決済の二重実行など)のリスクがありました。 このコードでは、context.step で囲まれた部分は自動的にチェックポイントが作成されます。リトライ時、完了済みのステップはスキップされ、保存された結果が使われるため、安全に再開できます。
2. インフラ定義(ASL)からの解放
Step Functions を使う場合、JSON(ASL)で「次はこの Lambda、失敗したらここへ」と記述する必要がありました。 Durable Functions なら、上記のように if 文による条件分岐やエラーハンドリングを使い慣れた Python で直接書けるため、ロジックの見通しが劇的に良くなります。
3. ステート管理コストの削減
「夜間ジョブの進捗を Parameter Store や DynamoDB に記録して、どこまで終わったか管理する」といった実装が不要になります。Lambda 自体が「状態(どこまで終わったか)」をログとして持っているため、開発者はビジネスロジックに集中できます。
外部サービスから再開する際の実装パターン
外部システムが AWS 認証を持っていない場合、API Gateway をエンドポイントとして「中継用 Lambda」を用意するのが一般的です。
- Durable Lambda:
callback_idを発行し、Slack や外部システムへ送付してwaitする。 - 外部システム: ユーザーの操作等を受け、
callback_idと 結果データ(Payload) を API Gateway へ POST。 - 中継 Lambda: AWS SDK を使用して、
callback_idと Payload を指定してcomplete_durable_taskAPI を実行。
まとめ:サーバーレス開発の新しい選択肢
AWS Lambda Durable Functions は、マイクロサービスの堅牢性とモノリスの開発体験を両立させる強力なツールです。
- 1 つの関数で長時間の処理が書ける
- 使い慣れた言語でリトライや待機が制御できる
- インフラ管理を意識せずステートフルなアプリが作れる
現時点では利用可能なリージョン(東京リージョン含む 15 リージョン)やランタイムに制限がありますが、今後のサーバーレスアーキテクチャのスタンダードの一つになることは間違いありません。