cursorを回しつつupdateすると同じドキュメントが二回更新される

mongoDBでcursorでコレクションを取得しつつupdateすると、同じドキュメントが二回updateされたり、全くされなかったりすることがあるようだ。

たとえば次のようなクエリをmongoシェルから流すと

db.Hoge.find().forEach(function(result) {
    db.Hoge.update({_id: result._id}, {$set: {x: result.x * 2, y: someObject}});
});

updateが二回行われてxが4倍になっているドキュメントや、xが変化していないドキュメントが存在していた。

これはドキュメントのサイズが変わりデータの再配置が行われ、natural orderが変化することによって同一のドキュメントが二回読まれたり、一度も読まれないドキュメントが出てしまったのだと思われる。

もしそうであればnatural orderを使わなければ問題ないはず。以下のようにsortに_idを指定したところ全てのドキュメントが一度だけupdateされた。

db.Hoge.find().sort({_id: 1}).forEach(function(result) {
    db.Hoge.update({_id: result._id}, {$set: {x: result.x * 2, y: someObject}});
});

また、ドキュメントのサイズが変わらないin-placeなupdateではデータが再配置されないので、やはり問題ないと思われる。以下のような$incのみのupdateでも意図通りに動作した。

db.Hoge.find().sort({_id: 1}).forEach(function(result) {
    db.Hoge.update({_id: result._id}, {$inc: {x: result.x}});
});

他の接続によってupdateやinsertされて再配置やチャンク移動が発生することもあるが、カーソルを回している最中はreadロックされ他の接続はwriteできないはずなので問題ないと思う。これは検証していないので実際のところは分からない。