最近PythonのTornadoというのを使っていて、はじめてノンブロッキングWebサーバをまともに触ったのですが、いままで自分がやってきたWebプログラミングとは結構違っていて、面白く感じました。
特に1つのアクセスが1つのクラスに対応するというのが面白かったです。これ説明したら「は?」って言われたんですけどね。
で、僕が管理しているサイトはほとんどWordPressなので、残念ながらいわゆる普通のPHPアプリケーションです。最近はBackbone.JSとかAngularJSでページ遷移なしにガンガンUIが変わるWebアプリケーションが流行りですが、そういうサイトは凄まじい数のAjaxリクエストが飛んでくるので、WordPressだとサーバがパンクしてしまいます。
特にWordPressのAjax APIは毎回WordPressの起動処理を行うので、大変遅いです。
じゃあNode.JSなりTornadoなりでサイトを作り直すのかというと、それもそれでメンドクサイですね。それに、データベース周りとか、せっかくWordPressの資産があるのだから、それを利用したいものです。
で、そういうノンブロッキングWebサーバになるPHPないかなーと探してみたところ、Reactというのがありました。

Reactを試してみる
このReactはComposerとかでサクッとインストールできます。Bring High Performance Into Your PHP App (with ReactPHP)っていう記事を見ながらやったところ、すぐできました。今回はこんな構成でインストールしてみます。
- ローカルのMacにはWordPressをインストール済み。ドメインは
takahashifumiki.infoで ルートディレクトリは/opt/local/www/fumiki - WebサーバはNginx + PHP-FPM
- Reactはルートディレクトリ直下の
reactにインストール
で、とりあえず動くとこまで持って行きます。
# とりあえずディレクトリに移動。 cd /opt/local/www/fumiki/react # composerでインストール composer require 'react/react=*’ # サーバ起動スクリプトを作る touch server.php
このserver.phpがメインになります。これを編集。上の参考サイトからまんまコピーして、コメントを追記してます。
<?php
// オートローダーを読み込み
require 'vendor/autoload.php’;
// アクセスごとにインクリメントしてく
$i = 0;
// メイン関数。アクセスごとにこれが起動。
$app = function ($request, $response) use (&$i) {
$response->writeHead(200, array('Content-Type' => 'text/plain'));
$response->end("Hello World $i\n");
$i++;
};
$loop = React\EventLoop\Factory::create();
$socket = new React\Socket\Server($loop);
$http = new React\Http\Server($socket, $loop);
$http->on('request', $app);
echo "Server running at http://127.0.0.1:1337\n";
$socket->listen(1337);
$loop->run();
で、再び黒い画面に戻り、コマンドラインからサーバを起動します。
php server.php //-> Server running at http://127.0.0.1:1337
これでサーバが起動したので、ブラウザからhttp://127.0.0.1:1337にアクセスしてみます。すると、”Hello World 1” という感じで表示され、リロードするごとにインクリメントしていきます。
サーバの終了はCtrl-Cです。
WordPressとReactを同居させる
さて、これだけだとなんのこっちゃですが、WordPressと連携してみます。まず、/reactでアクセスしたときはReactのサーバが反応し、それ以外だと普通のWordPressとして動くという設定にしましょう。たぶんですが、Nginxじゃないとダメだと思います。
で、Nginxの設定ファイルをこんな感じに設定します。httpブロックにReactサーバを追加、serverブロック(WordPressの設定がゴチャゴチャ書いてある部分)にプロキシへ渡す処理を書きます。
http{
# プロキシを定義
upstream react {
server 127.0.0.1:1337;
}
# WordPressの定義に追加
server{
# serverブロックにゴチャゴチャ書いてあるはず
location ~ ^/react {
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_pass http://react;
}
}
}
これでReactサーバを起動したままNginxを再起動すると、/reactでだけさっき作った Hellow World が出て、それ以外はWordPressとして普通に動くという仕組みになるはずです。
ここまででも「だからなんなの?」と感じが否めませんが、気にせず続けます。
WordPressのAjax APIとReactをパフォーマンス比較
さて、やっと本題です。WordPressには元々Ajax APIが存在しますが、ここで次のようなパフォーマンステストを行ってみます。
- データベースからブログのタイトルを表示するという関数を作成
- WordPressのAjax APIとReactで同じ関数を実行してみて、速度を比較
では、まず関数を定義しましょう。これはテーマのfunctions.php内に書いてしまいます。
get_optionを使わないのは、僕がMemcachedを使ってしまっているからですね。あまり深い意味はないです。
/*
* WordPressのデータベースに保存されたブログ名を返す
* @return string
*/
function fumiki_greeting(){
global $wpdb;
// 名前を毎回取得する
$name = $wpdb->get_var("SELECT option_value FROM {$wpdb->options} WHERE option_name = 'blogname'");
// クエリを空にして、次回も取得されるようにする
$wpdb->last_query = '';
// HTMLを生成
$html = <<<EOS
<html>
<head>
<title>Performance Test</title>
</head>
<body>
<h1>Hello, this site is %s</h1>
</body>
</html>
EOS;
return sprintf($html, esc_html($name));
}
では、Ajax経由でこの関数を実行してみましょう。同じくfunctions.php内に書きます。
add_action('wp_ajax_nopriv_greeting', function(){
echo fumiki_greeting();
exit;
});
これで takahashifumiki.info/wp-admin/admin-ajax.php?action=greeting にアクセスすると、HTMLが表示されます。
続いて、Reactのserver.phpも変えます。ポイントとしてはwp-load.phpを読み込むことですかね。
<?php
// wp-load.phpを読み込む。
require '../wp-load.php';
require 'vendor/autoload.php';
$app = function($request, $response) use ($wpdb) {
// Entry Point
$response->writeHead(200, array('Content-Type' => 'text/html'));
$response->end(fumiki_greeting());
};
$loop = React\EventLoop\Factory::create();
$socket = new React\Socket\Server($loop);
$http = new React\Http\Server($socket, $loop);
$http->on('request', $app);
echo 'Server running at http://127.0.0.1:1337'.PHP_EOL;
$socket->listen(1337);
$loop->run();
おそらく、wp-load.phpを読み込むことでエラーがぐわーっと出るかと思いますが、これはまあしょうがないですね。なんかのプラグインが if ($_SERVER[‘REQUEST_METHOD’] == ‘POST’) とか書いてるんですよ。これはPHPerのカルマみたいなもんです。
Reactを再起動したら、takahashifumiki.info/react にアクセスしてみます。すると、先ほどとまったく同じ画面が表示されます。

