[アップデート] AWS Step Functionsのステートマシン実行失敗時点からの再実行をサポートしました

Redriveで対応できない場合に備えて冪等性のあるワークフローを組んだり、リトライ処理を組み込むのは引き続き重要
2023.11.16

ステートマシンの途中から再実行したい

こんにちは、のんピ(@non____97)です。

皆さんはAWS Step Functionsのステートマシンの実行が失敗した時に、失敗したステートから再実行したいなと思ったことはありますか? 私はあります。

今までステートマシンが途中で終了した際に再実行する場合、ワークフローの最初から実行する必要がありました。

ステートマシンが実行失敗した際にワークフローの最初から実行する場合、正常に実行完了していた処理をもう一度実行することとなってしまいます。そのため、ワークフローは最初から実行し直すことを考慮して設計する必要がありました。

要するに冪等性が求められています。

ステートマシンにおいて冪等性を持たせたい場合は以下のような方法があります。

  1. 再実行時に実行済みのステートをスキップする
  2. エラーが発生した際に、途中の変更内容を削除して、ワークフローの開始時の状態にロールバックさせる

サーバーレスにおける冪等性の実装はbuilders.flashの以下シリーズが非常に参考になります。

冪等性を持たせるのは中々大変です。JP1やSystemwalkerなどのジョブ管理システムを使われたことがある方は、「ジョブネットみたいに途中から動かしたい...」ともどかしい思いをしたことがあるのではないでしょうか。

再実行時のスコープを狭くするため、ステートマシンを細かく分割された方もいるかと思います。

今回、アップデートでAWS Step Functionsがステートマシン実行失敗時点からの再実行をサポートしました。

AWS Blogsでも紹介されていますね。ステートマシン実行失敗時点からの再実行をRedriveと呼ぶようです。

AWS公式ドキュメントも充実していますね。

これにより、頑張って冪等性のあるワークフローを組む場面が大幅に減りそうです。また、ステートマシンの課金要素には状態遷移の数があるため、再実行時のコストを削減することにも繋がります。

実際に触ってみて、使い勝手を確認してみました。

いきなりまとめ

  • ステートマシン実行失敗時点からの再実行はRedriveと呼ぶ
  • APIはRedriveExecution
  • Redriveで実行するステートは選択できない
    • 必ず失敗したステートから実行することとなる
    • そのため、成功した実行のRedriveはできない
  • Redrive時にステートマシンのInputを変更することはできない
  • ワークフローを編集して、編集したワークフローでRedriveすることもできない
  • EventBridgeに発行されるStep Functions Execution Status Changeのスキーマが変更された
    • Redriveの回数や状態などが追加
  • Redriveで対応できない場合に備えて冪等性のあるワークフローを組んだり、リトライ処理を組み込むのは引き続き重要

やってみる

Redrive時にステートマシンのInputやワークフローの定義を変更することはできない件

以下のようにステートマシンのInputで渡された名前のLambda関数を呼び出すステートマシンを用意しました。

Lambda関数を呼び出すワークフロー

こちらのステートマシンを実行します。ステートマシンのInputにはあえてLambda関数の名前を指定しないでおきます。

ステートマシンの実行

ステートマシンの実行に失敗しました。呼び出すべきLambda関数の指定がないので当然です。

ステートマシンの実行が失敗したことを確認

ステートマシンをRedriveするためにRecover-Redrive from faillureをクリックします。

Redrive

Redriveの確認画面は以下のとおりです。

Redriveの実行

Inputを変更しようと思っていましたが、どうやらできないようです。

専用のAPIとしてRedriveExecutionが生えていました。

こちらのAPIを確認すると、Redriveでは失敗時と同じInputやステートマシンの定義、実行ARNが使用されると記載されていました。

Restarts unsuccessful executions of Standard workflows that didn't complete successfully in the last 14 days. These include failed, aborted, or timed out executions. When you redrive an execution, it continues the failed execution from the unsuccessful step and uses the same input. Step Functions preserves the results and execution history of the successful steps, and doesn't rerun these steps when you redrive an execution. Redriven executions use the same state machine definition and execution ARN as the original execution attempt.

RedriveExecution - AWS Step Functions

要するに、ステートマシンのInputが原因でステートマシンが失敗した場合のRedriveは解決策になり得ません。また、失敗箇所を修正するようにワークフローを編集しても、編集したワークフローでRedriveすることもできません。

今回の場合、こうなってしまうとどうしようもありません。ステートマシンのInputを変更するために最初から実行することになります。

試しにRedriveしてみます。

Redriveの実行結果

はい、失敗に失敗を重ねただけです。ただし、失敗したLambda関数の呼び出しから実行していることは確認できました。

