fbpx

メニュー

React + WordPressで高速なノンブロッキングAPIを作る

高橋文樹 高橋文樹

この投稿は 10年 前に公開されました。いまではもう無効になった内容を含んでいるかもしれないことをご了承ください。

最近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というのがありました。

ReactPHP
ReactPHP

Reactを試してみる

このReactはComposerとかでサクッとインストールできます。Bring High Performance Into Your PHP App (with ReactPHP)っていう記事を見ながらやったところ、すぐできました。今回はこんな構成でインストールしてみます。

  1. ローカルのMacにはWordPressをインストール済み。ドメインは takahashifumiki.info で ルートディレクトリは /opt/local/www/fumiki
  2. WebサーバはNginx + PHP-FPM
  3. 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が存在しますが、ここで次のようなパフォーマンステストを行ってみます。

  1. データベースからブログのタイトルを表示するという関数を作成
  2. 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 にアクセスしてみます。すると、先ほどとまったく同じ画面が表示されます。

Reactでも同じ画面
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のユーザー情報と組み合せたチャットルームやメッセージシステムなんかも作れますね。実は僕の本命はこっちだったりするんですけどね。終わり。

[429] [429] Client error: `POST https://webservices.amazon.co.jp/paapi5/getitems` resulted in a `429 Too Many Requests` response: {"__type":"com.amazon.paapi5#TooManyRequestsException","Errors":[{"Code":"TooManyRequests","Message":"The request was de (truncated...)

すべての投稿を見る

高橋文樹ニュースレター

高橋文樹が最近の活動報告、サイトでパブリックにできない情報などをお伝えするメーリングリストです。 滅多に送りませんので、ぜひご登録お願いいたします。 お得なダウンロードコンテンツなども計画中です。