SpringDataMongoDBのアップデートを試すめも

1.0.0.M4からアップデートしてなかったので1.1.0.GAにアップデートを試みる。
以下、元のコードを変更する必要があったところのめも。

$orの書き方が変わった

query.or(Query.query(Criteria.where("cnt").exists(false)), Query.query(Criteria.where("cnt").lt(5)));
query.addCriteria(new Criteria().orOperator(Criteria.where("cnt").exists(false)).orOperator(Criteria.where("cnt").lt(5)));

1.0.0.M5から変わったみたい。
Spring Data MongoDB - Reference Documentation

hintの書き方が変わった

List<TestDto> list = mongoTemplate.find(query, TestDto.class, new CursorPreparer() {
	@Override
	public DBCursor prepare(DBCursor cursor) {
	cursor.hint("key_1_id_1");
	return cursor;
	}
}, "collection"));

ちょうめんどくさい。
hint指定するときはsortとかlimitとかskipとかもCursorPreparerの中で書かないといけない。

query.withHint("key_1_id_1");

…!!
らくちん…!!

「_class」を宣言してるクラス名が変わった

org.springframework.data.mongodb.core.convert.DefaultTypeMapper

org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper


「_class」消したいときはこれまで通りDBObject#removeFieldするか、DefaultMongoTypeMapperのコンストラクタの引数が「_class」格納してる変数を書き換えてくれるので、null渡してインスタンス作ればいけるんじゃないかなーとか。
あとDtoのclass宣言につける@TypeAliasってアノテーションも追加されたっぽい。
まだ試してないけど「_class」の名前を任意で変更できるとかそういうアレだと思う。

あとは依存するjarの更新とかが必要みたいだけど、M3からM4ほどの大きな変更は必要なさそうでよかった。

SpringDataのMongoWriterの挙動めも

あんまり調べられてないけど。
なんかマルチスレッドでMongoWriter#writeを使ったとき、JVM起動直後にいくつかのドキュメントがフィールドが不足した状態でinsertされることがありました。
MongoWriter#writeを使ってる具体的なコードは以下のようなかんじ。

Dto dto = new Dto();
dto.setA("a");
dto.setB("b");
dto.setC("c");
DBObject dbObject = new BasicDBObject();
mongoTemplate.getConverter().write(dto, dbObject);
dbObject.removeField(DefaultTypeMapper.DEFAULT_TYPE_KEY);
mongoTemplate.getCollection("testcol").insert(dbObject, WriteConcern.SAFE);

MongoTemplate#insert(Object objectToSave)だと「_class」が入っちゃうので上のような実装にしてますが、MongoTemplate#insert(Object objectToSave)も内部的には同じようにMongoWriter#write呼んでるので再現するっぽい。


dtoが「a」、「b」、「c」というフィールドを持っているとき、上のコードでinsertを行うとどこぞでアルファベット順にソートされてるのか、DBにはアルファベット順に保存されるようです。
フィールド「b」が欠けてinsertされた場合、「c」も欠けている状態になっていました。
フィールド「a」が欠けて「b」や「c」が入るといった挙動は確認できませんでした。


マルチスレッドで実行する前に一度MongoWriter#writeを行ってやるとその後マルチスレッドで実行しても再現しなくなりました。
JVM起動直後にだけ出て、そのあとずっとマルチスレッドでまわしてても再現しないあたりなんかなー。なー。
と思ったらそのあとちょっと気になって、上の処理のあと続けて別のDTOを同じようにマルチスレッドでまわしたらそっちはそっちで再現しました。
変換するclassごとに一旦MongoWriter#writeをやってやらないといけないっぽい感じかなー。うーんうーん。


上のようにMongoTemplate#insert(Object objectToSave)を使わずに自分でMongoWriter#writeをやるならフィールドが欠損してないかチェックする余地はあるけど都度チェックっていうのもなんかなー。
でもやらないとShardKeyとかuniqueIndexじゃないときエラー吐かずにしれっとinsertするしなー。
時間があるときもうちょっと深くまで読み込んでみよう…。

MongoDBでコレクション名を変更

ちょっとわけあってコレクション名を変更してみたくなったので。
renameCollection — MongoDB Manual 2.4.8


なんかすごい簡単にできそう。
やってみる。

> show collections                               
testCol01
testCol02
> db.testCol01.renameCollection("col01");
{ "ok" : 1 } 
> show collections                               
col01
testCol02

