キーポイント
-
冪等性は一見難解に見えるが、予測可能性、信頼性、一貫性のために、現代のクラウドシステムにおけるシステムエンジニアリングの重要な一部であり続けている。
-
サーバーレスベースのシステムにidempotenceを適用するには、at-least-onceデリバリーのコンセプトのためにユニークな課題がある。
-
この記事では、このコンセプトがどのサーバーレスサービスにも再現可能であるかに関わらず、AWS Lambda関数でこれを実現する方法を示している。
-
at-least-onceデリバリーは、システムがコマンドや関数を呼び出す方法に影響し、一貫した結果を保証するために、あらかじめサーバーレス関数に冪等性を焼き付けておく必要がある。
-
手動で冪等性を処理することはコードライティングに複雑さを加えるので、可能であればAWS Lambda Powertoolsのような自動化やすぐに使えるツールを使って行うべきである。
-
追加されたソリューションが設定され、期待通りに動作することを検証するテストを書くことも不可欠だ。
キーポイント
◆ ◆ ◆
はじめに
プログラミングにおいて冪等性という言葉は、数学の議論やコンピュータ・サイエンスの講義で使われる複雑で難解な概念のように聞こえるかもしれない。しかし、その関連性は学問の域をはるかに超えている。
冪等性 (べきとうせい、idempotenceまたはidempotencyの訳)は、ソフトウェア・システムの予測可能性、信頼性、一貫性を保証する上で極めて重要な基本原則である。
この記事では、冪等性の概念を解明し、それが何を意味するのか、なぜ重要なのか、そして私たちがソフトウェアをどのように設計し、どのようにやりとりするのかを探る。ベテランの開発者であれ、コーディングの旅に出たばかりであれ、より堅牢で回復力のあるプログラムを書くためには、冪等性を理解することが重要だ。
冪等性とは何か?
冪等性とは、関数や演算の性質で、複数回適用しても1回適用したのと同じ結果になることだ。
言い換えれば、冪等性関数は繰り返し呼び出されても、最初の呼び出しを越えて結果が変わることはない。
例えば、数学では絶対値関数は冪等性である。なぜなら、ある数の絶対値を複数回取っても結果は変わらないからである。
絶対値関数を数値に1回適用しても、複数回適用しても、結果は同じである。
>> abs(-5) == abs(abs(-5)) == abs(abs(abs(-5))) # ... and so onTrue
なぜクラウドワークロードで冪等性を気にするのか?
クラウド・アプリケーションを開発する場合、この例ではAWSを使ってこのコンセプトを実証するが、"at-least-once"デリバリー/インボケーションのコンセプトを把握することが極めて重要である。この用語は、特定のターゲットが少なくとも1回、場合によっては複数回、イベントを受信したり呼び出されたりする可能性があることを意味する。開発者としては、同じイベントが複数回処理されるシナリオを予測し、対処することが不可欠だ。これは "起こるかどうか"ではなく、"いつ起こるか"の問題だ。そこで、冪等性がもっとも重要になる。冪等関数を書くことで、イベントが複数回処理されたとしても、結果は一貫性を保ち、意図しない副作用を回避し、AWSアプリケーションの信頼性と堅牢性に貢献している。
なぜat-least-onceデリバリーなのか?
冪等性がもたらす課題を理解するには、ボンネットの下の運用メカニズムを掘り下げることが不可欠だ。この説明では、Jitが幅広く書いているLambdaをベースにするが、SQSやSNSなど他のサービスにも関連がある。
Lambdaの非同期呼び出しをオーケストレーションする場合、実行に関わる2つの異なるプロセスを最初から最後までの認識が重要だ。最初のプロセスは、イベントをキューに入れることであり、その後のプロセスは、このキューからイベントまでの取得だ。マルチノードデータベースの複雑な性質と最終的な一貫性の概念を考慮すると、2つの同時実行ランナーが同じ呼び出しイベントを並行して処理するシナリオが時折発生する。
このようなイベントがどれくらいの頻度で発生するのかを理解するために、EventBridgeイベントによってトリガーされたLambda関数が大量のイベントを送ってLambdaを起動させるという実験を行った。私は、同じイベントでLambdaがトリガーされる頻度をIDで監視した。実験の結果、同じイベントが複数同時に実行されることは、数万回に1回の割合で発生することがわかった。
設計上の冪等関数
自然に 冪等関数を書くことは可能だ。データベース内のアイテムのステータスを"completed"に更新する関数の例を考えてみる。この関数は、何度呼び出されてもアイテムのステータスは"completed"のままであるため、冪等であると分類される。この冪等性という仮定は、リスナーやトリガーなど、データベーステーブルの変更を監視する外部要因がない限り成り立つことに注意が必要だ。
def handler(event: EventBridgeEvent, __) -> None: executions = boto3.resource('dynamodb').Table('Executions') executions.update_item( Key={'id': event['detail']['id']}, UpdateExpression='SET #status = :status', ExpressionAttributeNames={'#status': 'status'}, ExpressionAttributeValues={':status': 'COMPLETED'}, )
一般的なルールとして、可能な限り冪等であるように関数を設計することが推奨される。こうすることで、開発者はコードの中で手作業で冪等性を処理する心配がなくなり、複雑さと将来のメンテナンスコストを削減できる。
冪等関数
関数の中には、 設計上冪等ではないものもある。例えば、クライアントに通知メッセージを送信する関数は偶発的でないかもしれない。なぜなら、同じイベントで関数が2回実行されると、クライアントは2つの通知メッセージを受け取ることになり、ユーザー・エクスペリエンスが悪くなるからだ。その代わり、クライアントが受け取る通知メッセージは1つにしたい。そして、そこが冪等性が登場するところであり、冪等性を処理することがもっとも重要だ。
Lambda Powertoolsで冪等性の問題を解決
全ての関数が冪等であるであるわけではなく、Lambdaが同じイベントで呼び出されることがあることは理解している。ではどうするか?
幸運なことに、AWS は開発者ツールキットPowertools for AWS Lambdaを使って、関数を手動で冪等化する素晴らしいソリューションを提供している。このパッケージは開発者に "idempotent"デコレーターを提供し、同じイベントで複数の実行を処理するように設定できる。
これは、イベント内で設定可能な特定の値をハッシュ化し、特定のイベントの一意性を識別し、各イベントの実行状態をデータベースに保存することで動作している。
関数のコンテキストに到着する最初のユニークなイベントは、ストレージレイヤーのアイテムとして保存される。同じイベントに対する2回目の呼び出しが発生すると、デコレーターは実行がすでに開始または終了していることを認識し、2回目の実行を中止する。
使用例
idempotentデコレーターの使い方をもう少し詳しく見てみる。
from aws_lambda_powertools.utilities.idempotency import DynamoDBPersistenceLayerfrom aws_lambda_powertools.utilities.idempotency import IdempotencyConfig@idempotent( persistence_store=DynamoDBPersistenceLayer(table_name='IdempotencyTable'), config=IdempotencyConfig( event_key_jmespath='id', raise_on_no_idempotency_key=True, ),)def handler(event: EventBridgeEvent, __: Any) -> None: logger.info(f"Event: {event}") ExecutionsManager.from_event(event).complete_execution()
このように、idempotentデコレーターは、永続化レイヤー(この場合は IdempotencyTableという名前のDynamoDBテーブル)を使って設定されている。さらに、event_key_jmespathパラメータにidを渡すことで、デコレーターはid属性のみを使用してイベントオブジェクトの一意なハッシュを作成することが分かる。raise_on_no_idempotency_keyをTrueに設定するのは、イベント内でidが欠落するケースを避けるためだ。
解決策のテスト
純粋なコード行ではないものの、idempotentデコレーターをコードベースに追加することで、それが適切に設定され、期待通りに動作するかをテストするのは良い習慣だ。
Jit では、idempotentデコレーターをテストする効果的な方法を発見した。moto(AWSインフラストラクチャ用のPythonモッキングライブラリ)を利用して、Lambda関数が同じイベントで2回呼び出されるシナリオをシミュレートする。
from test_utils.aws import idempotencydef test_complete_execution_handler(executions_manager): idempotency.create_idempotency_table() # It's important to import the handler after moto context is in action from src.handlers.complete_execution import handler # Call the handler for the first time handler(event, None) # Validate an idempotency key was created idempotency.assert_idempotency_table_item_count(expected=1) # Assert status changed to completed and completed_at has updated execution = executions_manager.get_item(...) assert execution.status == COMPLETED assert execution.completed_at_ts >= datetime.utcnow().timestamp() - 1 # Call the handler for the second time complete_execution_handler(event, None) # noqa idempotency.assert_idempotency_table_item_count(expected=1) assert executions_manager.get_item(...) == execution # Assert nothing has changed (idempotency worked)
分かりやすく解説してみよう
-
idempotencyテーブルの作成: 最初のステップでは、motoコンテキスト内にidempotencyテーブルを作成する。idempotencyテーブルはAWSインフラストラクチャの複数のサービスで共有できるため、テーブルを作成し、さまざまなテストから呼び出すテストユーティリティを開発するのが現実的だ。
-
motoコンテキストでハンドラをインポート: 2番目のステップは、motoコンテキストがアクティブになった後にハンドラをインポートすることだ。motoコンテキストはboto3クライアントを模擬し、boto3クライアントはインポート中にデコレータで初期化されるため、これは非常に重要だ。
-
初回のハンドラー呼び出し: ハンドラーを初めて呼び出し、idempotencyテーブルにidempotencyキーが正常に作成されたことを検証する。
-
ステータスと完了の確認: 次のステップでは、実行のステータスが "completed"に変わり、"completed_at"のタイムスタンプが更新されたことを確認する。これにより、Lambda関数が正しくタスクを実行したことが確認される。
-
2回目のハンドラー呼び出し: 最後に、ハンドラーを2回目呼び出し、idempotencyキーが再度作成されず、実行時の属性が変更されていないことを確認する。これは、Lambda関数が冪等であり、同じイベントで再び実行されなかったことを示している。
ちょっとしたコツは、デコレーターの動作を理解するのにも役立つが、このようなテストをデバッグして、2回目の実行が決して行われていないことを確認し、検証することだ。
まとめ
なぜ 冪等化がシステムの予測可能性、信頼性、一貫性を向上させる基本的なプラクティスなのか、その理由がより明らかになることを願っている。失敗したオペレーションが普通ではなく、むしろ異常値である一方で、at-least-onceデリバリーはクラウドベースのシステムで 冪等化を推進する理由の1つとなっている。
この記事では、プログラミング言語としてPythonを使ったAWS Lambdaを例に挙げている。しかし、課題は他のプログラミング言語やサービスでも有効だ。例えばSQSでは、開発者は標準とFIFOタイプのキューを選択できる。標準的なキューでは、デリバリーは少なくとも一度(at-least-once)だが、FIFOでは一度きりの処理となり、標準的なキューに比べてスループットが低下し、コストも高くなる。これは、開発者が自分の側から余計なロジックを実装する必要がなく、既存の提供されているソリューションを利用できる場合の例である。
最近の運用では、冪等化のプラクティスの適用と、本番環境にデプロイする前にその有効性をテストすることの両方を可能にする優れたツールがある。上記の例を通して、私はあなたにもこれができる簡単で一般的な例を提供した。例とテストプロセスに従うことで、あなたの冪等コードが意図した通りに機能し、AWSインフラストラクチャに信頼性と一貫性を提供することを確信できる。