Search 索引生命周期

从 v3.4.0 起,集合访问器和 Model 可以创建、查看、更新、删除并等待 MongoDB Search 索引。同一套接口同时支持全文 search 索引和 vectorSearch 索引;查询向量索引请使用 vectorSearch()。

环境要求

  • MongoDB Atlas、带 mongot 的 MongoDB Community / Enterprise 8.2+,或本地镜像 mongodb/mongodb-atlas-local。
  • 普通 mongod(包括 mongodb-memory-server)不支持 Search 索引命令,调用会以 SEARCH_INDEX_UNSUPPORTED 拒绝。
  • 服务端异步构建索引。create、update、drop 在服务端接受命令后即返回,需要结果时调用 waitForSearchIndex()。

API

collection.createSearchIndex(spec: SearchIndexSpec, options?: { ifExists?: 'error' | 'skip' | 'update' })
  : Promise<{ name: string; action: 'created' | 'skipped' | 'updated'; created: boolean }>;
collection.createSearchIndexes(specs: SearchIndexSpec[]): Promise<string[]>;
collection.listSearchIndexes(options?: { type?: 'search' | 'vectorSearch' }): Promise<SearchIndexInfo[]>;
collection.getSearchIndex(name: string): Promise<SearchIndexInfo>;
collection.updateSearchIndex(name: string, definition: Record<string, unknown>): Promise<void>;
collection.dropSearchIndex(name: string, options?: { ignoreMissing?: boolean }): Promise<void>;
collection.waitForSearchIndex(name: string, options?: {
  until?: 'queryable' | 'ready' | 'dropped'; // 默认 'queryable'
  timeoutMs?: number;                        // 默认 300000
  intervalMs?: number;                       // 默认 2000,最小 100
  signal?: AbortSignal;
}): Promise<SearchIndexInfo>;

Model 上有同样的 7 个方法。

interface SearchIndexSpec {
  name: string;
  type: 'search' | 'vectorSearch';
  definition: Record<string, unknown>;
}

interface SearchIndexInfo {
  name: string;
  status: 'PENDING' | 'BUILDING' | 'READY' | 'FAILED' | 'STALE' | 'DELETING' | 'DOES_NOT_EXIST';
  queryable: boolean;
  type?: 'search' | 'vectorSearch';
  typeSource: 'server' | 'inferred' | 'unknown';
  id?: string;
  latestDefinition?: Record<string, unknown>;
  latestDefinitionVersion?: { version: number; createdAt: Date };
  statusDetail?: Array<Record<string, unknown>>;
  raw: Record<string, unknown>;
}

行为

方法行为
createSearchIndex先做本地校验,再检查是否已有同名索引。默认 ifExists: 'error' 时以 SEARCH_INDEX_EXISTS 拒绝;'skip' 不做改动(action: 'skipped');'update' 替换定义(action: 'updated'),但类型不同时以 INVALID_SEARCH_INDEX_DEFINITION 拒绝,需要先删除再创建。
createSearchIndexes全部校验通过后一次提交;不支持 ifExists,名称重复时以 SEARCH_INDEX_EXISTS 拒绝。
listSearchIndexes返回归一化后的条目;type 按解析出的类型过滤。
getSearchIndex索引不存在时返回 status: 'DOES_NOT_EXIST',不抛错。
updateSearchIndex替换定义,服务端随后重建;重建期间旧版本可以继续提供查询。上报的 queryable 不一致:Community 8.2.12 保持 true;atlas-local 8.3.13 在 search 索引重建期间上报 false,但 $search 仍返回结果(均为实测)。索引不存在时以 SEARCH_INDEX_NOT_FOUND 拒绝。
dropSearchIndex索引不存在时以 SEARCH_INDEX_NOT_FOUND 拒绝;传 ignoreMissing: true 则直接返回。
waitForSearchIndex每隔 intervalMs 查询一次,规则见下表。

vectorSearch 索引能否原地更新取决于部署环境。

部署对 vectorSearch 执行 updateSearchIndex
MongoDB Atlas按 MongoDB 官方文档支持原地更新,重建期间旧索引继续提供查询。monSQLize 未实测。
mongodb/mongodb-atlas-local(mongod 8.3.13)服务端拒绝:BadValue(2)"mappings" is required。已实测。
Community 8.2.12 + mongodb/mongodb-community-search接受。重建期间(20,000 文档 × 64 维约 30 秒)$vectorSearch 持续返回结果。已实测。

