「出張! #DevelopersIO IT技術ブログの中の人が語る勉強会」でLambdaとEventBridge Pipesを例にSQSコンシューマーの疎結合進化について語りました

クラスメソッド設立20周年イベント"Classmethod Odyssey"のサテライトイベントでメッセージキューについて語りました。"Odyssey"と言われて、何を思い出しますか?
2024.04.22

弊社技術ブログ「DevelopersIO」の書いた本人が過去記事を深掘って話すという勉強会の第3回が2024/04/18に開催されました。

今回は、2006から本稼働しているメッセージキューサービスのAmazon SQSのコンシューマー処理が2014年のAWS Lambda、2022年のAmazon EventBridge Pipesの登場とともに、疎結合に進化していったことについて、re:Invent 2022の EventBridge Pipes発表直後に書いた次のブログをベースに深堀りました。

EventBridge Pipesは非同期のメッセージ通信で幅広く使えるのに、今ひとつ認知・採用されていないなぁという思いと、相性が良いSQS-Lambda連携について、現在はEventBridge Pipesを挟む選択肢もあることを知ってもらいたくて、このテーマを取り上げました。

勉強会で使用した資料

メッセージキューとは?

まずはメッセージキューについておさらいしましょう。

メッセージキューは、プロデューサーとコンシューマーが非同期に通信するメッセージングパターンです。

マイクロサービスやイベント駆動アーキテクチャーなどでは疎結合であることが求められます。 疎結合を実現する中心的なAWSサービスの一つが、メッセージキューのAmazon SQSです。

Amazon SQSコンシューマー疎結合への旅

メッセージキューの重要性から、Amazon SQSは15年以上前から存在し、時代によってコンシューマー処理の選択肢も当然変わってきました。

今回発表ではAWSの進化によってコンシューマー処理が疎結合になってきたことについて

  1. モノリス
  2. Lambda
  3. EventBridge Pipes

という3つの変遷について紹介しましす。

モノリス時代

まずはモノリス時代です。

この時代では、アプリケーションがメッセージキューのすべての操作を担います。 具体的には、SQSキューをポーリングしてメッセージを受信し、処理してキューから削除するという一連の操作です。

また、メッセージ数に応じてスケーリングするような実行基盤を用意する必要もあります。

コンシューマーはモノリシックで非常に重厚な作りです。

import boto3
sqs = boto3.client('sqs')
SQS_QUEUE_URL = 'https://sqs.ap-northeast-1.amazonaws.com/123456789012/bar'

response = sqs.receive_message(
  QueueUrl = SQS_QUEUE_URL,
)

for message in response.get("Messages", []): # メッセージの受信
    print(message["Body"]) # 処理

    sqs.delete_message(
      QueueUrl = SQS_QUEUE_URL,
      ReceiptHandle = message['ReceiptHandle']
    ) # メッセージの削除

コンシューマーはメッセージ処理(ビジネスロジック)に注力したいのに

  • SQSをポーリング
  • メッセージを受信
  • メッセージを処理
  • メッセージをSQSから削除

といったSQSそのものの処理も必要です。

更には、SQSキューのメッセージ数に応じて、コンシューマーをAutoScaling等でスケールさせる必要もあります。

Lambda時代

2014年にLambdaが発表されて新しい時代がやってきました。

LambdaのトリガーにSQSを指定するだけで、SQSの操作をLambdaのイベントソースにオフロードできるようになり、メッセージ数に応じてLambdaもスケールします。

モノリス時代はアプリケーションにいろいろな処理が含まれていましたが、Lambda時代はSQSのことは忘れ、受け取ったメッセージの処理だけに注力できるようになり、コードが非常にシンプルになりました

def lambda_handler(event, context):
    for message in event['Records']:
        print(message['body'])

この3行だけで、実際に動作します。AWS SDKのインポート(import boto3)は不要です

ただし、LambdaイベントソースがSQSを裏で操作しているのでLambdaの実行ロールにはSQSのアクションも許可します。

コードと権限が一致しない点には注意が必要です。

ちなみに、クラスメソッドに入社して最初期に対応した案件の一つが、シェルスクリプトで書かれたKinesisコンシューマーのLambda化でした。

Amazon Kinesis Data Streamsはシャーディングでスケールアウトし、シャード単位でメッセージに順序がついているため、コンシューマーはシャード単位でのチェックポイント管理が求められます。

Lambdaの登場後、ストリーム処理に慣れ親しんだ超人でないととっつきにくかったKinesisが一気に身近になったのを思い出します。

EventBridge Pipesの登場

2022年に発表されたAmazon EventBridge Pipesとともに新しい時代がやってきます。

Pipesはコンシューマー処理をソース・ポーリング・ターゲットに3分割し、Lambdaの一機能だったSQS操作はPipesが担います。

def lambda_handler(event, context):
    for message in event:
        print(message['body'])

実装コードはLambdaイベントソース時代と同等ですが、Lambdaの実行ロールにSQSアクションは不要になりました。 代わりに、Pipesの実行ロールでSQSのメッセージ操作とLambda呼び出しを許可します

EventBridge Pipesを使うことで、誰が何を行うのか明確になり、疎結合性が向上しました。

  1. モノリス
  2. Lambda
  3. EventBridge Pipes

と時代が進むにつれ、メッセージキューSQSのコンシューマー処理が疎結合に進化していったと感じられたでしょうか?

参考