このブログを検索

2013/02/28

条件がIndexにある場合とない場合のMapReduceの速度比較

  • Object数:289,701
  • sort key : u_id asc , level asc
  • find: delete_flg = 0 (件数が多い方)
  • チューニングなどはしてない
    参考:1オブジェクト当たり500回ループする 
結論:3.のように条件になるFieldをIndexの先に指定するのが早くなった。(個人の感想ですw)

1.INDEX:u_id asc、level  asc で実施したとき。
ntoreturn:1 keyUpdates:0 numYields: 2983 locks(micros) W:6563 r:848342771 w:13221 reslen:143 720053ms

2.INDEX:u_id asc、 level  asc、 delete_flg asc で実施したとき。
ntoreturn:1 keyUpdates:0 numYields: 2983 locks(micros) W:4156 r:830311738 w:9896 reslen:143 703717ms
 3.INDEX:delete_flg asc、u_id asc、level  asc で実施したとき。
ntoreturn:1 keyUpdates:0 numYields: 2983 locks(micros) W:4137 r:806274333 w:9771 reslen:143 683357ms
4.INDEX:u_id asc, level  asc、delete_flgの条件なし
ntoreturn:1 keyUpdates:0 numYields: 2983 locks(micros) W:3972 r:859051680 w:10412 reslen:143 730575ms

2013/02/25

NestedのRegexの検索

以下のdocumentで"_id"の"ymd”で部分一致(2013-01-05%)の検索をしたかった。

{ _id :{ ymd: "2013-01-05 02", i_id:2}, value {...}}
{ _id :{ ymd: "2013-01-05 03", i_id:5}, value {...}}

(1)$regexでやってみたが、nestedのfieldに対して$regexは出来ない模様。
  • 以下の様に作って見たが全て失敗した。
db.collections_name.find({ '_id.ymd' :  "/2013-01-05/"});
db.collections_name.find({ '_id.ymd' :  {$regex : "/2013-01-05/" } } ); 
  • _idだからと思い全てのフィールドで条件を付けてみたが失敗。