在所有实测环境中,服务端都不接受 updateSearchIndex 命令带 type 字段(IDLUnknownField),带上也没有用。服务端拒绝时,monSQLize 原样透出为 MONGODB_ERROR(带 serverCode),不会自动降级。

不能原地更新时,推荐用换名切换做到不中断:用新名字创建索引,waitForSearchIndex(newName, { until: 'queryable' }),把查询切到新名字,再删除旧索引。期间两个索引同时存在,会额外占用资源,并计入索引数量上限(Atlas Free 档 3 个 Search 索引,Flex 档 10 个)。如果能接受短暂中断,也可以先删除索引、等待 dropped、再重新创建;在它重新可查询之前检索会失败,中断时长随数据量增长(atlas-local 实测:50,000 文档 × 64 维约 1–2 秒)。

status 原样透传服务端的值。判断能否检索看 queryable,展示进度看 status。

索引类型

类型优先取服务端返回的顶层 type 字段(typeSource: 'server')。自建 mongot 不返回该字段(见 MongoDB 限制说明),此时根据 latestDefinition 推断(typeSource: 'inferred'):fields 数组中有 type: 'vector' 的项即为 vectorSearch,有 mappings 键即为 search。两者都没有时不返回 type,typeSource 为 'unknown',这类条目不会被 listSearchIndexes({ type }) 匹配。

waitForSearchIndex

until何时返回遇到 FAILED索引不存在
queryablequeryable === true仅在不可查询时以 SEARCH_INDEX_BUILD_FAILED 拒绝最多再等 30 秒,之后以 SEARCH_INDEX_NOT_FOUND 拒绝
readystatus === 'READY'以 SEARCH_INDEX_BUILD_FAILED 拒绝同上
droppedstatus === 'DOES_NOT_EXIST'继续等待直接返回

超时以 SEARCH_INDEX_WAIT_TIMEOUT 拒绝,details[0].last 是最后一次观察到的状态。signal 中止时原样以 signal.reason 拒绝。

writePathPolicy

createSearchIndex、createSearchIndexes、updateSearchIndex、dropSearchIndex 与 createIndex() 一样属于 management 操作。在 model-only 且 management: 'inherit' 时,Model 调用放行,直接调用集合会以 INVALID_OPERATION 拒绝。读方法(listSearchIndexes、getSearchIndex、waitForSearchIndex)不受限制。

错误码

码何时出现
INVALID_SEARCH_INDEX_DEFINITION本地校验失败(名称、类型、定义、选项),或 ifExists: 'update' 的目标是另一种类型的索引
SEARCH_INDEX_EXISTS已有同名索引且 ifExists: 'error',或 createSearchIndexes 中名称重复
SEARCH_INDEX_NOT_FOUNDupdateSearchIndex / dropSearchIndex 的目标不存在;waitForSearchIndex 在宽限期后仍未看到索引
SEARCH_INDEX_UNSUPPORTED部署不支持 Search 索引命令
SEARCH_INDEX_BUILD_FAILEDwaitForSearchIndex 观察到 FAILED
SEARCH_INDEX_WAIT_TIMEOUTwaitForSearchIndex 超过 timeoutMs

由服务端返回的错误都把驱动原错误放在 cause。没有专用码的服务端错误以 MONGODB_ERROR 拒绝,details[0] 带 serverCode、serverCodeName、serverMessage,并附加 error.monsqlize = { operation, namespace, indexName }。详见错误码参考。

示例

const docs = msq.collection('docs');

await docs.createSearchIndex({
  name: 'embedding_idx',
  type: 'vectorSearch',
  definition: {
    fields: [
      { type: 'vector', path: 'embedding', numDimensions: 1536, similarity: 'cosine' },
      { type: 'filter', path: 'tenantId' },
    ],
  },
}, { ifExists: 'skip' });
await docs.waitForSearchIndex('embedding_idx', { until: 'queryable', timeoutMs: 600_000 });

await docs.createSearchIndex({ name: 'text_idx', type: 'search', definition: { mappings: { dynamic: true } } });
const info = await docs.getSearchIndex('text_idx');
console.log(info.status, info.queryable);

await docs.dropSearchIndex('text_idx', { ignoreMissing: true });
await docs.waitForSearchIndex('text_idx', { until: 'dropped' });