SpringData使ってMongoDBごにょごにょ

SpringDataDocumentの1.0.0.M3を使ってごにょごにょしてたらcollectionに「_class」って名前でinsertに使ったDTOクラスが完全限定名でぶちこまれてました。
やあよ!やあよ!


MorphiaだとEntityアノテーションに「noClassnameStored」って属性があったのでそこで制御できたんですが…。
どこでやってんだろーなーとソースを眺めたらどうやら「org.springframework.data.document.mongodb.convert.MappingMongoConverter」がやってるみたい。
CUSTOM_TYPE_KEYって名前でStringのフィールド作ってそこに「_class」ってぶちこんでいるかんじ。
しかし特になんかフラグ渡してやれば入れないでくれるというわけでもなさそうな…。
いったいどうすれば…!


こうか…!

DBObject dbObject = new BasicDBObject();
mongoTemplate.getConverter().write(dto, dbObject);
dbObject.removeField(MappingMongoConverter.CUSTOM_TYPE_KEY);
mongoTemplate.getCollection("collection01").save(dbObject, WriteConcern.SAFE);


いやいや…!
うまくいくけど…!いくけども…!!

MongoDBでdumpとかrestoreとか

diskいっぱいになってきたのでdb.repairDatabase()で綺麗にしちゃおうかな!と軽い気持ちで実行したら「Cannot repair database」とMongoDBさんに怒られました。
db.rapairDatabase()を実行するには現在の使用disk容量と同じだけのdiskが必要になるっぽく、disk使用量が50%余裕で超えてたので怒られたみたいです。
mongod起動時に選択したデータ保存ディレクトリのmoveChunk配下はどうやら消していい(Can I remove old files in the moveChunk directory?)らしいのでそいつらを消してみたりもしましたがまだまだ足りない。
詰んだ?もしかして詰んだ?と思いながら検索したら以下のページが引っかかりました。
Google Groups

  • copy the data files to disk that has more space and compact it from there then copy it back
  • mount _tmp folder underneath the database folder to other disk and repairDatabase()

うむ。
もっと余裕あるとこにコピーするか_tmpに余裕あるdiskマウントしろよって感じでしょうか。
移すところもなかったのとDB単位のdumpも試してみたかったので、上のどちらにも従わずにdump→restoreする方法を選んでみます。


mongosのポートを指定してdump実行。

$ mongodump --port 27030 --db test3
connected to: 127.0.0.1:27030
DATABASE: test3  to     dump/test3
        test3.table01 to dump/test3/table01.bson
                 5 objects
        test3.system.indexes to dump/test3/system.indexes.bson
                 14 objects
        test3.table01 to dump/test3/table01.bson
                512700/7982533  6%
                1346500/7982533 16%
                1837400/7982533 23%
(略)
                 7982533 objects

終わったみたい。結果を確認します。

$ ls -hal dump/test3/
合計 2.7G
drwxrwxr-x 2 cy_mongodb cy_mongodb 4.0K  6月 22 14:09 .
drwxrwxr-x 4 cy_mongodb cy_mongodb 4.0K  6月 22 14:08 ..
-rw-rw-r-- 1 cy_mongodb cy_mongodb   54  6月 22 14:09 autoIncMgr.bson
-rw-rw-r-- 1 cy_mongodb cy_mongodb 1.8G  6月 22 14:09 table01.bson
-rw-rw-r-- 1 cy_mongodb cy_mongodb 918M  6月 22 14:09 table02.bson
-rw-rw-r-- 1 cy_mongodb cy_mongodb 1.8K  6月 22 14:08 system.indexes.bson

20GBのShard*3が全てdisk60%超えてた割に小さい。
indexの情報もdumpされてるけどそれ自体は持ってないとか、予め多めに容量確保してるとかそのへんがでかいんだろうなー。


dumpが完了したのでDBをdropします。こわい。

> use test3
switched to db test3
> db.dropDatabase()
{ "dropped" : "test3", "ok" : 1 }

やったった!
collectionはdropしても物理削除されないで残るのでdb.repairDatabase()とかする必要があるみたいですが、DBはdropした時点で各shardのDB名のディレクトリごと(ここでいうとtest3ディレクトリ)物理削除されるようです。


