MongoDB Atlas Search and Vector Search

MongoDB Search and AI Retrieval (Atlas Search + Vector Search)

Atlas Search (Full-Text, Lucene-based)

Index Creation

from pymongo.operations import SearchIndexModel

# Atlas Search index
search_index = SearchIndexModel(
    definition={
        "mappings": {
            "dynamic": True,  # auto-index all string fields
            # OR explicit:
            "fields": {
                "title": [
                    {
                        "type": "string",
                        "analyzer": "lucene.standard"
                    }
                ],
                "price": [{"type": "number"}],  # for faceting/ranges
                "category": [{"type": "stringFacet"}],  # for category facets
                "createdAt": [{"type": "date"}]
            }
        }
    },
    name="default",
    type="search"
)
collection.create_search_index(model=search_index)

$search Operator

db.products.aggregate([
  {
    $search: {
      index: "default",  // optional if named "default"
      compound: {
        must: [
          { text: { query: "mongodb atlas", path: "title" } }
        ],
        should: [
          { text: { query: "cloud database", path: "description" } }
        ],
        filter: [
          { range: { path: "price", gte: 10, lte: 500 } },
          { equals: { path: "inStock", value: true } }
        ]
      },
      highlight: { path: "description" },
      sort: { score: { $meta: "searchScore" }, _id: 1 }  // stable sort for pagination
    }
  },
  {
    $project: {
      title: 1, price: 1,
      score: { $meta: "searchScore" },
      highlights: { $meta: "searchHighlights" }
    }
  },
  { $limit: 20 }
])

Text Analyzers

Analyzer Use when
lucene.standard Default; good for most English text
lucene.english English with stemming (runs → run)
lucene.keyword Exact string match (no tokenization)
lucene.whitespace Split on whitespace only
lucene.french, lucene.spanish, etc. Language-specific stemming
Custom analyzer nGram for partial matching, custom filters

Autocomplete

// Index definition with autocomplete analyzer
"title": [{
  "type": "autocomplete",
  "tokenization": "edgeGram",  // or nGram
  "minGrams": 2,
  "maxGrams": 10
}]

// Query
{
  $search: {
    autocomplete: { query: "mon", path: "title" }
  }
}
db.products.aggregate([
  {
    $searchMeta: {  // returns only facet metadata (not documents)
      facet: {
        operator: {
          text: { query: "laptop", path: "name" }
        },
        facets: {
          categoryFacet: { type: "string", path: "category", numBuckets: 10 },
          priceRanges: {
            type: "number",
            path: "price",
            boundaries: [0, 100, 500, 1000, 5000]
          }
        }
      }
    }
  }
])

Synonyms

// Create a synonym collection
db.synonyms.insertMany([
  { mappingType: "equivalent", synonyms: ["laptop", "notebook", "computer"] },
  { mappingType: "explicit", input: ["iphone"], synonyms: ["iphone", "apple phone", "ios phone"] }
])

// Reference in index definition
"analyzers": [],
"mappings": { ... },
"synonyms": [{
  "name": "mySynonyms",
  "analyzer": "lucene.standard",
  "source": { "collection": "synonyms" }
}]

// Use in query
{ $search: { text: { query: "laptop", path: "name", synonyms: "mySynonyms" } } }

Score Diagnostics (BM25 tuning)

{
  $search: {
    text: { query: "mongodb", path: "title" },
    scoreDetails: true  // adds detailed BM25 score breakdown
  }
}
// Output includes: $meta: "searchScoreDetails"
// Shows TF, IDF, field weight contribution per matching term

For deterministic pagination (not skip/limit):

// First page
{
  $search: {
    index: "default",
    text: { ... },
    sort: { score: { $meta: "searchScore" }, _id: 1 },  // _id for tiebreaking
    searchAfter: null  // no cursor for first page
  }
}

// Subsequent pages (use PIT + searchAfter)
{
  $search: {
    text: { ... },
    sort: { score: { $meta: "searchScore" }, _id: 1 },
    searchAfter: lastPageCursor  // { $binary: "..." } from previous result
  }
}
// Get cursor from: { $meta: "searchSequenceToken" }

See mongodb-atlas-vector-search for the complete reference. Key summary:

// $vectorSearch operator
{
  $vectorSearch: {
    index: "vector_index",
    path: "embedding",
    queryVector: embeddingArray,  // [0.1, 0.3, ...]
    numCandidates: 150,           // candidate pool (>= limit)
    limit: 10,
    filter: { category: "electronics" }  // pre-filter (must be in index)
  }
}

Hybrid Search ($rankFusion)

Combine Atlas Search (BM25) with Vector Search (HNSW) results:

db.products.aggregate([
  {
    $rankFusion: {
      input: {
        pipelines: {
          fullText: [
            { $search: { text: { query: "fast laptop", path: "name" } } },
            { $limit: 20 }
          ],
          semantic: [
            { $vectorSearch: {
              index: "vector_idx",
              path: "embedding",
              queryVector: semanticEmbedding,
              numCandidates: 200, limit: 20
            }}
          ]
        }
      },
      combination: {
        weights: { fullText: 0.4, semantic: 0.6 }
      }
    }
  },
  { $limit: 10 }
])

Auto Embedding (Voyage AI)

Skip the external embedding pipeline:

// Index: Atlas embeds the `content` field automatically using Voyage AI
{
  "fields": [{
    "type": "vector",
    "path": "embedding",
    "numDimensions": 1024,
    "similarity": "cosine",
    "embeddingDefinition": {
      "provider": "voyageAI",
      "model": "voyage-3-large",
      "inputField": "content"
    }
  }]
}

// Query: no need to compute embedding externally
{ $vectorSearch: { ..., "queryString": "user's search query" } }

Search Node Architecture

For production, use dedicated Search Nodes to isolate Atlas Search / Vector Search workloads from OLTP:

When to Use Atlas Search vs Text Indexes

Aspect Atlas Search Text Indexes
Relevance ranking BM25 (sophisticated) Simple tf-idf
Analyzers 20+ language analyzers, custom English + basic
Autocomplete Yes (edgeGram, nGram) No
Facets Yes ($searchMeta) No
Highlighting Yes No
Synonyms Yes No
Performance at scale Dedicated Search Nodes COLLSCAN risk on large
Setup complexity Higher (index required) Lower

Always prefer Atlas Search for production full-text search. Text indexes are only appropriate for very small collections or simple dev prototypes.

Anti-Patterns

References