TL;DR
SQSのFIFOは、「メッセージの先頭から順番にconsumerにわたるよ」くらいの認識でいると痛い目にあう。
正確には
メッセージの先頭から順番にconsumerが処理し終わる(諦めてDeadletterQueueに移動することも含む)
ことを保証している。
ドキュメント
https://dev.classmethod.jp/articles/sqs-new-fifo/ をなんとなく読んでいて以下の記述が気になった。
グループの先頭しかメッセージを取れませんので、100万件のメッセージが入っていても、グループが1つであれば、ワーカは2つ以上あっても意味がありません。例えば、メッセージの重複だけを排除したいだけで、順序は必要なく、高いスループットでワーカーによる処理を行いたい場合は、メッセージ毎にユニークなUUID等でグループIDを付ければOKです。
「ワーカは2つ以上あっても意味がありません」とは?
AWSの主張をみてみると、
Messages that belong to the same message group are always processed one by one, in a strict order relative to the message group (however, messages that belong to different message groups might be processed out of order).
とあり、processed one by one
とある。
さらに、
When messages that belong to a particular message group ID are invisible, no other consumer can process messages with the same message group ID.
とある。
つまり、「メッセージの先頭から順番にconsumerが処理し終わる」ことを保証していると理解した。
検証
とても簡単にできる。
前提として、
- FIFO
- DeadletterQueueが設定され、最大受信数10
- 可視性タイムアウト 1分
のキューを想定する。consumerがlong pollingでずっとメッセージをpollingしている状況を想定する。
Invalidなメッセージでconsumerがエラーをはいて処理をしない(キューから削除しない)メッセージAと正常なメッセージB/C/Dを用意する。
AWSのコンソール画面にて、
- 00:00にメッセージグループID
xxx
、メッセージ重複排除 ID1
にてメッセージAを配信 - 直後に以下の順で配信
- メッセージグループID
xxx
、メッセージ重複排除 ID2
にてメッセージBを配信 - メッセージグループID
xxx
、メッセージ重複排除 ID3
にてメッセージCを配信 - メッセージグループID
yyy
、メッセージ重複排除 ID4
にてメッセージDを配信
- メッセージグループID
メッセージ重複排除 IDは今回の検証では重複させたくないので全て異なる値にしている。
この場合、consumerはどのように処理をするのか想像してみよう!
正解は
- メッセージAは00:00,00:01,00:02,00:03,00:04,00:05,00:06,00:07,00:08,00:09と10回consumeしてどれもエラーを吐いておわる
- メッセージDは00:00にすぐに処理終了
- メッセージB -> メッセージCの順に00:10に処理される。00:10まではconsumerからはメッセージは見えない。
ドキュメントを注意深く読めば予想通りの結果になるかと思う。
対策
メッセージグループIDを適切に設計しましょうにつきる。
ほとんどの場合でユーザーID(ユーザを特定するキー)を指定するか、乱数を指定してしまえば要件を満たせるのでは。
後者の場合はFIFOキューによってメッセージが重複して配信されないという恩恵のみうけることになる。前者の場合は「入金イベント」「出金イベント」の二つのイベントに対して、1ユーザに対しては順番が保証されているので期待する挙動となる。
一方、誤って「入金イベント」を意味するUserCharged
みたいなイベント名をメッセージグループIDにしてしまうと、ある処理が詰まると全ての「入金イベント」が処理できなくなり地獄を見るだろう。