サーバーレスアプリケーション開発におけるエラーハンドリング

~  イベント駆動のデータ加工、連携処理パターン ~

2023-08-02
デベロッパーのためのクラウド活用方法

Author : 大磯 直人

今回はイベント駆動のデータ加工、連携処理パターンにおけるサーバーレスのエラーハンドリングを AWS で実現する際に、抑えておくべきポイントについて紹介します。イベントトリガーの連携実行パターンでは、メッセージの退避 / 破棄を利用したエラーハンドリングをご説明していきます。

オープニング記事 でもサーバーレスエラーハンドリングの基礎と絡めてご説明しておりますので、まだご覧になられていない方は、そちらからご覧頂けると理解が深まります !

この連載記事のその他の記事はこちら

選択
  • 選択
  • 第 1 回 オープニング
  • 第 2 回 Web API パターン
  • 第 3 回 イベント駆動のデータ加工、連携処理パターン
  • 第 4 回 マイクロバッチ・ストリーミングパターン
  • 第 5 回 ワークフローパターン 前編
  • 第 6 回 ワークフローパターン 後編

ユースケース

イベントトリガーでサーバーレスのアプリケーションを実行させるパターンも、非常に多くのアーキテクチャで利用されているパターンになります。ファイルがアップロードされたことをトリガーに、アップロードされた画像ファイルのリサイズを行ったり、外部アプリケーションの状態変化のイベントを受け取り、後続の処理を開始するなどの要件は、様々なシステムで見受けられるかと思います。どちらのユースケースも、発生したイベントを受け取った時のみ動的にリソースが必要とされ、イベントが無いときにはリソースを開放しコストを削減したいはずであり、まさにサーバーレスと相性の良いユースケースになります。

AWS がまとめているサーバーレスパターン集である「形で考えるサーバーレスパターン」における代表的な適用シーン/ユースケースと実装形の中で、 イベント駆動によるデータ加工、連携処理としてのサーバーレスアプリケーションは、以下のパターンに適応できます。

  • 画像処理、シンプルなデータ加工
  • 大量データ処理の並列分散化
  • イベント駆動の業務処理連携
  • サービス / SaaS 連携、パイプ処理

パターン特性

イベント駆動のデータ加工、連携処理パターンのサーバーレスアプリケーションの特性として、イベントの生成をトリガーとした、Push 形式の非同期呼び出しの性質を持ちます。

前提の用語の説明になりますが、イベント駆動の文脈における登場概念として、イベントメッセージの生成を行うプロデューサーと、メッセージの処理を行うコンシューマーが存在します。そして、プロデューサーとコンシューマーの間には、イベントメッセージを受け取り、適切なコンシューマーにプッシュするイベントルーターがいます。

イベントルーターによって、イベントはほぼリアルタイムで配信され、イベントが発生するとコンシューマーはすぐに処理を実行できます。 プロデューサーはコンシューマーから分離されており、疎結合な状態を実現します。

このような前提を踏まえて、Push 形式の非同期呼び出しの性質をもつ、イベント駆動のデータ加工、連携処理パターンのサーバーレスアプリケーションは、Web API パターンの記事のケースと同様に、メッセージを送る責任がクライアント (本ケースではコンシューマー) にあるため、サーバーサイドにあるサーバーレスアプリケーションは純粋な処理ロジックのみに注力できます。

一方、Web API パターンと異なる点として、クライアントであるプロデューサーへ、コンシューマーの処理結果を応答として返すことはしません。このため、プロデューサーの責任はあくまでメッセージを送るところまでであり、その後のコンシューマーの処理の成否に応じてリトライをかけるといったエラーハンドリングはイベントルーター側の役割になります。


AWS サービスとアーキテクチャ

イベント駆動のデータ加工において頻出するパターンとして、ファイルのアップロードをトリガーに、アプリケーションを起動させるユースケースがあります。このユースケースを実現する際に AWS では、ファイルのアップロード先として Amazon S3、アプリケーションの実行環境として AWS Lambda が利用されます。

S3 は AWS のオブジェクトストレージのサービスです。S3 にオブジェクトが保存されたことをトリガーに、Lambda が起動することが出来ます。AWS のファイル処理の際の王道パターンで、「形で考えるサーバーレスパターン」 における代表的な適用シーン/ユースケースのうち、「画像処理、シンプルなデータ加工」 で記載されているアーキテクチャで利用されます。

