非同期処理をWordPressのwp_cronを使って投げるように改修した
うちのサイトでは、PC版右サイドバーの人気記事ランキングを Google Analytics から取得していますが、たまたま更新処理のタイミングに当たると、数秒ですがレスポンスが遅れる。という弱点がありました。
そこで、ランキング更新処理に wp_cron の単発スケジュールを使って非同期処理させるよう改修したので、メモしておきます。
普通にcron使えばいいじゃん。と言われそうな気もしますが、どうせライブラリを書くなら cron が使えない環境でも使える方が縛りが少ないよなぁ。と思ったので。
ページのリクエストの都度 Google API を叩いていては、遅い上にあっという間に API 利用制限に引っかかってしまうので、APCキャッシュを活用してGoogle APIを叩く回数をかなり減らしています。
ちなみにPHPのAPCキャッシュにはCLI版とWEBサーバ版とでデータストアが繋がっていない、という制約がありますが(APCuキャッシュは未確認)、Wordpress の wp_cron での非同期処理ならWEBサーバ側のPHPとして実行されるため、この問題をクリアできる。という利点もあるんですよね。
今回は、ランキング集計部分は抜きにして、Wordpress で PHP の非同期処理を行う。という部分だけを抜き出して書いておきます。
wp_cron(スケジューラ)で呼び出される側
wp_cron により呼び出される処理実部は以下の様に記述します。
function cron_test_function() { $fp = fopen ('/var/www/hoge/hoge.log',"a"); fwrite($fp, cron_test_function called.\n"); fclose($fp); apc_store('test_key','test_value', 120); } add_action( 'cron_test_handle', 'cron_test_function' );
ポイントは、この処理を WordPress のテンプレートではなく、function.php 内、またはfunction.php から直接 require したソース中に記述する点です。
実際のブログラムでは、自前の透過キャッシュマネージャ経由でキャッシュにアクセスしています。そうすることで、APCu や memcached にも柔軟に対応できますし、Wordpress の Transient cache にすげ替えればレンタルサーバでも動せる余地が出てきますからね。
このほか期限付き更新ロック処理と、更新完了時のロック解除処理とかとかもあるので、もっとゴチャゴチャした事はやっています。あと、Wordpress 非依存なクラス設計にもしています。
実装上、キャッシュは「消極的期限切れ」と「完全期限切れ」の2段階で期限を設定しています。これはつまり、「更新が必要な時刻だけどデータはまだ消えてないからキャッシュからデータを持ってこれる」期間を自前で用意している。ということです。
別途ページキャッシュシステムを導入している場合は、そことも整合を取ってこれらキャッシュや更新ロックの有効期限を適切に設定する必要があります。
wp_cronの処理をスケジュールする
今回は極力早く非同期処理をキックしたいので、次のようなコードにしました。
if ( ! wp_next_scheduled( 'cron_test_handle' ) ) { $now = time(); // Resister wp-cron event. if (is_null($args)) { wp_schedule_single_event( $now , 'cron_test_handle' ); } else { wp_schedule_single_event( $now , 'cron_test_handle' , $args ); } // Kick. spawn_cron( $now ); // spawn_cron will not run more than once every 60 seconds. }
wp_cron は通常、次回アクセス時にそれ以前分のスケジュールが実行される仕様ですが、spawn_cron() を呼び出す事で即時実行としています。
ちなみに spawn_cron() は、60秒に1回以上呼び出しても無視される仕様なので、デバッグ中は留意しましょう。
特にテスト中は APC などのユーザーデータキャッシュの有効期間を短く設定するでしょうから、その兼ね合いで理解しずらい挙動が観測されやすいです。慣れが必要なところですね。
wp_schedule_single_event() は、コールバック関数に渡す引数も設定できます。上記ソースの $args がその例で、これはArray型で指定しますが、受け取り側はArray型でなく、Array内の各要素が別々の引数に分離されて渡される仕様になっています。
デバッグの方法
PHPらしくもない非同期処理。という事でデバッグには多少の慣れが必要です。
デバッガ好きなら NetBeans でデバッガに接続してなんとかしちゃう方法もあるようですが、そこまでする事もなかろう。という事で、試していません。
print デバッグでなんとかなる規模なら、先の「cron_test_function() 」のサンプルソースで出力しているログファイルを詳細版にして、「tail -f」で見れば十分かと思います。
また、wp-cron のスケジューリング状態の確認には、Wordpress プラグインの「WP Crontrol」が便利です。
このプラグインをインストールすると、ダッシュボード左側の「ツール」→「Crontrol」から登録されている wp_cron のスケジュールを確認できるようになります。
wp_schedule_single_event() は同一スケジュールが10分以内に存在すると無視したような気がするので、動作確認時はそこも押さえておく必要があります。
あとがき
重たい機能をサイトに持たせたいけど、レスポンスが悪くなるのは嫌だ!という場合は、こんな方法で非同期処理を導入するのもアリ。かもしれません。
ただし、この方法は、ある程度安定したアクセス数があることが前提にはなります。ユーザーがアクセスして初めて集計処理をキックするわけですからね。
特に、別途キャッシュプラグインやCDN、逆プロキシを導入している場合は、実際のページ生成頻度はアクセス数よりかなり少ないですから、cronでキックした CLI版PHP から APC 以外のキャッシュに書く方が良い…というケースもあるかもしれません。