Skip to content
ExponaBLOG
MONGODB, NOSQL & DATA MODELING · DAY 078 / 100

Upgrading MongoDB versions: patterns, pitfalls and fixes

Stay current safely.

Expona Admin8 min read

Upgrading MongoDB versions: patterns, pitfalls and fixes

MongoDB version upgrades are inevitable, but they're also one of the most high-stakes operations a database team runs. Get the process wrong and you risk data corruption, replication lag, or worse—a production outage during peak hours. In this guide, we'll walk through the patterns, pitfalls and fixes that separate smooth upgrades from the kind that keep you awake at night.

Why version upgrades matter

Each MongoDB release brings performance improvements, new features, security patches, and sometimes breaking changes to wire protocols or index behavior. Staying on an unsupported version leaves your team exposed to bugs, security vulnerabilities, and diminishing vendor support. But rushing an upgrade without proper planning is equally dangerous.

The key to safe upgrades is understanding what changes between versions, preparing your infrastructure, and executing methodically. Let's break down the proven approach.

Read release notes — Check compatibility changes

Before you touch a single server, spend time with the official MongoDB release notes for your target version. This isn't bureaucratic busywork—it's where you discover breaking changes that could silently break your application.

Pay special attention to:

  • Wire protocol changes — Older drivers may not communicate correctly with newer servers.
  • Index behavior — Some versions change how indexes are built or managed, which can affect query performance or cause indexes to be dropped.
  • Aggregation pipeline changes — New stages, removed stages, or changed semantics in existing stages.
  • Deprecated features — MMAPv1 storage engine, certain replication features, or authentication methods.
  • Platform support — Some versions drop support for older OS versions; make sure your infrastructure qualifies.

For example, MongoDB 5.0 removed support for MMAPv1 storage engine entirely. If your deployment still uses it, you have a hard requirement to migrate to WiredTiger before upgrading. MongoDB 4.0 changed how transactions work and how certain aggregation operations behave. Upgrading without reading about these changes means your team discovers them in production.

Create a compatibility matrix for your stack:

javascript
// Compatibility checklist for MongoDB 5.0 upgrade
{
  current_version: "4.4.15",
  target_version: "5.0.8",
  
  checks: {
    storage_engine: "WiredTiger", // Required; MMAPv1 not supported in 5.0+
    driver_version: "Node.js driver 4.x", // 4.x supports both 4.4 and 5.0
    platform: "Ubuntu 20.04 LTS", // Verify OS support
    features_used: [
      "aggregation $lookup", // Check semantics changes
      "multikey indexes",
      "TTL indexes"
    ],
    deprecated_features: [
      "collStats wconcern param" // Removed in 5.0
    ]
  }
}

Document your findings in a shared wiki or issue tracker. This becomes your upgrade runbook.

Upgrade drivers first — New drivers support older servers

A common misconception is that you must upgrade servers before drivers. The truth is the opposite: modern drivers are backward-compatible with older servers, but old drivers often fail against new servers.

Upgrade your application drivers to a version that supports both your current and target MongoDB versions. This gives you a safe window to test your code against the new driver without forcing a server upgrade immediately.

The typical version compatibility matrix looks like this:

MongoDB Server Node.js Driver 3.x Node.js Driver 4.x Node.js Driver 5.x
4.0 ✓ ✓ ✗
4.4 ✓ ✓ ✗
5.0 ✗ ✓ ✓
6.0 ✗ ✗ ✓

Here's the recommended sequence:

  1. Upgrade driver to a version supporting both old and new server versions.
  2. Deploy driver change to staging and production (against current MongoDB version).
  3. Run a week or two of smoke tests and monitoring.
  4. Proceed with server upgrade.

If you skip this and jump straight to server upgrade, you risk the moment the new server starts: your old driver can't connect, handshakes fail, and you're now firefighting an outage.

javascript
// Example: upgrading from Node.js driver 3.x to 4.x
// Old driver (3.x) — will fail against MongoDB 5.0+
const { MongoClient } = require('mongodb');
const client = new MongoClient('mongodb://localhost:27017');

// New driver (4.x) — works with both MongoDB 4.4 and 5.0
// Just update package.json and change connection syntax slightly
import { MongoClient } from 'mongodb';

const client = new MongoClient('mongodb://localhost:27017', {
  maxPoolSize: 10,
  // New driver requires explicit options for some behaviors
});

Test your application thoroughly against the new driver before touching the database layer. Look for deprecation warnings in logs—these hint at what breaks when servers upgrade.

Rolling upgrades — Upgrade secondaries, then step down the primary

In a replica set, never upgrade the primary first. Upgrade secondaries one at a time, then step down the primary and upgrade it last. This keeps your cluster operational throughout the process.

