RareJob Tech Blog

レアジョブテクノロジーズのエンジニア・デザイナーによる技術ブログです

AWS SQS High Throughput FIFOキューへダウンタイム無しで移行しました

こんにちは、 プラットフォームグループの池田です。

実稼働しているAWS SQSをスタンダードキューからHigh Throughput FIFOキューへダウンタイム無しで移行しました。 本記事では移行時の進め方とシステム詳細ついて紹介します。

業務背景

レアジョブ英会話サービスの講師検索機能は専用の検索サービスAPIを通して提供されています。検索サービスAPIは検索に必要なデータをElasticsearch上にレプリケーションデータとしてストアしています。

検索サービスのデータベースには大きく分けてレッスンのデータと講師のデータがストアされており、データ作成更新はそれぞれ異なったライフサイクルでデータを連携させる必要があります。

今回実施した移行で扱ったSQSはレッスンデータの作成、更新、削除を検索サービスのデータベースへ連携する際に利用しているものです。

システム概要

SQSの移行に関して、検索サービスへ連携するためにSQSへメッセージを送信するシステム(この記事ではコンポーネントと呼ぶことにします)と、メッセージを受信し検索サービスのデータベースにストアするコンポーネントの2つが関係します。

[送信コンポーネント]
もともと対象のSQS送信を担当するコンポーネントを一元的にしていました。そのおかげで移行作業の負担を小さくできました。 AWS上のリソースとして送信コンポーネントAWS Beanstalk(Worker type)から構築したAuto Scaling設定されたEC2上で動いています。

[受信コンポーネント]
対象SQSのメッセージを受信しデータベースへストアするコンポーネントは、SQSをトリガーとするLambdaで動いています。

移行の動機

検索サービスへのレッスンデータ更新を上述のコンポーネントを利用し連携しています。 各レッスンデータは作成→予約→キャンセル→削除のようなステータス変更を伴い、リアルタイム同期が要件であるためそのステータス変更をストリーム処理させる必要があります。

既存ではスタンダードSQSを利用していました。システム構築当時には今回移行させたHigh Throughput FIFOキューがまだ提供されておらずスループット制限の観点からスタンダードSQSを採用することになりました。

しかしスタンダードSQSは送信タイムスタンプの順に対してSQS内部にて順番が前後しメッセージを受信することもある仕様です。このことにより、レッスンの作成→そのレッスンを予約する等の順番を保証するべき箇所が前後し、以下の図のようなエラーが低頻度であるものの発生していました。

上述のエラーが発生したままにしておくとビジネス的な問題になりますが、これまでは以下のリカバリーの仕組みで業務的に大きい影響を与えないようにしていました。

  • エラーが発生した場合はSQSへメッセージを戻し再送信するようにさせ同じエラーが3回発生した場合はメッセージをデッドレターキューへ格納させる。
  • 検索サービスのデータベースに送信タイムスタンプをつけてレッスンデータを格納することで、受信側コンポーネントにて順番の整合性を担保させるバリデーションを持たせる。
  • 上記2つの補填でも不整合が出た場合、レッスンデータのマスター側となるデータベースとの差分がある場合に検知し、別途検索サービスのデータベースへ埋め合わせをするバッチを定期的に実行させる。

上記の補填の仕組みにて最終的にはリカバリーはされますが、反映までのラグが出ることなりサービスにとって望ましくない状態でした。

その課題解決のためにHigh Throughput FIFOキューへ移行することにしました。

実際に今回の移行によって上記の問題が一切発生されなくなりました。

AWS SQS FIFOキューについて

FIFOキューは順番保証と重複排除をサポートしており順番保証はMessage Groupと呼ばれる単位で保証されます。

それぞれに対応するMessage Group IDとDeduplicate IDをどの値にするか設計し送信側実装にて指定する必要があります。今回の対応で指定した内容は後述のコード改修の箇所で言及しています。

