2026年5月30日

2026年5月30日

AWS SQSでメッセージキューを設定する方法|可視性タイムアウト・DLQ・WordPressの重い処理を非同期にする

Amazon SQS(Simple Queue Service)でメッセージキューを使うには、キューを作って「可視性タイムアウト」「ロングポーリング」「デッドレターキュー(DLQ)」の3つを用途に合わせて設定し、送る側はメッセージを入れるだけ、受け取る側(ワーカー)は取り出して処理し終えたら削除する、という流れを作ります。この記事では、WordPressで時間のかかる処理をリクエストから切り離す例を使って、キューの種類の選び方、設定値の意味、PHPからの送信、WP-CLIで動くワーカーの書き方、失敗したメッセージの扱いまでを説明します。

設定の上限値や既定値は見直されることがあるため、数値は公式ドキュメントの最新の記載(2026年10月現在)で確認してから決めてください。

なぜキューを挟むのか

WordPressで投稿を公開したときに外部サービスへデータを送る、画像を加工する、大量のメールを配る、といった処理をその場で実行すると、管理画面の応答が遅くなり、外部サービスが落ちていれば公開操作そのものが失敗します。

キューを挟むと、WordPressは「この処理をしてほしい」というメッセージをSQSに入れた時点で応答を返せます。実際の処理は別のワーカーが後から行い、失敗しても再試行できます。WP-Cronでも似たことはできますが、WP-Cronはサイトへのアクセスをきっかけに動くため、アクセスが少ないと処理が遅れ、複数台構成では同じ処理が重なることがあります。処理量が多い、失敗時に再試行させたい、複数台のサーバーで分担したい、といった場合にSQSが向いています。

スタンダードキューとFIFOキューの選び方

項目 スタンダード FIFO
届く回数 少なくとも1回(まれに重複する) 重複排除の仕組みがある
順序 保証されない メッセージグループ内で保証される
名前 自由 末尾が .fifo
向いている処理 順番に意味がない処理の大半 同じ注文の処理を順番どおりに行う、など

迷ったらスタンダードを選び、ワーカー側で「同じメッセージを2回処理しても結果が変わらない」作りにしておくのが基本です。これを冪等(べきとう)性といいます。FIFOを選んでも、ワーカーが処理途中で落ちれば同じメッセージは再び取り出されるので、冪等に作る必要があることは変わりません。

キューを作る:3つの設定値の意味

可視性タイムアウト

ワーカーがメッセージを受け取ると、そのメッセージは一定時間ほかのワーカーから見えなくなります。この時間が可視性タイムアウトです。時間内に削除されなければ、処理に失敗したとみなされ、再び取り出せる状態に戻ります。

ここが短すぎると、処理中のメッセージが別のワーカーにも渡って二重処理になります。1件の処理にかかる最長時間より十分長くするのが目安です。処理時間がばらつく場合は、処理の途中で ChangeMessageVisibility を呼んで延長する方法もあります。

ロングポーリング

受信の待ち時間(ReceiveMessageWaitTimeSeconds)を最大の20秒にしておくと、メッセージが無いときにSQSが少し待ってから応答します。空の応答が減り、APIの呼び出し回数と費用を抑えられます。特に理由が無ければ20秒にしておきます。

デッドレターキュー(DLQ)

何度処理しても失敗するメッセージ(データが壊れている、対象の投稿が削除済み、など)は、そのままでは延々と再試行されます。受信回数が上限(maxReceiveCount)を超えたメッセージを別のキューに移す設定がDLQです。先にDLQを作り、そのARNを本体のキューに指定します。

aws sqs create-queue --queue-name wp-jobs-dlq --region ap-northeast-1
aws sqs create-queue --queue-name wp-jobs --region ap-northeast-1 \
  --attributes file://wp-jobs-attributes.json
{
  "VisibilityTimeout": "120",
  "ReceiveMessageWaitTimeSeconds": "20",
  "MessageRetentionPeriod": "1209600",
  "RedrivePolicy": "{\"deadLetterTargetArn\":\"arn:aws:sqs:ap-northeast-1:123456789012:wp-jobs-dlq\",\"maxReceiveCount\":\"5\"}"
}

