Introduction
When a database write disappears after you thought it was safe, or when your replica set returns stale data during a failover, you've hit the boundary where MongoDB's durability and consistency guarantees matter most. Write concern and read concern: best practices explained is essential knowledge for anyone operating MongoDB in production, because the default settings in older versions can surprise you—and the modern defaults, while safer, still need thoughtful tuning for your application's risk profile.
Over this series we've built up our data modeling intuition; now we need to understand how MongoDB actually promises to keep that data safe. Write concern controls whether MongoDB confirms a write is durable before returning to your application. Read concern controls whether your application sees data that might be rolled back if the primary crashes. Together, they let you choose your trade-off between speed and safety.
w: 1 is fast but can lose writes on failover
The simplest write concern is w: 1, which tells MongoDB: "Return success as soon as the primary acknowledges the write to memory." This is blazingly fast because the primary doesn't wait for any replication; it just writes to its own journal buffer and sends back OK.
Here's the catch: if the primary crashes before those writes are synced to disk or replicated to secondaries, they're gone forever. Your application thinks the write succeeded, but it never made it to the replica set's persistent storage.
// Dangerous in production: primary crash = data loss
const collection = db.collection('orders');
await collection.insertOne(
{ orderId: '12345', amount: 99.99, status: 'paid' },
{ writeConcern: { w: 1 } }
);
// Primary crashes 100ms later → order is lost
This becomes a real problem in high-volume systems. Imagine an e-commerce platform processing 1,000 orders per second with w: 1. A single unplanned restart of the primary could silently lose hundreds of orders. Your payment processor thinks the payment was recorded, but MongoDB never persisted it.
We often see teams choose w: 1 early on when latency is a bottleneck—perhaps they're ingesting sensor data or tracking user clicks—and they rationalize: "We can retry from the client if needed." But once you ship that code and data starts flowing, retrofitting higher write concerns becomes an operational headache.
The lesson: w: 1 is appropriate only for data you can afford to lose or easily regenerate (session caches, analytics staging tables). For anything involving money, identity, or compliance, move up.
w: majority survives failover
Setting w: majority tells MongoDB: "Don't return success until this write is acknowledged by a majority of replica set members." If your replica set has three nodes, that means the primary and at least one secondary must acknowledge the write before MongoDB returns to your application.
Why does this matter? Because if the primary fails right after returning success, at least one secondary has that write. When the replica set elects a new primary, that write is still there. Your data survives the failover.
// Safe across failovers
const collection = db.collection('orders');
await collection.insertOne(
{ orderId: '12345', amount: 99.99, status: 'paid' },
{ writeConcern: { w: 'majority' } }
);
// Primary crashes → write is still on a secondary
// Secondary gets elected → write persists
There's a latency cost: the primary has to wait for replication to complete before returning. In our internal tests across a three-node cluster in the same data center, w: majority adds roughly 5–15ms compared to w: 1, depending on network conditions and write size. For a ride-hailing platform tracking ride requests, that's often an acceptable trade-off. For a real-time bidding system, it might not be.
Here's a practical example: a user signs up for your SaaS platform. You want to be certain that signup is recorded before you return the confirmation page. Using w: majority:
async function createAccount(userData) {
const result = await accountsCollection.insertOne(userData, {
writeConcern: { w: 'majority', j: true }
});
// By the time we reach here, the write is on the primary AND a secondary
// Even if the primary dies, the account exists
return { accountId: result.insertedId, status: 'created' };
}
Notice the j: true option: that's the journal flag, and it pairs beautifully with w: majority. It ensures writes are journaled on disk, not just in memory. You can also set a timeout; if replication doesn't complete within (say) 5 seconds, the write fails rather than hanging forever.
readConcern majority avoids rollbacks
Write concern protects outgoing data; read concern protects incoming data. When you read from a MongoDB replica set, you could accidentally read data that will be rolled back if the primary crashes and a different secondary becomes primary.
readConcern: majority says: "Only show me data that a majority of the replica set has acknowledged." This prevents you from reading data that's about to disappear. The trade-off is latency: MongoDB has to check which writes are on the majority before returning them to you.
Picture a scenario: the primary writes a transaction record, and the secondary replicates it. Your read-heavy application queries a secondary and sees that transaction. But before replication spreads further, the primary crashes. The new primary is actually the secondary you didn't query, and it never saw that transaction. Rollback happens; the transaction you read is now gone from the cluster.
With readConcern: majority:
// Reads will never be rolled back
const transaction = await transactionsCollection.findOne(
{ transactionId: 'tx-789' },
{ readConcern: { level: 'majority' } }
);
// If we see this transaction, a majority of the cluster has it
// It won't disappear due to a primary failover
This is crucial for financial systems. If your trading engine reads an order price with readConcern: local (the default) and that price later rolls back due to a failover, you've traded on stale data. Compliance and risk teams will not be happy.
One subtlety: readConcern: majority enforces what MongoDB calls "majority committed" reads. There's a small delay while the server verifies that a majority has the data. In a well-tuned LAN with three nodes, we typically see an extra 2–5ms. In a geographically distributed cluster, it could be longer.
Defaults are safe in modern versions
MongoDB 4.0 and later ship with sensible defaults: w: 1 with j: true (journaled) for write concern, and readConcern: local for reads. The journaling default is the key safety net; it means writes are durable on the primary even without replication, as long as the server doesn't lose power.
For most teams, these defaults are a solid starting point. You get reasonable durability without paying the full latency cost of w: majority on every write.
However, "safe default" doesn't mean "set and forget." You still need to:
- Audit critical operations: Identify writes that must survive failover (payments, identity changes, compliance records). Upgrade those to
w: majority. - Use readConcern: majority where reads must match writes: If you're reading back something you just wrote and need to be certain it won't roll back, use it. A financial report generator, for example, should use
readConcern: majority. - Test failovers: Actually kill your primary and watch your application. Does it handle the latency spike? Do writes timeout? Does it retry appropriately?
- Monitor replication lag: If secondaries are lagging,
readConcern: majorityreads will block until they catch up. Use MongoDB's metrics to catch this.
A real case from a SaaS platform we worked with: they used defaults everywhere, including on a user preferences update. When the primary crashed, the secondary elected as new primary had stale user preferences. Users got logged out or saw wrong settings until the data synced. After that incident, they moved user preferences to w: majority but left less critical logs at w: 1.
Common mistakes
Setting w: majority for everything. It's not wrong per se, but it's wasteful. Latency adds up when you're writing millions of documents. Reserve w: majority for data that truly must survive failover. For counters, temporary caches, or bulk analytics ingestion, w: 1 with journaling is fine.
Using readConcern: majority without write concern parity. If you write with w: 1 and read with readConcern: majority, you're reading data that might not exist. You've paid the read latency cost but not actually solved the problem. Pair them: if reads use majority, writes should too.
Forgetting the j: true flag. You can write with w: 1 and still get durability if j: true (the default in 4.0+). But if you're on an older version and someone explicitly disables journaling to save I/O, you've lost that safety net. Always double-check your configuration.
Not testing failover under load. Write concern latencies are acceptable when the network is calm. But if you failover during peak traffic, replication lag spikes, and w: majority writes start timing out. Your team gets paged at 3 AM. Test this before it's an emergency.
Setting timeouts too short. A timeout of 100ms on w: majority in a multi-region cluster will fail constantly. Tune timeouts to your network conditions, not your impatience.
Key takeaways
w: 1is fast but loses writes if the primary crashes before replication. Use it only for data you can afford to lose.w: majorityis slower but survives failover. Pair it with critical writes (payments, accounts, compliance records).readConcern: majorityprevents reading data that might be rolled back. Use it where reads must be consistent with failover behavior.- Modern MongoDB defaults are reasonable, but audit your critical paths and upgrade them deliberately.
- Always test failover scenarios. Theory is nice; reality under load is what matters.
- Monitor replication lag; high lag with
readConcern: majoritywill block reads until it improves.






Comments
Loading comments…