Redriveの成功パターンの確認

気を取り直してRedriveの成功パターンを確認します。

ステートマシンのIAMロールにアタッチされているIAMポリシーにて、以下のようにLambda関数の呼び出しができないようにしてあげます。

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Deny",
			"Action": [
				"lambda:InvokeFunction"
			],
			"Resource": [
				"arn:aws:lambda:us-east-1:<AWSアカウントID>:function:*"
			]
		}
	]
}

この状態でステートマシンを実行します。

権限不足によりステートマシンの実行が失敗

権限不足により失敗していますね。

IAMポリシーを修正して、Lambda関数を呼び出せるようにします。

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Allow",
			"Action": [
				"lambda:InvokeFunction"
			],
			"Resource": [
				"arn:aws:lambda:us-east-1:<AWSアカウントID>:function:*"
			]
		}
	]
}

この状態でRedriveします。

Redriveの実行

すると、今度は正常に実行が完了しました。Redrive最高です。

Redrive成功

EventBridgeに発行されるイベントの確認

Redriveの機能が追加されたにあたってEventBridgeに発行されるイベントスキーマが変更されています。

ステートマシン実行失敗時のStep Functions Execution Status Changeを確認します。

{
    "version": "0",
    "id": "10887a14-cee9-1902-2b23-2cb76b7a2f0f",
    "detail-type": "Step Functions Execution Status Change",
    "source": "aws.states",
    "account": "<AWSアカウントID>",
    "time": "2023-11-16T06:38:03Z",
    "region": "us-east-1",
    "resources": [
        "arn:aws:states:us-east-1:<AWSアカウントID>:execution:MyStateMachine-gddoyum7f:0e3a7824-7ec2-4ced-9aef-61f71fbef1b5"
    ],
    "detail": {
        "executionArn": "arn:aws:states:us-east-1:<AWSアカウントID>:execution:MyStateMachine-gddoyum7f:0e3a7824-7ec2-4ced-9aef-61f71fbef1b5",
        "stateMachineArn": "arn:aws:states:us-east-1:<AWSアカウントID>:stateMachine:MyStateMachine-gddoyum7f",
        "name": "0e3a7824-7ec2-4ced-9aef-61f71fbef1b5",
        "status": "FAILED",
        "startDate": 1700116674994,
        "stopDate": 1700116682923,
        "input": "{\n  \"FunctionName\": \"test\"\n}",
        "output": null,
        "stateMachineVersionArn": null,
        "stateMachineAliasArn": null,
        "redriveCount": 0,
        "redriveDate": null,
        "redriveStatus": "REDRIVABLE",
        "redriveStatusReason": null,
        "inputDetails": {
            "included": true
        },
        "outputDetails": null,
        "error": "Lambda.AWSLambdaException",
        "cause": "User: arn:aws:sts::<AWSアカウントID>:assumed-role/StepFunctions-MyStateMachine-gddoyum7f-role-4e4co5vgy/peFCootmIyFjdbshoxqJHoBZMjcgLDCv is not authorized to perform: lambda:InvokeFunction on resource: arn:aws:lambda:us-east-1:<AWSアカウントID>:function:test with an explicit deny in an identity-based policy (Service: AWSLambda; Status Code: 403; Error Code: AccessDeniedException; Request ID: f14c8058-6987-4603-a9a2-2dc036844257; Proxy: null)"
    }
}

以下のようにRedrive関連の4つのフィールドが追加されていることが確認できます。

  • redriveCount : Redriveの回数
  • redriveDate : Redriveした時間
  • redriveStatus : Redriveが可能かどうかのステータス
  • redriveStatusReason : Redriveができない場合の原因

redriveStatusredriveStatusReasonの値とその説明はDescribeExecutionのAPI Referenceに記載ありました。

redriveStatus

Indicates whether or not an execution can be redriven at a given point in time.

  • For executions of type STANDARD, redriveStatus is NOT_REDRIVABLE if calling the RedriveExecution API action would return the ExecutionNotRedrivable error.
  • For a Distributed Map that includes child workflows of type STANDARD, redriveStatus indicates whether or not the Map Run can redrive child workflow executions.
  • For a Distributed Map that includes child workflows of type EXPRESS, redriveStatus indicates whether or not the Map Run can redrive child workflow executions.

You can redrive failed or timed out EXPRESS workflows only if they're a part of a Map Run. When you redrive the Map Run, these workflows are restarted using the StartExecution API action.

Type: String

Valid Values: REDRIVABLE | NOT_REDRIVABLE | REDRIVABLE_BY_MAP_RUN

redriveStatusReason

When redriveStatus is NOT_REDRIVABLE, redriveStatusReason specifies the reason why an execution cannot be redriven.

