サーバ側でのコードの実行

Based on v35 (2011-05-15更新) - オリジナル

Mongoはデータベースプロセス内でのコードの実行をサポートしています。

クエリー中の $where 句とファンクション

find() オペレーション用の通常形式のドキュメントスタイルのクエリに加え、SQLスタイルのWHERE句や完全なJavascriptファンクションを使うこともできます。

このクエリーモードを使うと、データベースはコレクションの中の一つずつのオブジェクトに対して、ファンクションを実行するか、条件を評価します。

文字列を指定した場合、"this"でオブジェクトを表します(下記の例を参照)。 Javascriptファンクションの場合、通常のJavascriptのシンタックスを使う事ができます。

Mongoシェル 内で次の4つの文は等価です。

db.myCollection.find( { a : { $gt: 3 } } );
db.myCollection.find( { $where: "this.a > 3" });
db.myCollection.find( "this.a > 3" );
db.myCollection.find( { $where: function() { return this.a > 3;}});

最初の文が一番好ましいです。 これは、わずかに他と比べ速いです。これは、オプティマイザが簡単に理解し使うインデックスを選ぶことができるからです。

データスタイルのfind条件と、ファンクションを混ぜることもできます。これは、データスタイルの表現が先に評価され、もしマッチしない場合には、その後の評価をする必要がないので、パフェーマンス面で有利になることがあります。加えて、データベースは、与えられた条件のフィールドから、インデックスを使用するかどうかを検討します。二つの形式を組み合わせるには、 クエリーオブジェクトに $where フィールドとして評価したいファンクションを渡します。たとえば、

db.myCollection.find( { active: true, $where: function() { return obj.credits - obj.debits < 0; } } );
db.myCollection.find( { active: true, $where: "this.credits - this.debits < 0" } );

Map/Reduce

MongoDBは、Javascriptベースのサーバサイドでのmap/reduceをサポートしています。詳しくは、 map/reduce ドキュメント を参照してください。

db.eval() の使用

長く走るジョブでは、 db.eval()の代わりに map/reduce を使ってください。db.eval は他の操作をブロックします

db.eval() はデータベースサーバで(Javascriptで書かれた)ファンクションを実行するために使います。
これは、たくさんのデータを処理する必要がある場合に便利です。このようなケースでは、データのネットワーク転送がボトルネックになるからです。
db.eval() は、サーバ上で呼び出され、ファンクションの戻り値を返します。実行に失敗した場合には例外が投げられます。

簡単な例として、これは 3 + 3 をサーバで実行して取得します。

> db.eval( function() { return 3+3; } );
6
>

コレクション内のすべてのドキュメントで、指定したフィールド foo を削除したいとしましょう。クライアントサイドでやるとしたら以下のようになります。

function my_erase() {
  db.things.find().forEach( function(obj) {
                       delete obj.foo;
                       db.things.save(obj);
                     } );
}

my_erase();

my_erase() をクライアント側で呼ぶと、コレクションのすべてのコンテンツがサーバからクライアントに転送され、また送り返されます。
この代わりに、 eval() へこのファンクションを渡すことができます。そしてサーバ側の実行環境で実行されます。 サーバでは、 db 変数は、現在のデータベースにセットされます。

db.eval(my_erase);

> myfunc = function(x){ return x; };

> db.eval( myfunc, {k:"asdf"} );
{ k : "asdf" }

> db.eval( myfunc, "asdf" );
"asdf"

> db.eval( function(x){ return x; }, 2 );
2.0

評価中にエラーが起きた場合 (サーバ側でのnull pointer exceptionとか)、次の形で例外が投げられます。

{ dbEvalException: { errno : -3.0 , errmsg : "invoke failed" , ok : 0.0 } }

Mongoの count() ファンクションと等価なことを eval() でする例です。

function mycount(collection) {
return db.eval( function(){return db[collection].find({},{_id:ObjId()}).length();} );
}

アトミックに、インクリメントといくつかの計算をするために、 db.eval() を使用する例です。

function inc( name , howMuch ){
    return db.eval(
        function(){
            var t = db.things.findOne( { name : name } );
            t = t || { name : name , num : 0 , total : 0 , avg : 0 };
            t.num++;
            t.total += howMuch;
            t.avg = t.total / t.num;
            db.things.save( t );
            return t;
        }
    );
}

db.things.remove( {} );
print( tojson( inc( "eliot" , 2 )) );
print( tojson( inc( "eliot" , 3 )) );

eval の制限

書き込みロック (write lock)

eval はデフォルトではwrite lockを取得します。これは、 eval 内で、write lockする別のコマンドを実行できないことを意味します。たとえば、レプリカセットを走らせていて、新しいメンバーを追加したいとします。ドライバから、次のようなことを実行しようとするかもしれません。

db.eval("rs.add('ip-address:27017')");

繰り返しますが、 eval は現在のノードに対してwrite lockを取得します。そのため、この処理は動きません。なぜから、write lockされたノードが存在すると、新しいメンバーを追加できないからです。

正しい方法は、手動で新しいノードを追加することです。 rs.add は単に、 local.system.replSet コレクションにクエリを実行し、configオブジェクトを更新し、 replSetReconfig コマンドを実行します。これは、 eval を使わずにドライバから実行できます。

バージョン1.7.2で、 nolock オプションがdb.evalに追加されました。
shard

eval}}は、shardでも動かないことに注意してください。最終的にshardを使う予定なら、 {{eval は避けたほうがいいでしょう。

サーバサイドへのファンクションの保存

system.js という特別なコレクションがあります。これに、Javascriptのファンクションを繰り返し使うために保存することができます。ファンクションを保存するためには、

db.system.js.save( { _id : "foo" , value : function( x , y ){ return x + y; } } );

_id はファンクションの名前です。データーベース毎にユニークです。

一度これをしたら、 foo をどのJavascriptからでも呼ぶことができます。 (db.eval, $where, map/reduce)

http://github.com/mongodb/mongo/tree/master/jstests/storefunc.js にもっと例があります。

並列性に関するノート

eval() は、実行中、mongodプロセス全体をブロックします。このため、このオペレーションはアトミックですが、他の処理中の操作は止まります。

並列性が必要なときには、eval()の代わりにmap/reduceを使ってください。

サーバ上のmongoシェルで .js ファイルを実行

これは、バッチの管理処理を実行するのに適したテクニックです。 mongo をサーバ上で実行し、 localhost に接続します。 このコネクションはとても早く、遅延も少ないです。 db.eval() は他の操作をブロックするので、db.eval()よりもこの方法は使いやすいです。

バージョン1.7.2で、 nolock オプションがdb.evalに追加されました。

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