MongoDB: Replica Set – Switchover

MongoDB: Replica Set – Switchover (Change the Primary and the Priority)

Table of Contents



1. Overview and Golden Rules

In Oracle Data Guard, a switchover is a planned role change with no data loss. MongoDB has no SWITCHOVER command, but the same result is done with two things: change the priority, then step up the new primary. This guide moves the primary role from mongo01 to mongo02 and makes mongo02 the preferred primary.

HostnameIPRole
mongo01192.168.2.101Primary (priority 2)
mongo02192.168.2.102Secondary
mongo03192.168.3.103Secondary

Before and after the switchover:

HostnameBeforeAfter
mongo01PRIMARY, priority 2SECONDARY, priority 1
mongo02SECONDARY, priority 1PRIMARY, priority 2
mongo03SECONDARY, priority 1SECONDARY, priority 1 (no change)

Replica set name: rs0. The service is mongod.service, managed with systemctl only. No restart of mongod is needed for this switchover.

💡 Golden rules:
• Change the priority and switch the primary. Doing only the step down is not enough: with mongo01 still at priority 2, it would take the primary role back by itself.
• Do it in a quiet window. Clients see a short interruption (usually a few seconds) while the new primary is elected.
• mongo02 must be SECONDARY, healthy and caught up (about 0 seconds lag) before you start.
• All 3 members stay up during the work, so the majority is never at risk.
• Run the configuration change on the primary only.

2. Pre-Checks Before the Switchover

Step 1 – Connect and check member health. All members must be healthy.

/app/mongodb/7.0.43/bin/mongosh --host localhost -u admin --authenticationDatabase admin
Enterprise rs0 [primary] test> rs.status().members.map(m => m.name + " " + m.stateStr + " health=" + m.health)
[
'mongo01:27017 PRIMARY health=1',
'mongo02:27017 SECONDARY health=1',
'mongo03:27017 SECONDARY health=1'
]

Step 2 – Check the replication lag. mongo02 must be at or close to 0 seconds behind the primary.

Enterprise rs0 [primary] test> rs.printSecondaryReplicationInfo()
source: mongo02:27017
{
syncedTo: 'Sun Nov 08 2026 04:29:31 GMT-0500 (Eastern Standard Time)',
replLag: '0 secs (0 hrs) behind the primary '          <------------------- no lag
}
---
source: mongo03:27017
{
syncedTo: 'Sun Nov 08 2026 04:29:31 GMT-0500 (Eastern Standard Time)',
replLag: '0 secs (0 hrs) behind the primary '          <------------------- no lag
}
Enterprise rs0 [primary] test>

Step 3 – Check the current priority and votes.

Enterprise rs0 [primary] test> rs.conf().members.map(m => ({ name: m.host, priority: m.priority, votes: m.votes }))
[
  { name: 'mongo01:27017', priority: 2, votes: 1 },   <-------- priority: 2
  { name: 'mongo02:27017', priority: 1, votes: 1 },
  { name: 'mongo03:27017', priority: 1, votes: 1 }
]

⚠️ Before the switchover: tell the application team about the window. Applications that use the full replica set URI with retryWrites=true handle the primary change automatically. Take a note of the current priorities so you can roll back.

3. Switchover: mongo01 to mongo02

   Order of work:

   1. Change the priority  : mongo02 = 2, mongo01 = 1   (run on the current primary, mongo01)
   2. Switch the primary   : mongo02 steps up            (run on mongo02)
   3. Verify               : states, priority, lag

Step 1 – Change the priority. Connect to the current primary (mongo01) and run:

Enterprise rs0 [primary] test> cfg = rs.conf()
Enterprise rs0 [primary] test> cfg.members.find(m => m.host == "mongo02:27017").priority = 2
Enterprise rs0 [primary] test> cfg.members.find(m => m.host == "mongo01:27017").priority = 1
Enterprise rs0 [primary] test> rs.reconfig(cfg)
{ ok: 1 }

OUTPUT:

Enterprise rs0 [primary] test> rs.conf().members.map(m => ({ name: m.host, priority: m.priority, votes: m.votes }))
[
  { name: 'mongo01:27017', priority: 2, votes: 1 },
  { name: 'mongo02:27017', priority: 1, votes: 1 },
  { name: 'mongo03:27017', priority: 1, votes: 1 }
]
Enterprise rs0 [primary] test> cfg = rs.conf()
{
  _id: 'rs0',
  version: 1,
  term: 14,
  members: [
    {
      _id: 0,
      host: 'mongo01:27017',
      arbiterOnly: false,
      buildIndexes: true,
      hidden: false,
      priority: 2,
      tags: {},
      secondaryDelaySecs: Long('0'),
      votes: 1
    },
    {
      _id: 1,
      host: 'mongo02:27017',
      arbiterOnly: false,
      buildIndexes: true,
      hidden: false,
      priority: 1,
      tags: {},
      secondaryDelaySecs: Long('0'),
      votes: 1
    },
    {
      _id: 2,
      host: 'mongo03:27017',
      arbiterOnly: false,
      buildIndexes: true,
      hidden: false,
      priority: 1,
      tags: {},
      secondaryDelaySecs: Long('0'),
      votes: 1
    }
  ],
  protocolVersion: Long('1'),
  writeConcernMajorityJournalDefault: true,
  settings: {
    chainingAllowed: true,
    heartbeatIntervalMillis: 2000,
    heartbeatTimeoutSecs: 10,
    electionTimeoutMillis: 10000,
    catchUpTimeoutMillis: -1,
    catchUpTakeoverDelayMillis: 30000,
    getLastErrorModes: {},
    getLastErrorDefaults: { w: 1, wtimeout: 0 },
    replicaSetId: ObjectId('6aede6a94cb205dabb21ac45')
  }
}
Enterprise rs0 [primary] test>
Enterprise rs0 [primary] test> cfg.members.find(m => m.host == "mongo02:27017").priority = 2
2
Enterprise rs0 [primary] test> cfg.members.find(m => m.host == "mongo01:27017").priority = 1
1
Enterprise rs0 [primary] test> rs.reconfig(cfg)
{
ok: 1,
'$clusterTime': {
clusterTime: Timestamp({ t: 1794130485, i: 1 }),
signature: {
hash: Binary.createFromBase64('h7+bP4y2IsnEU+2t3PKEvMc+EAQ=', 0),
keyId: Long('7705068198269288454')
}
},
operationTime: Timestamp({ t: 1794130485, i: 1 })
}
Enterprise rs0 [primary] test>

The members are found by host name, not by array position, so the right node is changed even if the order in the config differs from the node numbers.

💡 What happens now: mongo02 now has the highest priority and is caught up, so MongoDB can move the primary role to it by itself within a few seconds (a priority takeover). The shell may disconnect and reconnect. You may find that mongo02 is already PRIMARY after this step. Check in Step 2 before you run anything else.

Step 2 – Check who is primary now.

Enterprise rs0 [direct: secondary] test> rs.status().members.map(m => m.name + " " + m.stateStr)
[
'mongo01:27017 SECONDARY',
'mongo02:27017 PRIMARY',   <------ Now mongo02 is the primary 
'mongo03:27017 SECONDARY'
]
Enterprise rs0 [direct: secondary] test>

If mongo01 is still PRIMARY, do Step 3.

If mongo02 is already PRIMARY, skip Step 3 and go to Step 4.

Step 3 – Step up mongo02 (only if it is not yet primary). Connect to mongo02 and ask it to run for primary:

/app/mongodb/7.0.43/bin/mongosh --host mongo02 -u admin --authenticationDatabase admin

Enterprise rs0 [direct: secondary] test> db.adminCommand({ replSetStepUp: 1 })
{ ok: 1 }

mongo01 sees the higher term and steps down by itself. As an alternative, you can run rs.stepDown(120) on mongo01, and mongo02 wins the election because of its higher priority. replSetStepUp is more direct, because it makes sure mongo02 is the node that runs.

Step 4 – Verify the new roles.

Connect on Mongo02 and run : /app/mongodb/7.0.43/bin/mongosh --host localhost -u admin --authenticationDatabase admin

Enterprise rs0 [primary] test> rs.status().members.map(m => m.name + "  " + m.stateStr + "  health=" + m.health)
[
  'mongo01:27017  SECONDARY  health=1',
  'mongo02:27017  PRIMARY  health=1',
  'mongo03:27017  SECONDARY  health=1'
]

Enterprise rs0 [primary] test> rs.conf().members.map(m => ({ name: m.host, priority: m.priority }))
[
  { name: 'mongo01:27017', priority: 1 },
  { name: 'mongo02:27017', priority: 2 },
  { name: 'mongo03:27017', priority: 1 }
]

Step 5 – Check the lag and a test write. Both secondaries should follow the new primary with about 0 seconds of lag.