任意のプロデューサーをイベントソースとした連携処理を AWS 上で構築するためには、イベントルーターの役割を果たすサービスが活用されます。イベントルーターの役割を果たす AWS のサービスとしては、Amazon SNS、Amazon EventBridge などが挙げられます。

Amazon SNS はメッセージ配信を提供するサービスです。SNS のトピックに送られたメッセージをサブスクライバーに配信して、Lambda を起動します。ファンアウトと呼ばれる、一つのイベントを複製し、複数の処理のトリガーを発火させる際に利用され、「形で考えるサーバーレスパターン 」における代表的な適用シーン/ユースケースのうち、「イベント駆動の業務処理連携」 で記載されているアーキテクチャで利用されます。

EventBridge は予めセットされたスケジュールに合わせてイベントを生成したり、発生したイベントを用いてコンシューマを呼び出すサービスです。

イベントソースとして、アプリケーションや SaaS から発行されるイベントや、AWS サービスが発行するイベントなどがあり、「形で考えるサーバーレスパターン」における代表的な適用シーン/ユースケースのうち、「サービス / SaaS 連携、パイプ処理」 で記載されているアーキテクチャで利用されます。


エラーハンドリング

イベントトリガーの連携実行パターンのサーバーレスアプリケーションのエラーハンドリング

イベントトリガーの連携実行パターンのエラーハンドリングは、Web API パターン のようにクライアント側でエラーハンドリング処理を独自に実装するのとは異なり、必要なエラーハンドリングは AWS サービスに組み込まれています。

このような背景により、イベントトリガーの連携実行パターンで考慮すべきことは、AWS サービス側でのリトライの機構を適切に制御し、無駄なリトライによりシステム全体のパフォーマンスを低下させないこと、またアプリケーション内のエラーにおいてはエラー内容やロジックのべき等性の有無を考慮し、適切なイベントの処理を行うことになります。

そのため今回は「AWS サービスのリトライの制御」と「データの破棄と退避」にフォーカスを当ててご説明します。

AWS サービス組み込みのリトライ機構

AWS サービス組み込みリトライ機構が対象とするエラーは 2 種類あります。

一つは SNS または EventBridge などの、イベントソースが Lambda にメッセージを配信できないエラーです。SNS または EventBridge がリトライを試みても Lambda にイベントを送信できない場合、サービスごとの仕様に従った再試行ポリシー(※) にしたがって、AWS 管理のイベントルーターがリトライを試みます。

(※ :  SNS の再試行ポリシーEventBridge の再試行ポリシー)

もう一つは Lambda 関数がイベントを処理する際にエラーが発生する場合です。

イベントソースが S3、SNS、EventBridge のようなサービスによって呼び出される Lambda の起動方法を、非同期呼び出し と呼びます。Lambda 非同期呼び出しの場合は、 Lambda サービス側でリトライの責務を担うのですが、そこで発生しうるエラーとそのハンドリングのパターンは大きく 2 種類に分かれます。

1 つはスロットル起因などの実行数不足やサービス障害時などの、Lambda 関数が実行前のアプリケーション外で発生するエラーとして呼び出しのエラーになります。このリトライ機構は、イベントの最大有効期間 (Maximum Event Age) の設定を指定すると、 Lambda が非同期イベントキューにイベントを保持する存続期間を 60 秒から 6 時間の範囲で指定できます。一部のイベントソースからの実行要求が大量に発生し、他の Lambda 関数の実行を妨げてしまっている場合などに有効です。

もう 1 つはアプリケーション内で発生するエラーになります。Lambda 関数が実行後にエラーを返した場合は、Lambda がデフォルトで 2 回のリトライを行います。最大再試行回数 (Maximum Retry Attempts) を使用すると、0 から 2 までの任意の値に最大再試行回数をカスタマイズ可能となっており、後ほどご紹介する「データ退避のユースケース」において、システム全体のべき等性確保のために、エラー発生時は一切のリトライを行えない場合などに利用します。

以下の図のように、アプリケーション外で発生するエラーの場合は、イベントの最大有効期間 (Maximum Event Age)、アプリケーション内で発生するエラーの場合は、Maximum Retry Attempts (最大再試行回数) とエラーの起きる場所によって参照されるリトライの設定が異なる点がポイントです。

データの破棄と退避

イベントソースが Lambda にメッセージを配信できない場合や、Lambda 関数がイベントを処理する際のリトライポリシーを超えた場合は、デフォルトではイベントは破棄されてしまいます。