db.collections_name.find({ _id, {ymd : "/2013-01-05/", i_id : 5 } } );
db.collections_name.find({ _id, {ymd' :  {$regex : "/2013-01-05/", i_id :5 } } ); 

  • Regex自体の問題かと思い次の様にNestedされてないfieldでやったら検索出来た。
db.collections_name.find({ field_name : /おに/});

(2)仕方なく以下の様にして今回は検索した。
db.collection_name.find({'_id.ymd' : {$gte :'2013-01-05', $lte:'2013-01-05'}});
db.collections_name.find({'_id.ymd' : {$gte :'2013-01-05 00', $lte:'2013-01-05 23'}});

2013/02/21

MongoDBのDateで検索する際の落とし穴

MongoDbはISODateとして残る。
javascriptのnew ISODate()で登録すると GMTで登録される。
#参考にGMTとUSTはグリニッジ標準時をご覧ください。

これをphpのstrtotime()とかで日付を取得して検索すると
GMTとJSTの+9時間の時差が出る。

参考にphp.iniに「date.timezone = Asia/Tokyo」があった。

例)
(1) new ISODate(2013-01-02 01:00:00) で登録
    GMTで登録される。すなわち JST 2013-01-01 16:00:00である。

(2) 以下のようにphpで検索条件を作る。
  $start_date = new MongoDate(strtotime('2013-01-02 00:00:00'));
  array("_id" => array ('$gte'=>$start_date));

  print( date('T Y-m-d H:i:s',$start_date->sec) ) ; -> JST 2013-01-02 00:00:00'と表示される。

上記(1)をmongoで見るとISODate("2013-01-01T01:00:00Z")のように見えるから
注意しないと間違ってしまう。

登録するときは+09:00した時間で登録するか
あるいは検索条件に−09:00にしないと。。
参考になるサイトをreferenceに貼っておく。

しばらくはphpでnew MongoDate()する際に以下の様にしてる。
$GMT_TimeZone = new DateTimeZone('GMT');
$start_date_Ymd = new MongoDate(
    DateTime::createFromFormat('Y-m-d H:i:s',
        date("Y-m-d 00:00:00", strtotime("-31 days")), $GMT_TimeZone)->getTimestamp() );


2013/02/20

mongodbはHDDの容量が大きい

わけが有り32bitOSでKPI取得ツールを作成して運用中。
が、1ヶ月も立ってないのにstorage sizeが200M超え(Rockmongoから確認)!!

MySQLからCSVにしたデータは何M(何十M?)のはずだが
MongoDBは結構食う。

急ぎで作った(かつ、知識もなかった)から
英字で10文字以上のフィールド名をそのまま利用したり
CSVからのImportなので年月日と時分秒をそのまま文字として保持してる部分はある。
が、それでも怪しいので一段落したら調べる。

2013/03/05追記
CSVファイル容量の約2.5倍のDataSizeを食う事が分かった。
上のようにField名の工夫をしてない、日付をStringとして格納、
_idをObjectIdではなく個別にしてするなどの理由はあるがそれでもよりサイズが大きい事がわかった。


【容量記録】
| 日付 |SIZE | StorageSize | DataSize | Index Size |Objects | CSV size |DataSize増減|
| 2013/02/20 |-| 200m程度  | - | - | - |- |
| 2013/02/21 |1000m程度|294.52m | 209.55m | 18.4m | -|23m | -|
| 2013/02/22 |1023.75m| 387.91m | 258.68m | 22.8m | 862,368 |19m|49.13m|
| 2013/02/25 | 1.5G | 513.77m | 375.49m | 33.8m | 1,248,860 |46m |116.81m|
| 2013/02/26 | 1.5G | 514.54m | 404.36m | 33.15m | 1,344,394 | 12m |28.87m|
| 2013/02/26※1 | 1.5G | 170.60m | 127.56m | 11.45m | 453,500 | - |-276.8m|
| 2013/02/26※2 | 1.5G | 288.78m | 227.88m | 20.12m | 733,270 | 41.4m |100.32m|
| 2013/02/27 | 1023.75m | 320.09m | 242.35m | 26.96m | 789,443 | 13.7m |14.47m|
| 2013/02/28※4 | 1023.75m | 365.41m | 261.58m | 29.73m | 864,312 | 12.2m |19.23m|
| 2013/03/01※3  | 1.5G | 355.66m | 260.32m | 51.85m | 840,641| 4m |-1.26m|  

※1:02/26に不要と判断したcollectionを3つDropして、Importしないようにした。
| Collection | StorageSize | Index Size | Objects | lastExtentSize|
| log_rxxxx |166.64M | 13.8M | 571,957 | 48.45M |
| log_s_xxx |166.64M | 7.29M | 300,781 | 48.45M |
| t_c_xxxxx |10.6M | 0.58M | 18,246 | 8M |

※2:41.4mのCSVファイルを新規にimportして「_id」を指定する時としない時の容量を比較したが、
ObjectIDにしたほうが少し小さかった。
| Collection | StorageSize | Index Size | Objects | lastExtentSize|
| t_u_xxxxx  | 118.18m | 8.67m | 279,767 | 35.88m |:_idをObjectIDにした
| t_u_xxxxx  | 118.18m | 9.72m | 279,767 | 35.88m | : _idをIntにした

 ※3 約30万Objects程のcollectionに対してindexに_id以外に2パータンを追加した。
  totalIndexSizeが37.55mだった。複数キーのindexはサイズも大きい!!

 ※4 2m程はupdateされるデータ、不要ログの削除処理を追加

2013/02/15

MongoDBのMapreduceの落とし穴(undefinedが発生する理由と対策)

1)Mapreduceで集計をしたらreduceの処理途中にundefineのレコードがあり、一部集計が失敗した。

不良データを探そうとprint()で_idで調べたら以下のような結果となり不良データがないように見えた。
Map/reduce : _id
map : 1
map : 2
map : 3
reduce : 1
reduce : 2
reduce : 3
map : 4
map : 5
reduce : undefine
reduce : 4
reduce : 5

原因と対策方法は時間があるときに探そう。

2つのCollectionを纏めてMapreduce方法を探していたら
http://tebros.com/2011/07/using-mongodb-mapreduce-to-join-2-collections/
 if (this.FY2009 !== undefined && this.FY2009 !== null) {
 のようなCodeがあった。

2)上は解決したが、それでも集計結果が不一致。
以下の_id : 3 が集計されない。reduceの実行のタイミング怪しい?

Map/reduce :_id : group key(break key?)
map : 1 : 2013-01-01
map : 2 : 2013-01-01
map : 3 : 2013-01-02
reduce : 1  : 2013-01-01
reduce : 2 : 2013-01-01
reduce : 3 : 2013-01-02
map : 4 : 2013-01-02
map : 5 : 2013-01-03
reduce : undefined : undefined ← reduce : 3までの処理結果
reduce : 4 : 2013-01-02
reduce : 5 : 2013-01-03

原因:map -> reduce→map -> reduceが繰り返し処理される。
この繰り返しの時、前のreduceの結果を引き継ぐためにreduceのValue[0]に前回の処理結果が渡される。

例えば
emit( key: {user_id : [this.user_id], count:1}
reduce(key,v) { var reducedValue = {u_id:[], t_c:0}; ... }
にしたとき
同じkeyの中で2回目に呼ばれたv[0]はreducedValueのu_idとt_cの配列が来る。

http://ryooo321.blogspot.jp/2012/05/mongodbmapreduce.html からヒントを頂きました。

*2013/02/19追記
6576件のデータを集計してみたが
600件ずつ集めて、Groupkeyが変更される前の時点で
600件の配列を10回、576件の配列が1回繰り返しの処理に入って来た。

{Key: 2013-02-17 、件数: 201件}
{Key: 2013-02-18 、件数: 6576件}

reduce -> 2013-02-17 :(100件 X 2回)+1件呼出す
reduce -> 2013-02-18 :(100件 X 6回) X10回+(100件 X 5回)+76件 呼出す
finalize -> 2013-02-17 :1回 呼出す
reduce -> 2013-02-18 : (600件 X10回)+ (576件X1回) 呼出す // 新しい配列
finalize -> 2013-02-18 :1回 呼出す

結論は、
mapのemitで指定するValueのIDの名称とreduceのIDの名称を一致させるべき。(マニュアルにあったけど理解出来なかった。)

良い例)
emit( _id , { u_id : this.user_id, t_c : 1})
reduce (k, v) {
  var rValue = {u_id : "", t_c : 0};
  rValue.t_c += v.t_c;
  rValue.t_c += v.u_id + rValue.u_id;
 return rValue;
}

悪い例)
emit( _id , { user_id : this.user_id, count : 1})
reduce (k, v) {
  var rValue = {u_id : "", t_c : 0};
  rValue.t_c += v.count; // undefinedが発生する可能性有り
  rValue.t_c += v.user_id + rValue.u_id; // undefinedが発生する可能性有り
 return rValue;
}

