MongoDB · Wiki de Search

MongoDB Atlas Search

Do básico ao avançado: arquitetura, índices e analisadores, consultas, relevância, facets, cenários reais, análise de performance, monitoramento e troubleshooting. Cada seção traz exemplos de mongosh e links para a documentação oficial da MongoDB. O produto foi renomeado para MongoDB Search; "Atlas Search" continua sendo o nome mais usado.

31 seções
mongosh + docs oficiais
Atualizado em outubro/2026

Partes

ParteConteúdo
Fundamentos e arquiteturaO que é o MongoDB Search (antigo Atlas Search), como ele funciona por baixo, onde roda e quando vale mais do que $text ou $regex.
Índices e analisadoresComo definir, criar e acompanhar índices de busca, e como o texto é transformado em termos pesquisáveis.
ConsultasDo text básico ao compound com filtros, autocomplete e fuzzy, sempre com exemplos executáveis no mongosh.
Relevância e recursos de resultadoScore, facets, highlight, ordenação, paginação e sinônimos: o que transforma uma lista de resultados em uma boa experiência de busca.
Cenários reaisReceitas prontas para os casos mais comuns, com índice e consulta lado a lado.
PerformanceComo projetar índices e consultas rápidas, medir com explain e scripts e dimensionar os nós de busca.
Operação e troubleshootingMétricas, alertas e uma tabela de sintomas para diagnosticar problemas de índice, resultado e desempenho.
ReferênciasLinks oficiais da MongoDB usados nesta wiki.
Antes de copiar um comandoBancos, coleções, índices e valores dos exemplos são fictícios: troque pelos do seu ambiente. Limites e disponibilidade por versão mudam; confirme na documentação oficial correspondente ao seu cluster.

Fundamentos e arquitetura

O que é o MongoDB Search (antigo Atlas Search), como ele funciona por baixo, onde roda e quando vale mais do que $text ou $regex.

mongotLuceneBM25eventual consistency

O que é e como funciona

O MongoDB Search (conhecido como Atlas Search) é a busca full-text integrada ao MongoDB, baseada em Apache Lucene. Você mantém os dados no MongoDB e cria índices de busca (separados dos índices B-tree) que são consultados pelo estágio de aggregation $search, sem ETL para Elasticsearch/OpenSearch e sem manter dois bancos sincronizados.

PeçaPapel
mongodBanco de dados. Recebe a aggregation, delega o $search ao mongot e busca os documentos completos pelo _id.
mongotProcesso de busca (Lucene). Lê o change stream da coleção, mantém o índice Lucene e responde às consultas.
Índice de buscaEstrutura invertida (termos → documentos) definida por um JSON de mappings, analisadores e opções.
$search / $searchMetaEstágios de aggregation: o primeiro devolve documentos ranqueados; o segundo devolve só metadados (contagens, facets).
$vectorSearchBusca semântica por similaridade de vetores, no mesmo mecanismo (veja a seção de busca híbrida e vetorial).
Consistência eventualO índice é atualizado de forma assíncrona pelo change stream. Uma escrita pode levar instantes para aparecer na busca: read-after-write não é garantido. Não use $search onde precisa enxergar imediatamente o que acabou de gravar; para isso, consulte pelo _id ou por índices normais.

Documentação oficialVisão geral do MongoDB Search · Tutorial (quick start) · Como a busca funciona

Search × $text × $regex

Antes de adotar, compare com o que o MongoDB já oferece sem mongot:

Critério$search$text$regex
RelevânciaBM25 por padrão, com boost, constant e funções de scoreScore simples (textScore)Sem relevância
Tolerância a erro de digitação (fuzzy)Sim (fuzzy, até 2 edições)NãoNão
Autocomplete / prefixoSim (tipo autocomplete)NãoApenas prefixo ancorado usa índice
SinônimosSimNãoNão
Facets e contagensSim (facet, $searchMeta)Não (usar $group)Não
Highlight dos termosSimNãoNão
IdiomasDezenas de analisadores (lucene.portuguese, etc.)Alguns idiomasNão se aplica
Índice parcialVia view (8.0+)NãoNão
Custo operacionalProcesso mongot + índice extra no disco/RAMÍndice B-tree comumVarredura da coleção
Quando não usarPara igualdade, intervalos e ordenação simples por campo, os índices normais continuam melhores e mais baratos. Use busca full-text quando o usuário digita texto livre e espera relevância.

Documentação oficialDesign patterns · FAQ · Text Search ($text)

Implantação: acoplada × nós de busca dedicados

O mongot pode rodar no mesmo nó do mongod (modelo acoplado) ou em Search Nodes dedicados, escalados independentemente do banco.

