PracticeVideosBlogFlashcards

4.5 Atlas Search: Search Index Definitions & $search Queries

Key Takeaways

  • Exam objectives 2.18 and 2.19 sit inside the 51% CRUD domain and test MongoDB Search (Atlas Search), not the MongoDB Query Language.

  • A search index is a Lucene inverted index maintained asynchronously by the mongot process; it is eventually consistent and is never used by the query planner for find().

  • db.collection.createSearchIndex() names the index 'default' when the name argument is omitted and defaults the type to 'search'; with dynamic: false, a mappings.fields object is required.

  • $search and $searchMeta must be the first stage of an aggregation pipeline, with the sole exception of the first stage inside a $lookup sub-pipeline in MongoDB 6.0 and later.

  • Inside compound, must and should contribute to the relevance score, mustNot excludes documents, and filter constrains results without affecting the score exposed via $meta: 'searchScore'.

Last updated: September 2026

4.5 Atlas Search: Search Index Definitions & $search Queries

The official MongoDB Associate Developer exam guide places two objectives inside the 51% CRUD domain that are not about the MongoDB Query Language at all: objective 2.18 asks you to identify the correct command for defining a search index, and objective 2.19 asks you to identify the correct search query. Both refer to MongoDB Search (historically branded Atlas Search) — a relevance-based, full-text search engine that runs alongside the database. Because these two objectives sit in the heaviest-weighted domain, skipping them is one of the most common ways well-prepared candidates lose points.


1. A Search Index Is Not a Database Index

This is the single most important distinction in this section, and the exam tests it directly.

A database index (what db.collection.createIndex() builds) is a B-tree keyed on field values. It is updated synchronously, inside the same write operation that modifies the document, and it is consumed by the query planner to turn a COLLSCAN into an IXSCAN.

A search index is an inverted index — a mapping from terms to the documents containing those terms — built on Apache Lucene. It is maintained by a separate process called mongot, which follows the collection's change stream and updates the Lucene index asynchronously. Two consequences follow directly:

  • Search indexes are eventually consistent. A document you just inserted may not appear in $search results for a short interval. You cannot rely on read-your-own-write behavior.
  • Search indexes are invisible to the query planner. Creating a search index will never speed up an ordinary find(), and explain() on a find() will never show it.
Database IndexSearch Index
StructureB-treeLucene inverted index
Created withcreateIndex()createSearchIndex()
Queried withfind(), $match$search, $searchMeta
Update timingSynchronous with the writeAsynchronous via mongot
Used by query plannerYes (IXSCAN)No
Relevance scoringNone{ $meta: "searchScore" }

2. Defining a Search Index (Objective 2.18)

In mongosh, search indexes are created with db.<collection>.createSearchIndex(), available from MongoDB 7.0 (and backported to 6.0.7). The full signature takes three arguments, two of which are optional:

db.<collection>.createSearchIndex(
  <name>,        // optional string
  <type>,        // optional: "search" (default) or "vectorSearch"
  { <definition> }  // required
)

Two defaults are directly examinable:

  • If you omit name, the index is named default.
  • If you omit type, the index type is search.

Dynamic Mappings

The simplest definition tells MongoDB Search to index every supported field type automatically:

db.movies.createSearchIndex({ mappings: { dynamic: true } })
// -> creates a search index literally named "default"

Static Mappings

Static mappings enumerate exactly which fields are indexed and how each is analyzed. This is the production-grade choice: smaller indexes, faster builds, predictable scoring.

db.movies.createSearchIndex(
  "movieTitleIdx",
  {
    mappings: {
      dynamic: false,
      fields: {
        title: { type: "string" },
        plot:  { type: "string" },
        year:  { type: "number" }
      }
    }
  }
)

Exam trap: when mappings.dynamic is false, mappings.fields is required. A definition of { mappings: { dynamic: false } } with no fields object indexes nothing, and every $search query against it returns zero results — with no error to tell you why.

Search indexes can also be created from the Atlas UI (Search tab → Create Search Index, using either the Visual Editor or the JSON Editor), the Atlas CLI, Compass, the Atlas Admin API, and the official drivers. The equivalent database command is createSearchIndexes. Companion helpers are $listSearchIndexes (an aggregation stage), db.<collection>.updateSearchIndex(), and db.<collection>.dropSearchIndex().

3. Writing a Search Query (Objective 2.19)

A search query is an aggregation pipeline stage, not a find() filter. MongoDB Search provides two stages:

  • $search — runs the full-text query and returns the matching documents, ordered by descending relevance.
  • $searchMeta — returns only metadata about the results (facet buckets, total counts) without returning the documents themselves.

The First-Stage Rule

$search and $searchMeta must be the first stage of the aggregation pipeline. Placing any other stage ahead of them is an error, not a performance problem. This is the most frequently tested fact about search queries:

// INVALID — $match precedes $search
db.movies.aggregate([
  { $match:  { year: { $gte: 2000 } } },
  { $search: { text: { query: "space", path: "plot" } } }
])

// VALID — $search first, then filter and shape the results
db.movies.aggregate([
  { $search: { text: { query: "space", path: "plot" } } },
  { $match:  { year: { $gte: 2000 } } },
  { $project: { title: 1, year: 1, score: { $meta: "searchScore" } } },
  { $limit: 10 }
])

