2026年6月2日

2026年6月2日

RedisのPub/Subでリアルタイム通信を実装する方法(WordPress連携の例つき)

RedisのPub/Subは、発行側(PUBLISH)がチャンネルに送ったメッセージを、その瞬間に購読している側(SUBSCRIBE)へ即座に配る仕組みです。この記事では、コマンドで動きを確認したうえで、WordPressの投稿公開をRedisに発行し、Node.jsのWebSocketサーバー経由でブラウザへ通知する構成を組み立てます。あわせて「メッセージが消える」「接続が詰まる」といった、本番でつまずきやすい点と、Streamsとの使い分けも整理します。

Pub/Subの仕組みと向いている用途

Pub/Subの登場人物は3つです。メッセージを送る発行者、チャンネル名、そのチャンネルを購読している購読者です。発行者は購読者が誰なのかを知らず、購読者も発行者を知りません。両者はチャンネル名だけでつながるため、送る側と受け取る側を別々のプロセスや言語で作れるのが利点です。

一方で、Pub/Subには「保存しない」という大きな性質があります。PUBLISHされた時点で接続している購読者にだけ届き、その場にいなかった購読者には後から届きません。Redisが再起動した場合も、配信待ちのメッセージは残りません。

この性質から、向いている用途は次のようなものです。

  • 新着記事やコメントの「お知らせ」表示など、取りこぼしても致命的でない通知
  • 複数サーバー間でのキャッシュ削除の合図(各サーバーが受け取ったら手元のキャッシュを捨てる)
  • チャットやダッシュボードの画面更新のきっかけ

逆に、注文処理やメール送信のように「一件も落としてはいけない」処理には向きません。その場合は後半で触れるRedis Streamsやジョブキューを検討してください。

redis-cliで動きを確認する

まずはターミナルを2つ開き、手で動きを見ておくと、後のコードで問題が起きたときに切り分けやすくなります。1つ目のターミナルで購読を始めます。

redis-cli
127.0.0.1:6379> SUBSCRIBE news

2つ目のターミナルから発行します。

redis-cli PUBLISH news "hello"

PUBLISHの戻り値は、そのメッセージを受け取った購読者の数です。0が返る場合は、チャンネル名の綴り違いか、購読側が別のRedisインスタンスやデータベースにつないでいる可能性が高いです。なお、Pub/SubはSELECTで選んだデータベース番号とは関係なく、インスタンス全体で共有されます。

ワイルドカードで購読したい場合はPSUBSCRIBEを使います。PSUBSCRIBE wp:* とすれば wp:new-post や wp:comment をまとめて受け取れます。現在どのチャンネルに購読者がいるかは、次のコマンドで確認できます。

redis-cli PUBSUB CHANNELS "wp:*"
redis-cli PUBSUB NUMSUB wp:new-post

注意点として、SUBSCRIBE中の接続は購読専用の状態になり、RESP2プロトコルでは購読関連のコマンドやPINGなど一部しか受け付けません。アプリケーションでは、通常のGET/SETに使う接続と、購読用の接続を分けるのが基本です。

構成:WordPressは「発行」だけを担当する

PHPのリクエストは処理が終われば終了するため、WordPressの中でSUBSCRIBEを待ち続けるのは不向きです。そこで役割を次のように分けます。

  1. WordPress(PHP): 投稿が公開されたらRedisにPUBLISHする
  2. Node.jsの常駐プロセス: Redisを購読し、受け取った内容をWebSocketで配る
  3. ブラウザ: WebSocketにつなぎ、届いた通知を画面に表示する

この形なら、WordPress側の改修は数十行で済み、リアルタイム部分は常駐プロセスに任せられます。

WordPress側:mu-pluginでPUBLISHする

テーマのfunctions.phpに書くとテーマ変更で消えるため、wp-content/mu-plugins/ に置くのが扱いやすい方法です。ここではPHP拡張のphpredisを使います。作業前にサイトのバックアップを取り、ステージング環境で試してください。

<?php
/**
 * Plugin Name: Publish new posts to Redis
 */
add_action( 'transition_post_status', function ( $new_status, $old_status, $post ) {
	// 新しく公開された「投稿」だけを対象にする(更新のたびに通知しない)
	if ( 'publish' !== $new_status || 'publish' === $old_status || 'post' !== $post->post_type ) {
		return;
	}
	if ( ! class_exists( 'Redis' ) ) {
		return;
	}

	try {
		$redis = new Redis();
		$redis->connect( '127.0.0.1', 6379, 1.0 ); // 1秒で接続タイムアウト
		if ( defined( 'MY_REDIS_PASSWORD' ) ) {
			$redis->auth( MY_REDIS_PASSWORD );
		}
		$payload = wp_json_encode( array(
			'id'    => $post->ID,
			'title' => wp_strip_all_tags( get_the_title( $post ) ),
			'url'   => get_permalink( $post ),
		) );
		$redis->publish( 'wp:new-post', $payload );
	} catch ( RedisException $e ) {
		// Redisが止まっていても投稿の公開は止めない
		error_log( 'Redis publish failed: ' . $e->getMessage() );
	}
}, 10, 3 );