Acoplada (coupled)Search Nodes dedicados
Onde rodaMesmo nó do mongodNós próprios, mínimo de 2 (até 32), cobrados por hora
Uso indicadoTestes, protótipos, cargas pequenasProdução
DisponibilidadeFree, Flex e M10+M10+ (tiers S20 em diante)
IsolamentoBusca e banco disputam CPU, RAM e discoBusca isolada: o banco não sofre com queries pesadas
EscalaEscalar o cluster inteiroEscala só a busca (horizontal e vertical)
Regra prática para ficar acopladoFique no modelo acoplado apenas se tiver menos de ~2 milhões de documentos, menos de ~10 GB indexados e menos de ~10 mil consultas em 7 dias. Acima disso, migre para nós dedicados antes de ir para produção.
RAM decide a performanceO ideal é o índice caber na memória do nó de busca (cache do sistema de arquivos + heap da JVM). Reserve também ~20% de folga de disco para rebuilds. No modo acoplado, o cache do WiredTiger (50% da RAM em M40+, 25% em M30 ou menor) compete com o mongot.

Documentação oficialDeployment options · Compatibilidade de recursos

Índices e analisadores

Como definir, criar e acompanhar índices de busca, e como o texto é transformado em termos pesquisáveis.

mappingsdynamicanalyzerstatus

Criar o primeiro índice

Índices são criados na interface do Atlas (aba Search & Vector Search), pela Atlas CLI/Admin API ou pelo mongosh. Sem nome, o índice se chama default, que é o usado quando o $search não informa index.

mongosh
// Índice mais simples: indexa todos os campos dinamicamente
db.produtos.createSearchIndex("default", { mappings: { dynamic: true } })

// Listar índices e estado
db.produtos.getSearchIndexes().forEach(i =>
  print(i.name, "| status:", i.status, "| queryable:", i.queryable))

O índice passa por Pending → Building (initial sync) → Ready. Só consulte quando queryable: true.

StatusSignificadoAção
Pending / BuildingCriação ou initial sync em andamentoAguardar; acompanhar na aba de métricas
Ready / ActiveConsultável e sincronizadoNenhuma
StaleÍndice atrasado ou parado (ex.: documento ≥ 16 MB, oplog estourado, disco cheio)Ver Troubleshooting; pode exigir rebuild
FailedFalha ao construir (definição inválida, recursos)Corrigir a definição ou os recursos e recriar
Does not Exist / DeletingNome inexistente ou em remoçãoConferir o nome usado em index

Documentação oficialGerenciar índices · Definições de índice

Mappings: dinâmico × estático

dynamic: true indexa tudo e é ótimo para explorar. Em produção, prefira mapeamento estático: indexa só o que será pesquisado, gera índice menor, rebuilds mais rápidos e comportamento previsível.

JSON · índice estático para a coleção produtos
{
  "mappings": {
    "dynamic": false,
    "fields": {
      "nome": [
        { "type": "string", "analyzer": "lucene.portuguese" },
        { "type": "autocomplete", "tokenization": "edgeGram",
          "minGrams": 2, "maxGrams": 15, "foldDiacritics": true }
      ],
      "descricao": { "type": "string", "analyzer": "lucene.portuguese" },
      "categoria": { "type": "token" },
      "marca":     { "type": "token" },
      "preco":     { "type": "number" },
      "criadoEm":  { "type": "date" },
      "emEstoque": { "type": "boolean" }
    }
  }
}
Tipo de campoPara que serve
stringTexto analisado (tokenizado): text, phrase, wildcard, regex, highlight
tokenValor inteiro, sem tokenizar: equals, facets, ordenação por string
autocompleteTokens de prefixo (edgeGram/nGram) para busca ao digitar
number / daterange, near, facets numéricos/data e ordenação
booleanequals em verdadeiro/falso
geogeoWithin, geoShape, near por distância
document / embeddedDocumentsObjetos aninhados e arrays de objetos (embeddedDocument)
Um campo, vários tiposO mesmo caminho pode ter uma lista de definições (como nome acima): ele é indexado como string e também como autocomplete. Para o autocomplete funcionar de forma completa, indexe também o campo como string.

Documentação oficialTipo string · Tipo autocomplete · Referência de definição

Analisadores (analyzers)

O analisador define como o texto vira termos na hora de indexar (analyzer) e na hora de consultar (searchAnalyzer). Um mesmo analisador em ambos os lados é o caso comum; divergências são fonte clássica de "não acha o que deveria".

AnalisadorComportamento
lucene.standard (padrão)Divide por palavras, minúsculas, sem stemming nem stop words
lucene.simpleDivide em tudo que não é letra e usa minúsculas
lucene.whitespaceDivide apenas em espaços, preserva caixa
lucene.keywordO valor inteiro vira um único termo
lucene.portuguese / lucene.brazilianStop words e stemming de português: variações da mesma palavra (plural, flexões) casam entre si
CustomVocê monta: charFilters + tokenizer + tokenFilters

Analisador customizado: sem acentos e em minúsculas

JSON
{
  "analyzer": "semAcento",
  "searchAnalyzer": "semAcento",
  "mappings": { "dynamic": false,
    "fields": { "titulo": { "type": "string" } } },
  "analyzers": [
    {
      "name": "semAcento",
      "charFilters": [],
      "tokenizer": { "type": "standard" },
      "tokenFilters": [
        { "type": "lowercase" },
        { "type": "icuFolding" }
      ]
    }
  ]
}

Com esse analisador, "ação", "Acao" e "AÇÃO" viram o mesmo termo. Os nomes de analisadores customizados não podem começar com lucene., builtin. ou mongodb..

