はじめに
Lambda Durable Functionsとは
主な機能
# 通常のLambda
def lambda_handler(event, context):
result = process_order(event)
return result
# Lambda Durable Functions
@durable_execution
def lambda_handler(event, context: DurableContext):
# ステップごとにチェックポイント作成
result = context.step(process_order(event))
return result
Step Functionsとの比較
| Lambda Durable Functions | Step Functions | |
|---|---|---|
| 記述方式 | Python/JS/TS | JSON(ASL) |
| 学習コスト | ✅低(普段の言語) | △中〜高 |
| 動的フロー | ✅実行時に分岐決定可能 | △事前定義が必要 |
| 可視化 | △実行履歴のみ | ✅ビジュアルエディタ |
| AWS統合 | Lambda経由 | ✅200以上のサービスと直接統合 |
| 最大実行時間 | 1年 | Standard1年、Express5分 |
| 成熟度 | △2025/12〜(新規) | ✅2016〜(実績多数) |
| リージョン | △US-East-2のみ(2025/12時点) | ✅全リージョン |
| 強み |
通常のif文で分岐できる
コードレビューしやすい、IDEの補完が効く
|
ビジュアルで全体像の把握が容易
非エンジニアにも説明しやすい
実績が豊富
|
Lambda Durable Functionsを選ぶべきケース
Step Functionsを選ぶべきケース
実装比較
@durable_execution
def lambda_handler(event, context: DurableContext):
# バリデーション
validated = context.step(validate(event))
# 金額による動的分岐(実行時に決定!)
if validated['amount'] > 100000:
# 高額注文は承認フロー
callback = context.create_callback("approval")
context.step(send_approval(callback.callback_id))
approval = callback.result() # 待機
if not approval['approved']:
return {"status": "rejected"}
# 処理実行
result = context.step(process(validated))
return result
{
"States": {
"Validate": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:validate",
"Next": "CheckAmount"
},
"CheckAmount": {
"Type": "Choice",
"Choices": [{
"Variable": "$.amount",
"NumericGreaterThan": 100000,
"Next": "SendApproval"
}],
"Default": "Process"
},
"SendApproval": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke.waitForTaskToken",
"Next": "Process"
},
"Process": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:process",
"End": true
}
}
}
実装してみた 注文処理ワークフロー
import json
import logging
import random
from datetime import datetime, timezone
from typing import Dict, Any
from aws_durable_execution_sdk_python import (
DurableContext,
StepContext,
durable_execution,
durable_step,
)
from aws_durable_execution_sdk_python.config import (
Duration,
StepConfig,
CallbackConfig,
)
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# 各処理を@durable_stepデコレータで修飾します
@durable_step
def validate_order(step_context: StepContext, order_data: Dict[str, Any]) -> Dict[str, Any]:
"""注文確認"""
step_context.logger.info(f"Validating order: {order_data.get('order_id')}")
required_fields = ['order_id', 'customer_id', 'items', 'total_amount']
for field in required_fields:
if field not in order_data:
raise ValueError(f"Missing required field: {field}")
if order_data['total_amount'] <= 0:
raise ValueError("Invalid total amount")
step_context.logger.info("Order validation passed")
return {
"order_id": order_data['order_id'],
"customer_id": order_data['customer_id'],
"total_amount": order_data['total_amount'],
"status": "validated",
"validated_at": datetime.now(timezone.utc).isoformat()
}
@durable_step
def check_inventory(step_context: StepContext, order_data: Dict[str, Any]) -> Dict[str, Any]:
"""在庫確認"""
step_context.logger.info(f"Checking inventory for order: {order_data['order_id']}")
items = order_data.get('items', [])
reservation_id = f"RSV-{order_data['order_id']}-{datetime.now(timezone.utc).timestamp()}"
step_context.logger.info(f"Inventory available. Reservation ID: {reservation_id}")
return {
"order_id": order_data['order_id'],
"reservation_id": reservation_id,
"items_reserved": len(items),
"status": "reserved"
}
@durable_step
def send_approval_request(
step_context: StepContext,
callback_id: str,
order_data: Dict[str, Any]
) -> Dict[str, Any]:
"""承認リクエストの送信"""
step_context.logger.info(f"Sending approval request for order: {order_data['order_id']}")
step_context.logger.info(f"Callback ID: {callback_id}")
step_context.logger.info("Approval request sent successfully")
return {
"order_id": order_data['order_id'],
"callback_id": callback_id,
"status": "awaiting_approval",
"requested_at": datetime.now(timezone.utc).isoformat()
}
@durable_step
def process_payment(step_context: StepContext, order_data: Dict[str, Any]) -> Dict[str, Any]:
"""決済処理"""
step_context.logger.info(f"Processing payment for order: {order_data['order_id']}")
# 不安定なAPI呼び出しを想定
if random.random() > 0.95:
step_context.logger.warning("Payment processing failed (simulated), will retry")
raise Exception("Payment gateway timeout")
transaction_id = f"TXN-{order_data['order_id']}-{datetime.now(timezone.utc).timestamp()}"
step_context.logger.info(f"Payment processed. Transaction ID: {transaction_id}")
return {
"order_id": order_data['order_id'],
"transaction_id": transaction_id,
"amount": order_data['total_amount'],
"status": "paid",
"paid_at": datetime.now(timezone.utc).isoformat()
}
@durable_step
def arrange_shipment(step_context: StepContext, order_data: Dict[str, Any]) -> Dict[str, Any]:
"""配送手配"""
step_context.logger.info(f"Arranging shipment for order: {order_data['order_id']}")
tracking_number = f"TRACK-{order_data['order_id']}-{datetime.now(timezone.utc).timestamp()}"
step_context.logger.info(f"Shipment arranged. Tracking: {tracking_number}")
return {
"order_id": order_data['order_id'],
"tracking_number": tracking_number,
"status": "shipped",
"shipped_at": datetime.now(timezone.utc).isoformat()
}
# メインワークフローを@durable_executionデコレータで修飾。長時間実行とコールバック機能が可能に
@durable_execution
def lambda_handler(event: Dict[str, Any], context: DurableContext) -> Dict[str, Any]:
"""注文処理のメインワークフロー"""
try:
context.logger.info("=== Order Processing Workflow Started ===")
context.logger.info(f"Event: {json.dumps(event)}")
order_data = event.get('order', {})
if not order_data:
raise ValueError("Missing 'order' in event")
# Step 1: 注文確認
validated_order = context.step(validate_order(order_data))
if validated_order["status"] != "validated":
raise Exception("Order validation failed")
context.logger.info(f"Order validated: {validated_order}")
# Step 2: 在庫確認
inventory_result = context.step(check_inventory(validated_order))
context.logger.info(f"Inventory reserved: {inventory_result}")
# Step 3: 高額注文の場合は承認待ち
# Lambda Durable Functionsの核心機能!外部イベント(承認)を待ちます
if validated_order["total_amount"] > 100000:
context.logger.info("High-value order detected, requesting approval...")
# コールバックトークンを作成
callback = context.create_callback(
name="order-approval",
config=CallbackConfig(timeout=Duration.from_hours(48))
)
context.logger.info(f"Callback created with ID: {callback.callback_id}")
# 承認依頼を送信
approval_request = context.step(
send_approval_request(callback.callback_id, validated_order)
)
context.logger.info(f"Approval request sent: {approval_request}")
# 最重要!この処理で実行が一時停止し、承認を待つ
# Lambdaは終了するが状態は保持され、外部から承認が来るまで最大48時間待機する
context.logger.info("Waiting for approval callback...")
approval_result = callback.result()
# 承認後、処理が再開
context.logger.info(f"Approval received: {approval_result}")
if not approval_result.get("approved", False):
context.logger.warning("Order was rejected")
return {
"status": "rejected",
"order_id": validated_order["order_id"],
"reason": approval_result.get("reason", "Not approved")
}
else:
context.logger.info("Standard order, no approval needed")
# Step 4: 決済処理
context.logger.info("Processing payment...")
payment_result = context.step(process_payment(validated_order))
if payment_result["status"] != "paid":
raise Exception("Payment processing failed after retries")
context.logger.info(f"Payment successful: {payment_result}")
# Step 5: 配送手配
shipment_result = context.step(arrange_shipment(validated_order))
context.logger.info(f"Shipment arranged: {shipment_result}")
# 完了
final_result = {
"status": "completed",
"order_id": validated_order["order_id"],
"transaction_id": payment_result["transaction_id"],
"tracking_number": shipment_result["tracking_number"],
"completed_at": datetime.now(timezone.utc).isoformat()
}
context.logger.info("=== Order Processing Workflow Completed ===")
context.logger.info(f"Final result: {json.dumps(final_result)}")
return final_result
except Exception as error:
context.logger.error(f"Workflow failed: {str(error)}")
return {
"status": "failed",
"error": str(error),
"order_id": event.get('order', {}).get('order_id', 'unknown'),
"failed_at": datetime.now(timezone.utc).isoformat()
}

