アナログCPU:5108843109

ゲームと音楽とプログラミング(酒と女とロックンロールのノリで)

PHP×mongoDBでいろいろな検証

環境
VMWareでCentOS5.8
PHP 5.4
・MongoDB 2.6

countの速度検証

// $collection は new MongoCollection 云々で定義したやつ
// $condition は取得条件の配列

// ①
$count_a = $collection->count($condition);
// ②
$count_b = $collection->find($condition)->count();

①②それぞれ1万回連続実行を5回試行しました。(単位は秒)

2.81 3.28 2.99 2.75 2.45
2.86 3.21 3.07 2.81 2.59

①の方がほんのり速いですね。

カーソルの挙動

まあそもそもカーソルを理解できてない感あるのですが

// $collection は new MongoCollection 云々で定義したやつ
// $condition は取得条件の配列

$cursor = $collection->find($condition);

// ①取得結果を時間かけてぐるぐる
foreach ($cursor as $document)
{
    // 3秒待つ
    usleep(3000000);

    // 表示
    var_dump($document['str']);
}

①が時間かけて動いてる間にinsert連打したらどうなるか。

結果、まあinsert内容はここでは反映されませんでした。
しかも、insertはここの処理が終わった後に実行…ってロックかかってんのかよ!!
このやり方で時間のかかる処理をするのはよろしくなさそうですね。

調べてみた感じ、DBレベルのロックである模様。コレクションレベルですらないのか…
ただし、バージョン2.8からドキュメントレベルのロックになった模様?
http://blog.odoruinu.net/2014/11/13/mongodb-2-8-0-rc0/

collectionとってくるときの速度検証

$mongo = new MongoClient("mongodb://localhost:27017");

// ①
$db = $mongo->selectDB("DB名");
$collection = new MongoCollection($db, "コレクション名");
// ②
$collection = $mongo->selectDB("DB名")->selectCollection("コレクション名");

①②それぞれ100回連続実行を5回試行しました。(単位はミリ秒)

0.508 0.532 0.549 0.542 0.557
0.544 0.525 0.518 0.573 0.595

変わんね
まあこれなら取り回しやすい①で良いかな。
いろんなDB使う状況よりも、1つのDBのいろんなコレクション使う状況の方が多いだろうし。
(当然「$db = ...」の行を処理時間計測部分から外すと①の方が速いです)

insert VS batchInsert(ついでにMySQLも)

とにかくInsertする速度を単純に計る。

  • データを100件用意($insert_list)
    • 中身は「'a' => "hoge_1"」~「'a' => "hoge_100"」
  • mongoにはフィールド「a」を入れる
  • mysqlには「test」テーブル(`id`(PK,autoincrement), `a`)を作って「a」に値を入れる
// $insert_list:array(array('a' => "hoge_1"), array('a' => "hoge_2"), ...)
// $collection:mongoのtestコレクション

// ①[MongoDB]insertを100回
for ($i = 0; $i < 100; $i ++)
{
    $collection->insert($insert_list[$i]);
}

// ②[MongoDB]batchInsertを1件×100回
for ($i = 0; $i < 100; $i ++)
{
    $collection->batchInsert(array($insert_list[$i]));
}

// ③[MongoDB]batchInsertを100件×1回
$collection->batchInsert($insert_list);

// ④[MySQL]INSERTを1件×100回
for ($i = 0; $i < 100; $i ++)
{
    クエリ実行("INSERT INTO `test`(`a`) VALUES (" . $insert_list[$i]['a'] . ") ");
}

// ⑤[MySQL]INSERTを100件×1回
// (面倒なのでクエリ組み立てのごちゃごちゃは省略 察しろ)
クエリ実行("INSERT INTO `test`(`a`) VALUES (" . $insert_list[0]['a'] . "), (" . $insert_list[1]['a'] . "), ... ");

まあこんな感じのを5回ずつ試行した結果がこちら。単位はミリ秒。

24.480 23.633 35.464 36.615 28.074
20.702 22.661 26.355 21.509 22.932
2.667 1.759 1.570 1.641 1.935
241.351 170.492 184.373 185.667 205.206
19.397 26.420 20.548 23.061 23.316

いい加減見づらいので平均出すと、
①29.7
②22.8
③1.9
④197.4
⑤22.5
ですね。わかっちゃいたけど④ェ…

意外なのが、たとえ1件ずつの処理でも、insertよりbatchInsertの方が速い…と言うほどではなくても、少なくとも同等であること。
書き方の統一を考えても、もしかして常にbatchInsert使った方がよいのでは…?

あとは、まとめての処理がめちゃくちゃ速いですね。
100件挿入が2ミリ秒程度で完了て。
並列処理に弱いとしても、データの参照・更新頻度によっては、こんだけ速けりゃカバーできそうな気もする。

日付の扱い方

まずは(SQLで言うところの)INSERT値やWHERE句など、日付をDBに送りつける場合。
PHPではユーザの入力値やなんやがあるので文字列で持ってることが多いんで、それを想定。

// 例によって$collectionはコレクションのやつ

// 日付が入っている変数を
$date_string = "2016-01-01 12:34:56";

// MongoDate型にキャストして
$insert_list = array(
    'date' => new MongoDate(strtotime($date_string)),
);

// insertじゃ
$collection->insert($insert_list);

まあ操作もめんどいんですが、DBを覗いてみると標準時で入ってるからキレそう。
PHPを介してINSERTしたりSELECTしたりする分には意識しなくていいけど)

次は(SQLで言うところの)SELECT。
日付な検索条件を送るときはINSERTとかと方法は同じですが、取得結果をまた文字列に直すパターンを想定して。

// 例によって$collectionはコレクションのやつ

// 検索条件設定(MongoDate型にキャスト)
$from_date = new MongoDate(strtotime(date("Y-m-d") . " 00:00:00"));
$to_date   = new MongoDate(strtotime(date("Y-m-d") . " 23:59:59"));
$where_list = array(
    '$and' => array(
        array('date' => array('$gte' => $from_date, '$lte' => $to_date)),
    ),
);

// 検索実行(配列化して確保)
$cursor = $collection->find($where_list);
$data_list = iterator_to_array($cursor);

// ここで$data_listを覗いてみると、日付(dateフィールド)の戻り値は
// array('date' => MongoDateObject('sec' => 1234567890, 'usec' => 0))
// という形式になっていたので
// secの部分をtime値として扱い文字列に直す
foreach ($data_list as $key => $val)
{
    $data_list[$key]['date_string'] = date("Y-m-d H:i:s", $val['date']->sec);
}

めんどい。

とりあえずここまで

長くなってきたのでここらでpost
後からこっそり追記したりします