かんたん!
もういっこも変更するよ!

> db.testCol02.renameCollection("col02");
{
        "assertion" : "You can't rename a sharded collection",
        "assertionCode" : 13138,
        "errmsg" : "db assertion failure",
        "ok" : 0
}

!?


どうやらShardingしてるコレクションの名前は変更できないみたい。
dumpしてrestoreみたいな方法しかないんだろうか…。
滅多にやる操作じゃないけどちょっと不便。


ついでなんで既にあるコレクション名に変更しようとしたらどうなるのか試してみる。

> db.col01.renameCollection("testCol02");
{
        "errmsg" : "exception: target namespace exists",
        "code" : 10027,
        "ok" : 0
}

エラーになった。しれっと上書きとかされたらどうしようかと思った。
存在しないコレクションを操作しようとした場合は以下のようなかんじ。

{
        "errmsg" : "exception: source namespace does not exist",
        "code" : 10026,
        "ok" : 0
}

うむ。

MongoDBにサロゲートペアをぶちこむ

MySQLのちょっと古いバージョンだとサロゲートペアをinsertしようとしたとき、サロゲートペア以降の文字が削除された状態でinsertされたりしたのでMongoDBだとどうなるのかなという実験です。
とりあえずサロゲートペアをぶち込んで中身確認。

> db.table01.find()
{ "_id" : ObjectId("4e4bbf0269810336e52481ee"), "num" : 4, "name" : "さろげーと叱ぺあ" }

ちゃんと入っているみたい。すてきね。
大丈夫だとは思いつつ怖いのでdump→restoreでぶっ壊れたりしないかもちょっと確認してみます。


dump実行!

mongodump --port 27020 --db test01 --collection table01
connected to: 127.0.0.1:27020
DATABASE: test01         to     dump/test01
        test01.table01 to dump/test01/table01.bson
                 5 objects

中身空っぽにしてー

> db.table01.remove()

restore!

$ mongorestore --port 27020 --db test01 --collection table01 dump/test01/table01.bson 
connected to: 127.0.0.1:27020
Wed Aug 17 22:18:28 dump/test01/table01.bson
Wed Aug 17 22:18:28      going into namespace [test01.table01]
Wed Aug 17 22:18:28      5 objects found

確認…!

> db.table01.find()
{ "_id" : ObjectId("4e4bbf0269810336e52481ee"), "num" : 4, "name" : "さろげーと叱ぺあ" }


いけてる!抱いて!
とりあえずMongoDBさんはサロゲートペアぶちこんでも大丈夫そう。
全部試さないとだめか。だめなのか。ひまなときに…やる…!

MongoDBで自動chunk分割がされなくてはまる

あるcollectionで以下のようにembeddedのキーをShardKeyに指定していてchunk分割されなかったのが事の発端。

test01.table01 chunks:
       shard01 1
       { "hoge" : { $minKey : 1 }, "map.key" : { $minKey : 1 } } -->> { "hoge" : { $maxKey : 1 }, "map.key" : { $maxKey : 1 } } on : shard01 { "t" : 1000, "i" : 0 }


embeddedがいけないのかなーと思ってembeddedをやめてみました。
StringとLongが混在するフィールドpiyoを作って、hogeとpiyoの複合ShardKeyに変更。
しかしデータを詰めども詰めどもchunk分割が発生しない。なんぞ。
embeddedも型の混在もだめなのかなーと思ってMongoDBのグループとか見てもそれっぽいのが出てこない。
mongosとconfigサーバ再起動とかしても上手くいかない。
とりあえずsharding構築し直しとか色々やってみるかーと思い、データ作り直すのも面倒なのでdump→drop→restoreを実施。


…分割されている…!!


つまりembeddedとか型の混在とか関係ないのか。データの入れ方に問題があるのか。
初心に帰ってちょっとChunk分割のドキュメントを見てみます。
Log In - MongoDB Wiki

Normally, splitting chunks is done automatically for you. Currently, the splits happen as a side effect of inserting (and are transparent).
In the future, there may be other cases where a chunk is automatically split.

えっ。
純粋なinsertでしか発生しない?えっ。
確かにこのcollection、データの追加はupdateのupsert:trueでしかやってない。えっ。
そういうこと?そういうことなの?


ためしにupdateのままで本来chunk分割されるサイズまでデータを入れ直し、やっぱり分割されないことを確認したのち適当にinsertしたら分割されました。
ああー。あああー。