では、この二つのURLに対してApache Benchをかましましょう。コマンドは ab -n 1000 -c 100 TARGET_URL ですね。
| WP Ajax | React | 意味 | |
|---|---|---|---|
| 要した時間 | 24.713秒 | 2.329秒 | 短いほどいい |
| 秒間リクエスト | 40.46 | 429.3 | 多いほどいい |
| 1リクエストの時間 | 24.713 | 2.329 | 小さいほどいい |
なんとなくですが、Reactの方が10倍ぐらい早い結果ですね。
まとめ
WordPressは起動するたびに毎回データベースへ接続して、どのプラグイン・テーマ使うかとか、俺のURLなんやねんとか、そういうことを毎度行います。したがって、Ajaxのエンドポイントもなんとなくもっさりしています。Reactにすると爆速になること請け合い。
もちろん、Reactを導入するとWordPressのAjax APIと似たようなものを自分で実装しなければいけないのですが、現時点のAjax APIはあんまり便利ではないので別に気にならないと思われます。
ここら辺実装すると使い物になるんじゃないですかね。
- ルーティングで、Reactサーバへのリクエストを適切なクラスに振り分ける処理。CakePHPとかのコントローラーを読み込んで行くようなヤツ。
- データベース接続が起動しっぱなしなので、適宜リフレッシュするなり、再接続するなりの処理。
- MemcachedなどのKVSを適切に利用する(Object Cache API経由で)
- WordPressの管理画面からReactサーバの健全性を確認できる
- Supervisordとかでデーモン起動する。で、Nginxをロードバランサー的に使えばOK。
注意点としては以下の通り。
- WordPressのユーザー周り(get_current_userとか)は1プロセスあたり1ユーザーを想定しているので、Reactだとうまく動かないかも。認証系はWordPress側で行ってからReactに向けるか、Cookie読み取りの処理をReactにやらせるのがスマートか。
- ノンブロッキングサーバで「起動時点からMySQLにつなぎっぱなし」というのが有りなのかどうかはわかりません。
Reactと同じ方法でRatchetというWebSocketサーバも実装できるので、WordPressのユーザー情報と組み合せたチャットルームやメッセージシステムなんかも作れますね。実は僕の本命はこっちだったりするんですけどね。終わり。
ステートフルJavaScript ―MVCアーキテクチャに基づくWebアプリケーションの状態管理
価格¥3,080
順位1,209,831位
著Alex MacCaw
翻訳牧野 聡
発行オライリージャパン
発売日2012 年 6 月 9 日