Dois idiomas no mesmo campo (multi)

JSON
{
  "mappings": { "dynamic": false, "fields": {
    "descricao": {
      "type": "string",
      "analyzer": "lucene.portuguese",
      "multi": { "descricao_en": { "type": "string", "analyzer": "lucene.english" } }
    }
  } }
}
mongosh · consultando o analisador alternativo
db.produtos.aggregate([
  { $search: { text: {
      query: "waterproof",
      path: { value: "descricao", multi: "descricao_en" } } } },
  { $limit: 5 }
])
Mudou o analisador, reconstruaAlterar analisadores ou mappings dispara um rebuild do índice. Ele ocorre sem downtime (o índice antigo atende até o novo ficar pronto), mas consome CPU, disco e RAM.

Documentação oficialVisão geral · Analisadores de idioma · Customizados · multi · Tokenizers · Token filters · Character filters

Consultas

Do text básico ao compound com filtros, autocomplete e fuzzy, sempre com exemplos executáveis no mongosh.

$searchtextcompoundautocompletefuzzy

text: a consulta básica

mongosh
db.produtos.aggregate([
  { $search: {
      index: "produtos_idx",          // omitido = "default"
      text: { query: "tenis corrida", path: ["nome", "descricao"] }
  } },
  { $limit: 10 },
  { $project: { _id: 0, nome: 1, preco: 1, score: { $meta: "searchScore" } } }
])

Por padrão text aplica OU entre os termos ("tenis" ou "corrida"); documentos com ambos pontuam mais. Para exigir todos, use matchCriteria: "all". Para frases na ordem exata, o operador phrase (com slop).

mongosh
// todos os termos (AND)
{ text: { query: "tenis corrida", path: "nome", matchCriteria: "all" } }

// frase exata, tolerando 1 palavra no meio
{ phrase: { query: "tenis de corrida", path: "nome", slop: 1 } }

Documentação oficialtext · phrase · Estágio $search

Fuzzy: tolerância a erros de digitação

mongosh
db.produtos.aggregate([
  { $search: { text: {
      query: "tenys",
      path: "nome",
      fuzzy: { maxEdits: 1, prefixLength: 2, maxExpansions: 50 }
  } } },
  { $limit: 5 }
])
OpçãoPadrãoEfeito
maxEdits2Máximo de edições (1 ou 2). Valor 1 já cobre a maioria dos erros e é mais barato
prefixLength0Letras iniciais que precisam bater exatamente; aumentar reduz custo e ruído
maxExpansions50Máximo de variações geradas por termo
Fuzzy é caroCada termo vira dezenas de variações. Use prefixLength ≥ 1 e maxEdits: 1 sempre que possível. Fuzzy também não combina com sinônimos na mesma consulta.

Documentação oficialtext · fuzzy

compound: combinando condições

compound é o operador central de aplicações reais. Cada cláusula tem um papel:

CláusulaEfeito no resultadoEfeito no score
mustDocumento precisa casar (AND)Contribui
mustNotDocumento não pode casarNão contribui
shouldOpcional (ou obrigatório conforme minimumShouldMatch)Contribui
filterPrecisa casar (AND)Não contribui e é mais barato
mongosh · busca de e-commerce
db.produtos.aggregate([
  { $search: { compound: {
      must: [ { text: { query: "tenis corrida", path: ["nome", "descricao"],
                        fuzzy: { maxEdits: 1, prefixLength: 2 } } } ],
      filter: [
        { equals: { path: "emEstoque", value: true } },
        { equals: { path: "categoria", value: "Calçados" } },
        { range:  { path: "preco", gte: 100, lte: 500 } }
      ],
      should: [
        { text: { query: "Acme", path: "marca", score: { boost: { value: 3 } } } }
      ],
      mustNot: [ { text: { query: "infantil", path: "nome" } } ]
  } } },
  { $limit: 20 },
  { $project: { nome: 1, marca: 1, preco: 1, score: { $meta: "searchScore" } } }
])
Regra de ouroTudo que é "sim ou não" (categoria, faixa de preço, status, estoque) vai em filter; o que decide ordem vai em must/should. Isso reduz o custo de scoring e deixa o ranking limpo.

Documentação oficialcompound · equals · range

Autocomplete (busca ao digitar)

Exige o tipo autocomplete no índice (veja o mapping estático acima). Os parâmetros do campo controlam o tamanho do índice:

OpçãoValoresNotas
tokenizationedgeGram, rightEdgeGram, nGramedgeGram casa o início da palavra e é bem menor que nGram
minGrams / maxGramsinteirosFaixa pequena = índice menor; maxGrams ≈ tamanho da maior palavra pesquisada
foldDiacriticstrue/falsetrue ignora acentos
analyzerex.: lucene.keywordKeyword + edgeGram dá "começa com" sobre o valor inteiro
mongosh
db.produtos.aggregate([
  { $search: { autocomplete: {
      query: "tenis ru",
      path: "nome",
      tokenOrder: "sequential",
      fuzzy: { maxEdits: 1, prefixLength: 1 }
  } } },
  { $limit: 8 },
  { $project: { _id: 0, nome: 1 } }
])