出来る限りupdateを使いたいので止む無くたまーにinsertをする形で対応しました。
運にもよるけど今のところ割とまともに分割は走っているのでとりあえずよかった。
futureが来い。

SpringDataでMongoDBクエリ範囲指定

MongoDBの範囲指定って以下のような感じでまとめないといけないんですが、SpringDataだとこれどうやるのかなーとちょっとハマりました。

db.collection.find({ "field" : { $gt: value1, $lt: value2 } } );

参考:Operators — MongoDB Manual 2.4.8


以下のように書いた場合…

query.addCriteria(Criteria.where("field").gt(value1));
query.addCriteria(Criteria.where("field").lt(value2));

MongoDBのコンソールでは以下と同じ扱いになってしまいます。

db.collection.find({ "field" : { $gt: value1}, "field" : {$lt: value2 } } );

ではどうするかというと滅茶苦茶単純でした。

query.addCriteria(Criteria.where("ctime").gt(value1).lt(value2));

かんたん!しにたい!

MongoDBのtypeおぼえがき

Javaでlongで宣言した値をぶちこんだら「NumberLong(1)」みたいな形でMongoDBに入ってたのでなるほどこうなるのかー程度にしか思ってなかったんですが、ふと気になってほんのちょっと調査をば。


とりあえず適当にテストデータを作ります。

> db.typetest01.save({id:1})
> db.typetest01.save({id:NumberLong(1)})
> db.typetest01.save({id:"1"})   
    
> db.typetest01.find()
{ "_id" : ObjectId("4e0b41e1c23fab52ef14942d"), "id" : 1 }
{ "_id" : ObjectId("4e0b41ebc23fab52ef14942e"), "id" : NumberLong(1) }
{ "_id" : ObjectId("4e0b41f2c23fab52ef14942f"), "id" : "1" }

うむ。
次にそれぞれの型を調べます。
typeに関しては以下を参考にします。
http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24type:type


「"1"」はまあStringなので2、「NumberLong(1)」は64bitであることを考えると恐らく64-bit integerだと思われるので18だろうと予測できます。

> db.typetest01.find({id:{$type:2}})          
{ "_id" : ObjectId("4e0b41f2c23fab52ef14942f"), "id" : "1" }
> db.typetest01.find({id:{$type:18}})
{ "_id" : ObjectId("4e0b41ebc23fab52ef14942e"), "id" : NumberLong(1) }

どうやら正解のようです。


では「1」はどうなってるんでしょうか。
特に型を指定してるわけではないのでdouble?それとも32-bit integer?

> db.typetest01.find({id:{$type:1}}) 
{ "_id" : ObjectId("4e0b41e1c23fab52ef14942d"), "id" : 1 }
> db.typetest01.find({id:{$type:16}})
> 

どうやらdoubleになってるようです。
ただ、Javaからintでぶち込んでみると、mongoのコンソール上では何の違いもないように見えるのに、32-bit integer(type16)として判断されてました。
コンソールで数字ぶち込んだ場合はdoubleなんですねー。ほーん。ほーん…。


あとfindの動きもちょっと確認。

> db.typetest01.find({id:1})            
{ "_id" : ObjectId("4e0b41e1c23fab52ef14942d"), "id" : 1 }
{ "_id" : ObjectId("4e0b41ebc23fab52ef14942e"), "id" : NumberLong(1) }
> db.typetest01.find({id:NumberLong(1)})
{ "_id" : ObjectId("4e0b41e1c23fab52ef14942d"), "id" : 1 }
{ "_id" : ObjectId("4e0b41ebc23fab52ef14942e"), "id" : NumberLong(1) }
> db.typetest01.find({id:"1"})          
{ "_id" : ObjectId("4e0b41f2c23fab52ef14942f"), "id" : "1" }

うむ。なるほど。


ついでなのでちょっとtypeごとのデータサイズとか調べてみようと思ったんですがなんかいまいち分からない。
Stringだとまあ言わずもがな文字数増えればデータサイズ増えるんですが、数値だと小さくてもある程度データサイズ確保しているような感じ。
NumberLongとか入ってるからもしかしてlong値の方がStringよりデータ食うのかなとか思ったりしましたがまあそんなことはなさそうな。
数値以外が入らない保証があるなら普通に数値にしておいたほうがよさそう。
0or1とかならStringのほうがいいのかなー。うーんうーん。

スポンサーリンク