19

かなり標準的な 32 文字の 16 進数のGUIDを使用しているとします。これはユーザー向けにランダムに生成されるため、保存する MongoDB コレクションへの書き込みを水平方向にスケーリングするためのシャード キーとして使用するのに最適であると判断しました。ユーザー情報 (および書き込みスケーリングが私の主な関心事です)。

また、トラフィック予測とテスト環境でのベンチマーク作業のために、少なくとも 4 つのシャードから始める必要があることもわかっています。

最後に、最初のデータ サイズ (ドキュメントの平均サイズ * 初期ユーザー数) についての適切なアイデアが得られました。これは約 120 GB になります。

初期ロードを適切かつ高速にして、4 つのシャードすべてを可能な限り活用したいと考えています。このデータを事前に分割して、4 つのシャードを利用し、初期データ ロード中にシャードで発生する必要がある移動、分割などの数を最小限に抑えるにはどうすればよいですか?

4

1 に答える 1

30

初期データ サイズ (120GB) はわかっており、MongoDB のデフォルトの最大チャンク サイズは 64MBです。64MB を 120GB に分割すると、1920 になります。これが、最初に調べる必要があるチャンクの最小数です。たまたま 2048 は 16 のべき乗を 2 で割ったものであり、GUID (シャード キー) が 16 進数ベースであることを考えると、1920 よりもはるかに扱いやすい数値です (以下を参照)。

注:この事前分割は、データをコレクションに追加する前に行う必要があります。データを含むコレクションで enableSharding() コマンドを使用すると、MongoDB はデータ自体を分割し、チャンクが既に存在するときにこれを実行することになります。これにより、非常に奇妙なチャンク分散が発生する可能性があるため、注意してください。

この回答の目的のために、データベースが呼び出されusers、コレクションが呼び出されると仮定しましょうuserInfo_idまた、GUID がフィールドに書き込まれると仮定しましょう。これらのパラメーターを使用して、に接続しmongos、次のコマンドを実行します。

// first switch to the users DB
use users;
// now enable sharding for the users DB
sh.enableSharding("users"); 
// enable sharding on the relevant collection
sh.shardCollection("users.userInfo", {"_id" : 1});
// finally, disable the balancer (see below for options on a per-collection basis)
// this prevents migrations from kicking off and interfering with the splits by competing for meta data locks
sh.stopBalancer(); 

ここで、上記の計算に従って、GUID 範囲を 2048 チャンクに分割する必要があります。そのためには、少なくとも 3 桁の 16 進数 (16 ^ 3 = 4096) が必要であり、それらを範囲の最上位桁 (つまり、左端の 3 桁) に配置します。繰り返しますが、これはmongosシェルから実行する必要があります

// Simply use a for loop for each digit
for ( var x=0; x < 16; x++ ){
  for( var y=0; y<16; y++ ) {
  // for the innermost loop we will increment by 2 to get 2048 total iterations
  // make this z++ for 4096 - that would give ~30MB chunks based on the original figures
    for ( var z=0; z<16; z+=2 ) {
    // now construct the GUID with zeroes for padding - handily the toString method takes an argument to specify the base
        var prefix = "" + x.toString(16) + y.toString(16) + z.toString(16) + "00000000000000000000000000000";
        // finally, use the split command to create the appropriate chunk
        db.adminCommand( { split : "users.userInfo" , middle : { _id : prefix } } );
    }
  }
}

それが完了したら、sh.status()ヘルパーを使用してプレイの状態を確認しましょう。

