Article
📬 SQSのロングポーリングをJavaで使う:空レスポンスと無駄なAPI呼び出しを減らす
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を繰り返しているなら、まずロングポーリングの設定を確認すると改善しやすいです。
参考
- Amazon SQS: Short and long polling
- Amazon SQS: Setting-up long polling
- Amazon SQS API: ReceiveMessage
- AWS SDK for Java 2.x: ReceiveMessageRequest
- AWS SDK for Java 2.x: Configure timeouts