2021-08-25

Amazon SNSがFIFOをサポートしたので使ってみた

yoneyama

はじめに

バックエンドエンジニアのyoneyamaです。 いつの間にかAmazon SNSがFIFOをサポートしていたので使ってみましたという記事です。

Amazon SNSがFIFO対応するまで

昨年10月よりAmazon SNSでのFIFOがサポートされているようです。 https://aws.amazon.com/jp/about-aws/whats-new/2020/10/amazon-sns-introduces-fifo-topics-with-strict-ordering-and-deduplication-of-messages/

弊社はマイクロサービスアーキテクチャを採用しており、各マイクロサービス間でのメッセージング手段として、かねてよりAmazon SNSとAmazon SQSを活用していました。

その中で、SQSキューからメッセージをコンシュームする際、メッセージの順序や重複排除を担保したい要件も存在していました。 ただ、以前まではAmazon SQSではFIFOがサポートされていたものの、Amazon SNSではFIFOがサポートされていなかったため、メッセージの順序や重複排除はアプリケーションで担保したうえで、 通常のAmazon SNS と Amazon SQSを利用していました。 FIFOのAmazon SQSを利用して、直接エンドポイントにエンキューするということも可能でしたが、その方法は採用しませんでした。 サポートされていなくて何が困るというと、基本的にはAmazon SQSにエンキューする際Amazon SNSがベストプラクティスと考えられているからです。

以下クラスメソッドさんの記事を参照していただくとわかりやすいですが、要は 「SNSはEメール、AWS Lambda、HTTPといったさまざまな様々なエンドポイントの形式に対応しており、拡張性があり、観察も容易になる」 というメリットがあるためです。

【AWS】SQSキューの前には難しいこと考えずにSNSトピックを挟むと良いよ、という話 https://dev.classmethod.jp/articles/sns-topic-should-be-placed-behind-sqs-queue/

以上より、多くの開発者にとってはAmazon SNSがFIFO対応したのは喜ばしいことではないかと思います。

注意点

これはAmazon SNSというよりもAmazon SQSについてなのですが、通常キューとして設定した場合は無制限の数のトランザクションがサポートされるのに対し、FIFOキューとして設定した場合は、毎秒300件までのメッセージがサポートされます。 高スループットバージョンも提供されている様ですが、こちらは記事執筆時点ではプレビュー版として提供されているため、プロダクション環境での運用は慎重に検討した方が良いでしょう(一部リージョンでのみの提供とされていますが、東京リージョンでもUI上から設定可能なことは確認できました)。 https://aws.amazon.com/jp/sqs/features/

よって、FIFOキューの導入の際は要求されるパフォーマンスと照らし合わせて導入を検討する必要があります。

実装

ここでは、簡単ではありますがGoでのAmazon SNSのPublishの実装例を紹介したいと思います。

通常のAmazon SNSとFIFOのAmazon SNSで必要なパラメータに差異があることをわかりやすくするため、それぞれの実装例を示します。 まず通常のAmazon SNSでのpublishです。

import (
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/service/sns"
)

func Publish(message, topicArn string) {

    result, err := r.svc.Publish(&sns.PublishInput{
        Message:  aws.String(message),
        TopicArn: aws.String(topicArn)
    })
    if err != nil {
        // error handling
    }
}

続いて、FIFOのAmazon SNSでのpublishです。

func PublishWithFIFO(message, topicArn, messageGroupID string) {

    result, err := r.svc.Publish(&sns.PublishInput{
        Message:  aws.String(message),
        TopicArn: aws.String(topicArn),
        MessageGroupId: messageGroupID
    })
    if err != nil {
        // error handling
    }
}

通常のSNSとFIFO SNSの大きな差異としては、MessageGroupIdの有無になります。 役割としては、複数のコンシューマーで並列処理をする場合に、この値で定義されたメッセージグループ各々での順序を保証する、というものになります(裏返すと、異なるメッセージグループ間のメッセージはFIFOでの処理が保障されないということになります)。 FIFOでの処理を保証したい具体的なユースケースとしては、以下の公式ドキュメントのようにユーザー単位でのものが考えられるでしょう。

単一の FIFO キューに複数の順序付けされたメッセージグループをインターリーブするには、メッセージグループ ID 値を使用します (たとえば、複数のユーザによるセッションデータ)。このシナリオでは、複数のコンシューマーでキューを処理できますが、各ユーザーのセッションデータは FIFO 方式で処理されます。 https://docs.aws.amazon.com/ja_jp/AWSSimpleQueueService/latest/SQSDeveloperGuide/using-messagegroupid-property.html

弊社サービスでのユースケースでは重複排除のみを保証すれば要件が満たせるため、この MessageGroupId には乱数を設定しています。

また、重複排除を保証したいケースでは SNS トピック作成において以下の画像のように コンテンツベースのメッセージ重複排除 にチェックをしておく必要があります(GUIの場合)。

スクリーンショット 2021-08-24 23.48.45

メッセージの内容に基づいてデフォルトのメッセージ重複排除を有効にします

とあるように、このオプションを設定した場合はメッセージの内容が同一である場合には重複排除がされるので、上記実装例の message に タイムスタンプのような動的な値を含めておく必要があります。

まとめ

上記のとおり、簡単な実装と設定のみでメッセージ処理順序と重複排除の保証ができることで、より本質的な機能開発にリソースを集中させることが可能になりました。 SNS & SQSの使用を前提としたアプリケーションにおいてこの要件を満たす場合は利用しない手はないのではないかと思います。

最新の記事