sql >> Base de Datos >  >> NoSQL >> MongoDB

Cómo dividir previamente mediante programación una clave fragmentada basada en GUID con MongoDB

Conocemos el tamaño de datos inicial (120 GB) y sabemos que el tamaño de fragmento máximo predeterminado en MongoDB es de 64 MB. Si dividimos 64 MB en 120 GB, obtenemos 1920, por lo que ese es el número mínimo de fragmentos con los que deberíamos empezar. Da la casualidad de que 2048 es una potencia de 16 dividida por 2, y dado que el GUID (nuestra clave de fragmento) está basado en hexadecimal, es un número mucho más fácil de manejar que 1920 (ver más abajo).

NOTA: Esta división previa debe hacerse antes cualquier dato se agrega a la colección. Si usa el comando enableSharding() en una colección que contiene datos, MongoDB dividirá los datos en sí y luego ejecutará esto mientras ya existen fragmentos, lo que puede conducir a una distribución de fragmentos bastante extraña, así que tenga cuidado.

A los efectos de esta respuesta, supongamos que la base de datos se llamará users y la colección se llama userInfo . Supongamos también que el GUID se escribirá en el _id campo. Con esos parámetros nos conectaríamos a un mongos y ejecuta los siguientes comandos:

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

Ahora, según el cálculo anterior, necesitamos dividir el rango de GUID en 2048 partes. Para hacer eso, necesitamos al menos 3 dígitos hexadecimales (16 ^ 3 =4096) y los colocaremos en los dígitos más significativos (es decir, los 3 más a la izquierda) para los rangos. Nuevamente, esto debe ejecutarse desde un mongos concha

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

Una vez hecho esto, verifiquemos el estado del juego usando sh.status() ayudante:

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

Tenemos nuestros fragmentos 2048 (más uno adicional gracias a los fragmentos mínimos/máximos), pero todos siguen en el fragmento original porque el balanceador está apagado. Entonces, volvamos a habilitar el balanceador:

sh.startBalancer();

Esto comenzará a equilibrarse de inmediato y será relativamente rápido porque todos los fragmentos están vacíos, pero aun así llevará un poco de tiempo (mucho más lento si compite con migraciones de otras colecciones). Una vez que haya transcurrido un tiempo, ejecute sh.status() una y otra vez (debería) tenerlo:2048 fragmentos bien divididos en 4 fragmentos y listos para una carga de datos inicial:

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

Ahora está listo para comenzar a cargar datos, pero para garantizar absolutamente que no se produzcan divisiones ni migraciones hasta que se complete la carga de datos, debe hacer una cosa más:apague el balanceador y la división automática durante la importación:

  • Para deshabilitar todo el equilibrio, ejecute este comando desde mongos:sh.stopBalancer()
  • Si desea dejar otras operaciones de equilibrio en ejecución, puede deshabilitarlas en una colección específica. Usando el espacio de nombres anterior como ejemplo:sh.disableBalancing("users.userInfo")
  • Para desactivar la división automática durante la carga, deberá reiniciar cada mongos utilizará para cargar los datos con --noAutoSplit opción.

Una vez completada la importación, invierta los pasos según sea necesario (sh.startBalancer() , sh.enableBalancing("users.userInfo") y reinicia mongos sin --noAutoSplit ) para devolver todo a la configuración predeterminada.

**

Actualización:Optimización de la velocidad

**

El enfoque anterior está bien si no tiene prisa. Tal como están las cosas, y como descubrirá si prueba esto, el balanceador no es muy rápido, incluso con fragmentos vacíos. Por lo tanto, a medida que aumenta la cantidad de fragmentos que crea, más tiempo llevará equilibrarlo. He visto que lleva más de 30 minutos terminar de equilibrar 2048 fragmentos, aunque esto variará según la implementación.

Eso podría estar bien para las pruebas, o para un clúster relativamente silencioso, pero tener el equilibrador apagado y no necesitar otras actualizaciones que interfieran será mucho más difícil de garantizar en un clúster ocupado. Entonces, ¿cómo aceleramos las cosas?

La respuesta es hacer algunos movimientos manuales temprano, luego dividir los fragmentos una vez que estén en sus respectivos fragmentos. Tenga en cuenta que esto solo es deseable con ciertas claves de fragmentos (como un UUID distribuido aleatoriamente) o ciertos patrones de acceso a datos, así que tenga cuidado de no terminar con una mala distribución de datos como resultado.

Usando el ejemplo anterior, tenemos 4 fragmentos, por lo que en lugar de hacer todas las divisiones y luego equilibrar, nos dividimos en 4. Luego, colocamos un fragmento en cada fragmento moviéndolos manualmente y, finalmente, dividimos esos fragmentos en el número requerido.

Los rangos en el ejemplo anterior se verían así:

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

Son solo 4 comandos para crearlos, pero ya que lo tenemos, ¿por qué no reutilizar el ciclo anterior en una forma simplificada/modificada?

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

Así es como se ve ahora:tenemos nuestros 4 fragmentos, todos en 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)                    

Dejaremos el $min parte donde está y mueve los otros tres. Puede hacer esto programáticamente, pero depende de dónde residan inicialmente los fragmentos, cómo haya nombrado sus fragmentos, etc., así que dejaré este manual por ahora, no es demasiado oneroso:solo 3 moveChunk comandos:

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 }          

Verifiquemos dos veces y asegurémonos de que los fragmentos estén donde esperamos que estén:

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)  

Eso coincide con nuestros rangos propuestos anteriormente, por lo que todo se ve bien. Ahora ejecute el ciclo original anterior para dividirlos "en su lugar" en cada fragmento y deberíamos tener una distribución equilibrada tan pronto como finalice el ciclo. Uno más sh.status() debería confirmar las cosas:

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    

Y ahí lo tienes:no hay que esperar al equilibrador, la distribución ya es uniforme.