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

Cómo optimizar el rendimiento de MongoDB

El excelente rendimiento de la base de datos es importante cuando desarrolla aplicaciones con MongoDB. A veces, el proceso general de servicio de datos puede degradarse debido a una serie de razones, algunas de las cuales incluyen:

  • Patrones de diseño de esquema inadecuados
  • Uso inadecuado o no uso de estrategias de indexación
  • Hardware inadecuado
  • Retraso de replicación
  • Técnicas de consulta de bajo rendimiento

Algunos de estos contratiempos pueden obligarlo a aumentar los recursos de hardware, mientras que otros no. Por ejemplo, las estructuras de consulta deficientes pueden hacer que la consulta demore mucho tiempo en procesarse, lo que provocará un retraso en la réplica e incluso la pérdida de algunos datos. En este caso, uno puede pensar que tal vez la memoria de almacenamiento no sea suficiente y que probablemente necesite escalarse. Este artículo analiza los procedimientos más apropiados que puede emplear para mejorar el rendimiento de su base de datos MongoDB.

Diseño del esquema

Básicamente, las dos relaciones de esquema más utilizadas son...

  • Uno a pocos
  • Uno a muchos

Si bien el diseño de esquema más eficiente es la relación uno a muchos, cada uno tiene sus propios méritos y limitaciones.

De uno a pocos

En este caso, para un campo dado, hay documentos incrustados pero no están indexados con la identidad del objeto.

He aquí un ejemplo sencillo:

{
      userName: "Brian Henry",
      Email : "[email protected]",
      grades: [
             {subject: ‘Mathematics’,  grade: ‘A’},
             {subject: English,  grade: ‘B’},
      ]
}

Una ventaja de usar esta relación es que puede obtener los documentos incrustados con una sola consulta. Sin embargo, desde el punto de vista de la consulta, no puede acceder a un solo documento incrustado. Entonces, si no va a hacer referencia a documentos incrustados por separado, será óptimo usar este diseño de esquema.

Uno a muchos

Para esta relación, los datos de una base de datos están relacionados con los datos de una base de datos diferente. Por ejemplo, puedes tener una base de datos para usuarios y otra para publicaciones. Entonces, si un usuario hace una publicación, se registra con la identificación del usuario.

Esquema de usuarios

{ 
    Full_name: “John Doh”,
    User_id: 1518787459607.0
}

Esquema de publicaciones

{
    "_id" : ObjectId("5aa136f0789cf124388c1955"),
    "postTime" : "16:13",
    "postDate" : "8/3/2018",
    "postOwnerNames" : "John Doh",
    "postOwner" : 1518787459607.0,
    "postId" : "1520514800139"
}

La ventaja de este diseño de esquema es que los documentos se consideran independientes (se pueden seleccionar por separado). Otra ventaja es que este diseño permite a los usuarios de diferentes identificaciones compartir información del esquema de publicaciones (de ahí el nombre One-to-Many) y, a veces, puede ser un esquema "N-to-N", básicamente sin usar la combinación de tablas. La limitación de este diseño de esquema es que debe realizar al menos dos consultas para obtener o seleccionar datos en la segunda colección.

Por lo tanto, cómo modelar los datos dependerá del patrón de acceso de la aplicación. Además de esto, debe considerar el diseño del esquema que hemos discutido anteriormente.

Técnicas de optimización para el diseño de esquemas

  1. Emplee la incrustación de documentos tanto como sea posible, ya que reduce la cantidad de consultas que necesita ejecutar para un conjunto de datos en particular.

  2. No utilice la desnormalización para documentos que se actualizan con frecuencia. Si un campo se va a actualizar con frecuencia, entonces existirá la tarea de encontrar todas las instancias que deben actualizarse. Esto dará como resultado un procesamiento de consultas lento, por lo tanto, abrumará incluso los méritos asociados con la desnormalización.

  3. Si es necesario obtener un documento por separado, entonces no es necesario utilizar la incrustación, ya que las consultas complejas, como la canalización agregada, tardan más en ejecutarse.

  4. Si la matriz de documentos que se van a incrustar es lo suficientemente grande, no los incruste. El crecimiento de la matriz debe tener al menos un límite límite.

Indización adecuada

Esta es la parte más crítica del ajuste del rendimiento y requiere una comprensión integral de las consultas de la aplicación, la proporción de lecturas y escrituras y la cantidad de memoria libre que tiene su sistema. Si usa un índice, la consulta escaneará el índice y no la colección.

Un índice excelente es aquel que involucra todos los campos explorados por una consulta. Esto se conoce como un índice compuesto.

