wp_cron を使った外部データ取得の非同期化とそのデバッグ方法

 人気記事のランキングデータをアナリティクスを使って取得していたのですが、データ取得はサイトへアクセスがあった時に前回から10分以上経っていたら更新という方法を使っていました。

この方法だとサイトを閲覧した時に更新のタイミングに当たった人は、通常の表示より2秒程度時間が余分にかかってました (^_^;)

良い方法がないか調べていたら ドンピシャの記事を見かけました

wp_cron という処理で実行を非同期化出来るらしいということでやってみました

wp_cron

概要

cronは指定した時間にコマンド、プログラムやその他のアクションを実行する為の、UNIX用に構築されたシステムです

wp_cron は、WordPress における指定した時間に行われるアクションをスケジュールする擬似的な cron 処理です

擬似的と言っているのは、正確な時間で処理されるわけではなく、サイトにアクセスがあった時に、実行するかが判定されるからです

wp_cron スケジューラに登録できるイベントは2種類あります

wp_schedule_single_event 特定の時間に一度だけ実行するもの
wp_schedule_event 定期的に、繰り返し間隔で実行するもの

定期的な処理としては、主にメインテナンス的なものに利用されています.

例えば、WordPress、テーマ、プラグイン等のバージョンアップのチェック、リンク切れ等のチェック、データベースの最適化等です

今回は wp_schedule_single_event で外部データの取得を非同期に行ってみます

仕組み

  1. 訪問者がサイトで任意のページを閲覧する
  2. WordPress の cron は、スケジューラに登録されているイベントの実行時間が経過しているかチェック
  3. 実行時間を超えていたイベントがある場合には、そのイベントに関連付けられたアクションが実行

従って、スケジューラに処理が登録されていても、アクセスがなければ実行されませんし、アクセスがあってもその時が基準となるので実行される時間は正確ではありません

ただし、ある程度のアクセウがあるサイトならば、おおむね指定してある時間に大きく遅れることなく実行されることが期待できます

要求と実行に分けて処理される

要求側

サイトの閲覧時にスケジューラに登録されているイベントの実行時間をチェックします

イベントの実行時間が過ぎていたら自サイトの wp-cron.php へ ?doing_wp_cron とパラメータを付けてHTTPリクエストを発行(トリガー)します

実行側

トリガーとして発行された HTTP リクエストにより、別プロセスの wordpress が起動

リクエストにより呼び出された wp_cron.php 中の処理でイベントにフックされている関数を実行し、プロセスを終了します

従って、時間のかかる処理等を、サイト閲覧側の処理から分離して別プロセスで実行することが出来ます

Wp_cron の処理は、 /wp-includes/cron.php と /wp-cron.php ファイルに記述されているので、詳細はプログラムを追いかけて見て下さい

使用例

ここでは、wp_schedule_single_event を使用してアナリティクスへの問い合わせを行う例を元に説明します

イベントの登録

スケジューラにイベント(cron_ga_getpopular)が登録されていないか確認して、

60秒経過以降に実行されるようにイベント(cron_ga_getpopular)を登録します

このイベントがアクションのフックポイントとなります

    if ( !wp_next_scheduled( 'cron_ga_getpopular' ) ) {
        wp_schedule_single_event( time() + 60, 'cron_ga_getpopular' );
    } 
    

登録自体は簡単でイベント名と登録されてからの実行時間を指定するだけです

アクションの登録

イベント(アクションポイント)にフックするアクション関数を登録します

ここではイベント(cron_ga_getpopular)に対して、クラス内に定義されている 関数をアクション(cron_ga_getpopular_exec)として登録しています

    add_action( 'cron_ga_getpopular', array( &$this, 'cron_ga_getpopular_exec' ) );

これも簡単です。イベント cron_ga_getpopular に アクション関数 cron_ga_getpopular_exec を関連付けしているだけです

アクション関数

イベントにより実行されるアクション関数を定義します

この関数は、wp_cron.php 処理中でアクションポイントにフックされている関数を呼び出して実行するので、サイトの wp-cron.php?doing_wp_cron へのアクセスで別プロセスで実行されます

cron_ga_getpopular_exec 関数はこんな内容です
この部分だけ見てもよくわからないと思いますが、こんな関数を呼び出しているんだ程度に見てください

    //アナリティクス 人気記事データ取得を次のアクセス時に別プロセス処理
    //wp_schedule_single_event() により CRON に登録
    public function cron_ga_getpopular_exec()
    {
        $access = $this->gaccess;
        if (!empty($access['token']) && !empty($access['pname'])) {
            $client = $this->client_create($access);
            if (!empty($client) ) {
                $result = $this->ga_getpopular($access, $client);
                if(!empty($result)) {
                    $access['result'] = $this->add_postinfo_garesult( $result, $access);                            
                    //問い合わせインターバル時間更新と人気記事ウィジェットキャッシュクリア
                    $access['time'] = strtotime( "now + 10 min");
                    update_option('ga_pvpopular', $access );
                    delete_transient( 'celtisone_popular_widget' );
                }
            }
        }
    }  

処理概要は、ga_getpopular 関数で外部サイトのアナリティクスデータ取得を行っています。また、 add_postinfo_garesult 関数でアナリティクスから取得した人気記事データの記事毎のアイキャッチデータ等をサイト内から取得しています

いずれも若干時間がかかる処理なので、この部分の処理を分離して実行出来ればメリットは大きいです

 

wp_cron で処理を行うのは、基本的にこれだけなので簡単に使用することが出来ます

但し、デバッグ作業は意外に面倒です (^_^;)

 

デバッグ方法

ここでは、デバッグでのポイントを紹介します

 WP Crontrol

 wp_cron のデバッグに必須の便利なプラグイン WP Crontrol を紹介します