tokenOrder: any (padrão) aceita os termos em qualquer ordem, com maior score para os sequenciais; sequential exige adjacência e ordem. O path do autocomplete deve ser um único campo, sem multi nem wildcard.

Documentação oficialautocomplete · Tipo autocomplete

Outros operadores úteis

OperadorUso
wildcard / regexPadrões em termos. Caros: use só quando não houver alternativa
nearAproximação numérica/data/geo que influencia o score (ex.: mais recente primeiro)
inValor em uma lista (token, number, date, objectId, uuid)
existsCampo presente no documento
moreLikeThis"Documentos parecidos com este"
queryStringSintaxe de consulta para usuários avançados (nome:tenis AND preco:[100 TO 500])
embeddedDocumentCasar condições dentro do mesmo objeto de um array
geoWithin / geoShapeBusca geográfica
Operadores obsoletosknnBeta e span estão depreciados. Para semântica, use $vectorSearch.

Documentação oficialnear · embeddedDocument · moreLikeThis · queryString

Relevância e recursos de resultado

Score, facets, highlight, ordenação, paginação e sinônimos: o que transforma uma lista de resultados em uma boa experiência de busca.

scorefacethighlightsearchAftersynonyms

Score e relevância

O score padrão é BM25: termos raros e frequentes no documento pesam mais, e campos curtos pontuam melhor que longos. Leia o score com { $meta: "searchScore" } e ajuste com a opção score de qualquer operador.

OpçãoEfeito
boost: { value: 3 }Multiplica o score base por 3 (valor positivo)
boost: { path: "popularidade", undefined: 1 }Multiplica pelo valor de um campo numérico (usa undefined se o campo não existir)
constant: { value: 5 }Substitui o score por um valor fixo (não combina com boost)
functionExpressões: add, multiply, path, score, constant, gauss, log, log1p
mongosh · relevância × nota do produto
db.produtos.aggregate([
  { $search: { text: {
      query: "camiseta", path: "nome",
      score: { function: { multiply: [
        { path: { value: "avaliacao", undefined: 2 } },
        { score: "relevance" }
      ] } }
  } } },
  { $limit: 5 },
  { $project: { _id: 0, nome: 1, avaliacao: 1, score: { $meta: "searchScore" } } }
])

Para entender por que um documento pontuou assim, peça detalhes do score (scoreDetails: true no $search e { $meta: "searchScoreDetails" } no $project).

Scores diferentes entre nósCom nós de busca dedicados ou leitura em secundários, o BM25 usa estatísticas locais de cada nó e o mesmo termo pode pontuar um pouco diferente. Para paginar de forma estável por score, defina similarity.type como stableTfl ou boolean no campo string/autocomplete.

Documentação oficialVisão geral de score · Modificar o score · Detalhes do score

Facets e contagens

Facets agrupam os resultados (categoria, faixa de preço, ano) e devolvem as contagens para os filtros laterais. Os campos precisam ser indexados como token (string), number ou date. Use $searchMeta para obter só os metadados.

mongosh · facets de categoria e faixa de preço
db.produtos.aggregate([
  { $searchMeta: {
      facet: {
        operator: { text: { query: "tenis", path: "nome" } },
        facets: {
          categoriaFacet: { type: "string", path: "categoria", numBuckets: 10 },
          precoFacet: { type: "number", path: "preco",
                        boundaries: [0, 100, 300, 1000], default: "outros" }
        }
      },
      count: { type: "lowerBound", threshold: 5000 }
  } }
])
Saída (resumida)
[{
  count: { lowerBound: Long("1243") },
  facet: {
    categoriaFacet: { buckets: [ { _id: "Calçados", count: Long("980") }, { _id: "Acessórios", count: Long("120") } ] },
    precoFacet:     { buckets: [ { _id: 0, count: Long("210") }, { _id: 100, count: Long("640") }, { _id: 300, count: Long("390") } ] }
  }
}]

Para trazer documentos e facets na mesma consulta, use $search com facet e leia $$SEARCH_META (padrão $facet + $replaceWith: "$$SEARCH_META").

Custo de facetA memória depende do número de valores únicos do campo (não de numBuckets). Evite facet em campos de alta cardinalidade (ids, e-mails) e limite o resultado com $limit antes de um $facet de aggregation.

Documentação oficialfacet · $searchMeta · Contagem

Highlight, ordenação e paginação

Highlight

Devolve trechos com os termos encontrados (type: "hit") e o texto ao redor. O campo deve ser string com store: true (padrão) e indexOptions: "offsets" (padrão). Não funciona com embeddedDocument.

mongosh
db.artigos.aggregate([
  { $search: {
      text: { query: "backup", path: "conteudo" },
      highlight: { path: "conteudo", maxNumPassages: 2 }
  } },
  { $limit: 5 },
  { $project: { titulo: 1, trechos: { $meta: "searchHighlights" } } }
])

Ordenação

mongosh
// ordena por campo indexado; desempate por _id para resultado determinístico
{ $search: { text: { query: "tenis", path: "nome" },
             sort: { preco: 1, _id: 1 } } }

