createIndexes() - Create indexes in batches

Create multiple indexes at once to improve deployment efficiency.

Overview

The createIndexes() method is used to create multiple indexes in batches. Compared with calling createIndex() multiple times, batch creation is more efficient and is especially suitable for initial deployment and index maintenance.

Usage Scenario:

  • Application initial deployment
  • Database migration
  • Index batch maintenance
  • Quickly build a test environment

Syntax

await collection(collectionName).createIndexes(indexSpecs)

Parameters

indexSpecs (required)

Index specification array, defining an index for each element.

Type: Array<Object>

Format:

[
  {
    key: { field1: 1 },      //Index key (required)
    name: "index_name",       //Index name (optional)
    unique: true,             //Unique index (optional)
    //...other indexing options
  },
  //...more index
]

Index specification fields:

FieldTypeRequiredDescription
keyObjectIndex key definition
nameStringIndex name (automatically generated if not specified)
uniqueBooleanWhether the index is unique
sparseBooleanWhether to use sparse index
expireAfterSecondsNumberTTL expiration time
partialFilterExpressionObjectPartial index filter conditions
collationObjectSorting Rules
Other options-See createIndex

Return value

Type: Promise<Array<String>>

Returns the created array of index names.

Format:

["email_1", "age_1", "city_1_age_-1"]

Comparison with createIndex

PropertiescreateIndexcreateIndexes
Create QuantitySingleMultiple
Network round tripOnce at a timeOnce
PerformanceSlowerFaster
Applicable scenariosSingle indexBatch creation
AtomicitySingle atomBatch of atoms

Performance comparison example:

//Method 1: Create one by one (3 network round-trips)
await collection("users").createIndex({ email: 1 });
await collection("users").createIndex({ age: 1 });
await collection("users").createIndex({ city: 1 });

//Method 2: Batch creation (1 network round trip) ✓ Faster
await collection("users").createIndexes([
  { key: { email: 1 } },
  { key: { age: 1 } },
  { key: { city: 1 } }
]);

Code Example

Example 1: Basic batch creation

import MonSQLize from 'monsqlize';
const msq = new MonSQLize({ ... });
const { collection } = await msq.connect();

//Create indexes in batches
const result = await collection("users").createIndexes([
  { key: { email: 1 } },
  { key: { age: 1 } },
  { key: { createdAt: -1 } }
]);

console.log("Index created:", result);
// ["email_1", "age_1", "createdAt_-1"]

Example 2: Mixed index types

//Create different types of indexes
await collection("users").createIndexes([
  //unique index
  {
    key: { email: 1 },
    unique: true,
    name: "email_unique"
  },
  //composite index
  {
    key: { city: 1, age: -1 },
    name: "city_age_idx"
  },
  //TTL index
  {
    key: { createdAt: 1 },
    expireAfterSeconds: 86400,  //24 hours
    name: "session_ttl"
  },
  //sparse index
  {
    key: { phone: 1 },
    sparse: true
  }
]);

Example 3: Application initialization

//Initialize index when application starts
async function initializeIndexes() {
  console.log("Initialize database index...");

  //Users collection
  await collection("users").createIndexes([
    { key: { email: 1 }, unique: true, name: "email_unique" },
    { key: { username: 1 }, unique: true, name: "username_unique" },
    { key: { status: 1 }, name: "status_idx" },
    { key: { createdAt: -1 }, name: "created_idx" }
  ]);
  console.log("✓ Users index has been created");

  //Products collection
  await collection("products").createIndexes([
    { key: { sku: 1 }, unique: true },
    { key: { category: 1, price: -1 } },
    { key: { name: "text" } }
  ]);
  console.log("✓ Products index has been created");

  //Orders collection
  await collection("orders").createIndexes([
    { key: { userId: 1, status: 1 } },
    { key: { orderNumber: 1 }, unique: true },
    { key: { createdAt: -1 } }
  ]);
  console.log("✓ Orders index has been created");

  console.log("✓ All index initialization completed");
}

//Called when the application starts
await initializeIndexes();

Example 4: Deployment script

//Deployment script: Create or update index
async function deployIndexes() {
  const indexDefinitions = {
    users: [
      { key: { email: 1 }, unique: true, name: "email_unique" },
      { key: { "profile.city": 1, "profile.age": 1 } }
    ],
    products: [
      { key: { category: 1, price: -1 } },
      { key: { tags: 1 } }
    ]
  };

  for (const [collName, indexes] of Object.entries(indexDefinitions)) {
    try {
      //Get existing index
      const existing = await collection(collName).listIndexes();
      const existingNames = existing.map(idx => idx.name);

      //Filter out the indexes that need to be created
      const toCreate = indexes.filter(idx => {
        const name = idx.name || Object.keys(idx.key).join('_');
        return !existingNames.includes(name);
      });

      if (toCreate.length > 0) {
        await collection(collName).createIndexes(toCreate);
        console.log(`✓ ${collName}: ${toCreate.length} indexes created`);
      } else {
        console.log(`✓ ${collName}: all indexes already exist`);
      }
    } catch (err) {
      console.error(`✗ ${collName}: ${err.message}`);
    }
  }
}