Para crear un índice único para un campo, puede usar este código:

db.collection.createIndex({“fields”: 1})

Para un índice compuesto, para crear la indexación:

db.collection.createIndex({“filed1”: 1, “field2”:  1})

Además de consultas más rápidas mediante el uso de la indexación, hay una ventaja adicional de otras operaciones como ordenar, muestras y limitar. Por ejemplo, si diseño mi esquema como {f:1, m:1} puedo hacer una operación adicional además de buscar como

db.collection.find( {f: 1} ).sort( {m: 1} )

Leer datos de la RAM es más eficiente que leer los mismos datos del disco. Por esta razón, siempre se recomienda asegurarse de que su índice se ajuste completamente a la RAM. Para obtener el tamaño de índice actual de su colección, ejecute el comando:

db.collection.totalIndexSize()

Obtendrá un valor como 36864 bytes. Este valor tampoco debería tomar un gran porcentaje del tamaño total de RAM, ya que debe satisfacer las necesidades de todo el conjunto de trabajo del servidor.

Una consulta eficiente también debería mejorar la selectividad. La selectividad se puede definir como la capacidad de una consulta para restringir el resultado utilizando el índice. Para ser más secante, sus consultas deben limitar el número de posibles documentos con el campo indexado. La selectividad se asocia principalmente con un índice compuesto que incluye un campo de baja selectividad y otro campo. Por ejemplo, si tiene estos datos:

{ _id: ObjectId(), a: 6, b: "no", c: 45 }
{ _id: ObjectId(), a: 7, b: "gh", c: 28 }
{ _id: ObjectId(), a: 7, b: "cd", c: 58 }
{ _id: ObjectId(), a: 8, b: "kt", c: 33 }

La consulta {a:7, b:"cd"} escaneará 2 documentos para devolver 1 documento coincidente. Sin embargo, si los datos para el valor a se distribuyen uniformemente, es decir,

{ _id: ObjectId(), a: 6, b: "no", c: 45 }
{ _id: ObjectId(), a: 7, b: "gh", c: 28 }
{ _id: ObjectId(), a: 8, b: "cd", c: 58 }
{ _id: ObjectId(), a: 9, b: "kt", c: 33 }

La consulta {a:7, b:"cd"} escaneará 1 documento y devolverá este documento. Por lo tanto, esto llevará menos tiempo que la primera estructura de datos.

ClusterControlConsola única para toda su infraestructura de base de datosDescubra qué más hay de nuevo en ClusterControlInstale ClusterControl GRATIS

Aprovisionamiento de recursos

La memoria de almacenamiento, la RAM y otros parámetros operativos inadecuados pueden degradar drásticamente el rendimiento de un MongoDB. Por ejemplo, si la cantidad de conexiones de usuario es muy grande, dificultará la capacidad de la aplicación del servidor para manejar las solicitudes de manera oportuna. Como se discutió en Aspectos clave para monitorear en MongoDB, puede obtener una descripción general de los recursos limitados que tiene y cómo puede escalarlos para que se ajusten a sus especificaciones. Para una gran cantidad de solicitudes de aplicaciones simultáneas, el sistema de base de datos se verá abrumado para mantenerse al día con la demanda.

Retraso de replicación

A veces, puede notar que faltan algunos datos en su base de datos o cuando elimina algo, aparece nuevamente. Por mucho que pueda tener un esquema bien diseñado, una indexación adecuada y suficientes recursos, al principio su aplicación se ejecutará sin contratiempos, pero luego, en algún momento, notará los últimos problemas mencionados. MongoDB se basa en el concepto de replicación donde los datos se copian de forma redundante para cumplir con algunos criterios de diseño. Una suposición con esto es que el proceso es instantáneo. Sin embargo, puede ocurrir algún retraso debido a fallas en la red o errores no manejados. En pocas palabras, habrá una gran brecha entre el momento en que se procesa una operación en el nodo principal y el momento en que se aplica en el nodo secundario.

Retrocesos con retrasos en las réplicas

  1. Datos inconsistentes. Esto está especialmente asociado con las operaciones de lectura que se distribuyen entre los secundarios.

  2. Si la brecha de retraso es lo suficientemente amplia, entonces una gran cantidad de datos no replicados pueden estar en el nodo principal y deberán reconciliarse en el nodo secundario. En algún momento, esto puede ser imposible, especialmente cuando no se puede recuperar el nodo principal.

  3. Si no se recupera el nodo principal, se puede obligar a ejecutar un nodo con datos que no están actualizados y, en consecuencia, puede descartar toda la base de datos para que se recupere el principal.