For executions of type STANDARD, or for a Distributed Map that includes child workflows of type STANDARD, redriveStatusReason can include one of the following reasons:

State machine is in DELETING status.

  • Execution is RUNNING and cannot be redriven.
  • Execution is SUCCEEDED and cannot be redriven.
  • Execution was started before the launch of RedriveExecution.
  • Execution history event limit exceeded.
  • Execution has exceeded the max execution time.
  • Execution redrivable period exceeded.

For a Distributed Map that includes child workflows of type EXPRESS, redriveStatusReason is only returned if the child workflows are not redrivable. This happens when the child workflow executions have completed successfully.

Type: String

Length Constraints: Maximum length of 262144.

DescribeExecution - AWS Step Functions

Redriveしたことによるイベントを眺めてみます。

Redrive開始

{
    "version": "0",
    "id": "f5d42225-8653-04b9-c835-53923f3949d6",
    "detail-type": "Step Functions Execution Status Change",
    "source": "aws.states",
    "account": "<AWSアカウントID>",
    "time": "2023-11-16T06:39:04Z",
    "region": "us-east-1",
    "resources": [
        "arn:aws:states:us-east-1:<AWSアカウントID>:execution:MyStateMachine-gddoyum7f:0e3a7824-7ec2-4ced-9aef-61f71fbef1b5"
    ],
    "detail": {
        "executionArn": "arn:aws:states:us-east-1:<AWSアカウントID>:execution:MyStateMachine-gddoyum7f:0e3a7824-7ec2-4ced-9aef-61f71fbef1b5",
        "stateMachineArn": "arn:aws:states:us-east-1:<AWSアカウントID>:stateMachine:MyStateMachine-gddoyum7f",
        "name": "0e3a7824-7ec2-4ced-9aef-61f71fbef1b5",
        "status": "RUNNING",
        "startDate": 1700116674994,
        "stopDate": null,
        "input": "{\n  \"FunctionName\": \"test\"\n}",
        "output": null,
        "stateMachineVersionArn": null,
        "stateMachineAliasArn": null,
        "redriveCount": 1,
        "redriveDate": 1700116743721,
        "redriveStatus": "NOT_REDRIVABLE",
        "redriveStatusReason": "Execution is RUNNING and cannot be redriven",
        "inputDetails": {
            "included": true
        },
        "outputDetails": null,
        "error": null,
        "cause": null
    }
}

Redrive失敗

{
    "version": "0",
    "id": "a1625904-11f7-900d-6c34-18375fcff8d2",
    "detail-type": "Step Functions Execution Status Change",
    "source": "aws.states",
    "account": "<AWSアカウントID>",
    "time": "2023-11-16T06:39:11Z",
    "region": "us-east-1",
    "resources": [
        "arn:aws:states:us-east-1:<AWSアカウントID>:execution:MyStateMachine-gddoyum7f:0e3a7824-7ec2-4ced-9aef-61f71fbef1b5"
    ],
    "detail": {
        "executionArn": "arn:aws:states:us-east-1:<AWSアカウントID>:execution:MyStateMachine-gddoyum7f:0e3a7824-7ec2-4ced-9aef-61f71fbef1b5",
        "stateMachineArn": "arn:aws:states:us-east-1:<AWSアカウントID>:stateMachine:MyStateMachine-gddoyum7f",
        "name": "0e3a7824-7ec2-4ced-9aef-61f71fbef1b5",
        "status": "FAILED",
        "startDate": 1700116674994,
        "stopDate": 1700116751634,
        "input": "{\n  \"FunctionName\": \"test\"\n}",
        "output": null,
        "stateMachineVersionArn": null,
        "stateMachineAliasArn": null,
        "redriveCount": 1,
        "redriveDate": 1700116743721,
        "redriveStatus": "REDRIVABLE",
        "redriveStatusReason": null,
        "inputDetails": {
            "included": true
        },
        "outputDetails": null,
        "error": "Lambda.AWSLambdaException",
        "cause": "User: arn:aws:sts::<AWSアカウントID>:assumed-role/StepFunctions-MyStateMachine-gddoyum7f-role-4e4co5vgy/JRgakUQNuRkmbVGLfzMzphFVJCiqkSjU is not authorized to perform: lambda:InvokeFunction on resource: arn:aws:lambda:us-east-1:<AWSアカウントID>:function:test with an explicit deny in an identity-based policy (Service: AWSLambda; Status Code: 403; Error Code: AccessDeniedException; Request ID: 81212526-0091-4829-8326-68a35f30577c; Proxy: null)"
    }
}

Redrive失敗(2回目)