エラーを引き起こすイベントのハンドリングとして、不正なイベントメッセージや、失われてもシステム上の不整合がない場合においては、データを破棄してしまうのも 1 つの手法です。ただし課金処理や注文処理など、データの破棄がビジネス上許容できないイベント処理も存在します。

このような処理のデータ破棄を防ぐために、Dead Letter Queue (DLQ) があります。DLQ は、正常に処理をすることができないメッセージの退避先として、他のキューにメッセージを転送することができる機能です。特定のメッセージを別のキューで管理し、後から処理が成功しない理由をデバッグする目的で役立ちます。DLQ は Amazon SQS の標準 SQS キューまたは標準 SNS トピックが対象になります。

また、Lambda サービスがイベントをリトライ処理する際のポリシーであるイベントの最大有効期間 (Maximum Event Age) が最大値に達するか、最大再試行回数 (Maximum Retry Attempts) が最大値に達するかの条件が満たされた場合には、失敗したイベントを別のサービスに送信する Lambda Destinations も利用できます。Lambda Destinations は、関数成功時と関数エラー時とそれぞれ Lambda 関数の実行結果に応じて、次の処理にメッセージを送信する機能であり、メッセージの送信先としては標準の SQS キュー、標準 SNS トピック、Lambda 関数、 EventBridge イベントバスがあります。エラーハンドリングの目的では、関数エラー時に Lambda Destinations を利用し、予め設定した送信先にイベントを送り、エラーになったイベントメッセージを適切に処理する事ができます。

データ退避のユースケース

原則リトライによってハンドリングが可能なエラーは、べき等性が担保されたバックエンドで再度処理をするのが一般的です。

しかしイベントによっては、「多重耐性がない」外部のサービスを利用している課金処理や注文処理など、安易なリトライ処理が許されない場合もあります。つまりシステム全体として、 べき等性を担保出来ないケースです (べき等性については、前回記事の「リトライとべき等性 (idempotency)」のパートもご参照ください)。

このようなケースには、失敗時の挙動として、Lambda Destinations や DLQ を利用したエラーハンドリングが有効です。先述の通り、Lambda Destinations や DLQ を利用すると、Lambda 関数の失敗時に別の処理の起動や別のメッセージの受け入れ先に転送することが出来ます。

失敗したイベントを受け取り、エラーの内容や回数に応じて、システム全体に不整合が発生しないように、ハンドリングするのが、データ退避のユースケースになります。その場合には、Lambda の設定値である Maximum Retry Attempts (最大再試行回数) を 0 に設定します。そして、Lambda Destinations や DLQ を利用した失敗時の処理の中で、一度処理に失敗している状態を考慮に入れた上で、リカバリやリトライロジックを実行し、エラーハンドリングを行います。

【具体的なコード例】

const axios = require("axios")
const transactionServiceURL = "https://none-idenpodentic-external.transaction.service.com/check-transaction"

const checkTransactionCompleted = async (eventId) => {
    return await axios.get(`${transactionServiceURL}?id=${eventId}`)
}

export const handler = async (event) => {
    // 多重耐性がない外部のサービスのステータスを確認
    const isCompleted = await checkTransactionCompleted({
        idempotencyKey: event.data.idempotencyKey,
    })
    
    if (!isCompleted) {
        // 再度処理を実行
    } else {
        // 通常の処理処理
    }
  
};

Dead Letter Queue と Lambda Destinations の使い分け

Lambda 関数がイベントを処理する際のリトライポリシーを超えた場合は、 Lambda Destinations と DLQ は同時に使用できます。そのためどちらか片方だけを選択しなければいけないわけではなく、システムの要件に応じて両方使うというケースもありますが、どちらか片方の利用で十分なユースケースにおいてどちらを選ぶべきかを判断するにあたっての両者の違いとしては、後続に渡される情報量に違いがあります。

以下のサンプルとなるデータをご覧ください。これは、EventBridge をトリガーとして Lambda でエラーが発生した時に、 Lambda Destinations と DLQとして 標準 SNS トピック経由で Email サブスクリプションを利用した際に受信するメールの内容です。

【Lambda Destination】

