🌏

【Amazon SQS】新規プロダクトにFIFOキューを導入した話

に公開

はじめに

はじめまして!ウェルスナビの片桐と申します。
私は新卒でバックエンド開発エンジニアとして入社し、新規プロダクト開発を行っているチームに配属され、サービスの一般公開に向けて開発を行っています。

7月に配属されてから約1ヶ月後、当時はまだ「Amazon SQS」という言葉すら知らなかった私が、社内で初めてとなるFIFOキューの導入に挑戦しました。

この取り組みを通して、新卒1年目のエンジニアとして直面した課題をどのように分析し、解決策を考え、実行に移し、どんな学びを得たのか——。
それらを実際に体験できたことは、非常に貴重な経験となりました。

今回は、その経験をもとに、以下の内容をお伝えしたいと思います。

  1. そもそもAmazon SQSとは何か
  2. FIFOキュー導入の背景
  3. どのように導入したのか
  4. 導入後に得られた効果
  5. 導入を振り返っての学び

FIFOキューの導入について知っていただくことはもちろん、ウェルスナビの新卒エンジニアがどのように業務へ挑戦し、成長・活躍しているのかという点も併せてお伝えできればと思います。

前提:Amazon SQSとは

まず、FIFOキューの話に入る前にAmazon SQSについて簡単に説明します。

Amazon SQS(Amazon Simple Queue Service)

Amazon SQS(Amazon Simple Queue Service)[1]は、アプリケーション間でメッセージを非同期にやり取りできるAmazonのメッセージキューサービスです。メッセージをキューに一時的にためることで、システム全体を疎結合にし、一時的に負荷を吸収しながらアクセスが集中しても安定して動作させることができます。

Amazon SQSの主要な構成要素

登場人物 説明
プロデューサ(Producer) メッセージをAmazon SQSに送信する側のアプリケーション
コンシューマ(Consumer) Amazon SQSからメッセージを受信して処理を行うプロセスやバッチ処理
キュー(Queue) メッセージを一時的に保存する中継地点
メッセージ(Message) プロデューサが送信しコンシューマが処理するデータ本体

構成イメージ図

なぜメッセージキューが必要なのか

「動画配信サービス」を例に考えてみましょう。
ユーザーがWebアプリに動画をアップロードすると、以下の処理が発生します。

  • 動画ファイルを一時保存する
  • 複数の解像度(1080p、720p、480pなど)へトランスコード(変換)する
  • サムネイルを生成する
  • 結果をデータベースに保存する

これらの処理をすべてアプリケーション内で直接実行しようとすると、動画アップロード機能と重いバックグラウンド処理が密結合になってしまい、システム全体の負荷が高まりやすいという問題があります。また、トランスコードなどの計算コストが高い処理が同じサービスに集中すると、一時的な負荷の上昇が他の機能にも影響し、レスポンス低下やスケールの難しさにつながります。

Amazon SQSを使った非同期化

ここで登場するのがAmazon SQSです。
Amazon SQSを使うと、処理を次のように分離できます。

  • Webアプリ(プロデューサ)が「動画ID」をAmazon SQSキューに送信する
  • ワーカー(コンシューマ)がそのメッセージを受け取り、トランスコードやサムネイル生成などをバックグラウンドで実行する
  • Webアプリは重たい処理をAmazon SQSに送信し、ユーザーに対しては即座に「公開処理中」などの応答を返す

こうすることで、

  • Webアプリとワーカー処理を疎結合にできる
  • ワーカーの処理が一時的に混み合っても、キューがバッファとして負荷を吸収してくれる
  • ワーカーを増やすだけでスケールアウトでき、大量の動画アップロードにも安定して対応できる

というメリットを享受することができます。

他の利用例

イメージが湧きやすいように一例として以下にユースケースをまとめています。

ユースケース 説明
ログ収集システム 各サーバーがログをAmazon SQSに送信し、別の分析基盤がまとめて処理
マイクロサービス連携 サービス間通信をAmazon SQS経由にして、片方が停止してもメッセージを保持可能

この章のまとめ

Amazon SQSは、処理を「すぐに終わらせる必要がない」「順番を守る or 高速に処理したい」などの要件がある、そんな非同期処理を扱うときに非常に有効なサービスです。
Webアプリ、バックエンド処理、マイクロサービス連携など、さまざまな分野で利用できます。

標準キューとFIFOキュー

上記で説明したAmazon SQSには「標準キュー(Standard Queue)」と「FIFOキュー(FIFO Queue)」の2種類があります。[2]

それぞれの特徴を以下の表にまとめました。

