AWS Step Functionsを利用してサーバーレスアプリケーションでのエラーに対処する

2021.06.18

こんにちは、yagiです。

前回に引き続き、Step Functions についての記載となります。

前回の最後で、Step Functions でエラーとなった場合は該当箇所を選択して詳細を確認できることに触れましたが、 今回は、その特徴を生かして、Step Functionsを利用してサーバーレスアプリケーションでのエラーに対処することで、 エラー処理含むワークフロー上のロジックと、ビジネスロジックを分離する例について、チュートリアルのサンプルを参考にしながら実行してみたいと思います。

今回は以下のAWS公式チュートリアルを参考に実施します。 サーバーレスアプリケーションでエラーに対処する AWS Step Functions と AWS Lambda を使用した場合

概要

サーバーレスアプリケーションでのエラーでは、模擬的にLambdaを使用します。以下(抜粋)の公式にも記載のある通り、ビジネスロジックと関係のないエラー処理を簡素化するため、 AWS Step Functions を使用して、このようなエラーを適切に処理するサーバーレスワークフローを設計し、実行します。

Lambda 関数は、処理されない例外が発生した場合、指定したタイムアウトよりも長い時間実行された場合、メモリ不足が発生した場合などに失敗することがあります。API スロットリングまたはソケットのタイムアウトなどの状況に対処するため、Lambda 関数 1 つずつにエラー処理ロジックを記述して管理すると、作業に時間がかかり、特に分散アプリケーションでは作業が複雑になる可能性があります。このコードを各 Lambda 関数に埋め込むと関数間に依存関係が生まれます。変更に応じてこのすべての関係を管理することは難しい可能性があります。

実装

API に模擬的にコールを行う Lambda 関数を作成します。 用意されたコードは以下となります。

class TooManyRequestsException(Exception): pass
class ServerUnavailableException(Exception): pass
class UnknownException(Exception): pass

def lambda_handler(event, context):
    statuscode = event["statuscode"]    
    if statuscode == "429":
        raise TooManyRequestsException('429 Too Many Requests')
    elif statuscode == "503":
        raise ServerUnavailableException('503 Server Unavailable')
    elif statuscode == "200":
        return '200 OK'
    else:
        raise UnknownException('Unknown error')

Step Functions から特定のリソースを参照する必要がある場合は、ARN が必要となるため、前回と同様 ARN を控えておきます。

Step Functions にリソースの実行権限を付与するIAMロールを作成します。今回は Lambda の実行となるため、AWSLambdaRole をアタッチします。

Step Functions を作成します。Resource を先ほど控えた Lambda の ARN で置換します。

定義のコードは以下となります。

{
  "Comment": "An example of using retry and catch to handle API responses",
  "StartAt": "Call API",
  "States": {
    "Call API": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME",
      "Next" : "OK",
      "Comment": "Catch a 429 (Too many requests) API exception, and resubmit the failed request in a rate-limiting fashion.",
      "Retry" : [ {
        "ErrorEquals": [ "TooManyRequestsException" ],
        "IntervalSeconds": 1,
        "MaxAttempts": 2
      } ],
      "Catch": [ 
        {
          "ErrorEquals": ["TooManyRequestsException"],
          "Next": "Wait and Try Later"
        }, {
          "ErrorEquals": ["ServerUnavailableException"],
          "Next": "Server Unavailable"
        }, {
          "ErrorEquals": ["States.ALL"],
          "Next": "Catch All"
        }
      ]
    },
    "Wait and Try Later": {
      "Type": "Wait",
      "Seconds" : 1,
      "Next" : "Change to 200"
    },
    "Server Unavailable": {
      "Type": "Fail",
      "Error":"ServerUnavailable",
      "Cause": "The server is currently unable to handle the request."
    },
    "Catch All": {
      "Type": "Fail",
      "Cause": "Unknown error!",
      "Error": "An error of unknown type occurred"
    },
    "Change to 200": {
      "Type": "Pass",
      "Result": {"statuscode" :"200"} ,
      "Next": "Call API"
    },
    "OK": {
      "Type": "Pass",
      "Result": "The request has succeeded.",
      "End": true
    }
  }
}

ワークフローの図と併せて確認すると分かりやすいのですが、今回は、エラーコードによって

  • Wait and Try Later
  • Server Unavailable
  • Catch All
  • OK

のいずれかに結果が振り分けられ、自動で処理がされるようなワークフローとなっています。

動作確認

エラーコードを指定してステートマシンを呼び出し、模擬APIを呼び出します。

まずは"statuscode": "200"で実行します。

Call API と OK のそれぞれでステップの入力と出力を確認します。

次に"statuscode": "503"で実行します。

イベント履歴より、Step Functions によって取得した入力がCall API に渡され、Lambda関数に渡され、503 Server Unavailable がCall API に渡され、最終的に、ExecutionFailedとなるところまでを追跡することができます。

"statuscode": "429"で実行します。

想定通り、Wait and Try Later でChange to 200 に変換して最終的に成功しています。

イベント履歴より、Lambda の呼び出しが連続して失敗した後、Wait and Try Later に入ることがわかります。

最後に"statuscode": "999"で実行します。

こちらも想定どおり、UnknownException がCall API に渡され、最終的にExecutionFailedとなるところまでをイベント履歴から追跡できます。

結論

AWS Step Functionsを利用してサーバーレスアプリケーションでのエラーに対処するステートマシンを作成してみました。 ビジネスロジックには関係のないエラー処理を分離することで、作業の簡素化が期待できるのではないかと思います。