{
    "version": "1.0",
    "timestamp": "2023-06-20T07:25:42.931Z",
    "requestContext": {
        "requestId": "c67f1f78-1c69-4302-96de-ec526117d89e",
        "functionArn": "arn:aws:lambda:${region}:${account-id}:function:error-handling:$LATEST",
        "condition": "EventAgeExceeded",
        "approximateInvokeCount": 1
    },
    "requestPayload": {
        "custom-text-from-an-event ": "sample value" // EventBridgeからの入力JSON文字列
    },
    "responseContext": {
        "statusCode": 200,
        "executedVersion": "$LATEST",
        "functionError": "Unhandled"
    },
    "responsePayload": {
        "errorType": "Error",
        "errorMessage": "err",
        "trace": [
            "Error: err",
            "    at Runtime.handler (file:///var/task/index.mjs:3:11)",
            "    at Runtime.handleOnceNonStreaming (file:///var/runtime/index.mjs:1086:29)"
        ]
    }
}

【DLQ】

{"custom-text-from-an-event ": "sample value"} // EventBridgeからの入力JSON文字列

Lambda Destinations に渡ってきている情報のほうが多いのがお分かり頂けるかと思います。DLQ にある情報はすべて Lambda Destinationsに含まれています。そのため原則としては、Lambda Destinations のほうが好ましいシーンが多いでしょう。

そのため、Lambda 関数がイベントを処理する際のリトライポリシーを超えた場合は、Lambda Destinations をご検討頂ければと思います。イベントソースが Lambda にメッセージを配信できない場合は、Lambda Destinations は利用出来ないので、DLQ を利用することになります。


まとめ

今回はイベント駆動のデータ加工、連携処理 を行うユースケースのエラーハンドリングについてご紹介しました。

イベント駆動のデータ加工、連携処理パターンのサーバーレスアプリケーションの特性として、非同期型の実行を持ちます。

イベントプロデューサーとなるサービスである、S3、SNS、EventBridge から生成されたイベントは、イベントルーターを介して、Push 形式で Lambda を実行します。その際に特徴的なのが、呼び出しクライアントが独自で実装ではないため、リトライの制御を AWS 管理のイベントルーターの機構に任せる点になります。

AWS 管理のイベントルーターのリトライ機構として、イベントソースが Lambda にメッセージを配信できない場合は、イベントソースのリトライポリシーにて制御し、Lambda 関数がイベントを処理する際のリトライポリシーを超えた場合は、アプリケーション外のエラーはイベントの最大有効期間 (Maximum Event Age) を利用して制御し、アプリケーション内のエラーであれば最大再試行回数 (Maximum Retry Attempts) を利用して制御します。

イベントソースが Lambda にメッセージを配信できない場合や、Lambda 関数がイベントを処理する際のリトライポリシーを超えた場合は、デフォルトではイベントは破棄されてしまいます。データの破棄がビジネス上許容できないイベント処理については、DLQ または Lambda Destinations によってデータを退避して、別の処理でエラーハンドリングを行う手法があります。

「多重耐性がない」外部のサービスを利用しているなどの理由でシステム全体でべき等性を担保出来ないケースにおいては 、リトライを行わず、Lambda Destinations を利用して、一度処理に失敗している状態を考慮に入れた上でのリトライロジックを実行し、エラーハンドリングを行います。

以上が イベント駆動のデータ加工、連携処理 を行うユースケースにおけるエラーハンドリングでした。次回は「バッチ・ストリーミングのETL」のユースケースにおけるサーバーレスアプリケーションのエラーハンドリングについてご紹介します。


サーバーレス学習のための関連資料

この連載記事のその他の記事はこちら

選択
  • 選択
  • 第 1 回 オープニング
  • 第 2 回 Web API パターン
  • 第 3 回 イベント駆動のデータ加工、連携処理パターン
  • 第 4 回 マイクロバッチ・ストリーミングパターン
  • 第 5 回 ワークフローパターン 前編
  • 第 6 回 ワークフローパターン 後編

builders.flash メールメンバーへ登録することで
AWS のベストプラクティスを毎月無料でお試しいただけます


筆者プロフィール

大磯 直人
アマゾン ウェブ サービス ジャパン合同会社
ソリューションアーキテクト

インターネット・Web サービスを提供されるお客様に対して技術支援を行っています。好きな食べ物は 肉・寿司・ラーメン です。空き時間は永遠に Youtube 見てます。好きな AWS サービスは AWS StepFunctions です。

AWS を無料でお試しいただけます

AWS 無料利用枠の詳細はこちら ≫
5 ステップでアカウント作成できます
無料サインアップ ≫
ご不明な点がおありですか?
日本担当チームへ相談する