# 少額注文
{
"order": {
"order_id": "ORD-2025-004",
"customer_id": "CUST-12345",
"items": [
{
"product_id": "PROD-A",
"quantity": 2,
"price": 5000
}
],
"total_amount": 25000,
"currency": "JPY"
}
}


# Lambda初期化
{
"time": "2025-12-12T09:47:40.432Z",
"type": "platform.initStart",
"record": {
"initializationType": "on-demand",
"phase": "init",
"runtimeVersion": "python:3.13.DurableFunction.v6",
"runtimeVersionArn": "arn:aws:lambda:us-east-2::...",
"functionName": "durable-order-processing",
"functionVersion": "$LATEST",
"instanceId": "2025/12/12/[$LATEST]1c19f9987529483484899cdb3e559f85",
"instanceMaxMemory": 536870912
}
}
# ワークフロー開始
{
"timestamp": "2025-12-12T09:47:41Z",
"level": "INFO",
"message": "=== Order Processing Workflow Started ===",
"logger": "root",
"requestId": "142df71e-3238-4cf7-bb88-ec6ff5eb0f63",
"executionArn": "arn:aws:lambda:us-east-2:************:function:durable-order-processing:..."
}
# 注文確認
{
"timestamp": "2025-12-12T09:47:41Z",
"level": "INFO",
"message": "Validating order: ORD-2025-004",
"logger": "root",
"requestId": "142df71e-3238-4cf7-bb88-ec6ff5eb0f63",
"executionArn": "arn:aws:lambda:us-east-2:************:function:durable-order-processing:..."
"operationName": "validate_order",
"attempt": 1,
"operationId": "1ced8f5be2db23a6513eba4d819c73806424748a7bc6fa0d792cc1c7d1775a97"
}
# 承認(少額注文なので承認がスキップされています)
{
"timestamp": "2025-12-12T09:47:42Z",
"level": "INFO",
"message": "Standard order, no approval needed",
"logger": "root",
"requestId": "142df71e-3238-4cf7-bb88-ec6ff5eb0f63",
"executionArn": "arn:aws:lambda:us-east-2:************:function:durable-order-processing:..."
}
# 完了
{
"timestamp": "2025-12-12T09:47:42Z",
"level": "INFO",
"message": "=== Order Processing Workflow Completed ===",
"logger": "root",
"requestId": "142df71e-3238-4cf7-bb88-ec6ff5eb0f63",
"executionArn": "arn:aws:lambda:us-east-2:************:function:durable-order-processing:..."
}
# レポート(課金対象の実行時間が2.338秒であることが確認できる)
{
"time": "2025-12-12T09:47:42.774Z",
"type": "platform.report",
"record": {
"requestId": "142df71e-3238-4cf7-bb88-ec6ff5eb0f63",
"metrics": {
"durationMs": 1447.982,
"billedDurationMs": 2338,
"memorySizeMB": 512,
"maxMemoryUsedMB": 89,
"initDurationMs": 889.95
},
"status": "success"
}
}
# 高額注文
{
"order": {
"order_id": "ORD-2025-006",
"customer_id": "CUST-67890",
"items": [
{
"product_id": "PROD-PREMIUM",
"quantity": 1,
"price": 150000
}
],
"total_amount": 150000,
"currency": "JPY"
}
}


