こんにちは。 NewsPicksエンジニアの美濃部です。
NewsPicksではAWSをインフラ基盤として利用しているのですが、この記事では実際に行ったSQSのコスト削減の話をしたいと思います。
結論から話すとタイトルにもありますが「SQSでメッセージを受信する時にはショートポーリングではなくロングポーリングにした方が大体においてコストが下がります」という内容です。
この話の発端はコスト削減の余地がないかを検討していてSQSのコストが高すぎないかと感じたところから調査が始まりました。SQSはAPIリクエスト数に応じた従量課金なのですがAPIリクエスト数を確認するとやはり多すぎると感じました。(ここはかなり感覚的なところなので何をもって多いと判断したかと言われると困るのですが)
NewsPicksでは「SQS+ワーカープロセス」で非同期処理を行うシステムがいくつかあり、これはよくあるワーカープロセスがループ内の処理でSQSのキューにあるメッセージを受信してメッセージを処理するというものです。
いくつかのワーカープロセスのコードを確認したところループ内でスリープ的な処理はなく異常な程のリクエストがSQSに送信されている事がわかりました。 とはいえスリープを入れるというのも違う気がしたので公式ドキュメントを確認したところ Amazon SQS コストの削減の記事に辿り着きました。
この記事の中の以下が重要なポイントになります。
Amazon SQS の使用コストを削減し、空のキューでの空の受信数 (メッセージを返さない ReceiveMessage アクションへのレスポンス) を減らすには、ロングポーリングを有効にします
メッセージの受信方法にはショートポーリングとロングポーリングの2つの方法があり、これは各キューの設定にあるメッセージ受信待機時間(ReceiveMessageWaitTimeSeconds)によって決まります。この値が0秒であればショートポーリング、1秒以上であればロングポーリングになります。
SQSにおけるショートポーリングとロングポーリングについて簡単に説明しておきます。
ショートポーリング
リクエストに対してキューにメッセージが存在してもしなくてもレスポンスをすぐに返すタイプの受信方法です。
ショートポーリングが悪というわけではないのですが、ワーカープロセスが本来やりたい事はメッセージを受信して処理する事なので、キューにメッセージが存在しなくてもレスポンスが即座に返ってくるのは嬉しくないわけです。しかもそのリクエストでも課金されてしまうわけですからなおさら嬉しくないです。
この「キューにメッセージが存在しなくてもレスポンスが即座に返ってくる」無駄なリクエストをできるだけなくす為には次に説明するロングポーリングで受信する必要があります。
ロングポーリング
リクエストに対して返却するデータがない場合は一定時間(最大でメッセージ受信待機時間まで)はSQS側でブロックしてレスポンスを返しません。キューにメッセージが存在しない場合、クライアントからするとなかなかレスポンスが返ってこないなという状況になります。 この待機時間の間でキューのメッセージが利用可能になった瞬間にそれを検知してレスポンスを返します。メッセージ受信待機時間まで必ずブロックしているわけではないという事がポイントです。
大抵の場合(メッセージがキューに溜まるタイミングにもよりますが)ロングポーリングの方がメッセージを受信するのが早いです。なぜならキューにメッセージが入った瞬間に、ロングポーリングであれば既にSQSとのTCPコネクションが確立されている確率が高くなるからです。
よって今回のようなワーカープロセスのループ内でSQSからメッセージを受信するようなケースではあえてショートポーリングにするメリットはないと思うのでロングポーリングにした方が良いと考えます。
ロングポーリング設定にしてコストを99%削減
実際に各キューのメッセージ受信待機時間を確認したところほぼ全てのキューの設定が0秒になっておりショートポーリングでメッセージ受信リクエストを行なっていた事がわかりました。
実際の対応としては全てのキューのメッセージ受信待機時間を最大の20秒に設定しただけなのですがAPIリクエスト数が激減し、結果的にコストを99%削減できました。嘘みたいな削減率ですが実際そういう結果になりました。
やった事は各キューに対して以下を実行しただけです。
aws sqs set-queue-attributes \ --queue-url ${SQS_QUEUE_URL} \ --attributes ReceiveMessageWaitTimeSeconds=20
放っておくとショートポーリング設定のキューが増えてしまう
全てのキューのメッセージ受信待機時間を最大の20秒にして一時的にコスト削減できたとしても、日々開発は行われ必要に応じてキューも増えていきます。キューのメッセージ受信待機時間のデフォルト値が0秒なので、キュー作成時に今回の件を意識していない限りはデフォルト値のまま作成されてしまい結果的にショートポーリングで受信するワーカープロセスが増えてAPIリクエストが増加していきます。
これをキューを作成する人に常に意識させるのも難しいので仕組みで解決しないといけません。
仕組みについてはいろいろ手法があると思いますが今であればConfigサービスを利用して新規キュー作成時、およびキュー設定変更時のイベントを受け取ってメッセージ受信待機時間を確認し、20秒でなければ20秒に更新するLambda関数を用意しておくなどで解決できそうです。
もしくは定期的に全てのキューのメッセージ受信待機時間を20秒にする処理を実行するのもありだと思います。
まとめ
今回SQSのコスト削減のお話をさせて頂きましたが、このような手間をかけずに効果絶大なものはすぐにでもやっていきたいところです。長期で見ればかなりのコスト削減になります。
またAWSではクラウド上で適切な設計判断を行えるガイドライン的な位置づけとしてWell-Architectedフレームワークというものを用意しており、そこに掲げられているフレームワークの 5 本の柱の1つに コスト最適化があります。 ここでは「最も低い価格でシステムを運用してビジネス価値を実現する能力」について記載されています。
よってコスト削減を行う場合には基本的にまず公式ドキュメントを見てその通りに実行するのが一番の近道だと思いますので是非参考にして頂ければと思います。