Old vs New: Legacy Methods & MMAPv1

MongoDB · Lesson 26 of 28 8 min read Updated

MongoDB has been around since 2009, and the internet is full of tutorials, Stack Overflow answers and company code written for versions that are long out of support. You will meet db.users.insert(...), find().count(), mapReduce and talk of the MMAPv1 storage engine. Some of it still runs, some prints warnings and some no longer works at all.

This lesson is your translation guide: for each old feature, what it did, why it changed and what to write instead. Start with freshly loaded data and use shop.

The shell: mongo became mongosh

The original shell was called mongo. It was replaced by mongosh, and it was removed from the server downloads in MongoDB 6.0. Most commands look the same, but the details differ:

Legacy mongo shellmongosh
Output with double quotes: "name" : "Pen"Output with single quotes and unquoted keys: name: 'Pen'
All numbers stored as doublesWhole numbers that fit in 32 bits stored as int
find().pretty() needed for readable outputOutput is formatted already; pretty() does nothing
ObjectId("...").str gives the hex stringUse .toString()
Startup file .mongorc.jsStartup file .mongoshrc.js
DBQuery.shellBatchSize = 50config.set('displayBatchSize', 50)

The number change matters for data: a collection filled half by the old shell and half by mongosh can hold qty: 1 as a double in some documents and an int in others, which confuses code that checks types. See BSON Data Types & ObjectId.

Deprecated CRUD methods

The old CRUD methods were replaced by methods whose names say exactly what they do. mongosh still runs most of the old ones, but prints a DeprecationWarning the first time you use each in a session:

Old methodUse insteadWhy it changed
insert(docOrArray)insertOne() / insertMany()One method with two behaviours was confusing
update(filter, doc, { multi })updateOne() / updateMany() / replaceOne()Easy to replace a whole document by accident
remove(filter, justOne)deleteOne() / deleteMany()Deleted all matches unless told otherwise
count() and find().count()countDocuments() / estimatedDocumentCount()Could return wrong numbers
save(doc)insertOne() or replaceOne() with upsertNot available in mongosh at all
copyTo()An aggregation ending in $outRemoved server feature
mapReduce()The aggregation pipelineSlower and harder to write
ensureIndex()createIndex()Old name kept only for compatibility

Here is the warning you see for count(), next to its modern replacement. Both return 5 accessories on fresh data:

MongoDB Shell
db.products.count({ category: 'Accessories' })
db.products.countDocuments({ category: 'Accessories' })
Output
DeprecationWarning: Collection.count() is deprecated. Use countDocuments or estimatedDocumentCount.
5
5

The answers match here, but count() without a filter reads a number from the collection's metadata, which can be wrong after an unclean shutdown or on a sharded cluster. countDocuments() always counts the matching documents, and estimatedDocumentCount() is the honest name for the fast metadata version.

insert() still works, warns, and returns the same result shape as insertMany():

MongoDB Shell
db.notes.insert({ text: 'Restock USB-C cables before Diwali' })
Output
DeprecationWarning: Collection.insert() is deprecated. Use insertOne, insertMany, or bulkWrite.
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('6ae45a1cc3f5e8a91d2b7a0f') }
}

The update() trap

In the legacy shell, update() with a plain document replaced the whole matching document:

MongoDB Shell
// Legacy shell: this replaced product 104 with { _id: 104, price: 249 }
db.products.update({ _id: 104 }, { price: 249 })

Every other field (name, category, stock, tags) silently disappeared. Countless production bugs came from this. In mongosh, update() passes your document to the modern update path, which refuses a document without update operators, so the line above now fails with MongoInvalidArgumentError: Update document requires atomic operators. The fix is to say what you mean:

MongoDB Shell
db.products.updateOne({ _id: 104 }, { $set: { price: 249 } })
Output
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}

Use replaceOne() on the rare occasions when you really want to replace a document. Reload the shop data afterwards.

mapReduce versus aggregation

mapReduce ran JavaScript functions on the server to group and summarise data. It was deprecated in MongoDB 5.0 in favour of the aggregation pipeline, which is faster, uses indexes and is easier to read. A legacy "revenue per status" job and its modern replacement:

MongoDB Shell
// Legacy: deprecated
db.orders.mapReduce(
  function () { emit(this.status, this.total); },
  function (key, values) { return Array.sum(values); },
  { out: { inline: 1 } }
)

// Modern
db.orders.aggregate([{ $group: { _id: '$status', revenue: { $sum: '$total' } } }])