The one narrow exception, added in MongoDB 6.0: $search may appear as the first stage inside a $lookup sub-pipeline.

Selecting the Index

If the index option is omitted, $search queries the index named default. When you created a named index, you must name it in the query — this mismatch is a classic exam distractor:

db.movies.aggregate([
  { $search: { index: "movieTitleIdx", text: { query: "space", path: "title" } } }
])

Common Search Operators

OperatorPurpose
textAnalyzed full-text match; supports fuzzy
phraseOrdered term sequence, with optional slop
autocompleteSearch-as-you-type against an autocomplete-typed field
equalsExact match on boolean, number, date, ObjectId, or string token
rangeNumeric, date, or string bounds (gt, gte, lt, lte)
wildcard / regexPattern matching against indexed terms
existsDocuments in which the indexed path is present
compoundCombines the above via must, mustNot, should, filter

Compound Queries and Scoring

compound is where relevance is actually shaped, and each clause behaves differently:

  • must — the clause is required and contributes to the score.
  • should — optional; every match raises the score. minimumShouldMatch sets how many should clauses must match.
  • mustNot — excludes matching documents.
  • filter — required, but does not affect the score. Use it for hard constraints such as a category or a date window so they cannot distort ranking.
db.movies.aggregate([
  { $search: {
      index: "movieTitleIdx",
      compound: {
        must:   [ { text: { query: "space", path: "title" } } ],
        should: [ { text: { query: "odyssey", path: "plot" } } ],
        filter: [ { range: { path: "year", gte: 1990 } } ]
      }
  } },
  { $project: { title: 1, score: { $meta: "searchScore" } } }
])

Relevance is exposed through the $meta: "searchScore" expression, mirroring the way $text exposes $meta: "textScore". Results already arrive sorted by descending score, so adding { $sort: { score: -1 } } is redundant unless you are combining scores with other criteria.

4. MongoDB Search vs. $text Full-Text Search

The guide's earlier coverage of text indexes (createIndex({ field: "text" }) queried with $text) is a different feature, and the exam will offer one as a distractor for the other.

$text + text indexMongoDB Search
Created bycreateIndex({ f: "text" })createSearchIndex(...)
Queried by$text inside find() or $match$search / $searchMeta
EngineMongoDB's own text indexApache Lucene via mongot
Text indexes per collectionOneMany search indexes allowed
Relevance metadata{ $meta: "textScore" }{ $meta: "searchScore" }
Fuzzy / autocomplete / highlightingNot supportedSupported
FacetingNot supported$searchMeta with facets
Stage positionAnywhere a filter is validMust be first

How to read these questions on exam day:

  1. If the stem says "define a search index", the answer uses createSearchIndex() with a mappings object — not createIndex(), and not a bare field list.
  2. If the stem shows a pipeline, check stage order first. $search anywhere but position one is wrong before you even read the operator.
  3. If the definition sets dynamic: false, confirm a fields object is present.
  4. If the query names an index, confirm that name was actually created; if no name was given at creation, the index is default.
  5. filter clauses inside compound are the correct tool when a constraint must not influence relevance ranking.
Test Your Knowledge

A developer runs db.movies.aggregate([{ $match: { year: { $gte: 2000 } } }, { $search: { text: { query: 'space', path: 'plot' } } }]) and the pipeline fails with an error. What is the defect?

A

$search cannot be combined with $match in the same pipeline under any circumstances

B

The text operator requires a fuzzy sub-document whenever path names a single field

C

$search must be the first stage of the aggregation pipeline, so placing $match ahead of it is invalid

D

The query is missing an explicit index: 'default' option, which is mandatory on every $search stage

Test Your Knowledge

A developer creates a search index with db.products.createSearchIndex({ mappings: { dynamic: true } }), omitting the name argument. A later $search stage that specifies index: 'products_idx' errors because the index does not exist. Why?

A

Omitting the name argument creates an index named default, so index: 'products_idx' refers to an index that was never created

B

Dynamic mappings cannot be addressed by name and are queryable only through $searchMeta

C

createSearchIndex() only registers a definition; a separate build step is required before the index is addressable

D

Search indexes inherit the collection name, so the index would have been named products

Test Your Knowledge

A search index is defined as { mappings: { dynamic: false } } with no other keys. The index builds without error, but every $search query against it returns zero documents. What is wrong?

A

dynamic: false is not a permitted value; mappings.dynamic must always be true

B

When dynamic is false, mappings.fields is required, and with no field definitions nothing in the collection is indexed

C

Static mappings require a top-level searchAnalyzer to be declared before any field can be indexed

D

dynamic: false indexes only the _id field, so queries must filter on _id alone

Test Your Knowledge

Which statement correctly distinguishes a MongoDB Search index from a standard database index?

A

Both are B-trees; a search index merely stores an extra relevance score alongside each index key

B

A search index is built by db.collection.createIndex() with the text type and is then queried using $search

C

A search index is a Lucene-backed inverted index maintained asynchronously by the mongot process and queried with $search, whereas a database index is a B-tree updated synchronously with the write and consumed by the query planner

D

Creating a search index supersedes the collection's database indexes, so subsequent find() operations automatically use it

Sections you finish are checked off in the contents.