この例では、可視性タイムアウトを120秒、ロングポーリングを20秒、メッセージの保持期間を14日(1209600秒)、5回受信して削除されなければDLQへ移す、という設定にしています。RedrivePolicy の値はJSONを文字列にしたもので、内側の引用符をエスケープする必要がある点に注意してください。アカウントIDは例です。

DLQの保持期間は、本体のキューと同じかそれより長くしておきます。スタンダードキューでは、保持期間は最初に本体のキューに入った時刻から数えられるため、DLQのほうが短いと、移った直後に期限切れで消えることがあります。

WordPressからメッセージを送る

AWS SDK for PHPを使い、投稿が公開されたときにジョブを1件入れる例です。キューのURLは wp-config.php の定数で渡します。

<?php
/**
 * Plugin Name: Queue jobs to SQS
 */
$wpdr_sdk = WP_CONTENT_DIR . '/mu-plugins/sqs-jobs/vendor/autoload.php';
if ( ! file_exists( $wpdr_sdk ) || ! defined( 'WPDR_SQS_QUEUE_URL' ) ) {
	return;
}
require_once $wpdr_sdk;

function wpdr_sqs_client() {
	return new Aws\Sqs\SqsClient( array(
		'region'  => 'ap-northeast-1',
		'version' => 'latest',
	) );
}

add_action( 'transition_post_status', function ( $new_status, $old_status, $post ) {
	if ( 'publish' !== $new_status || 'publish' === $old_status || 'post' !== $post->post_type ) {
		return;
	}
	try {
		wpdr_sqs_client()->sendMessage( array(
			'QueueUrl'    => WPDR_SQS_QUEUE_URL,
			'MessageBody' => wp_json_encode( array(
				'type'    => 'post_published',
				'post_id' => (int) $post->ID,
			) ),
		) );
	} catch ( Exception $e ) {
		error_log( 'SQS send failed: ' . $e->getMessage() );
	}
}, 10, 3 );

メッセージには投稿IDのような「処理対象を特定する最小限の情報」だけを入れ、本文や個人情報は入れません。ワーカーは受け取ったIDでデータベースから最新の内容を読み直します。こうするとメッセージのサイズ上限を気にせずに済み、キューに機密情報が残ることも避けられます。大きなデータを渡したいときは、データをS3に置いてそのキーだけを送るのが定石です。

WP-CLIでワーカーを動かす

ワーカーはWordPressの関数を使えると便利なので、WP-CLIのカスタムコマンドとして作ります。先ほどのmu-pluginの末尾に追加する例です。

if ( defined( 'WP_CLI' ) && WP_CLI ) {
	WP_CLI::add_command( 'wpdr-queue work', function ( $args, $assoc_args ) {
		$sqs       = wpdr_sqs_client();
		$max_loops = isset( $assoc_args['max-loops'] ) ? absint( $assoc_args['max-loops'] ) : 50;

		for ( $i = 0; $i < $max_loops; $i++ ) {
			$result   = $sqs->receiveMessage( array(
				'QueueUrl'            => WPDR_SQS_QUEUE_URL,
				'MaxNumberOfMessages' => 10,
				'WaitTimeSeconds'     => 20,
			) );
			$messages = $result->get( 'Messages' ) ?: array();

			foreach ( $messages as $message ) {
				$job = json_decode( $message['Body'], true );
				try {
					wpdr_handle_job( $job );
					// 処理が成功したときだけ削除する
					$sqs->deleteMessage( array(
						'QueueUrl'      => WPDR_SQS_QUEUE_URL,
						'ReceiptHandle' => $message['ReceiptHandle'],
					) );
				} catch ( Exception $e ) {
					// 削除しなければ可視性タイムアウト後に再試行され、上限を超えると DLQ へ移る
					WP_CLI::warning( 'job failed: ' . $e->getMessage() );
				}
			}
		}
	} );
}