await deployIndexes();

Example 5: Error handling

try {
  const result = await collection("users").createIndexes([
    { key: { email: 1 }, unique: true },
    { key: { age: 1 } }
  ]);

  console.log("✓ Index creation successful:", result);
} catch (err) {
  if (err.code === 'MONGODB_ERROR') {
    if (err.message.includes('Index already exists')) {
      console.log("Part of the index already exists");
    } else if (err.message.includes('duplicate key')) {
      console.error("There are duplicate values in the data and a unique index cannot be created");
    } else {
      console.error("Creation failed:", err.message);
    }
  }
}

Partial failure handling

Behavior description

MongoDB's createIndexes operation has the following characteristics:

Atomicity:

  • Batch operations are executed as a single command
  • If an index fails, the entire operation fails
  • Indexes created will be retained (will not be rolled back)

Failure Scenario:

//Assume email_1 index already exists
await collection("users").createIndexes([
  { key: { email: 1 } },      //failed (already exists)
  { key: { age: 1 } },         //will not be executed
  { key: { city: 1 } }         //will not be executed
]);
//The entire operation fails, the age and city indexes are not created
//Check and create one by one
async function safeCreateIndexes(collectionName, indexSpecs) {
  const results = {
    created: [],
    skipped: [],
    failed: []
  };

  //Get existing index
  const existing = await collection(collectionName).listIndexes();
  const existingNames = existing.map(idx => idx.name);

  for (const spec of indexSpecs) {
    const indexName = spec.name || generateIndexName(spec.key);

    //Skip existing indexes
    if (existingNames.includes(indexName)) {
      results.skipped.push(indexName);
      continue;
    }

    //try to create
    try {
      await collection(collectionName).createIndex(spec.key, spec);
      results.created.push(indexName);
    } catch (err) {
      results.failed.push({ indexName, error: err.message });
    }
  }

  return results;
}

//use
const results = await safeCreateIndexes("users", [
  { key: { email: 1 }, unique: true },
  { key: { age: 1 } },
  { key: { city: 1 } }
]);

console.log("Create:", results.created);
console.log("Skip:", results.skipped);
console.log("Failure:", results.failed);

Performance optimization

1. Batch vs single

Test comparison (create 10 indexes):

const indexes = [
  { key: { field1: 1 } },
  { key: { field2: 1 } },
  //... 8 more
];

//Method 1: Create one by one
console.time('Create one by one');
for (const idx of indexes) {
  await collection("test").createIndex(idx.key);
}
console.timeEnd('Create one by one');
//Create one by one: ~2000ms

//Method 2: Create in batches
console.time('Create in batches');
await collection("test").createIndexes(indexes);
console.timeEnd('Create in batches');
//Batch creation: ~500ms

Conclusion: Batch creation is 4 times faster

2. Background creation

//Big data collections are created in the background (without blocking)
await collection("large_collection").createIndexes([
  {
    key: { field1: 1 },
    background: true  //Background creation
  },
  {
    key: { field2: 1 },
    background: true
  }
]);

Note: The background option is deprecated in MongoDB 4.2+, but remains compatible.


Best Practices

1. Configuration management

// config/indexes.js
module.exports = {
  users: [
    { key: { email: 1 }, unique: true, name: "email_unique" },
    { key: { username: 1 }, unique: true },
    { key: { status: 1, createdAt: -1 } }
  ],
  products: [
    { key: { sku: 1 }, unique: true },
    { key: { category: 1, price: -1 } }
  ]
};

//use
const indexConfig = require('./config/indexes');

async function applyIndexes() {
  for (const [collName, indexes] of Object.entries(indexConfig)) {
    await collection(collName).createIndexes(indexes);
    console.log(`✓ ${collName} index has been created`);
  }
}

2. Environmental distinction

//Use different indexes in different environments
const indexConfig = {
  development: {
    users: [
      { key: { email: 1 } }  //Simplified development environment
    ]
  },
  production: {
    users: [
      { key: { email: 1 }, unique: true },
      { key: { status: 1, createdAt: -1 } },
      { key: { "profile.city": 1 } }
    ]
  }
};

const env = process.env.NODE_ENV || 'development';
await collection("users").createIndexes(indexConfig[env].users);

3. Version control

// migrations/001_create_indexes.js
module.exports = {
  version: 1,
  up: async (db) => {
    await db.collection("users").createIndexes([
      { key: { email: 1 }, unique: true },
      { key: { createdAt: -1 } }
    ]);
  },
  down: async (db) => {
    await db.collection("users").dropIndex("email_1");
    await db.collection("users").dropIndex("createdAt_-1");
  }
};


Reference resources