スケジューラに登録されている各種イベント情報を確認することが出来ます。また、イベントの編集、実行、削除を行うことが出来ます

インストールは、通常のプラグインと同じです

プラグインの新規追加画面で WP Crontrol を検索してインストールして下さい

イベント表示

管理画面の ツール に追加されている、Crontrol をクリックすると現在登録されているイベントを確認することが出来ます

crontrol-event

今回は、主にスケジューラに登録されたイベントの確認に使用します

wp_schedule_single_event を実行した後に、このページを表示すると正しく登録されていれば、ここに表示されるので実行時間等の情報も確認出来ます

ちなみに直近のイベントが一番上に表示され、実行されれば消えます

イベント登録

ここからイベントを直接登録することも出来ます

add-cron-event

 これも便利です

Next run を now にすると直ぐ実行されるので +30 sec 位を指定するとデバッグしやすいかも。詳しくは次の Netbeans でのデバッグで説明します (^^)

 

NetBeans でデバッグ

wp_cron で処理するアクション関数をデバッグするコツを紹介です

アクション関数は、いつものようにデバッガ使用としても、ブレイクがかかりません

じぇじぇ (^_^;)

 

方法がないわけではありません

Wp_cron の仕組みを考えるとイベント実行時間を経過した最初のアクセスで HTTPリクエストが wp_cron.php へ発行されます

そこで、WP Crontrol でイベント実行時間を確認後、最初のアクセスとして NetBeans で Xdebug を用いてデバッグしているのと同じブラウザの別タブから wp-cron.php アクセスします

例えば、ローカル環境の wordpress にサイトを構築していれば下記URLへアクセスします
http://localhost/wordpress/wp-cron.php?doing_wp_cron

すると、同じブラウザからのアクセスなので wp_cron.php の処理をデバッグすることが出来るので、アクションフックで実行される関数も NetBeans からデバッグ出来ます

つまり、サイトの任意のページへアクセスして発行されるHTTPリクエストの代わりに、直接 wp_cron.php へアクセスしてしまえということです

但し、通常ではイベントの最小時間間隔が10分なので効率は悪いです (^_^;))

そこで WP Crontrol を使用して Add Cron Event でイベント(cron_ga_getpopular)を30秒後ぐらいに指定して登録します

登録後、サイトの wp-cron.php?doing_wp_cron をアクセスすればOKです

ちょっと一手間必要ですが、これで NetBeans を使用してデバッグできます

 

HTTPリクエストの実行時間測定

Query Monitor

これは以前 WordPress サイトのボトルネックを見つける為の解析ツール紹介 で紹介しましたが、これを使ってトリガーとして発行した HTTP リクエストを確認出来ます

HTTPリクエストは、イベントの実行時間を経過した後の最初のアクセスでしか補足できないので、注意です

 HTTP-cURL

HTTPリクエストとその実行時間等の情報です

実行トリガーの HTTPリクエストは、 timeout 0.01秒、blocking false の非同期で行われているのですが、手元のローカル環境ではタイムアウトが 1秒以上かかっていて、期待より1秒以上無駄な時間がかかっていました

これでは、せっかく非同期化して別プロセスで実行させているのに効果半減です

タイムアウト処理が 10ms で動作していないようだったので、調べてみると cURL のタイムアウトが msec 単位で動作していないのではと思われました

PHPバージョン等の動作環境によるのかも? とりあえず手元の環境では1秒以上かかっています

HTTPリクエストのトランスポートを切り替える

WordPress のHTTPリクエスト処理は、cURL 以外にもあるらしいことがわかったのですが、どうしたら切り替えられるのかがよくわかりません

仕方ないので、ソースコードを追いかけて行くと、wp-includes/class-http.php の _get_first_available_transport 関数で指定しているようです

	public function _get_first_available_transport( $args, $url = null ) {
		$request_order = apply_filters( 'http_api_transports', array( 'curl', 'streams' ), $args, $url );

		// Loop over each transport on each HTTP request looking for one which will serve this request's needs
		foreach ( $request_order as $transport ) {
			$class = 'WP_HTTP_' . $transport;

			// Check to see if this transport is a possibility, calls the transport statically
			if ( !call_user_func( array( $class, 'test' ), $args, $url ) )
				continue;

			return $class;
		}

		return false;
	}

curl と streams が登録されていて、curl が優先されています

幸いな事にフィルターフックで操作できるようです、

ということで、さっそくこんな関数を作り、設定画面から curl と streams の優先順位を変えられるようにしてみました

// wp_cron で実行する http リクエストのプロトコルを変更
// class-http.php ファイルの _get_first_available_transport() 関数の使用プロトコルの優先順位を変更する
if ( ! function_exists( 'celtisone_http_api_transports' ) ) {

    function celtisone_http_api_transports( $transport, $args, $url ) {
        if(stripos($url, 'wp-cron.php?doing_wp_cron') !== false){
            $transport = array( 'streams', 'curl' );    // streams を優先するよう変更 
        }
        return $transport;
    }
    if ( $celtisone_options['cron_transport'] === 'streams' ){
        add_filter( 'http_api_transports', 'celtisone_http_api_transports', 10, 3 );   
    }
}


Streams を優先して実行してみると期待どおりです (^^)

HTTP-Streams

 但し、環境によっては cURL でも問題ないかも知れませんし、優先順位を変えるにしても他の通信処理関連にどの程度影響するかもわからないので、wp-cron.php?doing_wp_cron に対する場合のみ優先順位を変更しています

これでずっと気になっていたアナリティクスデータ取得タイミングでの表示の遅さ対策が何とかなったようです (^^)

 

 


まとめ記事紹介

go-to-top