// ordenar por relevância com desempate
{ $search: { text: { query: "tenis", path: "nome" },
             sort: { score: { $meta: "searchScore" }, _id: 1 } } }

Paginação sequencial com searchAfter / searchBefore

mongosh
// página 1: pegue o token de cada documento
db.produtos.aggregate([
  { $search: { index: "produtos_idx", text: { query: "tenis", path: "nome" },
               sort: { preco: 1, _id: 1 } } },
  { $limit: 10 },
  { $project: { nome: 1, preco: 1, token: { $meta: "searchSequenceToken" } } }
])

// página seguinte: reutilize o token do último documento da página anterior
db.produtos.aggregate([
  { $search: { index: "produtos_idx", text: { query: "tenis", path: "nome" },
               sort: { preco: 1, _id: 1 }, searchAfter: "<token-do-ultimo>" } },
  { $limit: 10 }
])
  • A consulta da página seguinte deve ser idêntica à que gerou o token (mesmos operadores, campos e valores).
  • Ordene por campo único/imutável (ex.: _id) para evitar duplicidade ou omissão em empates.
  • Para saltar páginas combine searchAfter + $skip + $limit; evite $skip grande sem searchAfter.
  • Para voltar uma página use searchBefore (resultado vem em ordem inversa).
  • Conte com count no $search ou em um $searchMeta, não com $count.

Documentação oficialHighlight · Sort · Paginação

Sinônimos

Sinônimos ficam em uma coleção própria e são referenciados no índice (um mapeamento de sinônimos por índice). Cada documento tem mappingType equivalent (todos os termos se equivalem) ou explicit (input expande para synonyms).

mongosh · coleção de sinônimos
db.sinonimos.insertMany([
  { mappingType: "equivalent", synonyms: ["tenis", "sapatilha", "calçado esportivo"] },
  { mappingType: "explicit", input: ["celular"], synonyms: ["smartphone", "telefone"] }
])
JSON · índice
{
  "mappings": { "dynamic": false, "fields": {
    "nome": { "type": "string", "analyzer": "lucene.portuguese" } } },
  "synonyms": [
    { "name": "sinonimosProdutos", "analyzer": "lucene.portuguese",
      "source": { "collection": "sinonimos" } }
  ]
}
mongosh · consulta
{ $search: { text: { query: "celular", path: "nome", synonyms: "sinonimosProdutos" } } }
CuidadosAtualize a coleção de sinônimos em lotes (cada escrita reprocessa o mapa). Sinônimos não combinam com fuzzy na mesma cláusula. Conjuntos muito grandes aumentam o tempo de build; o limite de documentos em Free/Flex é de 10.000.

Documentação oficialSinônimos · text · synonyms

Busca híbrida e vetorial

$vectorSearch encontra por significado (embeddings) no mesmo cluster. Combinando léxico ($search) e vetorial, a busca híbrida melhora a cobertura: o lado léxico acerta nomes, códigos e termos exatos, e o vetorial acerta intenção e sinônimos implícitos. Os resultados são fundidos com $rankFusion/$scoreFusion (versões recentes do servidor) ou com reciprocal rank fusion manual por $unionWith.

Escopo desta páginaAqui o foco é busca textual. Índices vetoriais têm definição própria (type: "vectorSearch"), dimensões, similaridade e quantização; consulte a documentação específica de Vector Search.

Documentação oficialMongoDB Vector Search · $rankFusion

Cenários reais

Receitas prontas para os casos mais comuns, com índice e consulta lado a lado.

e-commercehelpdesklogscatálogo

Catálogo de e-commerce (loja.produtos)

Objetivo: busca com filtros de categoria/preço, tolerância a erro, boost de marca e facets para o menu lateral. Use o índice estático da seção de mappings e a consulta compound da seção de consultas; os facets vêm de um $searchMeta com o mesmo operator e filtros.

mongosh · resultados + facets em uma ida
const operador = { compound: {
  must:   [ { text: { query: "tenis corrida", path: ["nome","descricao"],
                      fuzzy: { maxEdits: 1, prefixLength: 2 } } } ],
  filter: [ { equals: { path: "emEstoque", value: true } } ]
} };

db.produtos.aggregate([
  { $search: { index: "produtos_idx", ...operador } },
  { $limit: 24 },
  { $project: { nome: 1, preco: 1, marca: 1, score: { $meta: "searchScore" } } }
]);

db.produtos.aggregate([
  { $searchMeta: { index: "produtos_idx",
      facet: { operator: operador,
               facets: { marcas: { type: "string", path: "marca", numBuckets: 10 } } } } }
]);
  • Campos de filtro/facet (categoria, marca) como token; preco como number.
  • Nome com string + autocomplete; descrição só string.
  • Para "mais vendidos primeiro" use function com log1p do campo de vendas em vez de ordenar fora da busca.

Base de conhecimento / helpdesk

Texto longo, acentuação variada e necessidade de mostrar o trecho que casou.

