Skip to content

Copying MongoDB indexes programmatically

Posted on:February 26, 2023 at 08:30 AM

Rationale

When I was working on a project at work I needed to copy MongoDB indexes from one collection, and apply them on another collection (running on different MongoDB instance). Unfortunately, MongoDB Java driver doesn’t have MongoCollection.copy(...) method, so I needed to find a way to make it work. I have stumbled upon a question on Stack Overflow with exact same problem, sadly - without an answer at the time.

Copy & Modify & Paste JSON magic

If I would have this problem in JavaScript or Python, then solving it would be pretty easy (with help from Google and Stack Overflow). Quick look into similar StackOverflow question answer leaves us with a code snippet which copies indexes from source collection to destination collection with JavaScript:

var indexes = db.source.getIndexes(); // 1️⃣

indexes.forEach(function(index){
  delete index.v; // 2️⃣
  delete index.ns; // 3️⃣
  var key = index.key;
  delete index.key
  var options = [];
  for (var option in index) {
    options.push(index[option]);
  }
  db.destination.createIndex(key, options); // 4️⃣
});

But why it works?

Firstly, let’s see how an index definition is represented in MongoDB. Below you can find example indexes definitions (result of running db.source.getIndexes() - 1️⃣ command on my source MongoDB 3.6 database):

db> db.source.getIndexes()

[
  { v: 2, key: { _id: 1 }, name: '_id_', ns: 'db.source' },
  {
    v: 2,
    key: { _fts: 'text', _ftsx: 1 },
    name: '$**_text',
    ns: 'db.source',
    weights: { '$**': 1 },
    default_language: 'english',
    language_override: 'language',
    textIndexVersion: 3
  }
]

Secondly, let’s answer the question - how to create an index in MongoDB? There is a createIndex(keys, options) method, which takes keys and options arguments, where:

keys - A document that contains the field and value pairs where the field is the index key and the value describes the type of index for that field.

options - A document that contains a set of options that controls the creation of the index.

One last thing which we need to know is what are v - 2️⃣ and ns - 3️⃣ fields in each MongoDB index.

Those fields are not valid index options for index creation, so they should be omitted in process of copying (with one addition - since MongoDB 4.4 ns field is not returned from db.collection.getIndexes(), it doesn’t need to be omitted when copying indexes from newer MongoDB versions).

After preparing all required parameters, you can create index on destination collection by calling db.destination.createIndex(key, options) - 4️⃣.

How to do it with MongoDB Java Driver?

I have managed to implement approach described above with Kotlin and optimise it, to not call createIndex for each individual index, rather to create all indexes at once with one call. The trick requires using runCommand method from MongoDB Java driver, along with createIndexes command. The final result is:

import com.mongodb.client.MongoClients
import com.mongodb.client.MongoDatabase
import org.bson.Document

fun main() {
  MongoClients.create("mongodb://localhost:27017").use { mongoClient ->
    val db = mongoClient.getDatabase("db")
    val indexes = getIndexes(db, "source")
    createIndexes(db, "destination", indexes)
  }
}

private fun getIndexes(
  sourceDb: MongoDatabase,
  collectionName: String
) = sourceDb.getCollection(collectionName).listIndexes()
      .toList()
      .filterNot { it["name"] == "_id_" } // There is no need to copy _id index
      .map {
        it.apply {
          remove("v")
          remove("ns") // Not needed when copying from MongoDB > 4.4
        }
      }

private fun createIndexes(
  destinationDb: MongoDatabase,
  collectionName: String,
  indexes: List<Document>
) {
  destinationDb.runCommand(
    Document().append("createIndexes", collectionName)
      .append("indexes", indexes)
  )
}