Causas de la falla del nodo secundario

  1. Superando la potencia primaria sobre la secundaria con respecto a las especificaciones de CPU, IOPS de disco y E/S de red.

  2. Operaciones de escritura complejas. Por ejemplo, un comando como

    db.collection.update( { a: 7}  , {$set: {m: 4} }, {multi: true} )

    El nodo principal registrará esta operación en el registro de operaciones lo suficientemente rápido. Sin embargo, para el nodo secundario, tiene que buscar esas operaciones, leer en la RAM cualquier índice y páginas de datos para cumplir con algunas especificaciones de criterios, como la identificación. Dado que tiene que hacer esto lo suficientemente rápido para mantener la velocidad con la que el nodo principal realiza la operación, si el número de operaciones es lo suficientemente grande, habrá un retraso esperado.

  3. Bloqueo del secundario al realizar una copia de seguridad. En este caso puede que nos olvidemos de deshabilitar el primario por lo que seguirá con sus operaciones con normalidad. En el momento en que se libere el bloqueo, el retraso de la replicación tendrá una gran brecha, especialmente cuando se trata de una gran cantidad de copias de seguridad de datos.

  4. Edificio índice. Si se acumula un índice en el nodo secundario, todas las demás operaciones asociadas con él se bloquean. Si el índice es de ejecución prolongada, se producirá un problema de retraso en la replicación.

  5. Secundario no conectado. A veces, el nodo secundario puede fallar debido a desconexiones de la red y esto provoca un retraso en la replicación cuando se vuelve a conectar.

Cómo minimizar el retraso de replicación

  • Use índices únicos además de que su colección tenga el campo _id. Esto es para evitar que el proceso de replicación falle por completo.

  • Considere otros tipos de copias de seguridad, como instantáneas de un punto en el tiempo y del sistema de archivos, que no necesariamente requieren bloqueo.

  • Evite crear índices grandes, ya que provocan una operación de bloqueo en segundo plano.

  • Haz que la secundaria sea lo suficientemente potente. Si la operación de escritura es ligera, el uso de secundarios de poca potencia será económico. Pero, para cargas de escritura pesadas, el nodo secundario puede quedarse atrás del principal. Para ser más secante, el secundario debe tener suficiente ancho de banda para ayudar a leer los registros de operaciones lo suficientemente rápido para mantener su velocidad con el nodo principal.

Técnicas de consulta eficientes

Además de crear consultas indexadas y usar la Selectividad de consultas como se mencionó anteriormente, hay otros conceptos que puede emplear para agilizar y hacer que sus consultas sean efectivas.

Optimización de sus consultas

  1. Usando una consulta cubierta. Una consulta cubierta es aquella que siempre está completamente satisfecha por un índice, por lo que no necesita examinar ningún documento. Por lo tanto, la consulta cubierta debe tener todos los campos como parte del índice y, en consecuencia, el resultado debe contener todos estos campos.

    Consideremos este ejemplo:

    {_id: 1, product: { price: 50 }

    Si creamos un índice para esta colección como

    {“product.price”: 1} 

    Considerando una operación de búsqueda, este índice cubrirá esta consulta;

    db.collection.find( {“product.price”: 50}, {“product.price”: 1, _id: 0}  )

    y devuelva el campo product.price y el valor solamente.

  2. Para documentos incrustados, utilice la notación de punto (.). La notación de puntos ayuda a acceder a elementos de una matriz y campos de documentos incrustados.

    Accediendo a una matriz:

    {
       prices: [12, 40, 100, 50, 40]  
    }

    Para especificar el cuarto elemento, por ejemplo, puede escribir este comando:

    “prices.3”

    Accediendo a una matriz de objetos:

    {
    
       vehicles: [{name: toyota, quantity: 50},
                 {name: bmw, quantity: 100},
                 {name: subaru, quantity: 300}                    
    } 

    Para especificar el campo de nombre en la matriz de vehículos, puede usar este comando

    “vehicles.name”
  3. Compruebe si una consulta está cubierta. Para hacer esto, use db.collection.explain(). Esta función proporcionará información sobre la ejecución de otras operaciones, p. db.colección.explicar().agregar(). Para obtener más información sobre la función de explicación, puede consultar la función de explicación().

En general, la técnica suprema en lo que respecta a las consultas es el uso de índices. Consultar solo un índice es mucho más rápido que consultar documentos fuera del índice. Pueden caber en la memoria, por lo tanto, están disponibles en la RAM en lugar de en el disco. Esto hace que sea lo suficientemente fácil y rápido como para recuperarlos de la memoria.