時系列データとMongoDB2.4の新機能Hash based sharding
実は数年前(MongoDB 1.6〜1.8の時期)にログをリアルタイムに全てmongodbに叩き込んで期間毎に解析する仕組みを作ろうとして挫折した事がある。
理由は幾つかあったのだが主に
- cappadコレクションでは解析時の負荷とログ投入負荷が重なってしまう。
→ 最終的には書き込みが負けてoplogが溢れる。
- 時系列カラムをshard keyにするとchunk migrationが辛すぎる。
- そもそもshardingの信頼性が・・・
という理由だった。
今ならストレージエンジンが早くなったし再挑戦しても良いかもしれない。
ログは非同期に準リアルタイムに拾って投入。
出力元+時間でshard key。
なるべくグループコミットの効率が上がる塊を作る。
とか、、
お題
ともかく、今回は
時系列データを投入した時chunk migrationがどう動くのか?
の話。
そもそも時系列カラムをshard keyにするな!という話もあるのだが
構築時のデータ一斉投入とか、時系列解析しなきゃならないデータを扱ったりとかで
chunk migrationと戦わなければならない時もあるのが現実・・・
Shardingの基本
- split chunks
- MongoDBは複数のドキュメントをchunkと呼ばれる単位で管理している。
ドキュメントを投入し続けると当然chunkサイズが膨れていくが、ある閾値に達すると分割が行われる。
こうしてMongoDBは多くのchunkを持つ事になる。
- shardへのchunkの割り振り
- shardingを構成した時に各shardが担当するchunkと各chunkのshard key範囲が決まる。
何も指定しなければ一つのchunkが全ての範囲を担当する。
- chunk migration
- ドキュメントを投入して行くと、shard間でchunk数のバラツキが生じる。
一定以上のバラツキが生じた場合shard間でchunkの移動を行いバラツキを是正する
chunk split & chunk migration の解説
chunkの状況
この様なShardingコレクションを仮定する。
chunk1 | chunk2 | chunk3 | chunk4 | chunk5 |
---|---|---|---|---|
MIN < KEY <= 10 | < KEY <= 20 | < KEY <= 30 | < KEY <= 40 | < KEY <= MAX |
chunkの分配状況
shard1 | shard2 | shard3 |
---|---|---|
chunk1 | chunk3,chunk4 | chunk2,chunk5 |
chunk split
ここでKEY=60〜80を追加すると上記表の通り全てchunk5に入りchunk5のサイズが膨れる。
ある程度までchunk5が膨れると、chunk5でsplit chunkが起きる。
chunk1 | chunk2 | chunk3 | chunk4 | chunk5 | chunk6 |
---|---|---|---|---|---|
MIN < KEY <= 10 | < KEY <= 20 | < KEY <= 30 | < KEY <= 40 | < KEY <= 50 | < KEY <= MAX |
結果、shardとchunkの関係はこうなる。
shard1 | shard2 | shard3 |
---|---|---|
chunk1 | chunk3,chunk4 | chunk2,chunk5,chunk6 |
chunk migration
このchunkの分配状況は不均等なので、均等化されるようにchunkの移動(chunk migration)が起きる。
shard1 | shard2 | shard3 |
---|---|---|
chunk1,chunk5 | chunk3,chunk4 | chunk2,chunk5,chunk6 |
このchunkの移動は本来データベースにとって余計な操作であり、ネットワーク帯域、ディスクIOを大量に起こす好ましくない処理。
なのでchunk migrationはなるべく起こって欲しくない。
普通のsharingで時系列データ(shard keyが単調増加)のinsert
上記の表を見て貰うと分かるが、shard keyが単調増加する場合、insertは常に(最後のchunk)に入る。
よって最後のchunkを抱えているshardでchunk splitが起き続け、必ずchunk数の不均等が発生する。
実際のchunk数の推移
event | shard1 | shard2 | shard3 | comment |
---|---|---|---|---|
balancer stop | 最初chunk移動は止めてみる | |||
0 | 0 | 1 | ||
0 | 0 | 2 | ||
: | : | : | ||
0 | 0 | 10 | shard3のchunk数だけが増加 | |
balancer start | バランサーON | |||
2 | 2 | 6 | chunk移動が始まる | |
: | : | : | ||
6 | 6 | 6 | 暫くすると均等化 | |
balancer stop | バランサOFF | |||
6 | 6 | 7 | ||
6 | 6 | 8 | ||
6 | 6 | 9 | ||
: | : | : | ||
6 | 6 | 17 | またshard3だけが膨れる | |
balancer start | バランサーON | |||
7 | 7 | 15 | ||
10 | 9 | 12 | ||
11 | 10 | 11 | ||
11 | 10 | 14 | ||
11 | 11 | 13 | ||
12 | 11 | 15 | 一瞬不均等になっても・・・ | |
13 | 12 | 13 | chunk移動で均等化 | |
14 | 12 | 16 | ||
15 | 14 | 15 |
どうやらshard3に最後のchunkがあるらしく、必ずshard3でchunk splitが起きている。
つまり、
shard1とshard2 に存在する29個のchunkは全てshard3から移動してきたもの。
少なくとも29回のchunk移動が起きた・・・
激しすぎる!
hashed shard keyで時系列データ(shard keyが単調増加)のinsert
MongoDB2.4で追加されてhashed shard keyは、shard keyの値をそのままchunkに割り当てるのではなく一旦hash化する。
これだと単調増加shard keyであっても最後のchunkに追加されるとは限らず、理想的には均等にバラけるはず!
つまりchunk移動が起きずパフォーマンス的に有利!!!
実際のchunk数の推移
今回は最初から最後までbalancerを止めておく。(それでも均等になるハズ)
event | shard1 | shard2 | shard3 | comment |
---|---|---|---|---|
balancer stop | バランサーOFF | |||
2 | 2 | 2 | なぜかデータを入れる前から2chunkずつ | |
3 | 3 | 2 | ||
4 | 4 | 4 | ||
5 | 4 | 5 | ||
6 | 4 | 5 | なんとなく | |
7 | 8 | 7 | 均等 | |
8 | 8 | 8 | を保ちながら成長? | |
8 | 9 | 9 | ||
10 | 10 | 10 | ||
10 | 11 | 12 | ||
12 | 12 | 13 | ||
14 | 13 | 13 | ||
14 | 14 | 14 | ||
16 | 15 | 15 |
見よ!完璧だ!!
あれ?でもなんかchunk数が多くね?(入れたデータは同じ・・・)
そもそもスピードも同じ位・・・(chunk移動してないのに!!)
hashed shard keyの難点
まずhash値の分ドキュメントがデカくなる。(元々小さいドキュメントでは影響大)
ハッシュ値は64bit(=8byte)なので文字列カラム以外では大抵インデックスサイズもデカくなる。
充分大きいドキュメントならメリットあるのかな?
確かにchunk移動は起き難いが、どうも現状では圧倒的なパフォーマンスは得られそうに無い。
hashed shard keyの難点(使い勝手)
shard key をhash化してしまう為、範囲探索が出来ない。
KEY = 10 のドキュメントを探す場合は、1 shard を特定して処理できるが、、
- 10 を hashして 0xcccccccccccccccc を得る。
- 0xccccccccccccccccを担当しているchunkを持ってるshard を特定
- そのshardにクエリーを投げる。
10 < KEY < 15 のドキュメントを探す場合は
10と15の間には(少数も合わせて)無限のキーが有り得るしhashしてしまえば大小関係が消えるのでshardを特定できない。
結果グローバルクエリーになる。
- 10 < KEY < 15 の条件で全てのshardに問い合わせる。
これではスケールしない仕組みになってしまう。
hashed shard key 総括
基本的にはKVS相当の仕組みになってしまいMongoDBらしさは消える。
→ 基本キーを直指定すること。範囲という考え方は捨てる必要がある
ドキュメントサイズがそこそこ大きくて、兎に角insertをし続ける時、
またはMongoHadoopなど別フレームワークで解析する仕組みなら有効かもしれない。
でもInsertが激しい時系列データって普通小さいんだよなー・・・