アトミックな操作

based on v31

MongoDBのアトミックな操作に対する思想は、従来のデータベースと異なっています。 いくつかの理由から、MongoDBは、従来の方法のロックをサポートしていません。

  • まず、分散された環境で、ロックの情報を分散させるのは、コストが高く、そして遅いです。MongoDBの目標は、軽く速いことです。
  • 私たちはデッドロックという考えかたが嫌いです。 そういうことがない、シンプルでわかりやすいシステムにしたいです。
  • 私たちは、MongoDBを、リアルタイムな問題に対して、よく動くようにしたいです。オペレーションが大量のデータをロックしてしまうと、長い時間、軽い小さなクエリーを止めてしまうことがあります。("リアルタイム"に関しては、MongoDBはまだ完璧だとは言えません。しかし、ロックがあると、それをさらに難しくします。)

とは言え、明らかに、ドキュメントの更新をアトミックに行う方法は必要です。このためのいくつかの方法を見てみます。

modifierオペレーション

MongoDBのアップデートコマンドは、いくつかの modifier をサポートしています。この操作ではドキュメントのエレメントをアトミックに更新します。次のものがあります。

  • $set - 特定の値をセット
  • $unset - 特定の値をセット (バージョン1.3.0以上)
  • $inc - 値に対して指定した数でインクリメント
  • $push - 配列に値を追加
  • $pushAll - 配列に、複数の値を追加
  • $pull - 配列から値を削除
  • $pullAll - 配列から、複数の値を削除

これらのmodifierは、アトミックな実行をするのに使えます。

"値が変更されていなかったら更新"

もう一つの、atomicにアップデートをする戦略として、 "値が変更されていなかったら更新" という方法があります。 これは、

  1. オブジェクトを取得
  2. オブジェクトをローカルで修正
  3. "まだ変更前の値のままなら、新しい値でアップデートしろ" というリクエストを送る

このオペレーションが失敗した場合には、最初のステップからやりなおします。

例えば、一つのオブジェクトをinventoryから取得したいとします。もしそのオブジェクトが存在する場合、取得したinventoryから1引きます。次のコードは、mongoシェルを使いこれをするためのコードです。(どの言語でも同じようなファンクションがあるでしょう)

> t=db.inventory 
> s = t.findOne({sku:'abc'}) 
{"_id" : "49df4d3c9664d32c73ea865a" , "sku" : "abc" , "qty" : 30} 
> qty_old = s.qty; 
> --s.qty; 
> t.update({_id:s._id, qty:qty_old}, s); db.$cmd.findOne({getlasterror:1}); 
{"err" : , "updatedExisting" : true , "n" : 1 , "ok" : 1} // 成功 

上記の例では、skuから実際に1引けるかどうかというのを考えていません。そのため下のコードの方が優れていますが汎用性は低いです。 modifierオペレーション ($inc) を使います。一般的なアップデートに関して($incを使えないような)は、上記の"値が変更されていなかったら更新"の方法が推奨されます。

> t.update({sku:"abc",qty:{$gt:0}}, { $inc : { qty : -1 } } ) ; db.$cmd.findOne({getlasterror:1}) 
{"err" : , "updatedExisting" : true , "n" : 1 , "ok" : 1} // 成功 
> t.update({sku:"abcz",qty:{$gt:0}}, { $inc : { qty : -1 } } ) ; db.$cmd.findOne({getlasterror:1}) 
{"err" : , "updatedExisting" : false , "n" : 0 , "ok" : 1} // 失敗 

"存在していなかったらインサート"

もう一つの楽観的な並列処理として、存在しない場合だけインサートする、という方法があります。これは、条件に対してユニークインデックスを持っている場合にだけ可能です。次の例は、この方法を使って、_idを単純に増やしながらインサートする例です。

 
function insertObject(o) { 
    x = db.myCollection; 
    while( 1 ) { 
        // determine next _id value to try 
        var c = x.find({},{_id:1}).sort({_id:-1}).limit(1); 
        var i = c.hasNext() ? c.next()._id + 1 : 1; 
        o._id = i; 
        x.insert(o); 
        var err = db.getLastErrorObj(); 
        if( err && err.code ) { 
            if( err.code == 11000 /* dup key */ ) 
                continue; 
            else 
                print("unexpected error inserting data: " + tojson(err)); 
            } 
        break; 
    } 
} 

findしてmodify(またはremove)

findandmodifyコマンドドキュメン を参照してください。

複数のオブジェクトをアトミックに更新

基本的に、MongoDBは複数のドキュメントを一回の操作でのアトミックな更新をサポートしていません。しかし、アトミック性のために、複数のオブジェクトをネストすることで、1つのオブジェクトにすることができます。 db.eval() 命令は、複数の操作を一度で行うことができます。しかし、eval() のアトミック性は、shard環境のいくつかのケースではサポートされないので、推奨されません。


Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.

IF YOU HAVE A QUESTION, POST IT TO THE USER GROUP.

These pages are fine for comments, but for questions, your best bet will always be the MongoDB User Group.

blog comments powered by Disqus