MongoDB Search Index Reference

更新时间:
复制 MD 格式

MongoDB Search supports multiple index types, field mapping types, analyzers, and search operators ($search, $vectorSearch, and $rankFusion). Refer to this page when you write search queries or tune search performance.

Overview

MongoDB Search integrates dedicated search nodes (mongot) into a MongoDB instance to provide full-text search and vector search capabilities tightly coupled with the database. You can run multimodal search tasks through the MongoDB Query Language (MQL) without managing an external search system.

For the scope, architecture, activation, and node management of MongoDB Search, see MongoDB Search. This topic focuses on algorithm parameters and operators for defining indexes and running search queries.

Search index types

Full-text search indexes

Use the createSearchIndex method to create a full-text search index for use with the $search aggregation stage. You can map fields dynamically or statically.

Dynamic mapping

Set dynamic: true to automatically index all fields in the collection. This option is suitable for rapid prototyping.

db.reviews.createSearchIndex({
  name: "reviews_full_text_index",
  definition: {
    mappings: {
      dynamic: true
    }
  }
});

Static mapping

Specify the fields to index and their types. This approach is suitable for production environments because it reduces storage overhead by limiting the index scope.

db.movies.createSearchIndex({
  name: "movies_custom_index",
  definition: {
    mappings: {
      dynamic: false,
      fields: {
        title: { type: "string", analyzer: "lucene.standard" },
        genre: { type: "stringFacet" },
        year: { type: "number" },
        rating: { type: "number" },
        plot: { type: "string", analyzer: "lucene.standard" }
      }
    }
  }
});

Vector search indexes

Use the three-argument form of createSearchIndex with the type set to "vectorSearch" to create a vector search index for use with the $vectorSearch aggregation stage.

db.images.createSearchIndex(
  "vector_index",          // Index name.
  "vectorSearch",          // Index type.
  {
    fields: [
      {
        type: "vector",
        path: "embedding",              // Field that stores the vector.
        numDimensions: 1536,            // Number of dimensions in the vector.
        similarity: "dotProduct",       // Similarity metric.
        quantization: "scalar"          // Quantization method (optional).
      }
    ]
  }
);

Field mapping types

In the fields object of a static mapping index, specify the type for each field:

Type

Description

Use case

string

Analyzed string field for full-text indexing.

Titles, descriptions, comments, and other text that requires full-text search.

stringFacet

Unanalyzed string field used for facet aggregations.

Categories, tags, and other fields used to group documents.

number

Numeric types (int, long, float, and double).

Range queries on numeric fields such as price, rating, and year.

date

Date type.

Time range queries.

boolean

Boolean type.

Status filtering.

objectId

ObjectId type.

_id or reference fields.

geo

Geographic coordinates (GeoJSON).

Geospatial search.

token

Unanalyzed string for exact matching.

Exact matches on enumerated values, IDs, and SKUs.

autocomplete

Autocomplete type.

Search suggestions and prefix matching.

document

Embedded document.

Indexing nested object fields.

vector

Vector type (only for vector search indexes).

Vector similarity search.

Analyzers

An analyzer controls how text is tokenized and normalized. Specify an analyzer for a string field by setting the analyzer parameter.

Built-in analyzers

Analyzer

Description

Tokenization example

lucene.standard

Standard analyzer. Tokenizes text by Unicode text segmentation rules and converts to lowercase.

"The Quick Fox" → ["the", "quick", "fox"]

lucene.simple

Splits on non-letter characters and converts to lowercase.

"hello-world 123" → ["hello", "world"]

lucene.whitespace

Splits on whitespace only. Does not convert to lowercase.

"Hello World" → ["Hello", "World"]

lucene.keyword

Treats the entire field value as a single token.

"New York" → ["New York"]

lucene.english

English analyzer with stemming and stop word filtering.

"running quickly" → ["run", "quick"]

lucene.chinese

Chinese analyzer with Chinese word segmentation.

"全文搜索引擎" → ["全文", "搜索", "引擎"]

Custom analyzers

Build a custom analyzer by combining a tokenizer, token filters, and character filters:

db.collection.createSearchIndex({
  name: "custom_analyzer_index",
  definition: {
    mappings: {
      dynamic: false,
      fields: {
        content: {
          type: "string",
          analyzer: "myCustomAnalyzer"
        }
      }
    },
    analyzers: [
      {
        name: "myCustomAnalyzer",
        charFilters: [],
        tokenizer: {
          type: "standard"
        },
        tokenFilters: [
          { type: "lowercase" },
          { type: "stopword", tokens: ["the", "a", "an", "is"] }
        ]
      }
    ]
  }
});

Tokenizer types

Tokenizer

Description

standard

Tokenizes text by Unicode text segmentation rules.

whitespace

Splits on whitespace.

keyword

Treats the entire input as a single token.

regexCaptureGroup

Tokenizes by regex capture groups.

regexSplit

Splits by regex delimiter.

nGram

N-gram tokenization.

edgeGram

Edge N-gram (prefix tokenization).

uaxUrlEmail

Recognizes and preserves URLs and email addresses.

Token filters

Filter

Description

lowercase

Converts to lowercase.

stopword

Removes stop words.

stemming

Reduces tokens to their root form.

synonym

Expands tokens with synonyms.

shingle

Generates N-gram phrases.

trim

Trims leading and trailing whitespace.

length

Filters tokens by length.

icuFolding

Unicode folding (removes diacritics and similar marks).

icuNormalizer

Unicode normalization.

Full-text search operators ($search)

Run full-text search queries by using the $search aggregation stage. The following operators are supported.

text

Performs a basic full-text search. The query text is tokenized and matched against the target field.

db.reviews.aggregate([
  {
    $search: {
      index: "reviews_full_text_index",
      text: {
        query: "good quality",      // Query keywords.
        path: "comment"             // Search in the comment field.
      }
    }
  },
  { $limit: 5 },
  { $project: { _id: 0, comment: 1, score: { $meta: "searchScore" } } }
]);

Parameters:

  • query: The query keywords. Can be a string or an array of strings.

  • path: The field or fields to search. Can be a string or an array of strings.

  • fuzzy: Fuzzy matching configuration. Optional.

  • score: Score modifier. Optional.

  • synonyms: Name of a synonym mapping. Optional.

compound

Combines multiple search clauses by using Boolean logic.

db.movies.aggregate([
  {
    $search: {
      index: "movies_custom_index",
      compound: {
        must: [
          { text: { query: "Drama", path: "genre" } }
        ],
        mustNot: [
          { text: { query: "horror", path: "genre" } }
        ],
        should: [
          { text: { query: "award", path: "plot" } }
        ],
        filter: [
          { range: { path: "year", gte: 2000, lte: 2023 } }
        ]
      }
    }
  },
  { $project: { title: 1, genre: 1, year: 1, _id: 0, score: { $meta: "searchScore" } } }
]);

Clauses:

Clause

Meaning

Affects score

must

Documents must match all clauses.

Yes

mustNot

Documents must not match any of these clauses.

No

should

Matching documents receive a higher score.

Yes

filter

Documents must match, but the clause does not contribute to the score.

No (better performance)

phrase

Searches for terms that appear in order. Use the slop parameter to allow gaps between terms.

db.movies.aggregate([
  {
    $search: {
      index: "default",
      phrase: {
        query: "organized crime",
        path: "plot",
        slop: 0              // Allowed gap between terms (default 0, strict adjacency).
      }
    }
  },
  { $project: { title: 1, _id: 0, score: { $meta: "searchScore" } } }
]);
// Matches The Godfather (the plot contains the phrase "organized crime").

A slop value greater than 0 allows other terms between matched terms:

// slop: 5 allows up to 5 terms between "men" and "bond".
{ phrase: { query: "men bond", path: "plot", slop: 5 } }
// Matches The Shawshank Redemption ("men bond over a number of years").

range

Performs a range query on numeric or date fields.

{
  $search: {
    range: {
      path: "rating",
      gte: 8.5,
      lt: 10
    }
  }
}

Supported comparisons: gt (greater than), gte (greater than or equal to), lt (less than), lte (less than or equal to).

fuzzy (fuzzy matching)

Enabled within the text operator by setting the fuzzy parameter. Tolerates typos in queries.

db.movies.aggregate([
  {
    $search: {
      index: "default",
      text: {
        query: "Godfater",         // Typo.
        path: "title",
        fuzzy: {
          maxEdits: 1,             // Maximum edit distance (1 or 2).
          prefixLength: 2,         // Number of leading characters that must match exactly.
          maxExpansions: 50        // Maximum number of expansion variants.
        }
      }
    }
  },
  { $project: { title: 1, _id: 0, score: { $meta: "searchScore" } } }
]);
// Matches "The Godfather".

wildcard

Uses * (matches zero or more characters) and ? (matches a single character) for pattern matching. Matching occurs at the token level after analysis.

db.movies.aggregate([
  {
    $search: {
      index: "default",
      wildcard: {
        query: "inter*",
        path: "title",
        allowAnalyzedField: true
      }
    }
  },
  { $project: { title: 1, _id: 0, score: { $meta: "searchScore" } } }
]);
// Matches Interstellar (the "interstellar" token matches "inter*").

Parameters:

  • query: Wildcard pattern (* matches zero or more characters; ? matches a single character).

  • path: The field to search.

  • allowAnalyzedField: Must be set to true when targeting analyzed fields.

regex

Uses a regular expression for pattern matching. Based on the Lucene regex engine. Matching occurs at the token level after analysis.

db.movies.aggregate([
  {
    $search: {
      index: "default",
      regex: {
        query: ".*tion",
        path: "plot",
        allowAnalyzedField: true
      }
    }
  },
  { $project: { title: 1, _id: 0, score: { $meta: "searchScore" } } },
  { $limit: 5 }
]);
// Matches documents whose plot contains tokens ending in "tion" (such as redemption or fiction).

Parameters:

  • query: Regex pattern (supports ., *, +, ?, |, (), [ ], {n,m}, and similar syntax).

  • path: The field to search.

  • allowAnalyzedField: Must be set to true when targeting analyzed fields.

autocomplete

Works with the autocomplete field type to support prefix matching and search suggestions.

// Index definition: configure the autocomplete type for the title field.
db.movies.createSearchIndex({
  name: "ac_index",
  definition: {
    mappings: {
      dynamic: false,
      fields: {
        title: [
          { type: "autocomplete" },
          { type: "string", analyzer: "lucene.standard" }
        ]
      }
    }
  }
});

// Query: autocomplete on the input "inter".
db.movies.aggregate([
  {
    $search: {
      index: "ac_index",
      autocomplete: {
        query: "inter",
        path: "title"
      }
    }
  },
  { $project: { title: 1, _id: 0, score: { $meta: "searchScore" } } }
]);
// Matches Interstellar.

exists

Checks whether a specified field exists in a document.

db.movies.aggregate([
  {
    $search: {
      index: "default",
      exists: {
        path: "rating"
      }
    }
  },
  { $project: { title: 1, rating: 1, _id: 0, score: { $meta: "searchScore" } } },
  { $limit: 3 }
]);

Hybrid search ($rankFusion)

Note

To use $rankFusion, go to Parameter Settings and set both setParameter.featureFlagRankFusionBasic and setParameter.featureFlagRankFusionFull to true. The instance restarts automatically for the change to take effect.

The $rankFusion aggregation stage merges results from multiple search pipelines by using the Reciprocal Rank Fusion (RRF) algorithm. It is designed for hybrid scenarios that combine full-text search and vector search.

RRF algorithm

RRF calculates a fused score based on the ranking of each document in each subpipeline:

score = weight × (1 / (rank + 60))

Where rank is the document's ranking in the subpipeline result (starting from 1), and 60 is the RRF smoothing constant. When a document appears in multiple subpipelines, the final score is the sum of its scores from each subpipeline.

Syntax and parameters

db.collection.aggregate([
  {
    $rankFusion: {
      input: {
        pipelines: {
          "<pipeline_name_1>": [ /* Subpipeline 1 */ ],
          "<pipeline_name_2>": [ /* Subpipeline 2 */ ]
        }
      },
      combination: {                     // Optional.
        weights: {
          "<pipeline_name_1>": number,
          "<pipeline_name_2>": number
        }
      },
      scoreDetails: boolean            // Optional. Default: false.
    }
  }
]);

Parameters:

Parameter

Type

Required

Description

input.pipelines

object

Yes

Named subpipelines. Each subpipeline must be an "ordered pipeline" (starts with $search or $vectorSearch, or contains $sort).

combination.weights

object

No

Weight map for each subpipeline. Unspecified pipelines default to a weight of 1.

scoreDetails

boolean

No

Whether to include detailed per-pipeline scores in the result. Default: false.

Examples

Hybrid full-text and vector search

db.products.aggregate([
  {
    $rankFusion: {
      input: {
        pipelines: {
          fullTextSearch: [
            { $search: { index: "default", text: { query: "wireless headphones", path: "description" } } },
            { $limit: 20 }
          ],
          vectorSearch: [
            { $vectorSearch: { index: "vector_index", path: "embedding", queryVector: QUERY_VECTOR, numCandidates: 100, limit: 20 } }
          ]
        }
      },
      combination: {
        weights: {
          fullTextSearch: 0.3,
          vectorSearch: 0.7
        }
      }
    }
  },
  { $limit: 10 },
  { $project: { title: 1, score: { $meta: "searchScore" } } }
]);

View detailed scores

db.products.aggregate([
  {
    $rankFusion: {
      input: {
        pipelines: {
          textSearch: [
            { $search: { index: "default", text: { query: "laptop", path: "title" } } },
            { $limit: 10 }
          ],
          ratingSort: [
            { $sort: { rating: -1 } },
            { $limit: 10 }
          ]
        }
      },
      scoreDetails: true
    }
  },
  { $limit: 5 },
  { $project: { title: 1, score: { $meta: "searchScore" }, details: { $meta: "scoreDetails" } } }
]);

Subpipeline requirements

Each subpipeline must be an "ordered pipeline" that satisfies one of the following conditions:

  • Starts with a $search stage.

  • Starts with a $vectorSearch stage.

  • Contains a $sort stage.

Usage notes

  • We recommend that you control the number of results in each subpipeline with $limit to avoid processing too many candidate documents.

  • Pipelines that are not specified in weights default to a weight of 1. All weight values must be positive numbers.

  • scoreDetails: true includes per-pipeline rankings and scores in the result, which is useful for debugging and evaluating the fusion behavior.

scoreDetails output format

When scoreDetails: true, the structure of the details retrieved by { $meta: "scoreDetails" } is as follows:

// scoreDetails output example.
{
  value: 0.03252,  // Final RRF fusion score.
  description: "value output by reciprocal rank fusion algorithm, computed as sum of (weight * (1 / (60 + rank))) across input pipelines...",
  details: [
    {
      inputPipelineName: "textSearch",   // Subpipeline name.
      rank: 1,                           // Document ranking in this pipeline (starts at 1).
      weight: 1,                         // Pipeline weight.
      value: 0.6887,                     // Original score from this pipeline.
      details: []
    },
    {
      inputPipelineName: "vectorSearch",
      rank: 2,
      weight: 1,
      value: 0.9999,
      details: []
    }
  ]
}
// Final score: 1 × (1/(60+1)) + 1 × (1/(60+2)) = 0.01639 + 0.01613 = 0.03252

Scoring and result processing

Retrieving the relevance score

// Full-text search score.
{ $project: { score: { $meta: "searchScore" } } }

// Vector search score.
{ $project: { score: { $meta: "vectorSearchScore" } } }

Score modifiers

In full-text search operators, adjust the score by setting the score parameter:

// boost: Multiply the original score.
{ text: { query: "crime", path: "genre", score: { boost: { value: 5 } } } }
// Original score 0.575 → boosted score 2.874.

// constant: Set a fixed score value.
{ text: { query: "crime", path: "genre", score: { constant: { value: 10 } } } }
// All matching documents receive a score of 10.

Supported score modifiers:

  • boost: Multiplies the original score.

  • constant: Sets a fixed score value.

  • function: Computes the score by a custom function that can reference field values dynamically.

Highlight

Returns context snippets around matching text. Useful for surfacing matched keywords in search results.

db.movies.aggregate([
  {
    $search: {
      index: "default",
      text: { query: "war", path: "plot" },
      highlight: {
        path: "plot",
        maxCharsToExamine: 500,
        maxNumPassages: 3
      }
    }
  },
  {
    $project: {
      title: 1,
      _id: 0,
      score: { $meta: "searchScore" },
      highlights: { $meta: "searchHighlights" }
    }
  }
]);

storedSource

Stores original field values in the index so that they can be returned directly at query time without a collection lookup, reducing latency.

Index definition:

db.collection.createSearchIndex({
  name: "stored_index",
  definition: {
    mappings: { dynamic: true },
    storedSource: {
      include: ["title", "genre"]
    }
  }
});

Query with returnStoredSource:

db.movies.aggregate([
  {
    $search: {
      index: "stored_index",
      text: { query: "drama", path: "genre" },
      returnStoredSource: true
    }
  },
  { $project: { title: 1, genre: 1, _id: 0, score: { $meta: "searchScore" } } },
  { $limit: 3 }
]);

Facet aggregation ($searchMeta)

Use the $searchMeta aggregation stage to count documents by category, typically for faceted navigation in search results:

db.movies.aggregate([
  {
    $searchMeta: {
      index: "movies_custom_index",
      facet: {
        operator: {
          range: { path: "rating", gte: 8.0 }
        },
        facets: {
          genreFacet: {
            type: "string",
            path: "genre",
            numBuckets: 10
          },
          yearFacet: {
            type: "number",
            path: "year",
            boundaries: [1990, 2000, 2010, 2020]
          }
        }
      }
    }
  }
]);

Note: When you use $searchMeta, the genre field must be defined as stringFacet in the index.

Index management

Operation

Method

Create a full-text search index

db.collection.createSearchIndex({ name, definition })

Create a vector search index

db.collection.createSearchIndex(name, "vectorSearch", { fields })

List indexes

db.collection.aggregate([{ $listSearchIndexes: {} }])

List indexes (shorthand)

db.collection.getSearchIndexes()

Update an index definition

db.collection.updateSearchIndex(name, definition)

Drop an index

db.collection.dropSearchIndex(name)

Index build and state

After you create an index, mongot builds it asynchronously in the background. The index lifecycle includes the following stages:

  1. Index definition registration: After you call createSearchIndex, the index definition is registered in the mongot index catalog immediately. At this point, $listSearchIndexes returns the index (with the id, name, type, and latestDefinition fields).

  2. Initial sync: mongot performs a full sync from mongod and builds the index. This phase may take from several minutes to tens of minutes for large collections.

  3. Steady state: After initial sync, the index enters the steady state and can serve query requests normally.

How to confirm an index is ready:

  • $listSearchIndexes and getSearchIndexes() return the index information immediately after creation, but this does not indicate that the index build has finished. The current response does not include a status field.

  • The reliable way to confirm that the index is ready is to attempt a $search query. If the index is not ready, the query returns an error. If the query succeeds, the index is available.

  • You can also check the mongot log. When the message Transitioning from INITIAL_SYNC to STEADY_STATE appears, the index has finished building and is queryable.

Index updates

When you update an index definition, mongot rebuilds the new index in the background. The old index continues to serve queries during the rebuild and is replaced transparently after the new index is ready.

Usage notes

  1. Index build delay: An index is not queryable until its build finishes. Seeing the index in $listSearchIndexes does not mean the index is ready. Confirm the index status by running a $search query or by checking the mongot log.

  2. Index updates: When you update an index definition, mongot rebuilds the new index in the background. The old index continues to serve queries until the new one is ready and replaces it.

  3. Data sync latency: After data is written, it is typically searchable within seconds. The latency depends on factors such as the write load and document size.

  4. Use static mapping in production: We recommend that you use static mapping in production to limit index scope and reduce storage overhead.

  5. filter performance: The filter clause in a compound query does not contribute to the score and performs better than must. Use it for exact filter conditions.

  6. Vector dimension consistency: The numDimensions of a vector search index must match the output dimension of the embedding model.

  7. Analyzer naming: Built-in analyzer names use the lucene. prefix (for example, lucene.standard).

  8. wildcard and regex notes: When targeting analyzed (string) fields, you must set allowAnalyzedField: true. Matching occurs at the token level rather than against the original text.

  9. Backup and recovery: An instance restored from a backup does not include Search indexes. You must activate the Search service again and rebuild all Search indexes on the new instance.

  10. Data migration: When you migrate data with a tool such as DTS, Search indexes are not synchronized. You must recreate them manually on the target instance.