$ Lambda実行開始
{
"timestamp": "2025-12-12T09:53:58Z",
"level": "INFO",
"message": "=== Order Processing Workflow Started ===",
"logger": "root",
"requestId": "e8dac2cd-7b94-45ac-8e73-2882e3243387",
"executionArn": "arn:aws:lambda:us-east-2:************:function:durable-order-processing:..."
}
# 高額注文検出!注文金額が¥100,000を超えたため、承認待ちへ
{
"timestamp": "2025-12-12T09:53:58Z",
"level": "INFO",
"message": "High-value order detected, requesting approval...",
"logger": "root",
"requestId": "e8dac2cd-7b94-45ac-8e73-2882e3243387",
"executionArn": "arn:aws:lambda:us-east-2:************:function:durable-order-processing:..."
}
# コールバックID作成
{
"timestamp": "2025-12-12T09:53:58Z",
"level": "INFO",
"message": "Callback created with ID: Ab9h...kM/8",
"logger": "root",
"requestId": "e8dac2cd-7b94-45ac-8e73-2882e3243387",
"executionArn": "arn:aws:lambda:us-east-2:************:function:durable-order-processing:..."
}
そしてLambda Durable Functionsの核心でもあるWait機能のログです。ワークフローは CALLBACK_STARTED状態で保存され、承認を待ちます。
# Wait状態へ
{
"timestamp": "2025-12-12T09:53:59Z",
"level": "INFO",
"message": "Waiting for approval callback...",
"logger": "root",
"requestId": "e8dac2cd-7b94-45ac-8e73-2882e3243387",
"executionArn": "arn:aws:lambda:us-east-2:************:function:durable-order-processing:..."
}
# Lambdaの実行はここで一度終了されます。
{
"time": "2025-12-12T09:53:59.164Z",
"type": "platform.report",
"record": {
"requestId": "e8dac2cd-7b94-45ac-8e73-2882e3243387",
"metrics": {
"durationMs": 1003.52,
"billedDurationMs": 1004,
"memorySizeMB": 512,
"maxMemoryUsedMB": 91
},
"status": "success"
}
}

