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.
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ça
Papel
mongod
Banco de dados. Recebe a aggregation, delega o $search ao mongot e busca os documentos completos pelo _id.
mongot
Processo de busca (Lucene). Lê o change stream da coleção, mantém o índice Lucene e responde às consultas.
Índice de busca
Estrutura invertida (termos → documentos) definida por um JSON de mappings, analisadores e opções.
$search / $searchMeta
Estágios de aggregation: o primeiro devolve documentos ranqueados; o segundo devolve só metadados (contagens, facets).
$vectorSearch
Busca 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.
Antes de adotar, compare com o que o MongoDB já oferece sem mongot:
Critério
$search
$text
$regex
Relevância
BM25 por padrão, com boost, constant e funções de score
Score simples (textScore)
Sem relevância
Tolerância a erro de digitação (fuzzy)
Sim (fuzzy, até 2 edições)
Não
Não
Autocomplete / prefixo
Sim (tipo autocomplete)
Não
Apenas prefixo ancorado usa índice
Sinônimos
Sim
Não
Não
Facets e contagens
Sim (facet, $searchMeta)
Não (usar $group)
Não
Highlight dos termos
Sim
Não
Não
Idiomas
Dezenas de analisadores (lucene.portuguese, etc.)
Alguns idiomas
Não se aplica
Índice parcial
Via view (8.0+)
Não
Não
Custo operacional
Processo mongot + índice extra no disco/RAM
Índice B-tree comum
Varredura 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.
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 roda
Mesmo nó do mongod
Nós próprios, mínimo de 2 (até 32), cobrados por hora
Uso indicado
Testes, protótipos, cargas pequenas
Produção
Disponibilidade
Free, Flex e M10+
M10+ (tiers S20 em diante)
Isolamento
Busca e banco disputam CPU, RAM e disco
Busca isolada: o banco não sofre com queries pesadas
Escala
Escalar o cluster inteiro
Escala 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.
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.
Status
Significado
Ação
Pending / Building
Criação ou initial sync em andamento
Aguardar; acompanhar na aba de métricas
Ready / Active
Consultável e sincronizado
Nenhuma
Stale
Índice atrasado ou parado (ex.: documento ≥ 16 MB, oplog estourado, disco cheio)
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.
Valor inteiro, sem tokenizar: equals, facets, ordenação por string
autocomplete
Tokens de prefixo (edgeGram/nGram) para busca ao digitar
number / date
range, near, facets numéricos/data e ordenação
boolean
equals em verdadeiro/falso
geo
geoWithin, geoShape, near por distância
document / embeddedDocuments
Objetos 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.
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".
Analisador
Comportamento
lucene.standard (padrão)
Divide por palavras, minúsculas, sem stemming nem stop words
lucene.simple
Divide em tudo que não é letra e usa minúsculas
lucene.whitespace
Divide apenas em espaços, preserva caixa
lucene.keyword
O valor inteiro vira um único termo
lucene.portuguese / lucene.brazilian
Stop words e stemming de português: variações da mesma palavra (plural, flexões) casam entre si
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..
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.
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 } }
Máximo de edições (1 ou 2). Valor 1 já cobre a maioria dos erros e é mais barato
prefixLength
0
Letras iniciais que precisam bater exatamente; aumentar reduz custo e ruído
maxExpansions
50
Má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.
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.
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.
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ção
Efeito
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)
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.
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.
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.
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.
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 mappingTypeequivalent (todos os termos se equivalem) ou explicit (input expande para synonyms).
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.
$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.
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 mesmooperator e filtros.
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.
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+.
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ática
Por quê
Mapeamento estático, só campos pesquisados
Índice menor, build mais rápido, menos RAM
store: false em strings que não usará em highlight
Reduz o armazenamento (bloqueia highlight naquele campo)
autocomplete só nos campos que precisam; prefira edgeGram
nGram multiplica a quantidade de tokens
minGrams/maxGrams estreitos
Menos tokens por palavra; maxGrams ≈ maior palavra pesquisada
Facets em token/number/date
Estruturas colunares próprias para agregação
Evite multi e arrays de objetos aninhados sem necessidade
Cada 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ínimo
Cada campo guardado cresce o índice
Documentos abaixo de ~8 MB
Documentos ≥ 16 MB deixam o índice stale
Menos índices de busca por coleção
Cada í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.
O resultado fica em stages[0].$_internalSearchMongotRemote. Os campos principais:
Campo
O que mostrar
mongotQuery
A consulta enviada
explain.query
Árvore de queries do Lucene (BooleanQuery, TermQuery, BoostQuery...) com stats por nó
stats.context / match / score
millisElapsed e contagens: createWeight/createScorer, nextDoc, score
explain.collectStats
Tempo de coleta (inclui facet e sort)
explain.resultMaterialization
Tempo para ler e serializar os documentos (retrieveAndSerialize)
explain.resourceUsage
majorFaults (leituras de disco), userTimeMs, systemTimeMs, numBatches
explain.metadata
Versão do mongot, nome do índice e totalLuceneDocs
$_internalSearchIdLookup
totalDocsExamined: 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.
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.
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étrica
Quando olhar
Index Size / Search Disk Space Used
Crescimento do índice e folga de disco
JVM Heap Memory / Process Memory
Pressão de memória; risco de OOM
Normalized Process CPU / Process CPU
Gargalo de CPU em consultas ou indexação
Max Replication Lag
Atraso do índice em relação ao oplog (resultados desatualizados)
Page Fault
Índice não cabe na RAM (disco sendo lido)
Opcounters / Query Status
Volume e erros/sucessos de consultas
Fields Indexed / Max Number of Lucene Docs
Explosão de campos e proximidade do limite de 2,1 bi
Process Throttling
Indexaçã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.