読者です 読者をやめる 読者になる 読者になる

売上集計をバッチモード(DAILY_BATCH_MODE=true)でやるようにしたらサクサク動くようになった

売上集計機能

売上集計機能は、表や各種グラフ(折れ線、棒、円)で売上データをわかりやすく表示する、とても便利な機能。
2種類の方法で売上を集計できる(EC-CUBE2.1.2の話)。

  1. リアルタイム・・・「月度で集計する」や「期間で集計する」ボタンが押されたときにその場で集計→集計データ格納→集計データ取得→表示、と行う方法。
  2. バッチモード・・・あらかじめ売上データを集計しておき、「月度で集計する」や「期間で集計する」ボタンが押されたときには、すでに集計して格納済みのデータを取得→表示、と行う方法。

デフォルトはリアルタイム集計。
でも、リアルタイム集計だと処理時間が長くてタイムアウトになる。。。短い時間だとたまにうまくいくけど。

Fatal error: Maximum execution time of 30 seconds exceeded in /path/to/eccube/data/class/batch/SC_Batch_Daily.php on line 329

さらに、グラフの表示も同じロジック(集計→集計データ格納→集計データ取得→表示)を通って行っているので、
グラフがでない時もある。もったいない。。。とってもいいグラフなのに(涙)


ということで、バッチモードであらかじめ売上集計しておき、サクサク動くようにする

切り替えるにはまず、基本情報管理>パラメータ管理のDAILY_BATCH_MODEをtrueにしてあげる必要がある。


次に、売上集計を定期的におこなうバッチをつくって、cronにセットする。
/path/to/eccube/data/script/execute_batch_daily.php

<?php
/**
 * デイリーバッチ起動スクリプト
 */

require_once( dirname(__FILE__) . "/../install.php" );
define( "CLASS_PATH", DATA_PATH . 'class/' );
define( "CLASS_EX_PATH", DATA_PATH . 'class_extends/' );
define( "CACHE_PATH", DATA_PATH . 'cache/' );

require_once(CLASS_EX_PATH . "util_extends/GC_Utils_Ex.php");
require_once(CLASS_EX_PATH . "util_extends/SC_Utils_Ex.php");
require_once(CLASS_EX_PATH . "db_extends/SC_DB_MasterData_Ex.php");
require_once(CLASS_EX_PATH . "db_extends/SC_DB_DBFactory_Ex.php");
require_once(CLASS_PATH . "SC_DbConn.php");
require_once(CLASS_PATH . "SC_Query.php");
require_once(CLASS_PATH . "SC_SelectSql.php");

require_once(CLASS_EX_PATH . 'batch_extends/SC_Batch_Daily_Ex.php');
require_once(CLASS_EX_PATH . "SC_Initial_Ex.php");
// アプリケーション初期化処理
$objInit = new SC_Initial_Ex();
$objInit->init();

$usage = <<<USAGE

php -f execute_batch_daily.php start term
集計期間を指定して売上集計をします。

 start   集計開始日。何日前の売上から集計するか。1 から 365以内で指定。
 term    集計対象期間。何日分の売上を集計するか。1 から 365以内で指定。

例:昨日の売上を集計します。
php -f execute_batch_daily.php 1 1


USAGE;



if ( $argc != 3 ) {
	echo ( $usage );
	exit(1);
}

$start = $argv[1];
$term = $argv[2];
if ( !is_numeric( $start ) || !is_numeric( $term ) ) {
	echo ( $usage );
	exit(1);
}

$start = intval( $start );
$term = intval( $term );

if ( $start < 1 || $start > 365 || $term < 1 || $term > 365 ) {
	echo ( $usage );
	exit(1);
}

if ( $term > $start ) {
	echo ( $usage );
	exit(1);
}

$objBatch = new SC_Batch_Daily_EX();
$objBatch->lfStartDailyTotal( $term, $start, true );
?>


/path/to/eccube/data/class/batch/SC_Batch_Daily.phpのlfStartDailyTotalを3点、少し修正。

  • キャンセルや削除になった受注詳細情報を削除してるのをコメントアウト(なぜ削除してるんだろう・・・)。
  • 「最後のバッチ実行からLOAD_BATCH_PASS秒経過していないと実行しない。」のログがうまく出ないので修正。
  • 集計のforループ。
