0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SQSのロングポーリングをJavaで使う:空レスポンスと無駄なAPI呼び出しを減らす

0
Posted at

Amazon SQSのコンシューマーでは、メッセージがない状態でReceiveMessageを短い間隔で繰り返すと、空レスポンスが増えます。

この無駄を減らす仕組みが**ロングポーリング(Long Polling)**です。

ロングポーリングは、メッセージがない場合に即座に空レスポンスを返すのではなく、一定時間だけSQS側で待機してから結果を返します。

AWS公式ドキュメントでは、WaitTimeSecondsを0より大きくするとロングポーリングが有効になります。最大待機時間は20秒です。

ショートポーリングとの違い

WaitTimeSeconds = 0の場合はショートポーリングです。

ショートポーリング
Consumer -> ReceiveMessage -> SQS -> すぐ応答

ロングポーリングでは、メッセージが無ければ指定時間まで待ちます。

ロングポーリング
Consumer -> ReceiveMessage(waitTimeSeconds = 20) -> SQS
                                         ├─ メッセージ到着: すぐ返す
                                         └─ 到着しない: 最大20秒待つ

「20秒を指定したら必ず20秒待つ」わけではありません。待機中にメッセージが到着すれば、その時点でレスポンスが返ります。

Java SDK 2.xで実装する

SqsClientでは、ReceiveMessageRequestにwaitTimeSecondsを指定します。

import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.Message;
import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest;
import software.amazon.awssdk.services.sqs.model.ReceiveMessageResponse;

public class SqsConsumer {
    private final SqsClient sqsClient;
    private final String queueUrl;

    public SqsConsumer(SqsClient sqsClient, String queueUrl) {
        this.sqsClient = sqsClient;
        this.queueUrl = queueUrl;
    }

    public void pollOnce() {
        ReceiveMessageResponse response = sqsClient.receiveMessage(
                ReceiveMessageRequest.builder()
                        .queueUrl(queueUrl)
                        .waitTimeSeconds(20)
                        .maxNumberOfMessages(10)
                        .build()
        );

        for (Message message : response.messages()) {
            process(message);
        }
    }

    private void process(Message message) {
        System.out.println(message.body());
    }
}

waitTimeSeconds(20)は、メッセージがない場合に最大20秒待つ指定です。

maxNumberOfMessages(10)は、1回のReceiveMessageで最大10件まで返してよいという指定です。SQS APIの上限も10件です。

ロングポーリングで何が改善するのか

AWS公式ドキュメントでは、ロングポーリングには主に次の効果があります。

  • メッセージが無いときの空レスポンスを減らす
  • 不要なReceiveMessage呼び出しを減らす
  • false empty responseを減らす

false empty responseとは、キューにメッセージがあるのに、そのReceiveMessageでは空レスポンスになる状態です。

ショートポーリングはSQS内部の一部サーバーをサンプリングして確認します。ロングポーリングではより広く確認するため、この状態を減らせます。

継続的なワーカーではループする

実際のコンシューマーでは、1回受信して終わりではなく継続的にポーリングします。

while (!Thread.currentThread().isInterrupted()) {
    ReceiveMessageResponse response = sqsClient.receiveMessage(
            ReceiveMessageRequest.builder()
                    .queueUrl(queueUrl)
                    .waitTimeSeconds(20)
                    .maxNumberOfMessages(10)
                    .build()
    );

    for (Message message : response.messages()) {
        process(message);
    }
}

ロングポーリング自体が待機するため、空レスポンス時に短いsleepを入れて高頻度再試行するような設計は基本的に不要です。

キュー単位でも設定できる

ロングポーリングはリクエスト単位だけでなく、キュー属性ReceiveMessageWaitTimeSecondsでも設定できます。

aws sqs set-queue-attributes \
  --queue-url "$QUEUE_URL" \
  --attributes ReceiveMessageWaitTimeSeconds=20

このコマンドの意味は次の通りです。

  • aws: AWS CLIを実行する
  • sqs: Amazon SQSの操作を選ぶ
  • set-queue-attributes: 既存キューの属性を変更する
  • --queue-url: 対象キューのURLを指定する
  • ReceiveMessageWaitTimeSeconds=20: デフォルトの受信待機時間を20秒にする

毎回同じ待機時間を使うならキュー側に設定し、用途ごとに変えたい場合はリクエスト側のwaitTimeSecondsを使うと整理しやすくなります。

HTTPタイムアウトを短くしすぎない

ロングポーリングでよくあるハマりどころがHTTPクライアント側のタイムアウトです。

例えばSQS側に20秒待つよう指定していても、HTTP側が10秒でタイムアウトする設定なら、SQSが正常に待機している途中でクライアント側が失敗します。

AWS公式ドキュメントでも、HTTPレスポンスタイムアウトはWaitTimeSecondsより長く設定するよう案内されています。

WaitTimeSeconds = 20秒
HTTP response/read timeout > 20秒

AWS SDK for Java 2.xでは、API call timeoutやHTTPクライアントのtimeoutを明示的に設定できます。ロングポーリングの待機時間だけを変更して、HTTP側の設定を確認し忘れないことが重要です。

Visibility Timeoutとは別物

ロングポーリングとVisibility Timeoutは役割が違います。

設定 役割
Long Polling メッセージを受信するまでどれだけ待つか
Visibility Timeout 受信後、同じメッセージを他のワーカーからどれだけ隠すか
ReceiveMessage開始
  ↓ Long Polling
メッセージ受信
  ↓ Visibility Timeout
処理完了・DeleteMessage

ロングポーリングを20秒にしても、処理可能時間が20秒になるわけではありません。処理時間に合わせて設計するのはVisibility Timeout側です。

複数キューを1本の処理で順番に待たない

複数キューを扱う場合にも注意が必要です。

Queue Aを最大20秒待つ
↓
Queue Bを最大20秒待つ
↓
Queue Cを最大20秒待つ

このように1つの処理で順番に待つと、Queue Aが空の間にQueue Bへ届いたメッセージの処理開始が遅れる可能性があります。

AWS公式のベストプラクティスでは、複数キューをロングポーリングする場合、キューごとに別スレッドを使う方法が案内されています。

Javaでは専用ワーカー、Executor、非同期クライアントなどを使い、各キューを独立して待てる構成にすると扱いやすくなります。

20秒固定が常に正解ではない

AWSは多くの場合20秒のロングポーリングを推奨していますが、アプリケーションの停止要求へ素早く反応したい場合や、ネットワーク・プロキシの制約がある場合は短い値を選ぶこともあります。

一方で待機時間を極端に短くすると、空レスポンスとAPI呼び出しが増え、ロングポーリングのメリットが小さくなります。

まとめ

  • WaitTimeSeconds > 0でロングポーリングになる
  • 最大待機時間は20秒
  • メッセージ到着時は待機時間の途中でも返る
  • 空レスポンスと不要なAPI呼び出しを減らせる
  • HTTPタイムアウトはWaitTimeSecondsより長くする
  • Long PollingとVisibility Timeoutは別の設定
  • 複数キューは独立して待てる構成にする

SQSワーカーが空のReceiveMessageを繰り返しているなら、まずロングポーリングの設定を確認すると改善しやすいです。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?