Other server-side JavaScript has a more mixed history: $where, $function and $accumulator were deprecated in 8.0 and un-deprecated in 9.0, which runs them on a new WebAssembly-based engine. They are still slower than native operators, so prefer $expr and the aggregation operators you learned in The Aggregation Pipeline. Older commands such as group and db.eval() were removed long ago.

Storage engines: MMAPv1 and WiredTiger

The storage engine is the part of the server that writes documents to disk. MongoDB's original engine, MMAPv1, mapped data files directly into memory. It was simple but had serious limits:

  • Coarse locking. Writes locked a whole database in early versions and a whole collection later, so busy collections queued up.
  • No compression. Data used much more disk space.
  • Wasted space. Documents that grew had to be moved, leaving gaps, and files had to be padded.

WiredTiger became the default engine in MongoDB 3.2. It brought document-level concurrency (two writes to different documents in one collection do not block each other), compression of data and indexes (Snappy by default, with zlib and zstd available), checkpoints plus a write-ahead journal for crash recovery, and the foundation for transactions. MMAPv1 was deprecated in 4.0 and removed in 4.2. If an old article tunes MMAPv1 settings such as smallfiles or padding factors, ignore it; every supported server runs WiredTiger.

Other legacy patterns in application code

  • Callbacks in drivers. Node.js driver code with function (err, result) callbacks predates driver 5.0, which removed them. Use async/await, as in The Node.js Driver.
  • Old connection options. useNewUrlParser and useUnifiedTopology have had no effect since driver 4 and should be deleted.
  • background: true on index builds. Ignored since 4.2, where all builds use the same optimised process.
  • Very old drivers. Servers since 6.0 no longer accept some outdated network message formats, so applications using very old drivers cannot connect at all. Upgrade drivers before you upgrade the server.

Common mistakes

  • Copying old tutorials line by line. Check every method against the modern list above before using it.
  • Ignoring DeprecationWarnings. They mark code that may break in a later release; fix them when you see them.
  • Assuming count() is exact. Use countDocuments() when the number matters.
  • Mixing shells on one collection. Old-shell doubles and mongosh ints in the same field cause type surprises.

Key points

  • The legacy mongo shell was removed in MongoDB 6.0; mongosh replaced it, with different output and number handling.
  • insert(), update(), remove() and count() are deprecated in mongosh; use insertOne/insertMany, updateOne/updateMany/replaceOne, deleteOne/deleteMany and countDocuments/estimatedDocumentCount.
  • save() does not exist in mongosh, and update() without operators now fails instead of replacing the document.
  • mapReduce is deprecated; use the aggregation pipeline. $where and $function were un-deprecated in 9.0 but remain slower.
  • MMAPv1 was removed in 4.2; WiredTiger, the default since 3.2, gives document-level concurrency, compression and journaling.
  • Remove callbacks, obsolete connection options and background index options from old application code.

Exercise

Modernise this legacy script so it runs in mongosh without warnings and with the same intent:

db.products.insert([{ _id: 113, name: 'Phone Tripod', price: 699 }, { _id: 114, name: 'Laptop Sleeve', price: 799 }]); db.products.update({ category: 'Accessories' }, { $inc: { stock: 5 } }, { multi: true }); db.products.remove({ stock: 0 }); print(db.products.find({ price: { $lt: 1000 } }).count());

Show solution
MongoDB Shell
db.products.insertMany([
  { _id: 113, name: 'Phone Tripod', price: 699 },
  { _id: 114, name: 'Laptop Sleeve', price: 799 }
])
db.products.updateMany({ category: 'Accessories' }, { $inc: { stock: 5 } })
db.products.deleteMany({ stock: 0 })
print(db.products.countDocuments({ price: { $lt: 1000 } }))

insert() with an array becomes insertMany(), update() with multi: true becomes updateMany(), and remove() without justOne deleted every match, so it becomes deleteMany(). The cursor count() becomes countDocuments() with the same filter. On fresh data the final count is 5: the three cheap products plus the two new ones, since the accessories restock means no product has zero stock left to delete.

Quick Quiz

1. Which method replaces the deprecated count() when you need an exact number of matches?

2. In the legacy shell, what did db.products.update({ _id: 104 }, { price: 249 }) do?

3. When was the MMAPv1 storage engine removed?

4. What is the modern replacement for most mapReduce jobs?

Learn MongoDB with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call