The process:

  1. Upgrade secondary 1: Stop the mongod process, replace the binary, restart. The replica set continues operating normally.
  2. Wait for replication: Ensure the upgraded secondary catches up fully (oplog sync complete). Monitor replication lag.
  3. Repeat for remaining secondaries: One at a time, with verification between each.
  4. Step down the primary: Issue rs.stepDown() from the primary. It demotes itself, and a secondary is elected as new primary.
  5. Upgrade the old primary: Now upgrade the node that was previously primary (now a secondary).

Here's what this looks like in practice:

javascript
// On the replica set primary, monitor replication status
rs.status()

// Check secondaries are caught up; optime should be close to primary
{
  "members": [
    {
      "name": "primary:27017",
      "stateStr": "PRIMARY",
      "optime": { "ts": Timestamp(...), "t": 5 }
    },
    {
      "name": "secondary1:27017",
      "stateStr": "SECONDARY",
      "optime": { "ts": Timestamp(...), "t": 5 } // Caught up
    },
    {
      "name": "secondary2:27017",
      "stateStr": "SECONDARY",
      "optime": { "ts": Timestamp(...), "t": 5 } // Caught up
    }
  ]
}

// After upgrading secondary1, primary demotes itself
rs.stepDown(300) // Step down, wait 300 seconds before re-election allowed

Why this order matters: if you upgrade the primary first, any schema or wire protocol differences between old and new versions could cause replication errors before your secondaries are ready. Rolling upgrades starting with secondaries ensures the primary is the last to change, minimizing replication surprises.

Monitor replication lag closely during the upgrade window. Use tools like Mongosh or your observability stack to watch rs.status() and check db.getReplicationInfo() for oplog window health.

Feature compatibility version — Set FCV after the upgrade succeeds

After all nodes are running the new binary, you must set the Feature Compatibility Version (FCV). FCV is a configuration setting that tells MongoDB which version's features to enable. It's a safety valve that lets you run a new binary while behaving like an older version during your testing window.

The upgrade workflow for FCV is:

  1. Upgrade all binaries: All secondaries and primary are now running the new MongoDB version binary.
  2. Leave FCV unchanged initially: The cluster still behaves like the old version. This is your test window.
  3. Run smoke tests and monitoring: Let the cluster run in production for hours or days with new binaries but old feature set.
  4. Set FCV to the new version: Only after validation, update FCV to enable new features.

Setting FCV must be done from the primary:

javascript
// Check current FCV
db.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })

// Output before upgrade (running 5.0 binary, but FCV still at 4.4)
{
  "featureCompatibilityVersion": {
    "version": "4.4"
  }
}

// After upgrading binaries, set FCV to 5.0
db.adminCommand({ setFeatureCompatibilityVersion: "5.0" })

// Verify it took effect
db.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })

// Once FCV is updated, new features become active
// This is when you might see index rebuilds, internal schema changes, etc.

Why the delay between binary upgrade and FCV change? It gives you an escape hatch. If something goes wrong after the binary upgrade, you can roll back the binaries without losing data. Once FCV is set to a new version, rolling back becomes much riskier because the cluster may have written data using new storage formats.

During this FCV validation window, run your full test suite against production, increase monitoring, and watch for any subtle bugs. Only then flip the FCV switch for real.

Common mistakes

Upgrading the primary first: This breaks replication if new and old versions have protocol differences. Always upgrade secondaries first.

Skipping driver upgrades: Upgrading servers with old drivers means connection failures and timeouts. The driver update is not optional.

Not reading breaking changes: Assuming all upgrades are seamless leads to discovering incompatibilities in production. Release notes exist for a reason.

Setting FCV immediately after binary upgrade: You lose your safety window. Always run in old FCV mode first to validate the new binaries.

Upgrading multiple major versions at once: Jump from 4.0 to 5.0 in a single step is not recommended. Go 4.0 → 4.2 → 4.4 → 5.0. Each hop lets you validate incrementally.

Ignoring replication lag during the upgrade: If a secondary falls behind during upgrade and then crashes, the entire cluster could be at risk if it was your only other replica. Monitor oplog window and replication status constantly.

Key takeaways

  • Read release notes for your target version before touching infrastructure—breaking changes are documented there.
  • Upgrade drivers before servers; new drivers support old servers, but old drivers fail on new servers.
  • Use rolling upgrades on replica sets: secondaries first, then step down the primary and upgrade it last.
  • Set Feature Compatibility Version only after validating the new binaries in production with the old FCV.
  • Monitor replication lag, oplog window, and connection health throughout the upgrade process.
  • Test every step in staging first; never assume upgrading versions is transparent.
  • Create a documented runbook for your specific stack and keep it updated for future upgrades.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles