MongoDBのShardingを試してみた。その2 Migration中の挙動について

Sharding環境にて、shard keyを使用しない以下のクエリに問題があることが知られている。
(第2回MongoDB勉強会の@doryokujinさんの発表参照。私の参加日記は ここ から)

  • Chunk Migration中のcount
  • 同時書き込み発生時のsingle-update()
  • 同時書き込み発生時のunique index
  • tmp_collectionが削除されないmapreduce

今回は、Sharding環境にて、chunkのMigration中の各クエリの挙動について検証します。
同時書き込みの発生再現は、難しいので今回は割愛します。

この検証では、mongodb 1.8.0を使用しています。
構成については、 MongoDBのShardingを試してみた。その1 を参照

Chunk Migration中のcount

現象
  • 正しい値より大きい値が出力される可能性がある。

イメージでみるとこんな感じ。

原因
  • Chunk Migration中は処理が完了するまで、移動元、移動先に同じchunkが存在する。
  • 完全にコピーが完了し、configサーバが更新されて完了となる。
検証

まずは、意図的にchunkの偏りを発生させます。
sharding設定前に100万件程のアクセスログを投入し、確認していこうかと思います。

[matsuou1@testsvr mongodb]$ ./bin/mongoimport -h localhost:10001 -d logdb -c logs10 --ignoreBlanks --type tsv --fields month,uid,timestamp,path,device /tmp/ mongodb/work/access_201003.log
connected to: localhost:10001
(略)
imported 946611 objects

shardingの設定を行い、chunkの分散を確認します。

[matsuou1@testsvr mongodb]$ ./bin/mongo localhost:10000/logdb
MongoDB shell version: 1.8.0
connecting to: localhost:10000/logdb
> db.logs10.count();
946611
> db.logs10.ensureIndex( { uid : 1 } );
> use admin
switched to db admin
> db.runCommand( { shardcollection : "logdb.logs10" , key : { uid : 1 } } );
{ "collectionsharded" : "logdb.logs10", "ok" : 1 }
> db.printShardingStatus();
(略)
                logdb.logs10 chunks:
                                shard0001       1
                                shard0002       1
                                shard0000       266
                        too many chunksn to print, use verbose if you want to force print

> db.printShardingStatus();
(略)
                logdb.logs10 chunks:
                                shard0001       25
                                shard0002       25
                                shard0000       218

最初、shard0000にあった全てのchunkが、shard0001、shard0002にmigrationが行われ、平準化されています。
全て平準化される前に、countを実施するとchunk migration途中のchunkが重複カウントされ、正しい値より大きな値が表示されるはず。

とりあえず、shard keyではないtimestampを条件にしたcountを10秒に1回程度実行してみましょう。

> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078 ★
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
285133 ★
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
285108 ★
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
285120 ★
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
285047
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
285128 ★
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
285140 ★
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284984 ★
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
285153 ★
まとめ

★がついている個所が、正しい値(284078)より多い数値が表示された。
表示される値は都度異なり、重複カウントされるchunkに依存する。
25回中6回、不正な値が出力された。約25%なので、migration中はかなりの確率で発生しうると言える。
運用中のchunk migration時にも気をつける必要があるが、single構成からsharding構成に変更する際も当然注意が必要。