Enterprise rs0 [direct: primary] test> rs.printSecondaryReplicationInfo()
source: mongo01:27017
{
  syncedTo: 'Sun Nov 08 2026 04:45:05 GMT-0500 (Eastern Standard Time)',
  replLag: '0 secs (0 hrs) behind the primary '       <------------------ no lag
}
---
source: mongo03:27017
{
  syncedTo: 'Sun Nov 08 2026 04:45:05 GMT-0500 (Eastern Standard Time)',
  replLag: '0 secs (0 hrs) behind the primary '       <------------------ no lag
}

# Create a table called test 
Enterprise rs0 [direct: primary] test> db.switchover_test.insertOne({ ts: new Date() }, { writeConcern: { w: "majority" } })
{
  acknowledged: true,
  insertedId: ObjectId('6af044b437211cbd3e2e7d2d')
}
Enterprise rs0 [direct: primary] test>

# Drop table called test
Enterprise rs0 [direct: primary] test> db.switchover_test.drop()
true
Enterprise rs0 [direct: primary] test>

An acknowledged insert with w: "majority" proves that the new primary accepts writes and replicates them to a majority. You can drop the test collection afterwards with db.switchover_test.drop().

✅ Switchover complete. mongo02 is the primary with priority 2, mongo01 is a secondary with priority 1, and no data was lost. The priority is stored in the replica set configuration, so it survives restarts and reboots.

4. Switch Back or Roll Back

To return to the original layout (mongo01 primary with priority 2), run the same procedure in reverse. Connect to the current primary (mongo02) and run:

Enterprise rs0 [primary] test> cfg = rs.conf()
Enterprise rs0 [primary] test> cfg.members.find(m => m.host == "mongo01:27017").priority = 2
Enterprise rs0 [primary] test> cfg.members.find(m => m.host == "mongo02:27017").priority = 1
Enterprise rs0 [primary] test> rs.reconfig(cfg)

Then check the roles as in Step 2. If mongo01 is not primary yet, connect to mongo01 and run db.adminCommand({ replSetStepUp: 1 }).

⚠️ If something goes wrong in the middle: do not restart mongod. Run rs.status() to see the real state. If the priority change was done but the primary did not move, run Step 3. If you want to cancel, set the priorities back as shown above. As long as 2 of 3 members are up, the replica set keeps working.

5. Quick Reference

TaskCommand
Member statesrs.status().members.map(m => m.name + " " + m.stateStr)
Replication lagrs.printSecondaryReplicationInfo()
Priority and votesrs.conf().members.map(m => ({ name: m.host, priority: m.priority, votes: m.votes }))
Change priority of one membercfg = rs.conf(); cfg.members.find(m => m.host == "mongo02:27017").priority = 2; rs.reconfig(cfg)
Make a chosen secondary primarydb.adminCommand({ replSetStepUp: 1 }) (run on that secondary)
Step down the current primaryrs.stepDown(120)
Block a node from becoming primaryrs.freeze(120), cancel with rs.freeze(0)
Test write with majoritydb.switchover_test.insertOne({ ts: new Date() }, { writeConcern: { w: "majority" } })

💡 Tip: keep a second shell open on a node that stays up during the work, and re-run the member status command after each step. It is the quickest way to see where the set is.

6. Common Mistakes and Troubleshooting

• Doing only the step down and not the priority change. The node with the highest priority takes the primary role back.
• Changing the priority by array position (cfg.members[1]) without checking the host. Use find(m => m.host == ...).
• Running rs.reconfig() on a secondary. Run it on the primary.
• Switching over to a lagging secondary. Check the lag first.
• Restarting mongod to “apply” the change. It is not needed. The change is applied online.
• Forgetting to update your documents and monitoring that assume mongo01 is the primary.

SymptomCheck
rs.reconfig() fails with “not primary”You are on a secondary. Connect to the current primary and run it again.
replSetStepUp fails with “Election failed”The node is not caught up, or it cannot see a majority. Check rs.printSecondaryReplicationInfo(), wait for 0 lag, and try again.
mongo01 becomes primary againCheck the priorities with rs.conf(). mongo01 must have a lower priority than mongo02 after the change.
Priority change did not move the primaryA priority takeover can take a few seconds. Check rs.status() again, then use replSetStepUp on mongo02.
Application errors during the switchA short interruption is expected. Use retryWrites=true and the full URI: mongodb://mongo01:27017,mongo02:27017,mongo03:27017/?replicaSet=rs0.
Caution: Your use of any information or materials on this website is entirely at your own risk. It is provided for educational purposes only. It has been tested internally, however, we do not guarantee that it will work for you. Ensure that you run it in your test environment before using.
Thank you
Rajasekhar Amudala