Kimura A
a.kim****@live*****
2013年 9月 18日 (水) 18:48:17 JST
木村です。
須藤さん励ましありがとうございます(ノ_;)
とりあえず使用した2つのシェルを貼ります(ファイル自体も添付してお送りします)。
いずれもCakePHPのシェルなので、もし意味が読み取りにくい部分がありましたらご質問ください。
コメントはなるべく細かく書き込んでおきました。
必要であれば一部の変数をベタ書きに修正するなどして、多少整理することもできます。
また、Insertシェルについてはデータ生成部分をもう少し素のMySQLクエリに近い形に書き直すことも可能です。
お手数をおかけしますが、ご確認どうぞよろしくお願いします。
○Insertシェル
/var/www/vhosts/my-domain.com/cake/app/Console/Command/InsertShell.php
<?php
App::uses('AppShell', 'Console/Command');
/**
* mroonga「WHERE IN 多数」クエリのテスト用データ生成シェル
*
* @property Model $Model
*/
class InsertShell extends AppShell {
/**
* オプションの準備
*
* @var array
*/
public $optionParams = array(
'force' => array(
'short' => 'f',
'help' => 'Remove "mroonga_errored" rock file and do continue.'
),
'minutes' => array(
'short' => 'm',
'help' => 'Minutes to work. Default 5'
)
);
/**
* オプションの許可
*/
public function getOptionParser() {
$parser = parent::getOptionParser();
$parser->addOptions($this->optionParams);
return $parser;
}
/**
* メイン処理
*
* @return void
*/
public function main() {
$database = 'webTest'; // 接続先DB:「webTest」ならtestデータベース
$fields = array('code', 'description', 'id', 'vendor_id', 'tag_pool'); // 生成対象カラム
$limit = 100; // 1度にいくつのレコードをINSERTするか
$minutes = empty($this->params['minutes']) ? 5 : $this->params['minutes']; // 実行を許可する最大分数。cronによる動作時の重複回避用
$sleep = 1; // 一括INSERTクエリの連続実行時に何秒インターバルを置くか
$headRoomSec = 10; // 実行許可分間のうち処理の重複回避のために何秒の余裕を残すか
// モデルの準備
$this->Model = ClassRegistry::init('Product');
if ($database && $database !== 'default') {
// 接続先DBの変更
$this->Model->setDataSource($database);
}
// forceオプション対応
if ((!empty($this->params['force']) || !$this->Model->find('first', array('fields' => array('id')))) && file_exists(TMP . 'mroonga_errored')) {
// forceオプション有効もしくはproductsテーブルが空で、かつロックファイルが存在する場合:ロックファイルを消去して処理実行
CakeLog::debug("mroonga_erroredファイルを削除して{$limit}件単位の一括INSERT処理({$minutes}分間)を開始します");
@unlink(TMP . 'mroonga_errored');
} else {
// 通常は開始ログのみ出力
CakeLog::debug("{$limit}件単位の一括INSERT処理({$minutes}分間)を開始します");
}
// ロックファイルの存在チェック
if (file_exists(TMP . 'mroonga_errored')) {
// Selectシェル側でエラー発生後はINSERT処理回避
CakeLog::debug("mroonga_erroredファイルが存在するためINSERT処理を回避します");
return;
}
// 時間超過を回避しつつ連続実行
$startTS = time(); // 処理開始TS
$timeLimitTS = $startTS + (60 * $minutes) - $headRoomSec; // 想定するタイムリミットTS:$headRoomSec秒の余裕を確保
$loopSecMax = 0; // 1回のループに要した最大秒数を保持
for ($i = 0; $i < ($minutes * 20); $i++) {
$loopStart = time();
if ($i) {
sleep($sleep);
}
$this->insert($fields, $limit);
$loopSec = time() - $loopStart;
$loopSecMax = max(array($loopSecMax, $loopSec));
// 最も長時間を要したループの所要時間を基準に、トータル処理時間が所定秒数を超過しないかチェック:Selectシェル側処理との重複回避
$loopEndTS = time();
if ($loopSecMax > ($timeLimitTS - $loopEndTS)) {
$loopCount = $i + 1;
$totalSec = $loopEndTS - $startTS;
$maxId = $this->getMaxId();
$dbNameData = $this->Model->query('SELECT database() as `db`;', false);
$dbName = $dbNameData[0][0]['db'];
CakeLog::debug("{$minutes}分間のタイムリミットが近いため処理を終了します。DB名:{$dbName}/最大products.id:{$maxId}/トータル動作秒数:{$totalSec}/ループ回数:{$loopCount}/ループ最大所要秒数:{$loopSecMax}/ヘッドルーム秒数:{$headRoomSec}");
break;
}
}
}
public function getMaxId() {
$record = $this->Model->find('first', array(
'fields' => array('id'),
'order' => 'Product.id DESC'
));
return empty($record['Product']['id']) ? 0 : $record['Product']['id'];
}
public function insert($fields, $limit) {
$data = array();
$id = $this->getMaxId();
$vendorId = 1;
for ($i = 0; $i < $limit; $i++) {
$record = array();
$id++;
foreach ($fields as $field) {
switch ($field) {
case 'code':
$record[$field] = "code{$id}";
break;
case 'code_sub':
$record[$field] = "code_sub{$id}";
break;
case 'description':
$record[$field] = $this->getText();
break;
case 'id':
break;
case 'vendor_id':
$record[$field] = 1;
break;
case 'tag_pool':
$record[$field] = $this->getText();
break;
case 'title':
$record[$field] = "タイトル{$id}";
}
}
$data[] = $record;
}
if (!$this->Model->saveAll($data, array('callbacks' => false, 'validate' => false))) {
CakeLog::debug("レコードの新規保存に失敗しました@" . __FILE__ . ' - L.' . __LINE__);
}
}
public function getText($nLengthRequired = 1000) {
if (!$nLengthRequired) {
$nLengthRequired = rand(100, 300);
}
// 5文字程度で半角スペース区切りになった文字列データを生成するためスペースを混入させておく
$sCharList = "abcde fghij klmno pqrst uvwxy zABCD EFGHI JKLMN OPQRS TUVWX YZ012 34567 89_-, .";
mt_srand();
$sRes = '';
for($i = 0; $i < $nLengthRequired; $i++) {
$sRes .= $sCharList{mt_rand(0, strlen($sCharList) - 1)};
}
return $sRes;
}
}
?>
○Selectシェル
/var/www/vhosts/my-domain.com/cake/app/Console/Command/SelectShell.php
<?php
App::uses('AppShell', 'Console/Command');
/**
* mroonga「WHERE IN 多数」クエリの検証用シェル
*
* @property Model $Model
*/
class SelectShell extends AppShell {
/**
* メイン処理
*
* @return void
*/
public function main() {
$database = 'webTest'; // 接続先DB:「webTest」ならtestデータベース
$field = 'code'; // WHERE IN構文の対象カラム
$doCountUp = false; // SELECT前にcount値のUPDATE処理を実行するか否か
// モデルの準備
$this->Model = ClassRegistry::init('Product');
if ($database && $database !== 'default') {
// 接続先DBの変更
$this->Model->setDataSource($database);
}
// WHERE IN構文用の条件を実データテーブルから300件ランダム取得
$records = $this->Model->find('all', array(
'fields' => array($field),
'limit' => 300,
'order' => 'rand()'
));
$vals = Hash::extract($records, "{n}.Product.{$field}");
// SELECTクエリの構成
$query = (
"SELECT * " .
"FROM `products` AS `Product` " .
"WHERE `Product`.`" . $field . "` IN ('" . implode("', '", $vals) . "');"
);
// 【オプション】カウントアップ実行
if ($doCountUp) {
$countUpId = rand(1, 200000);
$query = "UPDATE products SET count=count+1 WHERE id={$countUpId}; " . $query;
}
// SELECT実行
$result = null;
try {
$result = $this->Model->query($query, false);
} catch(Exception $e) {
$result = false;
}
// DB名の確認(安心用)
$dbNameData = $this->Model->query('SELECT database() as `db`;', false);
$dbName = $dbNameData[0][0]['db'];
$queryLen = strlen($query);
// メッセージの構成
$msg = "{$dbName}データベースにおける「";
if ($doCountUp) {
$msg .= "Product.id=={$countUpId}カウントアップ → ";
}
$msg .= "WHERE Product.{$field} IN 多数」クエリチェック";
if (is_array($result)) {
$msg = "【○】{$msg}成功";
} else {
$msg = "【×】{$msg}失敗";
if (!file_exists(TMP . 'mroonga_errored')) {
// Insertシェル停止用のロックファイルを作成
touch(TMP . 'mroonga_errored');
chmod(TMP . 'mroonga_errored', 0666);
}
}
$msg = "{$msg}。クエリ長={$queryLen}字";
// ログを出力
CakeLog::debug($msg);
}
}