比較項目 標準キュー(Standard Queue) FIFOキュー(FIFO Queue)
順序保証 ベストエフォート(順序は保証されない) 同一メッセージグループID内は送信順序を厳密に保持(先入れ先出し)
メッセージ重複制御 なし 重複排除機能により、同一メッセージの重複を防止可能
スループット(処理性能) 非常に高い(ほぼ無制限) 標準:1 秒あたり最大 300 メッセージ(バッチ使用時最大 3,000 メッセージ)
高スループットモードではさらに拡張可能
遅延 低レイテンシ(ほぼ即時配信) 標準より若干遅い(順序維持と重複排除のため)
料金 FIFOより安価 標準より高価(順序保証・重複排除のための追加コスト)
配信回数保証 少なくとも1回(At-least-once delivery) 重複排除により実質的に1回だけ(Exactly-once)の処理が可能
バッチ処理 最大 10 件/リクエスト 最大 10 件/リクエスト(高スループットモードでは拡張可)

標準キューとFIFOキューの動作イメージ

標準キュー(Standard Queue)

  • 順序保証がなく、メッセージの処理順が前後する可能性がある
  • 同じメッセージが重複して配信される可能性がある
  • 「少なくとも1回(At-least-once)」の配信が保証されるが、重複が発生することがある

標準キューの動作イメージ図

Best effort ordering with possible duplicates
(順序はベストエフォートで、重複配信の可能性あり)

FIFOキュー(FIFO Queue)

  • 送信順序が厳密に保証され、常に先入れ先出し(First-In-First-Out)で処理される
  • 重複排除機能により、同一メッセージの再配信を防ぐことができる
  • 「少なくとも1回(At-least-once)」配信されるが、重複排除により実質的に1回だけ(Exactly-once)の処理が可能

FIFOキューの動作イメージ図

Exactly once and first in, first out
(重複なし・先入れ先出しで処理)

この章のまとめ

標準キューは、メッセージの順序が厳密でなくてもよいケース、または大量のリクエストを高速に処理したいケースに適しています。
例)ログ収集、イベント配信、バッチ通知、非同期ジョブ処理など。

FIFOキューは、メッセージの順序を厳密に守る必要がある、もしくは同一メッセージの重複処理を避けたいケースに適しています。
例)決済・送金・在庫管理・予約処理など、一度しか実行してはいけない重要な処理など。

本題:FIFOキュー導入の背景と結果

ここからは、前述の内容を踏まえて、FIFOキューを導入することになった背景とその結果について紹介していきます。

構成:新規開発プロダクトのアーキテクチャ

まず前提として、今回新規開発を行っているアプリケーションの構成について説明します。

インフラ基盤にはAmazon EKS(Amazon Elastic Kubernetes Service)を採用しており、本番環境では2台のコンシューマ構成(=同一アプリケーションを2インスタンス稼働させるスケールアウト構成)で運用しています。

当初はAmazon SQSの標準キューを使用し、負荷の高い非同期処理を別サービスへオフロードする構成を採用していました。動作としては、コンシューマが一定間隔でAmazon SQSをポーリングし、メッセージを取得して処理を行う仕組みです。

大まかな構成図

この構成自体は当初問題なく動作していましたが、運用を続ける中で次のような現象が確認されました。

問題:本番環境で発生していたエラー

Amazon SQSの標準キューは、「メッセージの順序が保証されない」「同一メッセージが重複して配信される可能性がある」という仕様を持っています。

この仕様により、本番環境では同一メッセージが複数のコンシューマに配信されるケースが発生していました。その結果、2台のコンシューマが同じメッセージを処理しようとし、データベースでエラーが発生していました。

この問題は本番環境特有のタイミングでのみ発生し、デバッグやログ追跡も難しく、再現性が非常に低いものでした。
つまり、「いつ・どこでエラーが起きるのか」が分からず、根本原因の特定が困難な問題でした。

方針:原因調査と仮説立て

当初は、直接の原因は「標準キューの仕様による重複配信」による楽観ロック競合(Optimistic Lock Exception)が発生するケースだと考えていました。
もともと、同一メッセージを複数キュー内に入れないことを目的としてFIFOキューの導入を行おうとしており、FIFOキューを導入すれば重複排除の機能によって楽観ロック競合は解消されると想定していました。

しかし、実際にFIFOキューを導入しても同様のエラーは解消されませんでした。

ログ解析の結果、当初の仮説である「標準キューの仕様による重複配信」に加え、「可視性タイムアウトの設定値」が原因の一因である可能性が浮上しました。

可視性タイムアウトの問題

可視性タイムアウト[3]とは、コンシューマがメッセージを受信したあと、一定時間そのメッセージを他のコンシューマから見えなくするための仕組みです。

この時間内に処理が完了すれば問題ありませんが、処理が可視性タイムアウトを超過すると、Amazon SQSはそのメッセージを再びキュー上で可視状態に戻します。
その結果、別のコンシューマが同じメッセージを再度受信し、重複処理が発生する可能性があります。

今回のケースでは、以下のような流れで問題が発生していました。

  1. メッセージが送信される
  2. コンシューマ1がメッセージをポーリングして受信
  3. 可視性タイムアウトが30秒を経過し、可視状態に戻る
  4. コンシューマ2が同じメッセージを再度受信
  5. コンシューマ1の処理結果をDBに保存
  6. コンシューマ2の処理結果をDBに保存し、楽観ロック競合が発生

