Buri Memo:

アイデアや気づきとかが雑に書き殴られる

@golevelup/nestjs-rabbitmq で実装した consumer が queue を消化できなくなった

このライブラリで作成した consumer が queue を消化できなくなり、メモリ使用率がじわじわと上昇し(300MB/8h 位の速度) OOM で停止してしまった。
RabbitMQ の GUI やサーバーのメトリクスを見ると以下のような状況になっていた。

  • 起動しているすべての consumer が Unacked メッセージを持った状態で止まっている
  • Redelivered が 1,000~2,000/s になっている
  • RabbitMQ ・ consumer 間のネットワーク通信量が大きく上がった

github.com

原因

  1. JSON として不正なメッセージを consumer が受け取る
  2. ライブラリ側のデシリアライズが失敗した際、nack されメッセージが queue の先頭に戻される
  3. requeue されたメッセージを再び consumer が受け取る
  4. [1] へ戻る、を永遠に繰り返す

このようなループを繰り返したことで queue の消化ができなくなってしまっていた。
メモリが上昇した理由は分からない。ループによるリソースの消費が速すぎて、ガベージコレクションが間に合わなかったとかかもしれない。

対策

No way to handle failed serialization (non-json) · Issue #137 · golevelup/nestjs · GitHub で説明されていた。

@golevelup/nestjs-rabbitmq@1.15.0 以上で追加された allowNonJsonMessages を true に設定することで JSON として不正なメッセージをハンドラが受け取れるようになる。それを basic.ack で消化してしまったり、basic.reject で削除したり、デッドレターキューに移動することで requeue ループを防ぐ。

@RabbitSubscribe({
    exchange,
    routingKey: [nonJsonRoutingKey],
    queue: 'subscribeQueue',
    allowNonJsonMessages: true,
})