FIFOキューの拡張という位置づけでHigh throughput FIFOキューが提供されており、ノーマルなFIFOキューに対して重複排除をMessage Groupごとに限定させることで全体のスループットが向上できるようになりました。

SQS上の設定によってHigh Throughput FIFOキューに変更します。具体的な設定内容は後述のtemplate.yml上の設定で記載しています。

今回の移行の事前確認としてHigh Throughput FIFOキューのパフォーマンスを測る検証を実施しました。詳細は記事の主旨から外れるので記載しませんが、検証の結果から性能面でもサービスの将来的なリクエスト密度の増加に対しても十分耐えうると判断できました。

移行ステップ

受信コンポーネントと対象SQSのAWSリソースはSAMを利用したCloudFormation管理で構築したのでtemplate.ymlファイルを更新しCIパイプラインを通したSAMデプロイで適用させてSQS移行を実施しました。

移行のステップとして以下のような流れで実施しました。

Step1: 受信コンポーネントにおいてHigh Throughput FIFOキューとLambdaのセットを新しく作成。送信コンポーネントにて環境変数に応じて新旧どちらのSQSを送信先にするか選択できるバージョンをデプロイする。

Step2: 送信コンポーネントにて送信先を新規SQSに指定する環境変数へと変更する。

このときの送信コンポーネント送信先の切り替えのタイミングで新旧2つの受信コンポーネントがそれぞれ受け付けることでダウンタイムを発生させませんでした。

Step3: 受信コンポーネントにおいて不要になったスタンダードSQSとLambdaのセットを削除する。

コード改修について

SQS送信コンポーネントのコード実装は以下のようにメッセージに対して上述のMessageGroupIdMessageDeduplicationIdを設定させる改修をします。

MessageGroupIdはレッスンごとのユニークIDを指定し、MessageDeduplicationId は 送信側コンポーネントがエンドユーザー側から受け取るリクエストIDを指定することで保証したい順序と重複を設定しています。

   if featureToggleOfWhichSQS.isFifoQueue {
        messageInput.MessageGroupId = aws.String(eventEncodedID)   // レッスンIDを message group ID に指定
        messageInput.MessageDeduplicationId = aws.String(requestID) // エンドユーザー側から受け取るリクエストIDを重複排除IDに指定
    }

High Throughput FIFOキューのtemplate.yml上の設定

新規に作成したHigh Throughput FIFOキューのリソース箇所はSAMのtemplate.ymlにて以下の設定で構築しました。

FIFOキューにするためにはQueueNameの末尾は.fifoにしないといけません。DeduplicationScope: messageGroupFifoThroughputLimit: perMessageGroupIdを指定することでHigh Throughput FIFOキューになります。

  SQSEventQueueFIFO:
    Type: 'AWS::SQS::Queue'
    Properties:
      QueueName: sqs_event.fifo  # must end with .fifo suffix
      FifoQueue: true # FIFO queue or not
      DelaySeconds : 0 # (default)
      MessageRetentionPeriod: 1209600 # 14 days (max)
      ReceiveMessageWaitTimeSeconds: 20 # 20 sec (max) : Long Polling
      VisibilityTimeout: 30 # 30 sec (default)
      ContentBasedDeduplication: false # For first-in-first-out (FIFO) queues, specifies whether to enable content-based deduplication.
      DeduplicationScope: messageGroup # messageGroup is required for High throughput for FIFO # whether message deduplication occurs at the message group or queue level
      FifoThroughputLimit: perMessageGroupId # perMessageGroupId is required for High throughput for FIFO # whether the FIFO queue throughput quota applies to the entire queue or per message group
      RedrivePolicy:
        deadLetterTargetArn: !GetAtt SQSEventDLQFIFO.Arn
        maxReceiveCount: 3

感想

SAMとElastic Beanstalkの仕組みでAWSリソースを構築できていたことにより、テスト環境での検証の保証レベルとオペレーションの安全性が上がり対応工数を小さくできました。AWSが提供する機能の恩恵をできるだけ最大限に受けられるようにシステムを保っていくことが重要と再確認しました。