実装:FIFOキューの導入と設定調整

これらの分析を踏まえ、単に標準キューからFIFOキューへ切り替えるだけでなく、「どのように FIFOキューを設計し、運用へ導入するか」に重点を置いて対応を行いました。

主な変更点[4]

  • Amazon SQS(キュー設定)

    • FIFOキューを新規作成。
    • 可視性タイムアウトの延長[5]
      • 実際の処理時間を計測し、必要十分な時間を持たせた値に調整。
      • (デフォルト:30秒 → 適正値へ延長)
    • 配信遅延の設定。
      • 即時配信により、プロデューサがデータベースへのINSERTを実行中に、コンシューマがそのレコードをUPDATEしようとしてロールバックされる問題を回避。
      • (デフォルト:即時配信 → 5秒遅延へ変更)
  • アプリケーション(サーバー)

    • messageGroupIdの指定を追加。
      • FIFOキューでは設定必須であり、グループID単位でメッセージの順序が保証される。
    • Amazon SQSエンドポイントを標準キューからFIFOキューに切り替え。

検証:動作確認プロセス

設定変更後、以下の2段階で検証を実施しました。

1. 開発環境
ローカル環境からAmazon SQSに接続し、FIFOキューの疎通確認、メッセージ順序保証、および重複排除の動作を検証しました。

この際、Amazon SQSと互換性のあるオープンソースのメッセージキューシステムであるElasticMQを使用し、ローカル環境でもFIFOキューを用いた非同期処理を再現できるよう構築を行いました。

2. 検証環境(ステージング)
自動化したツールを用いて、数百件規模のメッセージ送受信テストを実施しました。

検証の過程で、FIFOキューのデフォルト設定(即時配信)により、プロデューサがデータベースへのINSERTを実行中に、コンシューマがそのレコードをUPDATEしようとしてしまうケースが発生し、結果としてロールバックが発生しました。
このような競合を避けるため、Amazon SQSの配信遅延を設定し、トランザクション完了後にメッセージが配信されるよう調整したところ、安定動作を確認できました。
(プロデューサのINSERTとコンシューマのUPDATEのタイムラグは、わずか100ミリ秒ほどだったため、理論上は起こりうるものの、実際に配信遅延の設定で問題が解決したのは正直驚きでした。)

結果:本番環境で安定稼働

上記の検証を全て行なった上で本番環境にリリースを実施しました。
その後、本番環境での楽観ロック競合は一切発生していません。
FIFOキューの導入により、メッセージ処理の一意性が担保され、可視性タイムアウトの最適化により重複処理の再発も防止できました。
結果として、非同期処理全体の信頼性が大幅に向上しました。

おわりに

今回が初めての執筆となりましたが、記事を書く中で改めて多くの気づきを得ることができました。

例えば、本番環境で発生したエラーへの対応については、FIFOキューを導入する以外にも、
データの整合性を保つために楽観ロックから悲観ロックへの切り替えを検討することで解消できた可能性がある点です。

また、Amazon SQSからメッセージを1件ずつ取得するのではなく、最大10件をまとめて受信し、マルチスレッドや非同期処理を用いて複数メッセージを同時に処理することで、スループットの向上や待機時間の短縮といったパフォーマンス改善が期待できたのではないかという点も挙げられます。(後に実装しました。)

そして、Amazon SQSに詳しい方ならご存じの通り、標準キューの仕様による重複配信は確かに発生し得るものの、実際にはごくまれに起こる現象です[6]。つまり、今回の主なエラー要因は「重複配信」そのものではなく、可視性タイムアウトの設定値にあったと言えます。

結果的に、今回導入したFIFOキューによって問題は解消しましたが、設定を適切に見直せば、例外をゼロにはできないものの標準キューでも同様の解決が可能だったかもしれないという点にも気づきを得ました。

本番環境特有のタイミングでのみ発生する不具合に対して、的確な原因を突き止め、再現性を持って解決策を見出すことの難しさを改めて実感しました。
こうした課題は、今後もエンジニアとしてずっと向き合い続けていくものだと思います。

この記事を通して、ウェルスナビの新卒エンジニアがどのような挑戦や取り組みを行ってきたのかを知っていただくとともに、同じ課題に取り組む方々の参考になれば幸いです。

最後まで読んでいただき、本当にありがとうございました。

脚注
  1. Amazon Simple Queue Serviceとは? - Amazon Simple Queue Service ↩︎

  2. Amazon SQS と処理の重複 後編 ~ FIFO キューの特徴 ↩︎

  3. Amazon SQS と処理の重複 前編 ~ 可視性タイムアウトの役割 ↩︎

  4. Amazon SQS の可視性タイムアウト - Amazon Simple Queue Service ↩︎

  5. create-queue — AWS CLI 2.31.38 Command Reference ↩︎

  6. Amazon SQS の少なくとも 1 回の配信 - Amazon Simple Queue Service ↩︎

WealthNavi Engineering Blog

Discussion