早速restoreといきたいところですが、DBをdropした状態でどかんといくと特定のShardに偏ってえらいことになったりしそうなので、予めShardingの設定をしておきます。

> use test3
switched to db test3

> db.stats()
{
        "raw" : {
                "mongodb01:27018" : {
                        "db" : "test3",
                        "collections" : 0,
                        "objects" : 0,
(略)
        "ok" : 1
}
> show dbs  
admin   (empty)
config  0.1875GB
test    (empty)
test3   (empty)

明示的に未作成のDBに対して操作をしてDBを作成します。
多分これやらずにすぐ下の作業してもいいんでしょうけども。
Sharding開始…!

> use admin
switched to db admin
> db.runCommand({enablesharding:"test3"})
{ "ok" : 0, "errmsg" : "already enabled" }

怒られた…!
状態を確認します。

> db.printShardingStatus()               
--- Sharding Status --- 
  sharding version: { "_id" : 1, "version" : 3 }
  shards:
      { "_id" : "shard01", "host" : "mongodb01:27018" }
      { "_id" : "shard02", "host" : "mongodb02:27018" }
      { "_id" : "shard03", "host" : "mongodb03:27018" }
  databases:
        { "_id" : "admin", "partitioned" : false, "primary" : "config" }
        { "_id" : "test", "partitioned" : false, "primary" : "shard02" }
        { "_id" : "test3", "partitioned" : true, "primary" : "shard01" }

なんかShardingできてるらしい。
過去にShardingしてたDBと同じ名前のDBが作られると設定が引き継がれちゃうかんじでしょうか。
流石にcollectionは有効になってなさそうなのでindex作成からやります。

> db.table01.ensureIndex({id:1})
> show collections
table01
> use admin
switched to db admin
> db.runCommand({shardcollection:"test3.table01",key:{id:1}});
{ "collectionsharded" : "test3.table01", "ok" : 1 }
> db.printShardingStatus()
(略)
        { "_id" : "test3", "partitioned" : true, "primary" : "shard01" }
                test3.table01 chunks:
                                shard01 1
                        { "id" : { $minKey : 1 } } -->> { "id" : { $maxKey : 1 } } on : shard01 { "t" : 1000, "i" : 0 }

うむ。
準備できたのでrestoreを実行します。

$ mongorestore --port 27030 --db test3 dump/test3
connected to: 127.0.0.1:27030
Wed Jun 22 14:25:53 dump/test3/table01.bson
Wed Jun 22 14:25:53      going into namespace [test3.table01]
                18779116/1831938804     1%
                35891872/1831938804     1%
                58814363/1831938804     3%
                83954523/1831938804     4%
(略)
Wed Jun 22 14:50:32 { _id: ObjectId('4e00a67ca1915e8b84809654'), ns: "test3.table02", key: { id: 1.0 }, name: "id_1", v: 0 }
Wed Jun 22 14:50:32      14 objects found

おわった!
db.mycoll.totalSize()の結果が3.7GBくらいのDBで25分くらいかかりました。
migration中はrestoreが遅くなるのでさもありなんという感じではありますが、プロダクト環境ではまあ_tmpにマウントとかが現実的な気がするなーと思いました。
ファイルシステムがext3だったりもするのでもろもろスペック次第では大分変わるのかもしれませんけれども。
ちなみにrestore前に張っておいたindexとrestore対象ファイルで持っているindexは重複しているもの(shardkeyのために貼ったindexなど)もありましたが、重複を含め特に問題なくindexは張られているみたいです。
なーるほーどなー。

MongoDBの$renameで遊ぶ

あるドキュメントでMapを使ってたんですが、ちょっとキー名を変更したくなったのでキー名を変更する方法をドキュメントで探してみました。

Modify Documents — MongoDB Manual 2.4.8

「$rename」!それっぽい!
フィールド名は変更できそうだけど果たしてMapのキーも変更できるのか。
とりあえずテストデータを作成します。

> db.table02.save({name:"hoge",map:{"1":"piyo","2":"fuga"}});
> db.table02.find()                                          
{ "_id" : ObjectId("4dff4df130d500ba704d8616"), "name" : "hoge", "map" : { "1" : "piyo", "2" : "fuga" } }

ためしにDotNotationを使ってmapのキー「2」を「3」に変更してみます。

> db.table02.update({},{$rename:{"map.2":"map.3"}},false,true);
> db.table02.find()                                            
{ "_id" : ObjectId("4dff4df130d500ba704d8616"), "map" : { "1" : "piyo", "3" : "fuga" }, "name" : "hoge" }

できた…!


ついでなので色々遊んでみます。
Mapの要素を外に出すこととかできるんでしょうか。
それでもMongoDBなら…MongoDBならきっとなんとかしてくれる…!!

> db.table02.update({},{$rename:{"map.3":"name2"}},false,true);
> db.table02.find()                                            
{ "_id" : ObjectId("4dff4df130d500ba704d8616"), "map" : { "1" : "piyo" }, "name" : "hoge", "name2" : "fuga" }

おおすげえできた。
逆も試してみます。

> db.table02.update({},{$rename:{"name2":"map.2"}},false,true);
> db.table02.find()                                            
{ "_id" : ObjectId("4dff4df130d500ba704d8616"), "map" : { "1" : "piyo", "2" : "fuga" }, "name" : "hoge" }

MongoDBかっこいい。抱いて。


既にあるMapのキーに変更とかしたらどうなるんでしょうか。
エラー吐くのかな。でもMongoDBさんだからな…。

> db.table02.update({},{$rename:{"name":"map.2"}},false,true); 
> db.table02.find()                                           
{ "_id" : ObjectId("4dff4df130d500ba704d8616"), "map" : { "1" : "piyo", "2" : "hoge" } }

流石MongoDBだなんともないぜ。
なんのエラーも無く上書いてくれました。こわい。


あんまりやらない気がしますが覚えておくといいことがあるかもしれない。あるといいな。

moveChunkでドキュメントが消える件の検証

色々いじって確実に再現するっぽいパターンがあったのでめも。

// mongosで対象DBに入ってindex作成(いずれかをDESCに)
> db.table01.ensureIndex({userId:1,targetuserId:-1})

// adminDBに入ってcollectionのSharding開始。フィールド自体は同じものを指定
> db.runCommand({shardcollection:"test2.table01",key:{userId:1,targetuserId:1}})  

// index確認
> db.table01.getIndexes()                 
[
        {
                "name" : "_id_",
                "ns" : "test2.table01",
                "key" : {
                        "_id" : 1
                },
                "v" : 0
        },
        {
                "_id" : ObjectId("4ddd1c686975e1482915920c"),
                "ns" : "test2.table01",
                "key" : {
                        "userId" : 1,
                        "targetuserId" : -1
                },
                "name" : "userId_1_targetuserId_-1",
                "v" : 0 
        },…(1)
        {
                "ns" : "test2.table01",
                "key" : {
                        "userId" : 1,
                        "targetuserId" : 1
                },
                "name" : "userId_1_targetuserId_1",
                "v" : 0
        }…(2)
]

// テストデータをぶち込んだ後Shardingのステータスを確認
> db.printShardingStatus()
                test2.table01 chunks:
                                set02   2
                                set01   4
                        { "userId" : { $minKey : 1 }, "targetuserId" : { $minKey : 1 } } -->> { "userId" : "userId1", "targetuserId" : "targetUserId1" } on : set02 { "t" : 2000, "i" : 0 }
                        { "userId" : "userId1", "targetuserId" : "targetUserId1" } -->> { "userId" : "userId2", "targetuserId" : "targetUserId2" } on : set01 { "t" : 3000, "i" : 6 }
(略)

// moveChunkするchunkの範囲にいるドキュメント数を確認
> db.table01.count({userId:"userId1"})
3

// moveChunkを実行
> db.runCommand({moveChunk:"test2.table01",find: { "userId" : "userId1",targetuserId:"targetUserId1" },to:"set03"})      
{ "millis" : 6164, "ok" : 1 }

// 移動元(ここでいうとset01)のログを確認
Thu May 26 00:25:51 [conn146] moveChunk number of documents: 55597
(略)
Thu May 26 00:30:13 [cleanupOldData] moveChunk deleted: 55599
// ↑移動対象を越えるドキュメントが削除されている

// 先ほど確認したドキュメント数を再度確認
> db.table01.count({userId:"userId1"})
1
// 減ってる

複合ShardKeyかつensureIndexでどれかのフィールドをDESC指定してindex作成((1)のindex)、shardcollectionではASCしか指定できないのでASCを指定すると自動でindex生成((2)のindex)。
この条件でmoveChunkを実行すると、ShardKey1に一致するドキュメントが移動されないにもかかわらず削除対象になってしまい、本来消えるべきでないドキュメントが消えてしまう。
ここでいう(2)のindexを予めensureIndexで作っておいた場合、同現象は再現しませんでした。
どうやらensureIndexで作成するとかshardcollectionで作成するとか関係なくて、複合ShardKeyの組み合わせが一致してるindexがあって、かついずれかにDESCが指定されているものがASCが指定されているものより先に作成されている場合に起きるみたい。
なので、

db.table01.ensureIndex({userId:1,targetuserId:-1})
db.table01.ensureIndex({userId:1,targetuserId:1})
db.runCommand({shardcollection:"test2.table01",key:{userId:1,targetuserId:1}})

これだと再現するが、

db.table01.ensureIndex({userId:1,targetuserId:1})
db.table01.ensureIndex({userId:1,targetuserId:-1})
db.runCommand({shardcollection:"test2.table01",key:{userId:1,targetuserId:1}})

これだと再現しない。


大分見当違いのことを書いてる可能性がありますが確認した範囲ではこんなかんじの挙動。うーんうーん。なぞだ。

moveChunkェ…

moveChunkするとなんかたまにデータが消える。なんぞ。


複合ShardKeyを利用してる状態でとあるChunkのMinがid:"hoge"、entryId:2000とかで、
かつid:"hoge"に一致するドキュメントが2個以上あるとき、
moveChunkのfindにおける引数にMinとまったく同じ値を入れて実行するとid:"hoge"に一致するentryId:2000以外のドキュメントが消える…。
oplog.rsにはinsertされたっぽいログが残ってるのになー。謎だなー。
再度ちゃんと確認したら残ってなかった。どのタイミングでこぼしてるんだ…。


バランサが悪さしてるのかなと思って止めたものの変わらず。なんだろう。詰んだ。

(2011.05.25追記)
移動元のログを見てみると

Mon May 23 10:11:24 [conn154] moveChunk number of documents: 8
(略)
Mon May 23 10:11:26 [conn154] moveChunk migrate commit accepted by TO-shard: { active: false, ns: "test2.table01", from: "set03/mongodb03:10001,mongodb03:10003,mongodb03:10002", min: { userId: "hoge", entryId: 66876 }, max: { userId: MaxKey, entryId: MaxKey }, state: "done", counts: { cloned: 8, clonedBytes: 1889, catchup: 0, steady: 0 }, ok: 1.0 }
(略)
Mon May 23 10:11:26 [conn154] moveChunk deleted: 10

のように対象となったドキュメントを超えるドキュメントが削除されてる。
moveChunkしてる間は他の動作が起こっているような感じもないしうーんうーん。
公式のグループに質問投げてみようかなーと思いつつ再度同じような状況作ってmoveChunkしてたら再現しなくなった。ええー。

chunkを手動で移動する

やり方ここに書いてありますがとりあえず試してみる。
moveChunk — MongoDB Manual 2.4.8


とりあえず現在のchunkの状態を確認。

> db.printShardingStatus()
                test2.table01 chunks:
                                set02   19
                                set03   19
                                set01   18


migrationの後なので偏ってませんが、試しにchunk数19のところからひとつ18のところに移動してみます。
まずはどのchunkを移動するか決めるためにconfigサーバで該当collectionのchunkの一覧を確認。

db.chunks.find({ns:"test2.table01"}).limit(10)
{ "_id" : "test2.table01-userId_\"hoge\"entryId_118415", "lastmod" : { "t" : 35000, "i" : 0 }, "ns" : "test2.table01", "min" : { "userId" : "hoge", "entryId" : NumberLong(118415) }, "max" : { "userId" : "hoge", "entryId" : NumberLong(120960) }, "shard" : "set03" }
{ "_id" : "test2.table01-userId_\"hoge\"entryId_123505", "lastmod" : { "t" : 37000, "i" : 0 }, "ns" : "test2.table01", "min" : { "userId" : "hoge", "entryId" : NumberLong(123505) }, "max" : { "userId" : "hoge", "entryId" : NumberLong(126050) }, "shard" : "set03" }
{ "_id" : "test2.table01-userId_\"hoge\"entryId_128582", "lastmod" : { "t" : 41000, "i" : 0 }, "ns" : "test2.table01", "min" : { "userId" : "hoge", "entryId" : NumberLong(128582) }, "max" : { "userId" : "hoge", "entryId" : NumberLong(131114) }, "shard" : "set03" }
(略)


ひとつだけ特定するために条件を絞ります。

> db.chunks.find({ns:"test2.table01",min : { userId : "hoge", entryId : NumberLong(128582) }})
{ "_id" : "test2.table01-userId_\"hoge\"entryId_128582", "lastmod" : { "t" : 41000, "i" : 0 }, "ns" : "test2.table01", "min" : { "userId" : "hoge", "entryId" : NumberLong(128582) }, "max" : { "userId" : "hoge", "entryId" : NumberLong(131114) }, "shard" : "set03" }


chunkのレンジは被らないはずなので、min〜maxの間の値を指定してやればそのレンジにあるchunkのみが移動できるはず。
以下の構文でchunkの移動を実行します。

db.adminCommand({moveChunk : $ns$, find : {$findquery$}, to : "$shardname$"})
  • $ns$:db.chunks.find()したときの「ns」の値を指定します。
  • $findquery$:普通にfindするときと同じ。今回でいうとdb.chunks.find()したときのmin〜maxの間の値を指定します。
  • $shardname$:シャード名。db.chunks.find()したときの「shard」の値を指定します。


これに従って書くと今回はこんなかんじのはず!
いざ実行!

> db.adminCommand({moveChunk:"test2.table01", find:{userId:"hoge", entryId:NumberLong(128582)}, to:"set01"})
{
        "assertion" : "invalid parameter: expected an object ()",
        "assertionCode" : 10065,
        "errmsg" : "db assertion failure",
        "ok" : 0
}


あれ!
なんぞ!
ああconfigサーバでやってた!mongosにきりかえます!

> db.adminCommand({moveChunk:"test2.table01", find:{userId:"hoge", entryId:NumberLong(128582)}, to:"set01"})
{ "millis" : 1501, "ok" : 1 }


できた!
確認!

> db.printShardingStatus()
                test2.table01 chunks:
                                set02   19
                                set03   18
                                set01   19

いけてる!


折角なのでもっかい同じクエリを流してみます。

> db.adminCommand({moveChunk:"test2.table01", find:{userId:"hoge", entryId:NumberLong(128582)}, to:"set01"})
{ "ok" : 0, "errmsg" : "that chunk is already on that shard" }

おこられた!すみません!

shardingェ…

shardingしてるcollectionのshardingを解除する方法がdropしかないみたいなのでやむを得ず試してみました。


drop前はdb.printShardingStatus()で表示されます。

> db.printShardingStatus()
(略)
                test2.table01 chunks:
                                set01   1
                        { "name" : { $minKey : 1 } } -->> { "name" : { $maxKey : 1 } } on : set01 { "t" : 1000, "i" : 0 }


dropしてみます。

> db.table01.drop()
true

db.printShardingStatus()でも表示されなくなりました。


よしおっけー問題なしと思ってこの作業の前にやってたテストデータのsaveをforで回しつつchunkの状態を確認してたら何故かshardingが上手くいってない。
mongosのログを確認してみます。

Mon May 16 22:19:43 [conn1] warning: could have autosplit on collection: test2.table02 but: splitVector command failed: { errmsg: "exception: [test2.table02] shard version not ok in Client::Conte...", code: 13388, ok: 0.0 }

なんぞ…。
なんかshardのバージョンおかしいぞコラと怒られてます。


insertされたshardのログも見てみる。

Mon May 16 22:19:43 [conn101] Assertion: 13388:[test2.table02] shard version not ok in Client::Context: client in sharded mode, but doesn't have version set for this collection: test2.table02 myVersion: 25|1

後半省略されてないのが表示されました。


その後もとりあえずテストデータをぶちこみ続けましたが、変動があるはずのchunksの値が全然変わらない。
それでも性懲りずにぶち込み続けたところ、急にchunksの値が変わり、migrationが始まりました。
何がトリガーになってるんだろう…。

スポンサーリンク