Lambda Powertools の冪等性

はじめに

Lambda Powertools において、DynamoDB を使用した冪等性の実装を追ってみる。

実装

persistence の一つとして DynamoDB の実装がある

PK は、関数名や関数の ARN と Lambda の入力イベントのハッシュ値を “#” で連結した値である。 Lambda 関数は、この PK を用いてロック(アイテムの作成・更新)を試みるため、重複したイベント(重複した Lmabda の起動)は、一方の実行がロックに失敗するため、exactly once を実現する。 なお、PK の値は、IdempotencyConfig()event_key_jmespath より変更可能であり、どの単位で冪等性を担保するか変更可能であることを意味する。

冪等性の実現のフローは次のようになっている。

  1. Lambda 関数起動時に DynamoDB のアイテムのステータスを InProgress に設定する

  2. 関数の任意の処理を実行する

  3. 関数の終了時に DynamoDB のアイテムに対して、ステータスを Completed に設定する

1.のロックが重要な部分であり、ロックには DynamoDB の 条件式(ConditionExpression) を使用している。 具体的に、a. PK のアイテムが存在しない場合にアイテムを作成する、b. アイテムが存在する場合も現在時刻が有効期限(expiry)を超えている場合アイテムを更新する、c. ステータスが InProgress で InProgress の有効期限(in_progress_expiry)が存在し InProgress の有効期限が現在時刻を超えている場合アイテムを更新する。上記を OR で条件式(ConditionExpression)に指定することで、いずれかの条件を満たしている場合にのみロック(アイテムを作成・更新)できる。 簡単に言えば、他の関数がまだロックをしていない(a.)や、ロックされていた場合(b.)や処理が進行中であっても有効期限が切れている場合(c.)、ロック可能という判断である。

なお、expiry はデフォルトで 1 時間であり、これは 1 時間以内の一度完了した処理は冪等となる。 また、in_progress_expiry_timestamp は Lambda 関数のタイムアウトするまでのタイムスタンプであり、Lambda がタイムアウトした場合に関数のリトライで再度ロックできるようにすることができる。

Lambda 関数のタイムアウトまでの残り時間は python ランタイムが提供している get_remaining_time_in_millis メソッドを使用している。

Lambda コンテキストオブジェクトを使用して Python 関数の情報を取得する

context メソッド get_remaining_time_in_millis — 実行がタイムアウトするまでの残り時間をミリ秒で返します。

懸念されうる一般的なフローについては、ドキュメントのシーケンス図がわかりやすい。 Idempotency request flow

例えば、2. の関数実行中にアプリケーションで例外が発生した場合は、アイテムは削除される。そのため、実行のリトライで再度ロック可能となる。

一方で、例えば、2.の実処理が成功したものの、3.の実行の完了の更新が失敗した場合、処理は完了しているものの、アイテムが削除されてしまう可能性や、Lambda 関数のタイムアウトを迎えてしまう可能性があるかもしれない。 同様に、アプリケーションのメインの処理が終わったものの、最後の後処理的なもので例外が発生した場合、アイテムは削除され、再実行される可能性があるので、適切に例外をハンドリングする必要があるかもしれない。

また、Lambda や、DynamoDB の AWS インフラストラクチャーに問題が生じた場合、2. と 3. の成功が保証されているわけではないため、exactly once の保証は難しいと考えられる。必要に応じて、Dead Letter Queue など検知する機構を準備するべきだろう。