Building Indexes

Building and Maintaining Indexes Safely

Since MongoDB 4.2 1,815 there is one build type. It takes an exclusive lock at the start and again at the end and allows reads and writes in between, so the old background: true option is accepted and ignored. Primary and secondaries build simultaneously, the primary waiting for a commit quorum of voting members before marking the index ready.

Re-creating and dropping an indexPython
db.runCommand({ createIndexes: 'orders',          // twice: the second is a no-op
  indexes: [{ key: { region: 1, createdAt: -1 }, name: 'region_createdAt' }] });
db.orders.createIndex({ region: 1, createdAt: -1 }, { name: 'other' });
db.orders.dropIndex('region_createdAt');
print(db.orders.validate().valid);
Output
{ numIndexesBefore: 15, numIndexesAfter: 15, note: 'all indexes already exist', ok: 1 }
MongoServerError: Index already exists with a different name: region_createdAt
{ nIndexesWas: 15, ok: 1 }
true

Re-issuing an identical createIndexes returns in about two milliseconds, so it is safe to run your index definitions on every deploy; the same key pattern under a different name is an error, so choose names once. The ESR build in The Equality, Sort, Range Rule took 306 ms over 100,000 documents; a collection a thousand times larger takes minutes to hours and is limited to maxIndexBuildMemoryUsageMegabytes, 200 MB per build by default, spilling to disk beyond that. validate() reports mismatches between data and indexes under a collection lock, or pass { background: true }.

A safe sequence for production:

  1. Build on one member at a time. A rolling build takes each secondary out of the set in turn, builds the index as a standalone and rejoins it, the primary last after a stepdown.

  2. Watch the progress. db.currentOp({ 'command.createIndexes': { $exists: true } }) reports a progress document with done and total; db.killOp(opid) aborts a build that is hurting.

  3. Hide before you drop. Dropping is instant, rebuilding is not. Hide the index, let a business cycle pass, drop only if nothing regressed.

Treat the index set as code: one module your deploy runs, stable names, additions reviewed like a migration. Every index in it is work on every write, forever.