1. Replica Sets & Elections
A replica set is a group of mongod processes holding
the same data set. One member is elected primary and accepts all
writes; the others are secondaries, continuously replicating the
primary's operations from its oplog (operations log) and serving
reads if configured to. If the primary becomes unreachable — a crash, a network
partition — the remaining members hold an election and promote one secondary to
primary automatically, usually within a few seconds.
rs.status()
// stateStr on each member reports "PRIMARY", "SECONDARY", or a transitional state
// like "STARTUP2" or "RECOVERING" while it catches up on the oplog
rs.isMaster()
// quick summary: which host is currently primary, and this connection's own role
An election requires a majority of voting members to agree — which is exactly why replica sets are deployed with an odd number of members (3, 5): a 3-node set tolerates one member going down and still has the 2-node majority needed to elect a primary; a 2-node set has no way to reach majority if either side is unreachable.
2. Write Concern
Write concern controls how many replica set members must acknowledge a write before the driver reports it as successful — the knob that trades latency against durability, per operation.
// w: 1 (default) -- acknowledged once the PRIMARY has applied it.
// Fast, but a primary crash before replication means this write can be lost.
db.orders.insertOne(doc, { writeConcern: { w: 1 } });
// w: "majority" -- acknowledged once a majority of members have applied it.
// Survives a primary failing over right after the write. Slightly slower.
db.orders.insertOne(doc, { writeConcern: { w: "majority" } });
// w: 0 -- fire-and-forget, no acknowledgment at all. Fastest, least safe.
db.metrics.insertOne(doc, { writeConcern: { w: 0 } });
w: "majority" is the right default for anything that matters — an
order, a payment record, an account balance change. w: 1 or
w: 0 are reasonable only for data where losing the occasional write
during a rare failover is genuinely acceptable, like a high-volume metrics stream.
3. Read Preference
Read preference controls which replica set members a read is allowed to target. Reading only from the primary guarantees you see the most recent write; reading from secondaries spreads load but risks replication lag — a secondary that hasn't yet applied the primary's most recent operations.
// primary (default) -- always the most current data, no lag risk
db.orders.find().readPref("primary");
// secondaryPreferred -- prefer a secondary, fall back to primary if none available
// Good for read-heavy reporting queries that can tolerate slightly stale data
db.orders.find().readPref("secondaryPreferred");
// secondary -- only ever read from a secondary; errors if none is reachable
db.orders.find().readPref("secondary");
A reasonable rule of thumb: user-facing reads that must reflect a write the user
just made (their own order confirmation, their own account balance) stay on
primary; background reporting or analytics queries that can tolerate
a few seconds of staleness are good candidates for
secondaryPreferred, taking load off the primary.
4. Standing Up a Local Replica Set
A 3-node replica set can run entirely on one machine for learning purposes — three
mongod processes on different ports, initialized as a set.
mkdir -p /data/rs0-0 /data/rs0-1 /data/rs0-2
mongod --replSet rs0 --port 27017 --dbpath /data/rs0-0 --bind_ip localhost &
mongod --replSet rs0 --port 27018 --dbpath /data/rs0-1 --bind_ip localhost &
mongod --replSet rs0 --port 27019 --dbpath /data/rs0-2 --bind_ip localhost &
mongosh --port 27017
rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "localhost:27017" },
{ _id: 1, host: "localhost:27018" },
{ _id: 2, host: "localhost:27019" }
]
});
rs.status(); // one member should now report stateStr: "PRIMARY"
An application connects with a replica set connection string listing every member — the driver discovers which one is currently primary, and automatically reconnects to the new primary after a failover without the application needing to handle that itself.
mongodb://localhost:27017,localhost:27018,localhost:27019/school?replicaSet=rs0
5. Watching a Failover Happen
The clearest way to understand automatic failover is to trigger one on your local set and watch the members react.
// From mongosh, connected to the current primary:
db.adminCommand({ replSetStepDown: 60 }); // steps this member down for 60 seconds
// Re-run rs.status() from any member -- within a few seconds, a different
// member's stateStr flips to "PRIMARY", and the stepped-down node becomes "SECONDARY"
For a few seconds while an election runs, the replica set has no primary and cannot accept writes — the driver queues or retries them rather than failing immediately, but an application under heavy load will still see elevated latency during that window. This is exactly why w: "majority" matters: it guarantees a write acknowledged just before a failover survived it.
6. Hands-on Exercise
Stand up a 3-node replica set and survive a failover
Build the local replica set from Section 4, then prove your application code keeps working through a forced failover.
Requirements:
- Start three
mongodprocesses on three ports with a shared--replSetname, and initiate the set withrs.initiate(). - Connect a small Node.js script using the full replica-set connection string (all three hosts,
?replicaSet=rs0) and insert 10 documents withwriteConcern: { w: "majority" }. - While the script is running a loop of inserts (one every second), force a failover with
replSetStepDownon the current primary from a separatemongoshsession. - Confirm the script's inserts pause briefly during the election and then continue succeeding once a new primary is elected — without you changing the connection string or restarting the script.
- Run
rs.status()afterward and confirm all 3 members show a healthystateStr(onePRIMARY, twoSECONDARY).
If your script errors out instead of pausing-and-continuing during the failover, check that you're catching and retrying transient network errors around each insert — the driver reconnects to the new primary automatically, but an unhandled rejection from a single failed attempt will still crash a script with no retry logic.
7. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
Why are replica sets almost always deployed with an odd number of members (3 or 5), not an even number (2 or 4)?
Why are replica sets almost always deployed with an odd number of members (3 or 5), not an even number (2 or 4)?
An election needs a majority of members to agree on the new primary. With an odd count, that majority is always clearly reachable even if some members are unavailable — a 3-node set can lose 1 member and still reach a 2-node majority. A 2-node set has no way to form a majority if the two members can't reach each other, since 1-of-2 isn't a majority.
Q2
What's the practical risk of using writeConcern: { w: 1 } (the default) for a payment record?
What's the practical risk of using writeConcern: { w: 1 } (the default) for a payment record?
w: 1 acknowledges a write as soon as the primary alone has applied it, before it's replicated to any secondary. If that primary crashes before replicating the write and a secondary is elected in its place, the write can be lost entirely, even though the application already received a success response. w: "majority" avoids this by waiting for a majority of members to have the write before acknowledging it.
Q3
When is readPref("secondaryPreferred") a reasonable choice, and when is it risky?
When is readPref("secondaryPreferred") a reasonable choice, and when is it risky?
It's reasonable for reporting or analytics queries that can tolerate a few seconds of replication lag and benefit from taking load off the primary. It's risky for any read that must reflect a write the same user just made — like showing an order confirmation immediately after placing it — since a lagging secondary might not have replicated that write yet.
Q4
Why does an application connect with a full replica-set connection string listing all members, instead of just the current primary's address?
Why does an application connect with a full replica-set connection string listing all members, instead of just the current primary's address?
The driver uses the full member list to discover which host is currently primary, and to automatically reconnect to whichever member gets elected primary after a failover. Hardcoding just today's primary address would break the application the moment a failover promoted a different member — the whole point of the replica-set connection string is that the application never needs to know or care which specific node is primary.