JSON
{
  "mappings": { "dynamic": false, "fields": {
    "titulo":   { "type": "string", "analyzer": "lucene.portuguese" },
    "conteudo": { "type": "string", "analyzer": "lucene.portuguese" },
    "produto":  { "type": "token" },
    "publicado":{ "type": "date" }
  } }
}
mongosh
db.artigos.aggregate([
  { $search: {
      compound: {
        must:   [ { text: { query: "redefinir senha", path: ["titulo","conteudo"] } } ],
        should: [ { text: { query: "redefinir senha", path: "titulo",
                            score: { boost: { value: 4 } } } },
                  { near: { path: "publicado", origin: new Date(), pivot: 1000*60*60*24*180 } } ],
        filter: [ { equals: { path: "produto", value: "app-mobile" } } ]
      },
      highlight: { path: "conteudo", maxNumPassages: 2 }
  } },
  { $limit: 5 },
  { $project: { titulo: 1, trechos: { $meta: "searchHighlights" } } }
])

O near em should favorece artigos recentes sem excluir os antigos; o boost no título prioriza quem tem o termo no título.

Busca em eventos/logs com filtro por período

Para volumes grandes de eventos, restrinja sempre por data em filter e indexe só o que for pesquisado.

JSON
{
  "mappings": { "dynamic": false, "fields": {
    "mensagem": { "type": "string" },
    "servico":  { "type": "token" },
    "nivel":    { "type": "token" },
    "ts":       { "type": "date" }
  } },
  "storedSource": { "include": ["servico", "nivel", "ts"] }
}
mongosh
db.eventos.aggregate([
  { $search: { index: "eventos_idx",
      compound: {
        must:   [ { phrase: { query: "connection refused", path: "mensagem" } } ],
        filter: [ { equals: { path: "nivel", value: "ERROR" } },
                  { range: { path: "ts", gte: ISODate("2026-10-12T00:00:00Z") } } ]
      },
      returnStoredSource: true,
      sort: { ts: -1, _id: 1 }
  } },
  { $limit: 50 }
])
storedSourcestoredSource guarda campos no índice e returnStoredSource: true evita o lookup no mongod, reduzindo latência. Armazene apenas o mínimo: cada campo aumenta o índice.

Documentação oficialstoredSource

Índice parcial com view (8.0+)

Indexe só um subconjunto (ex.: produtos ativos) criando o índice sobre uma view. Em 8.0+ é possível criar o índice na view; consultar a view diretamente exige 8.1+.

mongosh
db.createView("produtos_ativos", "produtos",
  [ { $match: { ativo: true } }, { $project: { nome: 1, descricao: 1, preco: 1, categoria: 1 } } ])

db.produtos_ativos.createSearchIndex("ativos_idx", { mappings: { dynamic: true } })
Limites de viewsViews são aceitas com pipelines simples ($match, $addFields, $set); consulte a lista atual de estágios suportados.

Documentação oficialÍndices em views · Compatibilidade

Performance

Como projetar índices e consultas rápidas, medir com explain e scripts e dimensionar os nós de busca.

explainindexqueryRAM

Boas práticas de índice

PráticaPor quê
Mapeamento estático, só campos pesquisadosÍndice menor, build mais rápido, menos RAM
store: false em strings que não usará em highlightReduz o armazenamento (bloqueia highlight naquele campo)
autocomplete só nos campos que precisam; prefira edgeGramnGram multiplica a quantidade de tokens
minGrams/maxGrams estreitosMenos tokens por palavra; maxGrams ≈ maior palavra pesquisada
Facets em token/number/dateEstruturas colunares próprias para agregação
Evite multi e arrays de objetos aninhados sem necessidadeCada variação duplica termos e estruturas
Evite explosão de campos (chaves dinâmicas)Mappings com chaves variáveis viram milhares de campos; modele como array de pares chave/valor
storedSource mínimoCada campo guardado cresce o índice
Documentos abaixo de ~8 MBDocumentos ≥ 16 MB deixam o índice stale
Menos índices de busca por coleçãoCada índice reprocessa toda escrita (amplificação de escrita)
Criar/recriar é pesadoCriação e rebuild consomem CPU, disco e RAM. Agende alterações de definição fora do pico. Rebuild também ocorre ao alterar mappings, partições, chave gerenciada pelo cliente, em initial sync e em algumas atualizações do mongot.

Documentação oficialPerformance de índice · Visão geral de performance

Boas práticas de consulta

Em vez deFaça
$match depois do $searchcompound.filter dentro do $search
$group para contagensfacet ($searchMeta)
$countopção count do $search/$searchMeta
$sort depois do $searchopção sort, near ou returnStoredSource
$skip grandesearchAfter/searchBefore
$regex/wildcard por padrãotext, autocomplete ou analisador adequado
compound aninhado redundanteAchatar cláusulas
$facet sobre milhares de docs$limit antes do $facet
Cláusulas equals/range/in em mustColocar em filter (sem scoring)
  • $limit logo após o $search reduz quantos documentos o mongod busca no mongot.
  • Projete ($project) apenas o necessário; documentos grandes encarecem o lookup pelo _id.
  • O limite do Lucene é 1024 cláusulas em uma BooleanQuery: listas enormes em should falham.
  • Cuidado com cursores longos do mongot em alertas de query targeting.

