WordPressでのバッチ処理ベストプラクティス

WordPressを作る仕事をしていると、ゼロから作るばかりではなく、いわゆるリプレース、つまり既存のサイトを受け持つケースが多々ある。まったく別のCMSや静的サイトをWordPressにする場合もあれば、すでに稼働しているWordPressサイトをあらたに受け持つケースも多い。

後者の場合、かなりの頻度で遭遇するのが、WordPress-Wayではないやり方である。プログラマではあるが、WordPressははじめて触った人などが作る処理がそのまま残されているというわけだ。今回はそんなケースとしてよくあるバッチ処理の作り方について説明しよう。

そもそもバッチ処理とは?

Wikipediaによると、次のような説明がなされている。

バッチ処理(バッチしょり)とは、コンピュータで1つの流れのプログラム群(ジョブ)を順次に実行すること。あらかじめ定めた処理を一度に行うことを示すコンピュータ用語。反対語は対話処理・インタラクティブ処理またはリアルタイム処理。

つまり、一連のわりと複雑な処理をまとめて行うような処理を示す。具体的な例としては次のようなものが考えられる。

  • ランキングの作成など、ある程度大きなデータ群を対象にする処理
  • twitterのつぶやき検索のような、外部のAPIを叩く時間のかかる処理

リアルタイム処理、つまりWordPressにアクセスしたユーザーに対して何かをするような処理としては、0.1秒を超える時点で相当遅いと考えて構わない。そういった処理は、管理画面でキャッシュを作っておいたり、バッチ処理であらかじめデータを作成しておいたりする。

WP CRONはバッチ処理に適当か?

WordPressにはWP CRONという実装があり、これはCRONタスク、つまり定期実行される処理をバックエンドに逃した(ように見せる)CRONの擬似実装である。このCRONはWordPressへのアクセスをトリガーとして行われるので、ある程度アクセスがあれば、定期実行がなされているのと似たような処理が可能だ。

もちろん、crontabに登録して wp-cron.php を叩くようにすれば、毎秒必ず実行されるようにできる。こちらの方がより確実だ。

<?php
// wp-config.php などに次のように書くと、WP CRONが止まる
define( 'DISABLE_WP_CRON', true );
crontab -e

* * * * * php /var/www/wordpress/wp-cron.php > /dev/null 2>&1

WordPress本体でもWP CRONは利用されており、たとえば予約投稿がそれにあたる。投稿は予約投稿になると、WP CRONにシングルイベントとして登録され、該当する時間がくると公開ステータスに変更される。

しかしながら、WP CRONはすべてのイベントがワンプロセスで一気に行われるので、重い処理を何個も登録するのには向いていないというのが筆者がこれまでの経験から得た感想だ。途中で処理が止まった場合、なにが原因かよくわからず、デバッグが困難を極める。そんなわけで、上記であげたようなランキングの実装は、WP CRONに適さない。

WP CLIでバッチ処理を作成する

そこで筆者がオススメしたいのが、WP CLIによるコマンドの作成だ。コマンドを作るのはそれほど難しくない。たとえば、上で定義したランキング作成のコマンドを wp capitalp ranking だとしよう。すると、コードは次のようになる。

<?php
/**
 * Command utility for Capital P.
 * 
 * WP_CLI_Command を拡張したクラス。
 */
class CapitalpCommand exntends WP_CLI_Command{
    /**
     *
     */
    public function ranking() {
        // ランキングを作成する
        return $ranking;
    }
}

このクラスファイルを含んだテーマなりプラグインなりを作成し、読み込んで登録する。筆者はいつもサイト特有の機能の場合はテーマに含めてしまい、functions.php から読み込んでいる。ポイントは、WP_CLI実行中でなければ読み込まないようにすること。

<?php
// これがfunctions.php のどこかの部分だとする。

if ( defined( 'WP_CLI' ) && WP_CLI ) {
    // ここまで来たということは、WP_CLI実行中だということ。
    require_once __DIR__ . '/CapitalpCommand.php';
    // コマンド登録。
    // 一番目の引数はコマンド名、2つ目はクラス名。
    WP_CLI::add_command( 'capitalp', 'CapitalpCommand' );
}

これでコマンドが登録される。PHPDoc形式コメントを書くと、WP CLIのヘルプとして表示されるので、コメントもきちんと書くとあとあと便利だろう。

クラスドキュメントが”Command utility for Capital P.”というコメントとして反映されている。

たとえば、このランキングの作成を毎週月曜の早朝4:00に行なう運用にしたとすると、登録すべきCronは次のようになる。

