シャーディングで最大の性能を引き出すためには、ボトルネックになっている処理を見極め、それを分散させることが最も重要です。また、シャーディングをすることによって新たに発生する処理の負荷や、できなくなる事についても考慮する必要があります。順番に説明していきましょう。
ボトルネックになっている処理を分散させる
遅いからといって、やみくもにシャーディングを組めばよいというものではありません。私は以前「処理が遅いので、一つのハードウェアの中にmongodを3プロセス立ててシャーディングしたけど、速くならないんですよ」という相談を受けたことがあります。その人はmongostatすら見ていない状態で、何がボトルネックかもわからず、とりあえずシャ ーディングをしたようです。もちろん、一つのハードウェアの中でシャーディングしても、コンピューティングリソースは増えないので、何も速くなりません。
シャーディングで速くなるためには、ボトルネックになっている処理が、シャーディングをすることによって解消されなければいけません。例えば、メモリ不足でディスクIOがボトルネックになっている状況であれば、シャーディングを組んでクラスタ全体のメモリ量を増やしてあげることにより、ボトルネックが解消するでしょう。
シャーディングによる処理性能向上を検討する場合も、考慮ポイントは単体構成の時と同じです。以下の3つを自身に問いかけるとよいでしょう。
- 改善したい処理は何ですか?読み込みですか、書き込みですか?
- 改善したいのはスループットですか、それともターンアラウンドタイムですか?
- 遅いのは何がボトルネックですか?ディスクIOですか、CPUですか、ネットワークですか?
これらがわかっていない状態でシャーディングに取り掛かってはいけません。例えば、ネットワークがボトルネックの書き込み処理のターンアラウンドタイムを改善するのに、シャーディングしても意味がありません。mongosを挟む分遅くなるだけです。
負荷分散するためにはシャードキーの設計が重要
シャーディング構成を組んだけれども、一つのノードに処理が集中しては意味がありません。負荷を均等に分散することが重要です。これができなければ、ボトルネックは解消しないでしょう。
では、どのようにシャードに均等に負荷を分散するのでしょうか?それは適切なシャードキーを選ぶことが全てです。mongosはシャードキーをもとにクエリを割り振るため、均等に負荷が分散できるかはシャードキーの選択にかかっています。
適切なシャードキーを選択するのは非常に難しいです。これはMongoDBに限った話ではなく、分散処理全般にいえる ことです。適切なシャードキーを選択するためには、アプリケーションの特性をしっかりと見極めたうえで、アプリケーションごとに設計する必要があります。
シャードキーを設計をする上で3つ代表的な考慮ポイントがあります。
- 何をTargetedクエリにして何をBroadcastクエリにするか
- 単調増加なシャードキーとランダムなシャードキーのどちらを選択するか
- どのようにチャンクを均等に分散させるか
これからそれらを紹介していきましょう。
point1.何をTargetedクエリにして何をBroadcastクエリにするかし
シャードキーの選択によって、同じクエリでもTagetedクエリになるか、Broadcastクエリになるかが変わってきます。
Targetedクエリであれば処理は一つのシャードの中で完結し、他のシャードに負荷はかかりません。システム全体のスループットを上げたいのであれば、Targetクエリになるようにシャードキーを設計し、並列度を高めることが有効でしょう。
Broadcastクエリであれば、処理は全シャードに分散します。大量集計などでCPUボトルネックになっている場合は、Broadcastクエリにすることにより、全シャードのCPUを同時に使うことができターンアラウンドタイムの向上が期待できます。ただし、各シャードの計算結果をまとめる処理をプライマリシャードが行うため、そこは新たなボトルネックになります。。
TargetedクエリとBroadcastクエリのどちらが良いということは一概には決まりません。何を速くし たいのを見極めて、使い分けていく必要があります。
point2.単調増加なシャードキーとランダムなシャードキーのどちらを選択するか
_idや時刻など、単調に増加していく値をシャードキーとする場合と、それにハッシュインデックスをつけるなどし てランダムなシャードキーにした場合では、どちらにもメリットデメリットがあります。
単調増加なシャードキーの場合、書き込みはすべて+∞を含む一つのチャンクに集中します。よって書き込みは分散 しないというデメリットがあります。一方メリットとしては、更新するインデックスが既にメモリに乗っていて、ディスクIOを発生させずに更新できる可能性が高いです。これを「ローカリティが高い」といいます。MongoDBではイ ンデックスがメモリに乗っていないと劇的に性能が劣化しますので、インデックスがメモリにのっていることは非常に重要です。
ランダムなシャードキーの場合は、書き込みが完全に分散するというメリットがある一方、インデックスがメモリに乗っていない可能性が高いというデメリットがあります。これを「ローカリティが低い」といいます。
ローカリティについて詳しく説明します。インデックスが変更されたとき、MongoDBはディスクにインデックスの変 更内容を書き込もうとしますが、書き込む単位はインデックスの1つのリーフだけではなく、それを含むIOブロックになります。それは、ディスクへの書き込みはOSで定められた特定サイズのブロック単位でしかできないためです。MMAPでは書き込み対象のIOブロックがメモリにマップされていればメモリに書き込んでディスクIOは発生しません( ディスクへのIOは非同期で行われます)。
図を書いて説明しましょう。
単調増加のシャードキーの場合、一つ前の書き込みとその次の書き込みは値が連続しているため、インデックスの中で物理的に近い場所を更新することになります。図の中では13,14,15が同一のIOブロックに格納されています。この状態であれば、13のリーフノードを作成したときに、ブロックがメモリに乗せられ、14と15のリーフノードを作るときにはディスクIOは発生しません。
一方、ランダムなシャードキーであれば、インデックスのいたるところをランダムに更新されるため、より多くのIOブロックを読む必要があります。メモリの大きさが十分ではない状況ではディスクIOが頻発して、遅くなるでしょう。