Documentação oficialPerformance de consulta

Analisando com explain

mongosh
db.produtos.explain("executionStats").aggregate([
  { $search: { text: { query: "tenys", path: "nome",
                        fuzzy: { maxEdits: 1, prefixLength: 1 } } } },
  { $limit: 10 }
])

O resultado fica em stages[0].$_internalSearchMongotRemote. Os campos principais:

CampoO que mostrar
mongotQueryA consulta enviada
explain.queryÁrvore de queries do Lucene (BooleanQuery, TermQuery, BoostQuery...) com stats por nó
stats.context / match / scoremillisElapsed e contagens: createWeight/createScorer, nextDoc, score
explain.collectStatsTempo de coleta (inclui facet e sort)
explain.resultMaterializationTempo para ler e serializar os documentos (retrieveAndSerialize)
explain.resourceUsagemajorFaults (leituras de disco), userTimeMs, systemTimeMs, numBatches
explain.metadataVersão do mongot, nome do índice e totalLuceneDocs
$_internalSearchIdLookuptotalDocsExamined: quantos documentos o mongod buscou pelo _id

Como ler: um majorFaults alto indica índice fora da RAM; match dominante sugere termos/expansões demais (fuzzy, wildcard); muitos numBatches e totalDocsExamined sugerem falta de $limit ou filtros aplicados fora do $search.

PlaygroundO Search Playground permite montar índice, dados e consulta de exemplo e ver o plano de explain sem tocar no cluster.

Documentação oficialexplain · Search Playground

Scripts de medição

mongosh · tempo e volume de uma consulta (repita 5x e compare)
function medir(coll, pipeline, vezes = 5) {
  const t = [];
  for (let i = 0; i < vezes; i++) {
    const t0 = Date.now();
    const n = db[coll].aggregate(pipeline).toArray().length;
    t.push(Date.now() - t0);
    if (i === 0) print("documentos:", n);
  }
  t.sort((a, b) => a - b);
  print(`min ${t[0]} ms | mediana ${t[Math.floor(t.length/2)]} ms | max ${t[t.length-1]} ms`);
}

medir("produtos", [
  { $search: { text: { query: "tenis", path: "nome" } } },
  { $limit: 20 }
]);
mongosh · resumo do explain
const ex = db.produtos.explain("executionStats").aggregate([
  { $search: { text: { query: "tenis", path: "nome" } } }, { $limit: 20 }
]);
const st = ex.stages[0].$_internalSearchMongotRemote;
printjson({
  lucene:   st.explain.query.type,
  ms:       st.explain.query.stats,
  coleta:   st.explain.collectStats,
  recursos: st.explain.resourceUsage,
  docsNoIndice: st.explain.metadata.totalLuceneDocs,
  docsBuscados: ex.stages[1].$_internalSearchIdLookup.totalDocsExamined
});
mongosh · saúde dos índices de todas as coleções
db.getCollectionNames().forEach(c => {
  db.getCollection(c).getSearchIndexes().forEach(i =>
    print(c.padEnd(24), i.name.padEnd(24), i.status.padEnd(10), "queryable:", i.queryable));
});

Dimensionamento e escala

  • Meça o tamanho do índice (métrica Index Size/Search Disk Space Used) e escolha nós com RAM suficiente para mantê-lo em cache.
  • numPartitions (1, 2 ou 4) divide o índice em partições para coleções muito grandes (acima de 2,1 bilhões de objetos Lucene por partição o índice fica stale) e exige nós dedicados.
  • Mais nós de busca aumentam o throughput de consultas e a disponibilidade; mais RAM/CPU por nó reduz latência.
  • Cluster com sharding: cada shard mantém seu índice; consultas fazem fan-out e merge.
  • Escalar nós de busca copia o índice por armazenamento de objetos; mudar o tipo de disco NVMe dispara initial sync.
  • Atualizações do mongot podem causar falhas breves: implemente retry na aplicação e use a janela de manutenção.

Documentação oficialDeployment options · Performance

Operação e troubleshooting

Métricas, alertas e uma tabela de sintomas para diagnosticar problemas de índice, resultado e desempenho.

métricasalertasstalereplication lag

Monitoramento e métricas

No Atlas: Cluster → Metrics (ou aba Search Nodes) e, na página Search & Vector Search, View Search Metrics. Não há métricas de busca em clusters Free.

MétricaQuando olhar
Index Size / Search Disk Space UsedCrescimento do índice e folga de disco
JVM Heap Memory / Process MemoryPressão de memória; risco de OOM
Normalized Process CPU / Process CPUGargalo de CPU em consultas ou indexação
Max Replication LagAtraso do índice em relação ao oplog (resultados desatualizados)
Page FaultÍndice não cabe na RAM (disco sendo lido)
Opcounters / Query StatusVolume e erros/sucessos de consultas
Fields Indexed / Max Number of Lucene DocsExplosão de campos e proximidade do limite de 2,1 bi
Process ThrottlingIndexação limitada por recursos
Via APIA Atlas Admin API expõe a métrica INDEX_SIZE_ON_DISK por índice (endpoints /fts/metrics/indexes/...), útil para alimentar dashboards e alertas próprios.

