Menu Docs
Página inicial do Docs
/
Manual do MongoDB
/ /

Otimização de aggregation pipeline

Nesta página

  • Otimização de projeção
  • Otimização de sequência de pipeline
  • Otimização de coalescência de pipeline
  • Otimizações de pipeline do mecanismo de execução de consulta com base em slot
  • Melhorar o desempenho com índices e filtros de documentos
  • Exemplo

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.

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.

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.

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.

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

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

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

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.

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

Se houver um estágio $skip entre os estágios $sort e $limit, o MongoDB agrupará o $limit no estágio $sort e aumentará o valor $limit pela quantidade de $skip. Consulte $sort + $skip + $limit Sequência para ver um exemplo.

[1] A otimização ainda será aplicada quando allowDiskUse estiver true e os itens n excederem o limite de memória de aggregation.

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 }

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 }

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

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.

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.

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 objeto queryPlanner estará no objeto de saída explain 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 objeto queryPlanner estará em explain.stages[0].$cursor.queryPlanner.

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 ou foreignField 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 objeto queryPlanner estará no objeto de saída explain 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 objeto queryPlanner estará em explain.stages[0].$cursor.queryPlanner.

As seções a seguir mostram como você pode melhorar o desempenho da aggregation usando índices e filtros de documento.

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 relevantes

  • Um í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ágio

Durante 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:

  • O pipeline sorts e groups pelo mesmo campo.

  • O estágio $group usa apenas o operador accumulator $first ou $last.

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:

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.

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.

Dica

Veja também:

Voltar

Pipeline de agregação