Otimização de aggregation pipeline
Nesta página
As operações de aggregation pipeline têm uma fase de otimização que tenta remodelar o pipeline para melhorar o desempenho.
Para ver como o otimizador transforma um pipeline de agregação específico, inclua a opção explain
no método db.collection.aggregate()
.
As otimizações estão sujeitas a alterações entre as versões.
Além de aprender sobre as otimizações do pipeline de agregação realizadas durante a fase de otimização, você também verá como melhorar o desempenho do pipeline de agregação usando indexações e filtros de documentos.
Você pode executar pipelines de agregação na IU para sistemas hospedados no MongoDB Atlas.
Otimização de projeção
O aggregation pipeline pode determinar se requer apenas um subconjunto dos campos nos documentos para obter os resultados. Nesse caso, o pipeline usa apenas esses campos, reduzindo a quantidade de dados que passam pelo pipeline.
$project
Colocação no palco
Quando você usa um estágio $project
, ele normalmente deve ser o último estágio do seu pipeline, usado para especificar quais campos devem ser retornados ao cliente.
É improvável que o uso de um estágio $project
no início ou no meio de um pipeline para reduzir o número de campos passados para estágios subsequentes melhore o desempenho, pois o banco de dados executa essa otimização automaticamente.
Otimização de sequência de pipeline
($project
ou $unset
$addFields
ou $set
ou) + $match
Otimização de sequência
Para um pipeline de agregação que contém um estágio de projeção ($addFields
, $project
, $set
, ou $unset
) seguido por um estágio $match
, o MongoDB move os filtros no estágio $match
que não exijam valores calculados no estágio de projeção para um novo estágio $match
antes da projeção.
Se um pipeline de agregação contiver vários estágios de projeção ou $match
, o MongoDB executará essa otimização para cada estágio do $match
, movendo cada filtro do $match
antes de todos os estágios de projeção dos quais o filtro não depende.
Considere um pipeline com os seguintes estágios:
{ $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 } } }
O otimizador divide o estágio $match
em quatro filtros individuais, um para cada chave no documento de consulta $match
. Em seguida, o otimizador move cada filtro antes do maior número possível de estágios de projeção, criando novos estágios $match
conforme necessário.
Dado este exemplo, o otimizador produz automaticamente o seguinte pipeline otimizado :
{ $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 } } }
Observação
O pipeline otimizado não é projetado para execução manual. Os pipelines originais e otimizados retornam resultados idênticos.
Você pode ver o pipeline otimizado no explain plan.
O filtro $match
{ avgTime: { $gt: 7 } }
depende do estágio $project
para computar o campo avgTime
. O estágio $project
é o último estágio de projeção nesse pipeline, portanto, o filtro $match
em avgTime
não pôde ser movido.
Os campos maxTime
e minTime
são computados no estágio $addFields
, mas não dependem do estágio $project
. O otimizador criou um novo estágio de$match
para os filtros nesses campos e o colocou antes do estágio $project
.
O filtro $match
{ name: "Joe Schmoe" }
não usa nenhum valor calculado nos estágios $project
ou $addFields
, então ele foi movido para um novo estágio $match
antes dos dois estágios de projeção.
Após a otimização, o filtro { name: "Joe Schmoe" }
está em um estágio $match
no início do pipeline. Isso gera o benefício adicional de permitir que a agregação use um índice no campo name
ao consultar inicialmente a coleção.
$sort
+ $match
Otimização de sequência
Quando você tem uma sequência com $sort
seguida por $match
, o $match
se move antes do $sort
para minimizar o número de objetos para classificar. Por exemplo, se o pipeline consistir nos seguintes estágios:
{ $sort: { age : -1 } }, { $match: { status: 'A' } }
Durante a fase de otimização, o otimizador transforma a sequência no seguinte:
{ $match: { status: 'A' } }, { $sort: { age : -1 } }
$redact
+ $match
Otimização de sequência
Quando possível, se o pipeline está no estágio $redact
imediatamente seguido pelo estágio $match
, a agregação pode, às vezes, adicionar uma parte do estágio $match
antes do estágio $redact
. Se o estágio $match
adicionado estiver no início de um pipeline, a agregação pode usar um índice e consultar a coleção para limitar o número de documentos que entram no pipeline. Para obter mais informações, consulte Melhorar o desempenho com índices e filtros de documentos.
Por exemplo, se o pipeline consistir nas seguintes etapas:
{ $redact: { $cond: { if: { $eq: [ "$level", 5 ] }, then: "$$PRUNE", else: "$$DESCEND" } } }, { $match: { year: 2014, category: { $ne: "Z" } } }
O otimizador pode adicionar o mesmo estágio $match
antes do estágio $redact
:
{ $match: { year: 2014 } }, { $redact: { $cond: { if: { $eq: [ "$level", 5 ] }, then: "$$PRUNE", else: "$$DESCEND" } } }, { $match: { year: 2014, category: { $ne: "Z" } } }
$project
/$unset
+ $skip
otimização de sequência
Quando você tem uma sequência com $project
ou $unset
seguida por $skip
, o $skip
se move antes de $project
. Por exemplo, se o pipeline consistir nas seguintes etapas:
{ $sort: { age : -1 } }, { $project: { status: 1, name: 1 } }, { $skip: 5 }
Durante a fase de otimização, o otimizador transforma a sequência no seguinte:
{ $sort: { age : -1 } }, { $skip: 5 }, { $project: { status: 1, name: 1 } }
Otimização de coalescência de pipeline
Quando possível, a fase de otimização aglutina um estágio do pipeline em seu antecessor. Em geral, a coalescência ocorre após qualquer otimização de reordenação de sequência.
$sort
+ $limit
coalescência
Quando um $sort
precede um $limit
, o otimizador pode coalescer o $limit
no $sort
se nenhuma etapa de intervenção modificar o número de documentos (por exemplo, $unwind
, $group
). O MongoDB não agrupará o $limit
no $sort
se houver estágios de pipeline que alterem o número de documentos entre os estágios $sort
e $limit
.
Por exemplo, se o pipeline consistir nas seguintes etapas:
{ $sort : { age : -1 } }, { $project : { age : 1, status : 1, name : 1 } }, { $limit: 5 }
Durante a fase de otimização, o otimizador agrupa a sequência da seguinte forma:
{ "$sort" : { "sortKey" : { "age" : -1 }, "limit" : NumberLong(5) } }, { "$project" : { "age" : 1, "status" : 1, "name" : 1 } }
Isso permite que a operação de classificação mantenha apenas os n
principais resultados à medida que avança, em que n
é o limite especificado, e o MongoDB só precisa armazenar itens n
na memória [1]. Consulte $sort
Operador e memória para obter mais informações.
Observação
Otimização de sequência com $skip
[1] | A otimização ainda será aplicada quando allowDiskUse estiver true e os itens n excederem o limite de memória de aggregation. |
$limit
+ $limit
coalescência
Quando um $limit
imediatamente segue outro $limit
, os dois estágios podem se aglutinar em um único $limit
, onde a quantidade limite é a menor das duas quantidades limites iniciais. Por exemplo, um pipeline contém a seguinte sequência:
{ $limit: 100 }, { $limit: 10 }
Então, o segundo estágio $limit
pode se aglutinar com o primeiro estágio $limit
e resultar em um único estágio $limit
, em que a quantidade limite 10
é a menor dos dois limites iniciais 100
e 10
.
{ $limit: 10 }
$skip
+ $skip
coalescência
Quando um $skip
segue imediatamente outro $skip
, os dois estágios podem se fundir em um único $skip
, em que o valor do salto é a soma dos dois valores do salto inicial. Por exemplo, um pipeline contém a seguinte sequência:
{ $skip: 5 }, { $skip: 2 }
Em seguida, o segundo estágio $skip
pode se aglutinar com o primeiro estágio $skip
e resultar em um único estágio $skip
, em que a quantidade 7
é a soma dos dois limites iniciais 5
e 2
.
{ $skip: 7 }
$match
+ $match
coalescência
Quando um $match
segue imediatamente outro $match
, os dois estágios podem se aglutinar em um único $match
combinando as condições com um $and
. Por exemplo, um pipeline contém a seguinte sequência:
{ $match: { year: 2014 } }, { $match: { status: "A" } }
Em seguida, o segundo estágio $match
pode se fundir com o primeiro estágio $match
e resultar em um único estágio $match
{ $match: { $and: [ { "year" : 2014 }, { "status" : "A" } ] } }
$lookup
, $unwind
e $match
coalescência
Quando $unwind
imediatamente segue $lookup
e o $unwind
opera no campo as
do $lookup
, o otimizador aglutina o $unwind
no estágio $lookup
. Isto evita a criação de grandes documentos intermediários. Além disso, se $unwind
for seguido por um $match
em qualquer subcampo as
do $lookup
, o otimizador também aglutinará o $match
.
Por exemplo, um pipeline contém a seguinte sequência:
{ $lookup: { from: "otherCollection", as: "resultingArray", localField: "x", foreignField: "y" } }, { $unwind: "$resultingArray" }, { $match: { "resultingArray.foo": "bar" } }
O otimizador agrupa os estágios $unwind
e $match
no estágio $lookup
. Se você executar a agregação com a opção explain
, a saída explain
mostrará os estágios aglutinados:
{ $lookup: { from: "otherCollection", as: "resultingArray", localField: "x", foreignField: "y", let: {}, pipeline: [ { $match: { "foo": { "$eq": "bar" } } } ], unwinding: { "preserveNullAndEmptyArrays": false } } }
Você pode ver o pipeline otimizado no plano de explicação.
Otimizações de pipeline do mecanismo de execução de consulta com base em slot
O MongoDB pode usar o slot-based query execution engine para executar determinados estágios do pipeline quando condições específicas forem atendidas. Na maioria dos casos, o mecanismo de execução baseado em slot fornece desempenho aprimorado e custos de CPU e memória mais baixos em comparação com o mecanismo de consulta clássico.
Para verificar se o mecanismo de execução baseado em slots é usado, execute a aggregation com a opção explain
. Esta opção fornece informações sobre o plano de query da aggregation. Para obter mais informações sobre como usar explain
com aggregations, consulte Retornar informações sobre a operação do aggregation pipeline.
As seções a seguir descrevem:
As condições quando o mecanismo de execução baseado em slot é usado para agregação.
Como verificar se o mecanismo de execução baseado em slot foi usado.
$group
Otimização
Novidades na versão 5.2.
A partir da versão 5.2, o MongoDB usa o mecanismo de query de execução baseado em slots para executar estágios $group
se:
$group
é o primeiro estágio do pipeline.Todos os estágios anteriores do pipeline também podem ser executados pelo mecanismo de execução baseado em slot.
Quando o mecanismo de execução de query baseado em slots é usado para $group
, os resultados da explicação incluem queryPlanner.winningPlan.queryPlan.stage:
"GROUP"
.
O local do objeto queryPlanner
depende do fato de o pipeline conter estágios após o estágio $group
que não podem ser executados usando o mecanismo de execução baseado em slots.
Se
$group
for o último estágio ou se todos os estágios após$group
puderem ser executados usando o mecanismo de execução baseado em slots, o objetoqueryPlanner
estará no objeto de saídaexplain
de nível superior (explain.queryPlanner
).Se o pipeline contiver estágios após
$group
que não podem ser executados usando o mecanismo de execução baseado em slots, o objetoqueryPlanner
estará emexplain.stages[0].$cursor.queryPlanner
.
$lookup
Otimização
Novidades na versão 6.0.
A partir da versão 6.0, o MongoDB pode usar o mecanismo de query de execução baseado em slots para executar os estágios $lookup
se todos os estágios anteriores no pipeline também puderem ser executados pelo mecanismo de execução baseado em slots e nenhuma das seguintes condições for verdadeira:
A operação
$lookup
executa um pipeline em uma collection unida. Para ver um exemplo desse tipo de operação, consulte Condições de união e subqueries em uma collection unida.Os
localField
ouforeignField
de$lookup
especificam componentes numéricos. Por exemplo:{ localField: "restaurant.0.review" }
.O campo
from
de qualquer$lookup
no pipeline especifica uma visualização ou coleção fragmentada.
Quando o mecanismo de execução de query baseado em slots é usado para $lookup
, os resultados da explicação incluem queryPlanner.winningPlan.queryPlan.stage: "EQ_LOOKUP"
. EQ_LOOKUP
significa "pesquisa de igualdade".
O local do objeto queryPlanner
depende do fato de o pipeline conter estágios após o estágio $lookup
que não podem ser executados usando o mecanismo de execução baseado em slots.
Se
$lookup
for o último estágio ou se todos os estágios após$lookup
puderem ser executados usando o mecanismo de execução baseado em slots, o objetoqueryPlanner
estará no objeto de saídaexplain
de nível superior (explain.queryPlanner
).Se o pipeline contiver estágios após
$lookup
que não podem ser executados usando o mecanismo de execução baseado em slots, o objetoqueryPlanner
estará emexplain.stages[0].$cursor.queryPlanner
.
Melhorar o desempenho com índices e filtros de documentos
As seções a seguir mostram como você pode melhorar o desempenho da aggregation usando índices e filtros de documento.
Indexes
Um aggregation pipeline pode usar índices da collection de entrada para melhorar o desempenho. O uso de um índice limita a quantidade de documentos que um estágio processa. Idealmente, um índice pode cobrir a query de estágio. Uma query coberta tem desempenho especialmente alto, pois o índice retorna todos os documentos correspondentes.
Por exemplo, um pipeline que consiste em $match
, $sort
, $group
pode se beneficiar de índices em cada etapa:
Um índice no campo de query
$match
identifica eficientemente os dados relevantesUm índice no campo de classificação retorna dados em ordem de classificação para o estágio
$sort
Um índice no campo de agrupamento que corresponda à ordem
$sort
retorna todos os valores de campo necessários para o estágio$group
, tornando-a uma query coberta.
Para determinar se um pipeline usa índices, revise o plano de query e procure planos IXSCAN
ou DISTINCT_SCAN
.
Observação
Em alguns casos, o planejador de query usa um plano de índice DISTINCT_SCAN
que retorna um documento por valor-chave de índice. O DISTINCT_SCAN
executa mais rápido do que IXSCAN
se houver vários documentos por valor-chave. No entanto, os parâmetros de verificação do índice podem afetar a comparação de tempo de DISTINCT_SCAN
e IXSCAN
.
Para estágios iniciais do aggregation pipeline, considere indexar os campos de query. Os estágios que podem se beneficiar dos índices são:
$match
estágio- Durante o estágio
$match
, o servidor pode usar um índice se$match
for o primeiro estágio no pipeline, após qualquer otimização do planejador de queries. $sort
estágio- Durante o estágio
$sort
, o servidor pode utilizar um índice se o estágio não for precedido por um estágio$project
,$unwind
ou$group
. $group
estágioDurante o estágio
$group
, o servidor pode usar um índice para localizar rapidamente o documento$first
ou$last
em cada grupo se o estágio atender a ambas as condições:Consulte $group otimizações de desempenho para um exemplo.
$geoNear
estágio- O servidor sempre utiliza um índice para o estágio
$geoNear
, pois exige um índice geoespacial.
Além disso, os estágios posteriores do pipeline que recuperam dados de outras collections não modificadas podem usar índices nessas collections para otimização. Esses estágios incluem:
Filtros do documento
Se a operação de aggregation exigir apenas um subconjunto dos documentos em uma collection, filtre os documentos primeiro:
Use os estágios
$match
,$limit
e$skip
para restringir os documentos que entram no pipeline.Quando possível, coloque
$match
no início do pipeline para usar índices que verificam os documentos correspondentes em uma collection.$match
seguido de$sort
no início do pipeline é equivalente a uma única query com uma classificação e pode usar um índice.
Exemplo
$sort
+ $skip
+ $limit
sequência
Um pipeline contém uma sequência de $sort
seguido por um $skip
seguido por um $limit
:
{ $sort: { age : -1 } }, { $skip: 10 }, { $limit: 5 }
O otimizador executa $sort
+ $limit
Coalescência para transformar a sequência no seguinte:
{ "$sort" : { "sortKey" : { "age" : -1 }, "limit" : NumberLong(15) } }, { "$skip" : NumberLong(10) }
O MongoDB aumenta o valor de $limit
com a reordenação.