MongoDB 2.4 の新クエリー$setOnInsertを試した
$setOnInsertの機能と性能を検証をした。
結論
$setOnInsertには副作用は無さそう。積極的に使っていきたい。
$setOnInsertの機能
upsertと組み合わせて使う。
- upsert
- レコードが無かった場合はinsertとして働き
既にレコードがあった場合はupdateとして働く。
$setOnInsetで指定したフィールドはinsertの場合の時だけ初期値としてセットされ、updateとなった場合は無視される。
>use testdb // keyフィールドをユニークインデックスに指定 >db.cappedArray.ensureIndex({key:1},{unique:1}); // key:10 レコードを追加。 >db.cappedArray.save({ key:10, value:[ {s:10}, {s:20}, {s:30}, {s:40}, {s:50} ]}); >db.cappedArray.find({key:10}); { "_id" : ObjectId("514fb518f63edd201d14595f"), "key":10, "value":[{ "s":10},{"s":20 },{"s":30 },{"s":40},{"s" : 50 } ] } // $setOnInsert クエリー(value:[{s:0}] が初期値) > db.cappedArray.update( {key:10}, { $setOnInsert:{ value:[{s:0}] } }, {upsert:1}); // 確認:key:10は既に存在するので$setOnInsertで指定したvalueフィールドは何も更新されない。 >db.cappedArray.find({key:10}); { "_id" : ObjectId("514fb518f63edd201d14595f"), "key":10, "value":[{ "s":10},{"s":20 },{"s":30 },{"s":40},{"s" : 50 } ] } // key: 20 を指定してみると? > db.cappedArray.update( {key:20}, { $setOnInsert:{ value:[{s:0}] } }, {upsert:1}); // 確認:key:20は存在しないのでinsertされ、$setOnInsertで指定したvalueフィールドがセットされる。 { "_id" : ObjectId("514fb518c19c65ba8314e158"), "key" : 20, "value" : [ {"s":0} ] }
普通に使いたくなる機能だが、更にMongoDBでは
パフォーマンス向上の為にもこんな事がしたい場合があるのだ。
詳しくは、以下を参照のこと。
4.ドキュメントをプリアロケート(事前確保)する。
性能検証
- プロファイルの取り方
- 目的のDBに移ってdb.setProfilingLevel(level)
0 : 無効
1 : slowsオペレーション(遅い奴)だけ
2 : 全部オペレーション
use testdb db.setProfilingLevel(2)
プロファイルはdb.system.profile(capped)コレクションに溜まる。
$setOnInsert
単純insert(save)と違いがあるのか?
同じドキュメントをsave , update($setOnInsert) で入れてみて比較する。
- saveクエリー
$ db.cappedArray.save({ key:99, value:[ {s:10, v:'11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111'} ,{s:20, v:'22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222'} ,{s:30, v:'33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333'} ,{s:40, v:'44444444444444444444444444444444444444444444444444444444444444444444444444444444444444444'} ,{s:50, v:'55555555555555555555555555555555555555555555555555555555555555555555555555555555555555555'} ,{s:60, v:'66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666'} ,{s:70, v:'77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777'} ,{s:80, v:'88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888'} ,{s:90, v:'99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999'} ,{s:100,v:'00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'} ,{s:110,v:'11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111'} ,{s:120,v:'22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222'} ,{s:130,v:'33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333'} ,{s:140,v:'44444444444444444444444444444444444444444444444444444444444444444444444444444444444444444'} ,{s:150,v:'55555555555555555555555555555555555555555555555555555555555555555555555555555555555555555'} ,{s:160,v:'66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666'} ,{s:170,v:'77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777'} ,{s:180,v:'88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888'} ,{s:190,v:'99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999'} ,{s:200,v:'00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'} ] });
- saveクエリーのプロファイル
- ninserted:1 (1件insert)
2.2kb程度のドキュメントを新規insertするのに0.55ms程度のロックを行った
貧相なVMで検証してる為、遅いのは仕方がない。相対的に評価していく。
{ "op" : "insert", "ns" : "testdb.cappedArray", "ninserted" : 1, "keyUpdates" : 0, "numYield" : 0, "lockStats" : { "timeLockedMicros" : { "r" : NumberLong(0), "w" : NumberLong(550) }, "timeAcquiringMicros" : { "r" : NumberLong(0), "w" : NumberLong(41) } }, "millis" : 0, "ts" : ISODate("2013-03-26T03:23:56.323Z"), "client" : "127.0.0.1", "allUsers" : [ { "user" : "crumb", "userSource" : "admin" } ], "user" : "crumb@admin" }
- $setOnInsertクエリー
- 新規レコードなのでinsertと同じ処理になって欲しい。
db.cappedArray.update( {key:999}, {$setOnInsert:{ value:[ {s:10, v:'11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111'} : <中略> ,{s:200,v:'00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'} ] } }, {upsert:1});
- $setOnInsertクエリーのプロファイル
- nupdate:1(1件update)
updateのクエリー条件 query:{key:99}
fastmodinsert:true upsertがinsertになった事を示すようだ
lock時間は0.47ms 上のsaveとほぼ変わらない。
insert処理とupdate($setOnInsert)処理は性能面で変わりはないようだ。
積極的に使て行きたい。
{ "op" : "update", "ns" : "testdb.cappedArray", "query" : { "key" : 999 }, "updateobj" : { <略> }, "nscanned" : 0, "nupdated" : 1, "fastmodinsert" : true, "keyUpdates" : 0, "numYield" : 0, "lockStats" : { "timeLockedMicros" : { "r" : NumberLong(0), "w" : NumberLong(468) }, "timeAcquiringMicros" : { "r" : NumberLong(0), "w" : NumberLong(6) } }, "millis" : 0, "ts" : ISODate("2013-03-26T04:00:33.381Z"), "client" : "127.0.0.1", "allUsers" : [ { "user" : "crumb", "userSource" : "admin" } ], "user" : "crumb@admin" }
- (空アップデートの)$setOnInsertクエリーのプロファイル
- nscanned:1 既存レコードを発見した。
fastmod:true に変わった。高速(in-place)updateを選択した。
実際の更新処理はしていないので、ロック時間は短め。
問題なさそう。
{ "op" : "update", "ns" : "testdb.cappedArray", "query" : { "key" : 999 }, "updateobj" : { <略> }, "nscanned" : 1, "nupdated" : 1, "fastmod" : true, "keyUpdates" : 0, "numYield" : 0, "lockStats" : { "timeLockedMicros" : { "r" : NumberLong(0), "w" : NumberLong(146) }, "timeAcquiringMicros" : { "r" : NumberLong(0), "w" : NumberLong(5) } }, "millis" : 0, "ts" : ISODate("2013-03-26T04:13:12.385Z"), "client" : "127.0.0.1", "allUsers" : [ { "user" : "crumb", "userSource" : "admin" } ], "user" : "crumb@admin" }