mongos> sh.status()
--- Sharding Status ---
  sharding version: {
        "_id" : 1,
        "version" : 3,
        "minCompatibleVersion" : 3,
        "currentVersion" : 4,
        "clusterId" : ObjectId("527056b8f6985e1bcce4c4cb")
}
  shards:
        {  "_id" : "shard0000",  "host" : "localhost:30000" }
        {  "_id" : "shard0001",  "host" : "localhost:30001" }
        {  "_id" : "shard0002",  "host" : "localhost:30002" }
        {  "_id" : "shard0003",  "host" : "localhost:30003" }
  databases:
        {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
        {  "_id" : "users",  "partitioned" : true,  "primary" : "shard0001" }
                users.userInfo
                        shard key: { "_id" : 1 }
                        chunks:
                                shard0001       2049
                        too many chunks to print, use verbose if you want to force print

2048 個のチャンク (および最小/最大チャンクのおかげで 1 つ余分に) がありますが、バランサーがオフになっているため、それらはすべて元のシャードに残っています。それでは、バランサーを再度有効にしましょう。

sh.startBalancer();

これはすぐにバランスを取り始め、すべてのチャンクが空であるため比較的高速ですが、まだ少し時間がかかります (他のコレクションからの移行と競合している場合は、はるかに遅くなります)。しばらくしてからsh.status()再度実行すると、2048 個のチャンクがすべて 4 つのシャードに分割され、初期データ ロードの準備が整います。

mongos> sh.status()
--- Sharding Status ---
  sharding version: {
        "_id" : 1,
        "version" : 3,
        "minCompatibleVersion" : 3,
        "currentVersion" : 4,
        "clusterId" : ObjectId("527056b8f6985e1bcce4c4cb")
}
  shards:
        {  "_id" : "shard0000",  "host" : "localhost:30000" }
        {  "_id" : "shard0001",  "host" : "localhost:30001" }
        {  "_id" : "shard0002",  "host" : "localhost:30002" }
        {  "_id" : "shard0003",  "host" : "localhost:30003" }
  databases:
        {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
        {  "_id" : "users",  "partitioned" : true,  "primary" : "shard0001" }
                users.userInfo
                        shard key: { "_id" : 1 }
                        chunks:
                                shard0000       512
                                shard0002       512
                                shard0003       512
                                shard0001       513
                        too many chunks to print, use verbose if you want to force print
        {  "_id" : "test",  "partitioned" : false,  "primary" : "shard0002" }

これでデータの読み込みを開始する準備が整いましたが、データの読み込みが完了するまで分割や移行が発生しないことを完全に保証するには、もう 1 つ行う必要があります。インポート中はバランサーと自動分割をオフにします。

  • すべてのバランシングを無効にするには、mongos から次のコマンドを実行します。sh.stopBalancer()
  • 他のバランシング操作を実行したままにしたい場合は、特定のコレクションを無効にすることができます。上記の名前空間を例として使用します。sh.disableBalancing("users.userInfo")
  • ロード中に自動分割をオフにするにmongosは、オプションを使用してデータをロードするために使用するそれぞれを再起動する必要があり--noAutoSplitます。

インポートが完了したら、必要に応じて手順を逆にして ( sh.startBalancer()、 、なしsh.enableBalancing("users.userInfo")で を再起動)、すべてを既定の設定に戻します。mongos--noAutoSplit

**

更新: 速度の最適化

**

急いでいない場合は、上記の方法で問題ありません。現状では、これをテストするとわかるように、バランサーはそれほど高速ではありません - 空のチャンクでも。したがって、作成するチャンクの数を増やすと、バランスをとるのに時間がかかります。展開によって異なりますが、2048 個のチャンクのバランスを完了するのに 30 分以上かかることがわかりました。

テストや比較的静かなクラスターでは問題ないかもしれませんが、バランサーをオフにして他の更新を必要としないことは、忙しいクラスターでは確実に行うのがはるかに難しくなります. では、どうすれば高速化できるのでしょうか。

答えは、早い段階でいくつかの手動の移動を行い、チャンクがそれぞれのシャードに配置されたら分割することです。これは、特定のシャード キー (ランダムに分散された UUID など)、または特定のデータ アクセス パターンでのみ望ましいので、結果として不十分なデータ分散にならないように注意してください。

上記の例を使用すると、4 つのシャードがあるため、すべての分割を行ってからバランスを取るのではなく、代わりに 4 つに分割します。次に、手動で移動して各シャードに 1 つのチャンクを配置し、最後にそれらのチャンクを必要な数に分割します。

上記の例の範囲は次のようになります。

$min --> "40000000000000000000000000000000"
"40000000000000000000000000000000" --> "80000000000000000000000000000000"
"80000000000000000000000000000000" --> "c0000000000000000000000000000000"
"c0000000000000000000000000000000" --> $max     

これらを作成するのは 4 つのコマンドだけですが、それがあるので、上記のループを単純化/変更された形式で再利用してみませんか?

for ( var x=4; x < 16; x+=4){
    var prefix = "" + x.toString(16) + "0000000000000000000000000000000";
    db.adminCommand( { split : "users.userInfo" , middle : { _id : prefix } } ); 
} 

これが現在の thinks の外観です。4 つのチャンクがあり、すべて shard0001 にあります。

mongos> sh.status()
--- Sharding Status --- 
  sharding version: {
    "_id" : 1,
    "version" : 4,
    "minCompatibleVersion" : 4,
    "currentVersion" : 5,
    "clusterId" : ObjectId("53467e59aea36af7b82a75c1")
}
  shards:
    {  "_id" : "shard0000",  "host" : "localhost:30000" }
    {  "_id" : "shard0001",  "host" : "localhost:30001" }
    {  "_id" : "shard0002",  "host" : "localhost:30002" }
    {  "_id" : "shard0003",  "host" : "localhost:30003" }
  databases:
    {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
    {  "_id" : "test",  "partitioned" : false,  "primary" : "shard0001" }
    {  "_id" : "users",  "partitioned" : true,  "primary" : "shard0001" }
        users.userInfo
            shard key: { "_id" : 1 }
            chunks:
                shard0001   4
            { "_id" : { "$minKey" : 1 } } -->> { "_id" : "40000000000000000000000000000000" } on : shard0001 Timestamp(1, 1) 
            { "_id" : "40000000000000000000000000000000" } -->> { "_id" : "80000000000000000000000000000000" } on : shard0001 Timestamp(1, 3) 
            { "_id" : "80000000000000000000000000000000" } -->> { "_id" : "c0000000000000000000000000000000" } on : shard0001 Timestamp(1, 5) 
            { "_id" : "c0000000000000000000000000000000" } -->> { "_id" : { "$maxKey" : 1 } } on : shard0001 Timestamp(1, 6)                    

$minチャンクはそのままにして、残りの 3 つを移動します。これはプログラムで行うことができますが、チャンクが最初に存在する場所、シャードにどのように名前を付けたかなどによって異なりmoveChunkます.

mongos> sh.moveChunk("users.userInfo", {"_id" : "40000000000000000000000000000000"}, "shard0000")
{ "millis" : 1091, "ok" : 1 }
mongos> sh.moveChunk("users.userInfo", {"_id" : "80000000000000000000000000000000"}, "shard0002")
{ "millis" : 1078, "ok" : 1 }
mongos> sh.moveChunk("users.userInfo", {"_id" : "c0000000000000000000000000000000"}, "shard0003")
{ "millis" : 1083, "ok" : 1 }          

再確認して、チャンクが期待する場所にあることを確認しましょう。

mongos> sh.status()
--- Sharding Status --- 
  sharding version: {
    "_id" : 1,
    "version" : 4,
    "minCompatibleVersion" : 4,
    "currentVersion" : 5,
    "clusterId" : ObjectId("53467e59aea36af7b82a75c1")
}
  shards:
    {  "_id" : "shard0000",  "host" : "localhost:30000" }
    {  "_id" : "shard0001",  "host" : "localhost:30001" }
    {  "_id" : "shard0002",  "host" : "localhost:30002" }
    {  "_id" : "shard0003",  "host" : "localhost:30003" }
  databases:
    {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
    {  "_id" : "test",  "partitioned" : false,  "primary" : "shard0001" }
    {  "_id" : "users",  "partitioned" : true,  "primary" : "shard0001" }
        users.userInfo
            shard key: { "_id" : 1 }
            chunks:
                shard0001   1
                shard0000   1
                shard0002   1
                shard0003   1
            { "_id" : { "$minKey" : 1 } } -->> { "_id" : "40000000000000000000000000000000" } on : shard0001 Timestamp(4, 1) 
            { "_id" : "40000000000000000000000000000000" } -->> { "_id" : "80000000000000000000000000000000" } on : shard0000 Timestamp(2, 0) 
            { "_id" : "80000000000000000000000000000000" } -->> { "_id" : "c0000000000000000000000000000000" } on : shard0002 Timestamp(3, 0) 
            { "_id" : "c0000000000000000000000000000000" } -->> { "_id" : { "$maxKey" : 1 } } on : shard0003 Timestamp(4, 0)  

これは上記で提案した範囲と一致するため、すべて問題ないようです。上記の元のループを実行して、各シャードでそれらを「その場で」分割すると、ループが終了するとすぐにバランスの取れた分散が得られます。もう1つsh.status()確認する必要があります。

mongos> for ( var x=0; x < 16; x++ ){
...   for( var y=0; y<16; y++ ) {
...   // for the innermost loop we will increment by 2 to get 2048 total iterations
...   // make this z++ for 4096 - that would give ~30MB chunks based on the original figures
...     for ( var z=0; z<16; z+=2 ) {
...     // now construct the GUID with zeroes for padding - handily the toString method takes an argument to specify the base
...         var prefix = "" + x.toString(16) + y.toString(16) + z.toString(16) + "00000000000000000000000000000";
...         // finally, use the split command to create the appropriate chunk
...         db.adminCommand( { split : "users.userInfo" , middle : { _id : prefix } } );
...     }
...   }
... }          
{ "ok" : 1 }
mongos> sh.status()
--- Sharding Status --- 
  sharding version: {
    "_id" : 1,
    "version" : 4,
    "minCompatibleVersion" : 4,
    "currentVersion" : 5,
    "clusterId" : ObjectId("53467e59aea36af7b82a75c1")
}
  shards:
    {  "_id" : "shard0000",  "host" : "localhost:30000" }
    {  "_id" : "shard0001",  "host" : "localhost:30001" }
    {  "_id" : "shard0002",  "host" : "localhost:30002" }
    {  "_id" : "shard0003",  "host" : "localhost:30003" }
  databases:
    {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
    {  "_id" : "test",  "partitioned" : false,  "primary" : "shard0001" }
    {  "_id" : "users",  "partitioned" : true,  "primary" : "shard0001" }
        users.userInfo
            shard key: { "_id" : 1 }
            chunks:
                shard0001   513
                shard0000   512
                shard0002   512
                shard0003   512
            too many chunks to print, use verbose if you want to force print    

これで、バランサーを待つ必要はありません。分散はすでに均一になっています。

于 2013-10-30T01:35:41.643 に答える