2013/02/14

MapReduceのjavascriptのデバッグ方法

Mapreduceのjavascriptの処理の途中の値を見たいときは。

mongo シェルから実行するときは普通に
 print("user_id =" + user_id);
のようにすればmongodのログに出力された。

PHPから実行したときは?
あとで確認する。

2013/02/08

TASK#2 PHPで年月日でGroup byしてカウントする方法


SELECT DATE_FORMAT(  `create_date` ,  '%Y/%m/%d %H:00:00' ), COUNT(user_id)  FROM user_table GROUP BY DATE_FORMAT(`create_date`, '%Y%m%d %H') の実装方法

(1)mapreduceでjavascriptのdateformatを使って文字列を日付に変換させようとしたが、次のエラーが出て失敗。
exception: map invoke failed: JS Error: ReferenceError: DateFormat is not defined nofile_b:1

CSVでデータをImportしたときは 
日付のGroup Byをしたい場合は「年/月/日/時/分/秒」等で分類して持った方がやりやすいかも。

 
(2)Mapreduce関数を利用した(Scopeも利用した)
// get yyyyMMddのScope機能も利用
$scopeFunction = "function (y,m,d) {
        if ( m < 10) {m = '0' + m;}
        if ( d < 10) {d = '0' + d;}
        return y + m + d;
    }";