ログを確認すると、Lambdaが再び実行されていたのが確認できます。Wait状態から再開時、初期化及び新しいrequestIdが割り当てられ、別の呼び出しとして処理されます。重要なのは、Wait中にLambda実行が終了するため、個々のLambda実行は15分制限内に収まるが、ワークフロー全体としては15分を大幅に超える実行期間が実現されています。また、instanceIdが変わっていることから仮想的な初期化ではなくLambda実行環境も変わっていることが確認できました。
# # Lambda初期化開始
{
"time": "2025-12-12T10:08:44.725Z",
"type": "platform.initStart",
"record": {
"initializationType": "on-demand",
"phase": "init",
"runtimeVersion": "python:3.13.DurableFunction.v6",
"runtimeVersionArn": "arn:aws:lambda:us-east-2::runtime:d4fbf...",
"functionName": "durable-order-processing",
"functionVersion": "$LATEST",
"instanceId": "2025/12/12/[$LATEST]1c6a3a1dc9f949ef82e680da103e9eea",
"instanceMaxMemory": 536870912
}
}
# requestIdが変わっているのを確認できます
{
"time": "2025-12-12T10:08:45.580Z",
"type": "platform.start",
"record": {
"requestId": "823e5765-e3dc-42e8-b1d5-ff2a4b2ce5da",
"functionArn": "arn:aws:lambda:us-east-2:************:...
"version": "$LATEST"
}
}
承認を受信したことでcallback.resultが値を返し、Approval receivedが出力され、Waitが終了したことが確認できました。
# 承認データ受信
{
"timestamp": "2025-12-12T10:08:45Z",
"level": "INFO",
"message": "Approval received: {\"approved\": true}",
"logger": "root",
"requestId": "823e5765-e3dc-42e8-b1d5-ff2a4b2ce5da",
"executionArn": "arn:aws:lambda:us-east-2:************:function:durable-order-processing:..."
}
# 実行レポート
{
"time": "2025-12-12T10:08:46.098Z",
"type": "platform.report",
"record": {
"requestId": "823e5765-e3dc-42e8-b1d5-ff2a4b2ce5da",
"metrics": {
"durationMs": 518.101,
"billedDurationMs": 1370,
"memorySizeMB": 512,
"maxMemoryUsedMB": 87,
"initDurationMs": 851.443
},
"status": "success"
}
}
料金について(2025年12月 時点)
料金に関して詳細な説明がされているサイトがあまりありませんでしたので、詳しく調べてみました。情報は現時点のものであり、ご利用の際にはAWS公式サイト*1の確認をお願いします。また下記はx86_64 アーキテクチャを使用した場合であり、arm64アーキテクチャを使用した場合、料金が異なります。
・Lambda基本料金(US-East-2)
| 料金項目 | 単価 | 無料枠 |
|---|---|---|
| リクエスト料金(最初の60億GB秒/月) | $0.20/100万リクエスト | 100万リクエスト/月 *2 |
| 実行時間料金(最初の60億GB秒/月) | $0.0000166667/GB秒 | 40万GB秒/月 |
また別途実行時間料金がかかります。実行時間料金は、Lambda関数に割り当てるメモリ量によって異なり、デフォルトの128MBだと最初の60億GB秒/月までは1ミリ秒あたり0.0000000021ドルとなっています。
・Lambda Durable Functions 追加料金(US-East-2)
| 料金項目 | 単価 |
|---|---|
| Durable Operations(ステップ、待機、コールバックなど) | $8/100万オペレーション |
| データ書き込み | $0.25/GB |
| データ保持 | 1か月あたり$0.15/GB |
ここで注意したいのがWait中、Lambda実行料金はかからないがデータ保持料金はかかるという点です。 個人で使用する分には正直無視してよい金額ですが大規模システムだと、ある程度かかってくるようです。 AWS公式サイト*1に利用ケース例が載っていますので、詳細はそちらをチャックしてみてください。