ポイントは3つあります。1つ目は、transition_post_status で「公開以外から公開へ」変わった瞬間だけを拾うことです。save_post を使うと下書き保存や更新のたびに通知が飛びます。2つ目は、接続タイムアウトを短くし、例外を握りつぶさずログに残すことです。Redisが落ちているときに投稿画面が固まるのを防げます。3つ目は、パスワードを wp-config.php の定数(例では MY_REDIS_PASSWORD)に置き、コードに直接書かないことです。

Node.js側:購読してWebSocketで配る

Node.jsでは公式クライアントの redis(node-redis)と、WebSocketライブラリの ws を使う例です。node-redisのv4以降では、購読用に専用の接続を用意する書き方になります。

// server.mjs
import { createClient } from 'redis';
import WebSocket, { WebSocketServer } from 'ws';

const subscriber = createClient({ url: process.env.REDIS_URL || 'redis://127.0.0.1:6379' });
subscriber.on('error', (err) => console.error('Redis error', err));
await subscriber.connect();

// 外部からは直接つながせず、Nginxなどのリバースプロキシ経由で公開する
const wss = new WebSocketServer({ host: '127.0.0.1', port: 8080 });

await subscriber.subscribe('wp:new-post', (message) => {
  for (const client of wss.clients) {
    if (client.readyState === WebSocket.OPEN) {
      client.send(message);
    }
  }
});

このプロセスはsystemdやPM2などで常駐させ、落ちたら自動で再起動されるようにしておきます。ブラウザからはHTTPSのページ上で wss:// でつなぐ必要があるため、NginxなどでTLSを終端し、/ws/ のようなパスをこのポートへプロキシするのが一般的です。

ブラウザ側:届いた通知を表示する

const ws = new WebSocket('wss://example.com/ws/');
ws.addEventListener('message', (event) => {
  const data = JSON.parse(event.data);
  const link = document.createElement('a');
  link.href = data.url;
  link.textContent = '新着記事: ' + data.title; // innerHTMLは使わずテキストとして挿入
  document.querySelector('#new-post-notice').replaceChildren(link);
});

届いた文字列をinnerHTMLで差し込むと、タイトルに含まれた文字列がHTMLとして解釈される恐れがあります。textContentで入れておけば、エスケープの心配を減らせます。このスクリプトはテーマやプラグインから wp_enqueue_script で読み込みます。

本番運用でつまずきやすい点

切断中のメッセージは失われる

Node.js側が再起動している数秒間に公開された投稿の通知は届きません。ブラウザ側では、再接続したときに最新記事をREST API(/wp-json/wp/v2/posts)から取り直すなど、「取りこぼしたら補う」仕組みを用意しておくと安心です。

遅い購読者は切断される

購読者の受信が追いつかないと、Redis側の出力バッファにメッセージがたまります。Pub/Sub用の上限は client-output-buffer-limit pubsub で決まっており、超えると購読者の接続が切られます。切断が続く場合は、購読側の処理を軽くする、メッセージを小さくする(IDだけ送って詳細は別途取得する)といった対策を検討します。

Redisを外部に公開しない

Pub/Subに限らず、Redisのポートをインターネットに開けるのは危険です。bind 127.0.0.1 などで待ち受けを限定し、protected-mode を有効のままにし、パスワードまたはACL(Redis 6以降)で認証をかけてください。別サーバーからつなぐ場合は、プライベートネットワークやTLSを使います。

Redis Clusterでのチャンネル

Redis Clusterでは通常のPUBLISHがクラスタ全体に広がるため、ノード数が増えると負荷も増えます。Redis 7.0以降にはシャードの中だけで配信する Sharded Pub/Sub(SPUBLISH / SSUBSCRIBE)があります。クラスタで大量に配信する場合は、こちらを検討する価値があります。

Pub/SubとStreamsの使い分け

「切断中のメッセージも後から受け取りたい」「処理済みかどうかを管理したい」という要件があるなら、Redis Streams(XADD / XREADGROUP / XACK)が候補です。Streamsはメッセージをデータとして保存し、コンシューマーグループごとにどこまで読んだかを記録できます。

判断の目安は次のとおりです。

  • 今つながっている人に知らせれば十分 → Pub/Sub
  • 後から読む人や、処理の確認が必要 → Streams
  • メール送信や外部API呼び出しなど重い処理の分散 → Streamsまたは専用のジョブキュー

なお、RedisはライセンスがRedis 7.4以降で変更され、その後Redis 8でAGPLv3の選択肢が追加されるなど変化が続いています。オープンソースのフォークであるValkeyも同じPub/Subコマンドを備えています。どれを使うかは、利用するレンタルサーバーやクラウドの提供状況とあわせて確認してください(2026年10月現在)。

まとめ

  • Pub/Subは「今つながっている購読者」に即座に配る仕組みで、メッセージは保存されない
  • redis-cliのSUBSCRIBE / PUBLISHで先に動きを確認すると、問題の切り分けが速くなる
  • WordPressは transition_post_status で発行だけを担当し、購読とWebSocket配信は常駐プロセスに任せる
  • 接続タイムアウトと例外処理を入れ、Redisが止まっても投稿公開を妨げないようにする
  • Redisは外部公開せず、認証をかける。取りこぼせない処理にはStreamsやジョブキューを使う

お気軽にご相談ください

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