<?
    // 集計の開始
    function lfStartDailyTotal($term, $start, $command = false) {

        $now_time = time();

        // グラフ画像の削除
        $path = GRAPH_DIR . "*.png";
        system ("rm -rf $path");


        // 削除された受注データの受注詳細情報の削除
        $objQuery = new SC_Query();
/*
EC-CUBE全体をgrepしてみたが、なぜ受注詳細情報だけを削除してしまうのか不明なので削除しないでおく。
商品別集計もdel_flg=0でキャンセルステータスではない注文から集計してるので影響はなさそう。
        $where = "order_id IN (SELECT order_id FROM dtb_order WHERE del_flg = 1)";
        $objQuery->delete("dtb_order_detail", $where);
*/

        // 最後に更新された日付を取得
        $ret = $objQuery->max("dtb_bat_order_daily", "create_date");
        list($batch_last) = split("\.", $ret);
        $pass = $now_time - strtotime($batch_last);


        // 最後のバッチ実行からLOAD_BATCH_PASS秒経過していないと実行しない。
        if($pass < LOAD_BATCH_PASS) {
//            GC_Utils_Ex::gfPrintLog("LAST BATCH " . $arrRet[0]['create_date'] . " > " . $batch_pass . " -> EXIT BATCH $batch_date");
            $batch_next = date( "Y-m-d H:i:s", strtotime($batch_last) + LOAD_BATCH_PASS );
            GC_Utils_Ex::gfPrintLog("LAST BATCH " . $batch_last . " NEXT BATCH AFTER $batch_next -> EXIT BATCH");
            return;
        }

        // 集計
//        for ($i = $start; $i < $term; $i++) {
        for ($i = $start, $k = 0; $k < $term; $i--, $k++ ) {
            // 基本時間から$i日分さかのぼる
            $tmp_time = $now_time - ($i * 24 * 3600);

            $batch_date = date("Y/m/d", $tmp_time);
            GC_Utils_Ex::gfPrintLog("LOADING BATCH $batch_date");

            $this->lfBatOrderDaily($tmp_time);
            $this->lfBatOrderDailyHour($tmp_time);
            $this->lfBatOrderAge($tmp_time);

            // タイムアウトを防ぐ
            SC_Utils_Ex::sfFlush();
        }
    }
?>

最後に、cronにセット。毎晩2時に前日分の売上を集計するようにしてみた。

$ crontab -e

0 2 * * * /usr/local/bin/php -f /path/to/eccube/data/script/execute_batch_daily.php 1 1 2>&1 > /dev/null

リアルタイム集計でもタイムアウトを防ぐようにしてあげる

SC_Batch_Daily::lfRealTimeDailyTotal()内のforループ中の最後にSC_Utils_Ex::sfFlush();を追加した。

<?
    // リアルタイムで集計を実施する。集計が終了しているレコードは実施しない。
    /*
     $sdate:YYYY-MM-DD hh:mm:ss形式の日付
     $edate:YYYY-MM-DD hh:mm:ss形式の日付
    */
    function lfRealTimeDailyTotal($sdate, $edate) {
        $pass = strtotime($edate) - strtotime($sdate);
        $loop = intval($pass / 86400);

        for($i = 0; $i <= $loop; $i++) {
            $tmp_time = strtotime($sdate) + ($i * 86400);
            $batch_date = date("Y/m/d H:i:s", $tmp_time);
            $objQuery = new SC_Query();
            $arrRet = $objQuery->select("order_date, create_date", "dtb_bat_order_daily", "order_date = ?", array($batch_date));
            // すでにバッチ処理が終了しているかチェックする。
            if(count($arrRet) > 0) {
                list($create_date) = split("\.", $arrRet[0]['create_date']);
                list($order_date) = split("\.", $arrRet[0]['order_date']);
                $create_time = strtotime($create_date);
                $order_time = strtotime($order_date);
                // オーダー開始日より一日以上後に集計されている場合は集計しなおさない
                if($order_time + 86400 < $create_time || $tmp_time > time()) {
                    GC_Utils_Ex::gfPrintLog("EXIT BATCH $batch_date $tmp_time" . " " . time());
                    continue;
                }
            }
            GC_Utils_Ex::gfPrintLog("LOADING BATCH $batch_date");
            $this->lfBatOrderDaily($tmp_time);
            $this->lfBatOrderDailyHour($tmp_time);
            $this->lfBatOrderAge($tmp_time);

            // タイムアウトを防ぐ
            SC_Utils_Ex::sfFlush();
        }
    }

?>