MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Come pre-dividere in modo programmatico una chiave shard basata su GUID con MongoDB

Conosciamo la dimensione iniziale dei dati (120 GB) e sappiamo che la dimensione massima predefinita del blocco in MongoDB è 64 MB. Se dividiamo 64 MB in 120 GB otteniamo 1920, quindi questo è il numero minimo di blocchi con cui dovremmo iniziare. Si dà il caso che 2048 sia una potenza di 16 divisa per 2, e dato che il GUID (la nostra chiave shard) è basato su esadecimale, è un numero molto più facile da gestire rispetto a 1920 (vedi sotto).

NOTA: Questa pre-divisione deve essere eseguita prima tutti i dati vengono aggiunti alla raccolta. Se usi il comando enableSharding() su una raccolta che contiene dati, MongoDB dividerà i dati stessi e lo eseguirai mentre i blocchi esistono già, il che può portare a una distribuzione dei blocchi piuttosto strana, quindi fai attenzione.

Ai fini di questa risposta, assumiamo che il database verrà chiamato users e la raccolta si chiama userInfo . Assumiamo anche che il GUID venga scritto nel _id campo. Con questi parametri ci collegheremmo a un mongos ed esegui i seguenti comandi:

// 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(); 

Ora, secondo il calcolo sopra, dobbiamo dividere l'intervallo GUID in 2048 blocchi. Per farlo abbiamo bisogno di almeno 3 cifre esadecimali (16 ^ 3 =4096) e le inseriremo nelle cifre più significative (cioè le 3 più a sinistra) per gli intervalli. Anche in questo caso, dovrebbe essere eseguito da un mongos guscio

// 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 } } );
    }
  }
}

Fatto ciò, controlliamo lo stato di avanzamento utilizzando sh.status() aiutante:

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

Abbiamo i nostri blocchi 2048 (più uno in più grazie ai blocchi min/max), ma sono ancora tutti sullo shard originale perché il bilanciatore è disattivato. Quindi, riabilitiamo il bilanciatore:

sh.startBalancer();

Questo inizierà immediatamente a bilanciarsi e sarà relativamente veloce perché tutti i blocchi sono vuoti, ma ci vorrà ancora un po' di tempo (molto più lento se è in competizione con migrazioni da altre raccolte). Trascorso un po' di tempo, esegui sh.status() di nuovo e lì (dovresti) averlo - 2048 blocchi tutti ben suddivisi in 4 frammenti e pronti per un caricamento di dati iniziale:

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" }

Ora sei pronto per iniziare a caricare i dati, ma per garantire assolutamente che non si verifichino divisioni o migrazioni fino al completamento del caricamento dei dati, devi fare un'altra cosa:disattivare il bilanciatore e la suddivisione automatica per la durata dell'importazione:

  • Per disabilitare tutto il bilanciamento, esegui questo comando da mongos:sh.stopBalancer()
  • Se vuoi lasciare in esecuzione altre operazioni di bilanciamento, puoi disabilitarlo su una raccolta specifica. Usando lo spazio dei nomi sopra come esempio:sh.disableBalancing("users.userInfo")
  • Per disattivare la divisione automatica durante il caricamento, dovrai riavviare ogni mongos utilizzerai per caricare i dati con il --noAutoSplit opzione.

Una volta completata l'importazione, invertire i passaggi secondo necessità (sh.startBalancer() , sh.enableBalancing("users.userInfo") e riavvia mongos senza --noAutoSplit ) per riportare tutto alle impostazioni predefinite.

**

Aggiornamento:ottimizzazione per la velocità

**

L'approccio di cui sopra va bene se non hai fretta. Allo stato attuale delle cose, e come scoprirai se lo proverai, il bilanciatore non è molto veloce, anche con blocchi vuoti. Quindi, man mano che aumenti il ​​numero di blocchi che crei, più tempo ci vorrà per bilanciare. Ho visto che sono necessari più di 30 minuti per completare il bilanciamento di 2048 blocchi, anche se questo varia a seconda della distribuzione.

Potrebbe andare bene per il test o per un cluster relativamente silenzioso, ma avere il servizio di bilanciamento disattivato e non richiedere l'interferenza di altri aggiornamenti sarà molto più difficile da garantire su un cluster occupato. Allora, come acceleriamo le cose?

La risposta è fare alcune mosse manuali in anticipo, quindi dividere i pezzi una volta che si trovano sui rispettivi frammenti. Tieni presente che ciò è desiderabile solo con determinate chiavi shard (come un UUID distribuito casualmente) o determinati modelli di accesso ai dati, quindi fai attenzione a non finire con una cattiva distribuzione dei dati di conseguenza.

Usando l'esempio sopra abbiamo 4 frammenti, quindi invece di fare tutte le divisioni, quindi bilanciare, dividiamo invece in 4. Quindi mettiamo un pezzo su ogni frammento spostandolo manualmente, e infine dividiamo quei pezzi nel numero richiesto.

Gli intervalli nell'esempio sopra sarebbero simili a questo:

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

Sono solo 4 comandi per crearli, ma dato che ce l'abbiamo, perché non riutilizzare il ciclo sopra in una forma semplificata/modificata:

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

Ecco come appare ora:abbiamo i nostri 4 blocchi, tutti su 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)                    

Lasceremo il $min pezzo dov'è e sposta gli altri tre. Puoi farlo in modo programmatico, ma dipende da dove risiedono inizialmente i blocchi, da come hai chiamato i tuoi shard ecc. quindi lascerò questo manuale per ora, non è troppo oneroso - solo 3 moveChunk comandi:

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 }          

Controlliamo due volte e assicuriamoci che i blocchi siano dove ci aspettiamo che siano:

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)  

Ciò corrisponde alle nostre gamme proposte sopra, quindi tutto sembra a posto. Ora esegui il ciclo originale sopra per dividerli "sul posto" su ogni shard e dovremmo avere una distribuzione bilanciata non appena il ciclo finisce. Un altro sh.status() dovrebbe confermare le cose:

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    

E il gioco è fatto:nessuna attesa per il bilanciamento, la distribuzione è già pari.