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

中年engineerの独り言 - crumbjp

LinuxとApacheの憂鬱

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" }