{
    "version": "0",
    "id": "b3a4d59a-31ac-4e6c-e1f3-4abe0bba7879",
    "detail-type": "Step Functions Execution Status Change",
    "source": "aws.states",
    "account": "<AWSアカウントID>",
    "time": "2023-11-16T06:40:56Z",
    "region": "us-east-1",
    "resources": [
        "arn:aws:states:us-east-1:<AWSアカウントID>:execution:MyStateMachine-gddoyum7f:0e3a7824-7ec2-4ced-9aef-61f71fbef1b5"
    ],
    "detail": {
        "executionArn": "arn:aws:states:us-east-1:<AWSアカウントID>:execution:MyStateMachine-gddoyum7f:0e3a7824-7ec2-4ced-9aef-61f71fbef1b5",
        "stateMachineArn": "arn:aws:states:us-east-1:<AWSアカウントID>:stateMachine:MyStateMachine-gddoyum7f",
        "name": "0e3a7824-7ec2-4ced-9aef-61f71fbef1b5",
        "status": "FAILED",
        "startDate": 1700116674994,
        "stopDate": 1700116856509,
        "input": "{\n  \"FunctionName\": \"test\"\n}",
        "output": null,
        "stateMachineVersionArn": null,
        "stateMachineAliasArn": null,
        "redriveCount": 2,
        "redriveDate": 1700116848681,
        "redriveStatus": "REDRIVABLE",
        "redriveStatusReason": null,
        "inputDetails": {
            "included": true
        },
        "outputDetails": null,
        "error": "Lambda.AWSLambdaException",
        "cause": "User: arn:aws:sts::<AWSアカウントID>:assumed-role/StepFunctions-MyStateMachine-gddoyum7f-role-4e4co5vgy/lMgkdCqJMGkRfLZnfYQVlhhCKODCNaDD is not authorized to perform: lambda:InvokeFunction on resource: arn:aws:lambda:us-east-1:<AWSアカウントID>:function:test with an explicit deny in an identity-based policy (Service: AWSLambda; Status Code: 403; Error Code: AccessDeniedException; Request ID: 487bf582-66f7-4f63-85fe-554a5e19822f; Proxy: null)"
    }
}

Redrive成功

{
    "version": "0",
    "id": "2751ad77-efac-351f-1c2f-88f95f7db560",
    "detail-type": "Step Functions Execution Status Change",
    "source": "aws.states",
    "account": "<AWSアカウントID>",
    "time": "2023-11-16T06:42:29Z",
    "region": "us-east-1",
    "resources": [
        "arn:aws:states:us-east-1:<AWSアカウントID>:execution:MyStateMachine-gddoyum7f:0e3a7824-7ec2-4ced-9aef-61f71fbef1b5"
    ],
    "detail": {
        "executionArn": "arn:aws:states:us-east-1:<AWSアカウントID>:execution:MyStateMachine-gddoyum7f:0e3a7824-7ec2-4ced-9aef-61f71fbef1b5",
        "stateMachineArn": "arn:aws:states:us-east-1:<AWSアカウントID>:stateMachine:MyStateMachine-gddoyum7f",
        "name": "0e3a7824-7ec2-4ced-9aef-61f71fbef1b5",
        "status": "SUCCEEDED",
        "startDate": 1700116674994,
        "stopDate": 1700116949150,
        "input": "{\n  \"FunctionName\": \"test\"\n}",
        "output": "{\"statusCode\":200,\"body\":\"\\\"Hello from Lambda!\\\"\"}",
        "stateMachineVersionArn": null,
        "stateMachineAliasArn": null,
        "redriveCount": 3,
        "redriveDate": 1700116948644,
        "redriveStatus": "NOT_REDRIVABLE",
        "redriveStatusReason": "Execution is SUCCEEDED and cannot be redriven",
        "inputDetails": {
            "included": true
        },
        "outputDetails": {
            "included": true
        },
        "error": null,
        "cause": null
    }
}

redriveCountがカウントされていますね。

「特定の回数までは自動でRedriveを繰り返す」みたいなEventBridge Ruleも組めそうです。

冪等性のあるワークフローを組んだり、リトライ処理を組み込むのは引き続き重要

AWS Step Functionsがステートマシン実行失敗時点からの再実行をサポートしたアップデートを紹介しました。

Redriveを用いることで、リトライ処理では対応しきれない長期間の外的要因による中断時の再実行がかなりしやすくなりました。

また、ワークフローの最初から実行する必要がなくなったため、トラブルシューティングの時間も短くなりそうですね。

なお、紹介しているとおり、Redriveで対応できない場面もあります。そのような場面に備えて、冪等性のあるワークフローを組んだり、リトライ処理を組み込むのは引き続き重要だと考えます。

この記事が誰かの助けになれば幸いです。

以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!