Documentação oficialMonitoramento

Alertas e como reagir

AlertaSignificadoAção
Index Replication Lagmongot atrasado em relação ao oplog. Se sair da janela do oplog, o índice é reconstruído e fica staleAumentar oplog, escalar nós de busca, limitar escritas em massa; alertar a 30-50% da janela
Mongot paused initial sync / approaching stop thresholdDisco chegando a 85%+Aumentar disco, remover índices/campos, subir tier
Mongot stopped replicationDisco ≥ 90%: replicação pausada (retoma abaixo de 85%)Liberar espaço urgente
Max Lucene DocsPerto de 2,1 bi objetos por partiçãonumPartitions, sharding ou reduzir arrays aninhados
Insufficient disk space to rebuildRebuild não cabeAumentar disco antes de alterar definições
Ran out of memoryFalhas de indexação e consultaSubir RAM, reduzir índice/campos, ajustar consultas
Search Process is OverloadedFila de consultas acima do limiteSubir tier, reduzir concorrência, otimizar consultas
Error/Successful/Total QueriesTaxa de erros ou volume anormalCorrelacionar com deploys e picos

Documentação oficialAlertas de busca

Sintomas, causas e ações

SintomaCausa provávelComo confirmar e corrigir
$search retorna vazioÍndice com outro nome (usa default por padrão), campo não indexado, analisador diferenteConfira index e path; getSearchIndexes(); teste com dynamic: true
Erro "Failed to Execute search Command" / índice não encontradoCluster acoplado sobrecarregado, índice em construção ou staleVerifique queryable, métricas de CPU/RAM; migre para nós dedicados
Documento novo não apareceConsistência eventual ou replication lagVeja Max Replication Lag; aguarde; não dependa de read-after-write
Termo existe mas não casaStemming/stop word do analisador, caixa, acentosUse highlight/explain; troque analisador ou crie custom com icuFolding
Resultados irrelevantesSem boost/campos errados, fuzzy demaisAjuste boost, use filter, minimumShouldMatch, explain com score details
Índice staleDocumento ≥ 16 MB, oplog estourado, 2,1 bi objetos, disco cheioVer alertas; reduzir tamanho dos docs; numPartitions; aumentar recursos e reconstruir
Índice FailedDefinição inválida ou sem recursosRevisar JSON (analisadores, tipos) e logs/UI
Consulta lentawildcard/regex, fuzzy amplo, sem $limit, índice fora da RAMexplain executionStats; mover filtros para filter; subir RAM
Erro de cláusulas (BooleanQuery)Mais de 1024 cláusulasReduzir listas; usar in ou reorganizar a consulta
Highlight vazioCampo com store: false ou maxCharsToExamine baixoAjuste o mapping e o limite de caracteres
Facet erra/ausenteCampo string não indexado como tokenDefina token/number/date
Paginação repete/omite itensOrdenação por campo não único ou mutávelInclua _id no sort; stableTfl para score estável
Erro em time seriesColeções time series não são suportadasCopie para coleção comum
Busca some após pausar o clusterPausa remove índices/nós de buscaRecrie os índices ao retomar

Documentação oficialFAQ · Como a busca funciona

Versões e limites

RecursoDisponibilidade
Facets, sort, storedSource, $lookup/$unionWith com $search, gestão por mongosh/driver7.0+ (algumas em 6.0.x)
searchAfter/searchBefore6.0.13+, 7.0.5+, 8.0+
Índices em views8.0+; consulta direta à view em 8.1+; views em sharded e $rankFusion sobre views em 8.2+
Time seriesNão suportado (erro em 8.3+)
Definição de índice≤ 3 KB e ≤ 300 campos em Free/Flex; Free: 3 índices, Flex: 10

Documentação oficialCompatibilidade de recursos · Atlas Search

Checklist de produção

  • Mapeamento estático e analisadores testados com highlight/explain.
  • Search Nodes dedicados, com RAM para caber o índice e 20% de folga de disco.
  • Filtros em filter, $limit logo após o $search, paginação por searchAfter.
  • Alertas de replication lag, disco e CPU configurados.
  • Tamanho de oplog compatível com a janela de manutenção/cargas em massa.
  • Retry no cliente para falhas transitórias durante atualizações.
  • Rotina para recriar índices após pausar o cluster ou alterar a definição.
  • Documentos e arrays dentro dos limites; chaves dinâmicas evitadas.

Referências

Links oficiais da MongoDB usados nesta wiki.

docs oficiais

Documentação oficial

TemaLink
Visão geral e tutorialMongoDB Search · Tutorial
ÍndicesDefinições · Gerenciar · string · autocomplete
AnalisadoresVisão geral · Idioma · Custom
Operadorescompound · text · autocomplete · facet
ResultadosScore · Highlight · Sort · Paginação
PerformanceÍndice · Consulta · explain
OperaçãoDeployment · Monitoramento · Compatibilidade · Changelog
ApoioPlayground · Design patterns · FAQ