remove many indexes index create compound mongodb indexing sharding

many - MongoDB consulta el rendimiento de más de 5 millones de registros



show indexes mongodb (3)

Recientemente alcanzamos los> 2 millones de registros para una de nuestras colecciones principales y ahora empezamos a sufrir por los principales problemas de rendimiento en esa colección.

Los documentos en la colección tienen aproximadamente 8 campos que puede filtrar utilizando la interfaz de usuario y se supone que los resultados se ordenan por un campo de marca de tiempo donde se procesó el registro.

He agregado varios índices compuestos con los campos filtrados y la marca de tiempo, por ejemplo:

db.events.ensureIndex({somefield: 1, timestamp:-1})

También agregué un par de índices para usar varios filtros a la vez con la esperanza de lograr un mejor rendimiento. Pero algunos filtros aún tardan mucho tiempo en realizarse.

Me he asegurado de que el uso explique que las consultas usan los índices que he creado, pero el rendimiento aún no es lo suficientemente bueno.

Me preguntaba si sharding es el camino a seguir ahora ... pero pronto comenzaremos a tener aproximadamente 1 millón de nuevos registros por día en esa colección ... así que no estoy seguro de si se escalará bien ...

EDITAR: ejemplo para una consulta:

> db.audit.find({''userAgent.deviceType'': ''MOBILE'', ''user.userName'': {$in: [''[email protected]'']}}).sort({timestamp: -1}).limit(25).explain() { "cursor" : "BtreeCursor user.userName_1_timestamp_-1", "isMultiKey" : false, "n" : 0, "nscannedObjects" : 30060, "nscanned" : 30060, "nscannedObjectsAllPlans" : 120241, "nscannedAllPlans" : 120241, "scanAndOrder" : false, "indexOnly" : false, "nYields" : 1, "nChunkSkips" : 0, "millis" : 26495, "indexBounds" : { "user.userName" : [ [ "[email protected]", "[email protected]" ] ], "timestamp" : [ [ { "$maxElement" : 1 }, { "$minElement" : 1 } ] ] }, "server" : "yarin:27017" }

tenga en cuenta que deviceType tiene solo 2 valores en mi colección.


Esto es buscar la aguja en un pajar. Necesitaríamos algún resultado de explain() para aquellas consultas que no funcionan bien. Desafortunadamente, incluso eso solucionaría el problema solo para esa consulta en particular, así que aquí hay una estrategia sobre cómo abordar esto:

  1. Asegúrate de que no sea por falta de memoria RAM y paginación excesiva
  2. Habilite el perfilador de base de datos (utilizando db.setProfilingLevel(1, timeout) donde el timeout es el umbral para la cantidad de milisegundos que tarda la consulta o el comando, cualquier cosa más lenta se registrará)
  3. Inspeccione las consultas lentas en db.system.profile y ejecute las consultas manualmente usando explain()
  4. Intente identificar las operaciones lentas en el resultado de explain() , como scanAndOrder o nscanned grande, etc.
  5. Motivo de la selectividad de la consulta y si es posible mejorar la consulta utilizando un índice. De lo contrario, considere no permitir la configuración del filtro para el usuario final o déle un cuadro de diálogo de advertencia que indique que la operación puede ser lenta.

Un problema clave es que aparentemente está permitiendo que sus usuarios combinen filtros a voluntad. Sin intersección de índices, eso hará explotar dramáticamente la cantidad de índices requeridos.

Además, lanzar ciegamente un índice en cada consulta posible es una estrategia muy mala. Es importante estructurar las consultas y asegurarse de que los campos indexados tengan suficiente selectividad .

Supongamos que tiene una consulta para todos los usuarios con status "activo" y algunos otros criterios. Pero de los 5 millones de usuarios, 3 millones están activos y 2 millones no, por lo que más de 5 millones de entradas solo tienen dos valores diferentes. Tal índice no suele ser útil. Es mejor buscar primero los otros criterios y luego escanear los resultados. En promedio, al devolver 100 documentos, deberá escanear 167 documentos, lo que no perjudicará demasiado el rendimiento. Pero no es tan simple. Si el criterio principal es la fecha joined_at del usuario y la probabilidad de que los usuarios suspendan el uso con el tiempo es alta, puede que tenga que escanear miles de documentos antes de encontrar cien coincidencias.

Por lo tanto, la optimización depende en gran medida de los datos (no solo su estructura , sino también los datos en sí ), sus correlaciones internas y sus patrones de consulta .

Las cosas empeoran cuando los datos son demasiado grandes para la RAM, porque entonces, tener un índice es excelente, pero escanear (o incluso simplemente devolver) los resultados podría requerir obtener una gran cantidad de datos del disco al azar, lo que lleva mucho tiempo.

La mejor manera de controlar esto es limitar el número de tipos de consultas diferentes, no permitir consultas con información de baja selectividad e intentar evitar el acceso aleatorio a datos antiguos.

Si todo lo demás falla y si realmente necesita tanta flexibilidad en los filtros, podría valer la pena considerar un DB de búsqueda independiente que admita intersecciones de índice, busque los id de mongo desde allí y luego obtenga los resultados de mongo usando $in . Pero eso está plagado de sus propios peligros.

- EDITAR -

La explicación que publicó es un bello ejemplo de un problema con el escaneo de campos de baja selectividad. Aparentemente, hay muchos documentos para "[email protected]". Ahora, encontrar esos documentos y ordenarlos por fecha y hora es bastante rápido, ya que es compatible con índices de alta selectividad. Desafortunadamente, dado que solo hay dos tipos de dispositivos, mongo necesita escanear 30060 documentos para encontrar el primero que coincida con ''móvil''.

Supongo que se trata de algún tipo de seguimiento web, y el patrón de uso del usuario hace que la consulta sea lenta (¿cambiaría el móvil y la web a diario, la consulta sería rápida).

Hacer esta consulta en particular más rápido podría hacerse utilizando un índice compuesto que contenga el tipo de dispositivo, por ejemplo, usando

a) ensureIndex({''username'': 1, ''userAgent.deviceType'' : 1, ''timestamp'' :-1})

o

b) ensureIndex({''userAgent.deviceType'' : 1, ''username'' : 1, ''timestamp'' :-1})

Desafortunadamente, eso significa que las consultas como find({"username" : "foo"}).sort({"timestamp" : -1}); Ya no puede usar el mismo índice , así que, como se describe, la cantidad de índices crecerá muy rápidamente.

Me temo que no hay una solución muy buena para esto usando mongodb en este momento.


Mongo solo usa 1 índice por consulta. Entonces, si quiere filtrar en 2 campos, mongo usará el índice con uno de los campos, pero aún necesita escanear todo el subconjunto.

Esto significa que, básicamente, necesitará un índice para cada tipo de consulta para lograr el mejor rendimiento.

Dependiendo de sus datos, puede que no sea una mala idea tener una consulta por campo y procesar los resultados en su aplicación. De esta manera, solo necesitará índices en cada campo, pero puede que haya demasiados datos para procesar.