# 毎週月曜日の朝5時に実行
# 1. WordPressのディレクトリに移動 cd /var/www/wordpress
# 2. コマンドの実行 wp capitalp ranking
# 3. 処理結果の文字列を捨てる > /dev/null 2>&1
0 4 * * 1 cd /var/www/wordpress && wp capitalp ranking > /dev/null 2>&1

crontabを使うなとか、>/dev/null 2>&1 って書くなとか、実行ユーザーを誰(apache? nginx? root?)にするかなどはご利用の環境に応じて変わる。ちなみに、筆者は自分で管理するAWSを使う際、めんどくさいのでPHP-fpmのユーザーをec2-userに統一してしまっている。ログも全部ホームフォルダに出力している。

バッチ処理作成の秘訣1:  コマンドの中ですべてを行わない

これは特にバッチ処理に限った話ではないのだが、PHPファイル一つの中でダラダラと処理を書いてしまっているのをよく見る。時間がないのかもしれないが、極力クラスごとや関数ごとに分けよう。たとえば、「Google AnalyticsのAPIを叩いてランキングの投稿を作る」というバッチ処理でも、これぐらいは分けた方がよい。

  • capitalp_get_api APIとの接続が完了したドライバーを返す関数。失敗した場合はWP_Errorを返すなど。
  • capitalp_get_ranking ドライバーを利用してランキングを取得する関数。失敗した場合はこれも同じくエラーを返す。この中身ではcapitalp_get_apiを呼び出している。
  • capitalp_get_ranking_post APIが返した結果を元に、ランキングの配列を作る。WP_Postに変換したりすると、楽だろう。
  • capitalp_generate_ranking ランキングの配列を受け取って、投稿を作成する機能。

こうすれば、WP_CLIコマンドの中身は次のようにシンプルになる。

$ranking = capitalp_get_ranking();
if ( is_wp_error( $ranking ) ) {
  // errorメソッドを呼び出すと以降の処理は行われない。
  WP_CLI::error( $ranking->get_error_message() );
}
$ranking = capitalp_get_ranking_post( $ranking );
$result = capitalp_generate_ranking( $ranking );
WP_CLI::success( 'ランキングを作成しました' );

どのような設計にするか、ネーミングはどうするかについては経験がものをいうのだが、とにかくわけないとデバッグや環境の変更(ex. サーバーの引越し、ライブラリの変更)で死ぬ思いをする。

バッチ処理の秘訣2: デバッグ用コマンドも作っておく

バッチ処理は途中の結果が画面に表示できる類のものではないケースがほとんどなので、世界中のWordPress開発現場で毎日5億回は行われているだろう「var_dump & リロード」という手法が使えない。

したがって、デバッグ用のコマンドを作成するとよいだろう。たとえば、上の例でいえば、ランキングの取得だけがうまくいっているかどうかを確認するためのコマンド fetch_ranking などを定義しても良いかもしれない。

また、オプションを渡した時だけ、途中経過をすべて出力するようにするのも悪くないアイデアだ。

/**
 * コマンドには引数が渡ってくる
 *
 * @param array $assoc
 * @param array $assoc
 * @synopsis [--verbose]
 */
public function ranking( $args, $assoc ) {
  $verbose = isset( $assoc['verbose'] && $assoc['verbose'];
  $ranking = capitalp_ranking();
  if ( $verbose ) {
    print_r( $verbose );
  }
}

コマンドには引数が設定できるので、マニュアルを一読することをお勧めする。

バッチ処理の秘訣3: できる限り可逆的・反復可能な処理にする

バッチ処理は機械的に何度も行われる処理なので、できれば反復しても問題ない処理であることが望ましい。そうすれば、WordPressの投稿編集画面がわけのわからない数万件の投稿で埋め尽くされることもないだろうし、twitterアカウントが狂ったように同じことをつぶやき続けることもないだろう。

上の例で言えば、毎回投稿を作るのではなく、直近の投稿が存在すればそれを上書きする形にした方が安心だ。

 

以上、バッチ処理を設定する場合のベストプラクティスについて説明した。ここから先は有料会員に向け、次のようなチュートリアルをお届けする。ビデオ付きなので、それなりに参考になるだろう。

  • Google analyticsを叩いて、ランキングを作成する
  • ランキングができたら、登録に突っ込んで、「今週のランキング」を作成する
  • ランキングができたら、毎週月曜の朝10:30に公開予約。ついでに、公開した瞬間につぶやく。

続きの 11% を読み、添付されたファイルにアクセスするには、Gumroadでライセンスキーを取得してください!

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください