Las operaciones del pipeline de agregación tienen una fase de optimización que intenta remodelar el pipeline para mejorar el rendimiento.
Para ver cómo el optimizador transforma una canalización de agregación particular, incluya el explainOpción en el db.collection.aggregate() método.
Las optimizaciones están sujetas a cambios entre versiones.
Además de aprender sobre las optimizaciones de la canalización de agregación realizadas durante la fase de optimización, también verá cómo mejorar el rendimiento de la canalización de agregación utilizando índices y filtros de documentos.
Puedes ejecutar pipelines de agregación en la interfaz de usuario para implementaciones alojadas en MongoDB Atlas.
Optimización de proyección
El pipeline de agregación puede determinar si solo necesita un subconjunto de los campos en los documentos para obtener los resultados. Si es así, el pipeline solo utiliza esos campos, lo que reduce la cantidad de datos que pasan por el pipeline.
$project Puesta en escena
Cuando se usa una etapa $project, normalmente debería ser la última etapa del pipeline, utilizada para especificar qué campos devolver al cliente.
Usar una etapa $project al principio o en medio de un pipeline para reducir el número de campos que se pasan a las etapas posteriores del pipeline es poco probable que mejore el rendimiento, ya que la base de datos realiza esta optimización automáticamente.
Optimización de la secuencia del pipeline
($project $unset o $addFields o $set o) + $match Optimización de secuencia
Para una canalización de agregación que contiene una etapa de proyección ($addFields, $project, $set o $unset) seguida de una etapa $match, MongoDB mueve cualquier filtro en la etapa $match que no requiera valores calculados en la etapa de proyección a una nueva etapa $match antes de la proyección.
Si una canalización de agregación contiene múltiples etapas de $match o proyección, MongoDB realiza esta optimización para cada etapa $match, moviendo cada filtro $match antes de todas las etapas de proyección de las que el filtro no depende.
Considera un pipeline con las siguientes etapas:
{ $addFields: { maxTime: { $max: "$times" }, minTime: { $min: "$times" } } }, { $project: { _id: 1, name: 1, times: 1, maxTime: 1, minTime: 1, avgTime: { $avg: ["$maxTime", "$minTime"] } } }, { $match: { name: "Joe Schmoe", maxTime: { $lt: 20 }, minTime: { $gt: 5 }, avgTime: { $gt: 7 } } }
El optimizador descompone la etapa $match en cuatro filtros individuales, uno para cada clave en el documento de query $match. A continuación, el optimizador mueve cada filtro antes de tantas etapas de proyección como sea posible, creando nuevas etapas $match según sea necesario.
Dado este ejemplo, el optimizador produce automáticamente el siguiente pipeline optimizado:
{ $match: { name: "Joe Schmoe" } }, { $addFields: { maxTime: { $max: "$times" }, minTime: { $min: "$times" } } }, { $match: { maxTime: { $lt: 20 }, minTime: { $gt: 5 } } }, { $project: { _id: 1, name: 1, times: 1, maxTime: 1, minTime: 1, avgTime: { $avg: ["$maxTime", "$minTime"] } } }, { $match: { avgTime: { $gt: 7 } } }
Nota
El pipeline optimizado no está destinado a ejecutarse manualmente. Los pipelines originales y optimizados devuelven los mismos resultados.
Puedes ver el pipeline optimizado en el plan de ejecución.
El filtro $match { avgTime: { $gt: 7 } } depende de la etapa $project para calcular el campo avgTime. La etapa $project es la última etapa de proyección en este pipeline, por lo que el filtro $match en avgTime no se pudo mover.
Los campos maxTime y minTime se calculan en la etapa $addFields, pero no tienen dependencia de la etapa $project. El optimizador creó una nueva etapa $match para los filtros en estos campos y la colocó antes de la etapa $project.
El filtro $match { name: "Joe Schmoe" }no utiliza ningún valor calculado ni en la etapa $project ni en la etapa $addFields, por lo que se trasladó a una nueva etapa $match antes de ambas etapas de proyección.
Después de la optimización, el filtro { name: "Joe Schmoe" } está en una etapa $match al comienzo del pipeline. Esto tiene el beneficio adicional de permitir que la agregación use un índice en el campo name al realizar la consulta inicial de la colección.
$sort + $match Optimización de secuencia
Cuando tienes una secuencia con $sort seguida de una $match, el $match se mueve antes del $sort para minimizar el número de objetos a ordenar. Por ejemplo, si el pipeline consta de las siguientes etapas:
{ $sort: { age : -1 } }, { $match: { status: 'A' } }
Durante la fase de optimización, el optimizador transforma la secuencia a la siguiente:
{ $match: { status: 'A' } }, { $sort: { age : -1 } }
$redact + $match Optimización de secuencia
Cuando sea posible, cuando el pipeline tenga la etapa $redact inmediatamente seguida de la etapa $match, la agregación puede a veces añadir una parte de la etapa $match antes de la etapa $redact. Si la etapa $match añadida está al inicio de un pipeline, la agregación puede utilizar un índice y aplicar la query a la colección para limitar el número de documentos que ingresan en el pipeline. Consulta Mejorar el rendimiento con índices y filtros de documentos para obtener más información.
Por ejemplo, si el pipeline consta de las siguientes etapas:
{ $redact: { $cond: { if: { $eq: [ "$level", 5 ] }, then: "$$PRUNE", else: "$$DESCEND" } } }, { $match: { year: 2014, category: { $ne: "Z" } } }
El optimizador puede añadir la misma etapa $match antes de la etapa $redact:
{ $match: { year: 2014 } }, { $redact: { $cond: { if: { $eq: [ "$level", 5 ] }, then: "$$PRUNE", else: "$$DESCEND" } } }, { $match: { year: 2014, category: { $ne: "Z" } } }
$projectOptimización de secuencia /$unset $skip +
Cuando tengas una secuencia con $project o $unset seguida de $skip, el $skip se mueve antes de $project. Por ejemplo, si el pipeline consta de las siguientes etapas:
{ $sort: { age : -1 } }, { $project: { status: 1, name: 1 } }, { $skip: 5 }
Durante la fase de optimización, el optimizador transforma la secuencia a la siguiente:
{ $sort: { age : -1 } }, { $skip: 5 }, { $project: { status: 1, name: 1 } }
Optimización de la fusión de pipelines
Cuando sea posible, la fase de optimización fusiona una etapa del pipeline con su predecesora. Generalmente, la fusión ocurre después de cualquier optimización de reordenamiento de secuencias.
$sort + $limit Coalescencia
Cuando una $sort precede a una $limit, el optimizador puede fusionar la $limit en la $sort si ninguna etapa intermedia modifica el número de documentos (por ejemplo, $unwind, $group). MongoDB no fusionará la $limit en la $sort si hay etapas de pipeline que cambian el número de documentos entre las etapas $sort y $limit.
Por ejemplo, si el pipeline consta de las siguientes etapas:
{ $sort : { age : -1 } }, { $project : { age : 1, status : 1, name : 1 } }, { $limit: 5 }
Durante la fase de optimización, el optimizador fusiona la secuencia en lo siguiente:
{ "$sort" : { "sortKey" : { "age" : -1 }, "limit" : Long(5) } }, { "$project" : { "age" : 1, "status" : 1, "name" : 1 } }
Esto permite que la operación de clasificación solo mantenga los n mejores resultados a medida que avanza, donde n es el límite especificado, y MongoDB solo necesita almacenar n elementos en memoria [1]. Consulte $sort Operador y memoria para obtener más información.
Nota
Optimización de secuencias con $skip
| [1] | La optimización sigue aplicándose cuando allowDiskUse es true y los elementos de n superan el límite de memoria de agregación. |
$limit + $limit Coalescencia
Cuando una $limit sigue inmediatamente a otra $limit, las dos etapas pueden fusionarse en una sola $limit donde el monto límite es el menor de los dos montos límite iniciales. Por ejemplo, un pipeline contiene la siguiente secuencia:
{ $limit: 100 }, { $limit: 10 }
Entonces, la segunda etapa $limit puede fusionarse con la primera etapa $limit y resultar en una única etapa $limit, donde la cantidad límite 10 es el mínimo de los dos límites iniciales 100 y 10.
{ $limit: 10 }
$skip + $skip Coalescencia
Cuando una $skip sigue inmediatamente a otra $skip, las dos etapas pueden fusionarse en una sola $skip donde la cantidad de omisión es la suma de las dos cantidades iniciales. Por ejemplo, un pipeline contiene la siguiente secuencia:
{ $skip: 5 }, { $skip: 2 }
Entonces, la segunda $skip etapa puede fusionarse con la primera $skip etapa y resultar en una sola $skip etapa donde la cantidad de salto 7 es la suma de los dos límites iniciales 5 y 2.
{ $skip: 7 }
$match + $match Coalescencia
Cuando una $match sigue inmediatamente a otra $match, las dos etapas pueden fusionarse en una sola $match combinando las condiciones con un $and. Por ejemplo, un pipeline contiene la siguiente secuencia:
{ $match: { year: 2014 } }, { $match: { status: "A" } }
Entonces, la segunda etapa $match puede fusionarse con la primera etapa $match y dar lugar a una única $match etapa
{ $match: { $and: [ { "year" : 2014 }, { "status" : "A" } ] } }
$lookup, $unwind, y $match Coalescencia
Cuando $unwind sigue inmediatamente a $lookup, y el $unwind opera en el campo as del $lookup, el optimizador fusiona el $unwind en la etapa $lookup. Esto evita la creación de grandes documentos intermedios. Además, si $unwind es seguido por un $match en cualquier subcampo as del $lookup, el optimizador también unifica el $match.
Por ejemplo, un pipeline contiene la siguiente secuencia:
{ $lookup: { from: "otherCollection", as: "resultingArray", localField: "x", foreignField: "y" } }, { $unwind: "$resultingArray" }, { $match: { "resultingArray.foo": "bar" } }
El optimizador fusiona las etapas $unwind y $match en la etapa $lookup. Si ejecutas la agregación con la opción explain, la salida de explain muestra las etapas coalescidas:
{ $lookup: { from: "otherCollection", as: "resultingArray", localField: "x", foreignField: "y", let: {}, pipeline: [ { $match: { "foo": { "$eq": "bar" } } } ], unwinding: { "preserveNullAndEmptyArrays": false } } }
Puedes ver este pipeline optimizado en el plan de explicación.
El campo unwinding mostrado en el resultado explain anterior difiere de la etapa $unwind. El campo unwinding muestra cómo se optimiza internamente el pipeline. La etapa $unwind descompone un campo de arreglo de los documentos de entrada y produce un documento para cada elemento.
Optimizaciones del pipeline del motor de ejecución de queries basado en ranuras
MongoDB puede usar el motor de ejecución de query basado en ranuras para ejecutar ciertas etapas del pipeline cuando se cumplen condiciones específicas. En la mayoría de los casos, el motor de ejecución basado en ranuras ofrece un mejor rendimiento y menores costos de CPU y memoria en comparación con el motor de query clásico.
Para verificar que se utiliza el motor de ejecución basado en ranuras, se debe ejecutar la agregación con la opción explain. Esta opción genera información sobre el plan del query de la agregación. Para obtener más información sobre el uso de explain con agregaciones, consulte Información sobre la operación de pipelines de agregación.
Las siguientes secciones describen:
Las condiciones en las que se utiliza el motor de ejecución basado en ranuras para la agregación.
Cómo verificar si se utilizó el motor de ejecución basado en ranuras.
$group Optimización
Nuevo en la versión 5.2.
A partir de la versión 5.2, MongoDB usa el motor de ejecución de query basado en ranuras para ejecutar $group etapas si se cumple alguna de las siguientes condiciones:
$groupes la primera etapa en el pipeline.Todas las etapas anteriores en la pipeline también pueden ejecutarse mediante el motor de ejecución basado en ranuras.
Cuando se usa el motor de ejecución de query basado en ranuras para $group, los resultados de la explicación incluyen queryPlanner.winningPlan.queryPlan.stage:
"GROUP".
La ubicación del objeto queryPlanner depende de si el pipeline contiene etapas posteriores a la etapa $group que no pueden ejecutarse usando el motor de ejecución basado en ranuras.
Si
$groupes la última etapa o todas las etapas posteriores a$grouppueden ejecutarse usando el motor de ejecución basado en ranuras, el objetoqueryPlannerestá en el objeto de salidaexplainde nivel superior (explain.queryPlanner).Si el pipeline contiene etapas posteriores a
$groupque no pueden ejecutarse usando el motor de ejecución basado en ranuras, el objetoqueryPlannerestá enexplain.stages[0].$cursor.queryPlanner.
$lookup Optimización
Novedades en la versión 6.0.
A partir de la versión 6.0, MongoDB puede utilizar el motor de ejecución de query basado en ranuras para ejecutar las etapas $lookup si todas las etapas anteriores del pipeline también pueden ejecutarse con el motor de ejecución basado en ranuras y ninguna de las siguientes condiciones se cumple:
La operación
$lookupejecuta un pipeline en una colección externa. Para ver un ejemplo de este tipo de operación, consulta Condiciones de unión y sub-query en una colección externa.Los
localFieldoforeignFieldde$lookupespecifican componentes numéricos. Por ejemplo:{ localField: "restaurant.0.review" }.El campo
fromde cualquier$lookupen el pipeline especifica una vista o una colección fragmentada.
Cuando se usa el motor de ejecución de query basado en ranuras para $lookup, los resultados de la explicación incluyen queryPlanner.winningPlan.queryPlan.stage: "EQ_LOOKUP". EQ_LOOKUP significa “búsqueda de igualdad”.
La ubicación del objeto queryPlanner depende de si el pipeline contiene etapas posteriores a la etapa $lookup que no pueden ejecutarse usando el motor de ejecución basado en ranuras.
Si
$lookupes la última etapa o todas las etapas posteriores a$lookuppueden ejecutarse usando el motor de ejecución basado en ranuras, el objetoqueryPlannerestá en el objeto de salidaexplainde nivel superior (explain.queryPlanner).Si el pipeline contiene etapas posteriores a
$lookupque no pueden ejecutarse usando el motor de ejecución basado en ranuras, el objetoqueryPlannerestá enexplain.stages[0].$cursor.queryPlanner.
Mejore el rendimiento con índices y filtros de documentos
Las siguientes secciones muestran cómo puede mejorar el rendimiento de la agregación utilizando índices y filtros de documentos.
Indexes
Un pipeline de agregación puede utilizar índices de la colección de entrada para mejorar el rendimiento. El uso de un índice limita la cantidad de documentos que una etapa procesa. Idealmente, un índice puede cubrir la query de etapa. Una query cubierta tiene un rendimiento especialmente alto, ya que el índice devuelve todos los documentos coincidentes.
Por ejemplo, un pipeline que consta de $match, $sort, $group puede beneficiarse de índices en cada etapa:
Un índice en el campo de query
$matchidentifica eficientemente los datos relevantesUn índice en el campo de ordenación devuelve los datos en orden ascendente para la etapa
$sort.Un índice en el campo de agrupación que coincide con el orden de
$sortdevuelve todos los valores de campo necesarios para la etapa$group, convirtiéndola en una query cubierta.
Para determinar si un pipeline utiliza índices, se debe revisar el plan del query y buscar los planes IXSCAN o DISTINCT_SCAN.
Nota
En algunos casos, el planificador de query usa un plan de índice DISTINCT_SCAN que devuelve un documento por cada valor de clave de índice. DISTINCT_SCAN se ejecuta más rápido que IXSCAN si hay múltiples documentos por cada valor de clave. Sin embargo, los parámetros de escaneo de índices podrían afectar la comparación de tiempo de DISTINCT_SCAN y IXSCAN.
Para las primeras etapas del pipeline de agregación, se debe considerar la indexación de los campos de query. Las etapas que pueden beneficiarse de los índices son las siguientes:
$matchetapa- Durante la etapa
$match, el servidor puede utilizar un índice si$matches la primera etapa en el pipeline, después de cualquier optimización del planificador de queries. $sortetapa- Durante la etapa
$sort, el servidor puede utilizar un índice si la etapa no está precedida por una etapa de$project,$unwindo$group. $groupetapaDurante la etapa
$group, el servidor puede usar un índice para encontrar rápidamente el documento$firsto el documento$lasten cada grupo si la etapa cumple con ambas condiciones:Consultar Optimizaciones de rendimiento de $group para ver un ejemplo.
$geoNearetapa- El servidor siempre usa un índice para la etapa
$geoNear, ya que requiere un índice geoespacial.
Además, las etapas posteriores en el pipeline que recuperan datos de otras colecciones no modificadas pueden utilizar índices en esas colecciones para optimización. Estas etapas incluyen lo siguiente:
Filtros de Documentos
Si la operación de agregación requiere solo un subconjunto de los documentos en una colección, se deben filtrar primero los documentos:
Se deben usar las etapas
$match,$limity$skippara restringir los documentos que ingresan al pipeline.Cuando sea posible, se debe colocar
$matchal inicio del pipeline para utilizar índices que escaneen los documentos coincidentes en una colección.$matchseguido de$sortal inicio del pipeline equivale a una sola query con ordenación, y puede utilizar un índice.
Ejemplo
$sort + $skip + $limit Secuencia
Un pipeline contiene una secuencia de $sort seguido de un $skip seguido de un $limit:
{ $sort: { age : -1 } }, { $skip: 10 }, { $limit: 5 }
El optimizador lleva a cabo $sort + $limit Coalescencia para transformar la secuencia en lo siguiente:
{ "$sort" : { "sortKey" : { "age" : -1 }, "limit" : Long(15) } }, { "$skip" : Long(10) }
MongoDB aumenta la $limit cantidad con el reordenamiento.
Tip
explain opción en el
db.collection.aggregate()