function wpdr_handle_job( $job ) {
	if ( ! is_array( $job ) || empty( $job['type'] ) ) {
		throw new Exception( 'invalid message' );
	}
	if ( 'post_published' === $job['type'] ) {
		$post_id = absint( $job['post_id'] );
		if ( get_post_meta( $post_id, '_wpdr_synced', true ) ) {
			return; // 処理済みなら何もしない(重複して届いても安全にする)
		}
		// ここで外部サービスへの送信などを行う
		update_post_meta( $post_id, '_wpdr_synced', time() );
	}
}

要点は「処理が終わってから削除する」順番です。また、1回に最大10件を受け取る場合は、10件すべての処理が可視性タイムアウト内に終わる必要があります。間に合わない処理なら、受け取る件数を減らすか、タイムアウトを延ばします。先に削除してから処理すると、処理中にワーカーが落ちたときにメッセージが失われます。また、処理済みの印を投稿メタに残しておくことで、同じメッセージが2回届いても二重に処理しません。

ワーカーはsystemdなどで常駐させます。PHPを長時間動かし続けるとメモリが膨らむことがあるため、例では一定回数ループしたら終了し、systemdに再起動させる作りにしています。

# /etc/systemd/system/wp-queue-worker.service
[Unit]
Description=WordPress SQS worker
After=network-online.target

[Service]
User=www-data
WorkingDirectory=/var/www/html
ExecStart=/usr/local/bin/wp wpdr-queue work --max-loops=50
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

ユーザー名、パス、wp コマンドの場所は環境によって違うので合わせてください。sudo systemctl enable --now wp-queue-worker で起動し、journalctl -u wp-queue-worker -f でログを確認できます。

権限を分ける

送る側(Webサーバー)と受け取る側(ワーカー)では必要な権限が違います。EC2ならIAMロールで渡し、アクセスキーをコードに書かないようにします。

  • 送る側:sqs:SendMessage
  • 受け取る側:sqs:ReceiveMessage、sqs:DeleteMessage、sqs:ChangeMessageVisibility、sqs:GetQueueAttributes

どちらもリソースを対象のキューのARNに絞ります。Webサーバーが乗っ取られた場合でも、キューのメッセージを読んだり消したりできない状態にしておけます。

動作の確認と運用

  • キューの状態を見る:aws sqs get-queue-attributes --queue-url <URL> --attribute-names ApproximateNumberOfMessages ApproximateNumberOfMessagesNotVisible で、待っている件数と処理中の件数が分かります。待ち件数が増え続けるならワーカーが追いついていないか、止まっています。
  • DLQを監視する:DLQの件数が0より大きくなったらCloudWatchアラームで通知するようにしておくと、失敗に早く気づけます。
  • DLQから戻す:原因を直したら、コンソールのDLQ再処理(リドライブ)の機能で本体のキューへ戻せます。原因を直さずに戻すと、また同じように失敗してDLQに戻ってくるだけです。

キューの削除や設定変更は、処理待ちのメッセージを失う原因になります。本番のキューを触る前に、ステージング用に同じ設定のキューを作って試してください。

まとめ

  • 迷ったらスタンダードキューを選び、ワーカーは同じメッセージを2回処理しても問題ない作りにする。
  • 可視性タイムアウトは処理時間より長く、ロングポーリングは20秒、失敗が続くメッセージはDLQへ移す。
  • メッセージには投稿IDなど最小限の情報だけを入れ、個人情報や大きなデータは入れない。
  • ワーカーは処理に成功してからメッセージを削除する。WP-CLIのコマンドにしてsystemdで常駐させると扱いやすい。
  • 送る側と受け取る側でIAMの権限を分け、DLQの件数を監視する。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