$scope = new MongoCode($scopeFunction); // MongcodeでScopeを利用

// map 関数と reduce 関数を作ります
$map = new MongoCode("function() {

    var group_key = getYyyyMMdd(this.create_date_y,this.create_date_m,this.create_date_d);
    
    emit(group_key,1); }"); //GroupByのcreate_dateになるキー

$reduce = new MongoCode("function(k, vals) { 
    var count = 0; // 初期化
    for (var i in vals) {
        count += vals[i]; //count up
    }
    return count; }");
// phpのCommandで実行
$result = $db->command(array(
    "mapreduce" => "collection", 
    "map" => $map,
    "reduce" => $reduce,
    "scope" => array("getYyyyMMdd"=>$scope), // この設定に注意
    "out" => array("replace"=>"new_user_daily_count")
 ));

$cur_daily_counts = $db->selectCollection($result['result'])->find(); 

datetimeで範囲検索してカウントする

datetimeで範囲検索

検索のみ
db.user.find({create_date:{$gte:"2013-01-26 00:00:00",$lte:"2013-01-26 23:59:59"}})

カウント

db.user.find({create_date:{$gte:"2013-01-26 00:00:00",$lte:"2013-01-26 23:59:59"}}).count()


create_dateがdate型ではなかったが、String比較で抽出されたと思う。

2013/02/06

TASK#1 MySQL -> mongodb

1. Export does User Data of MySQL in CSV.
( use "select * from table_name into outfile "path/to/export.csv" fields terminated by ',';)

2. I used "mongoimport".

CSV file
user_id, user_pw , name <- header
10, test_pw,名前1 <- row
20, test_pw,名前2 <- row

$ mongoimport --db bad_kpi --collection {CSV file name} --type csv --file {csv path} --headerline --upsert

in mongo
{"_id":ObjectID(xx1), "user_id":10, "user_pw":”test_pw”, ”name”:”名前1"}

{"_id":ObjectID(xx2), "user_id":20, "user_pw":”test_pw”, ”name”:”名前2"}

【参考】
(1)primary keyがひとつであれば”_id"に書き換えて出力するとmongoimportの--upsert だけで重複更新を防げる。
(2)更新されたデータのimportも--upsert を指定すると_idが一致するだけで更新される。

【注意点】
(1)CSVからのdate型のimportはやはり出来なかった。- ISODateを指定したらdateの文字になる。
−{"$date" : 111111111} はエラーとなる。
(2)ISODateの入力ができないから年月日をバラバラにしていれたら数字になった。
例)CSV -> mongodb
date_m : ”02” -> "date_m" : 2

mongodb With PHP 構築

  • 環境
    • OS: CentOS5.5
    • PHP: 5.3.14
    • ZendFramework : 2.3
  •  インストール方法
  • http://docs.mongodb.org/ecosystem/drivers/php/にあるどおりしたら簡単に「/usr/lib/php/modules/mongo.so」が出来た。
  • が、権限が「rw-r--r--」のまま。実行権限が必要ではないかと思いとりあえず権限を与える。
 
  • Connection check
    • http://www.php.net/manual/en/mongo.tutorial.phpの例のまましたらすんなり出来た。

2013/02/04

MongoDBを使う上の基本を勝手に決めてみた。

  • Reference
    http://docs.mongodb.org/manual/reference/
    • About BSON Document
      http://docs.mongodb.org/manual/core/document/
    • MongoDB Extended JSON
      http://docs.mongodb.org/manual/reference/mongodb-extended-json/ 

  • Use Cases
    http://docs.mongodb.org/manual/use-cases/

  • SQL to MongoDB Mapping Chart
    http://jp.docs.mongodb.org/manual/reference/sql-comparison/ 

  • PHP Manual
    http://www.php.net/manual/en/mongo.tutorial.php