π Read/Write Path: Data Journey Through MongoDB
Understanding how data flows from your application through MongoDB's layers to disk and back
π The Restaurant Kitchen Story
Imagine MongoDB as a restaurant kitchen. Let's see how orders (data) flow...
Write Path: Taking an Order
Customer places order β Kitchen processes it
The Journey:
- Customer orders (Application sends write request)
- Waiter writes it on notepad (WiredTiger Cache - RAM)
- Kitchen log book updated (Journal - for safety)
- Every minute, notepad β recipe book (Checkpoint to disk)
- Receipt given to customer (Acknowledgment sent)
Result: Order recorded instantly (1ms), fully saved every 60s!
Read Path: Serving Food
Customer wants to know order status
The Journey:
- Customer asks about order (Application sends read request)
- Check waiter's notepad first (WiredTiger Cache - super fast!)
- If not on notepad β check recipe book (Read from disk - slower)
- Copy to notepad for next time (Cache it)
- Tell customer (Return data)
Result: Notepad (cache) = instant (100ΞΌs), Recipe book (disk) = slower (10ms)
π‘ The Key Insight
Cache (Notepad) = Fast but temporary
Disk (Recipe Book) = Slower but permanent
MongoDB balances speed and durability! π―
βοΈ Write Path: Complete Flow
π Write Path Timeline:
- 0ms: Application issues insert/update command
- ~0.5ms: MongoDB driver sends request to mongod
- ~1ms: mongod writes to WiredTiger Cache (RAM) - Write acknowledged here! β‘
- 50ms: Journal synced to disk (crash safety guaranteed)
- 60s: Checkpoint flushes cache to .wt data files (durable on disk)
π Read Path: Complete Flow
π Read Path Logic:
- Step 1: Application sends find/findOne query
- Step 2: mongod checks WiredTiger Cache first (RAM)
- Step 3a (Cache Hit): Data found β Return immediately (~100ΞΌs) β‘
- Step 3b (Cache Miss): Not in cache β Read from .wt files on disk (~10-50ms) π’
- Step 4: If from disk β Load into cache for future reads
- Goal: Keep cache hit ratio >90% for best performance!
βοΈ Write Concerns: Durability Levels
Write concern controls when MongoDB acknowledges a write -
Trade-off between speed vs durability
w: 0 (Unacknowledged)
Behavior: Fire-and-forget, no acknowledgment
Speed: β‘ Fastest (~0.1ms)
Durability: β οΈ No guarantee
Use case: Logs, analytics where loss is acceptable
w: 1 (Default - Acknowledged)
Behavior: Acknowledged when written to primary's cache
Speed: β‘ Fast (~1ms)
Durability: β Good (journal ensures recovery)
Use case: Most applications, balanced approach
w: "majority" (Replica Set)
Behavior: Acknowledged when written to majority of nodes
Speed: π’ Slower (~10-50ms)
Durability: β Excellent (survives node failures)
Use case: Critical data (banking, healthcare)
j: true (Journaled)
Behavior: Acknowledged when written to journal on disk
Speed: π’ Slower (~50ms)
Durability: β Excellent (crash-safe)
Use case: Single node with high durability needs
π‘ Configuration Example:
// Fast, default
db.users.insertOne(doc, { writeConcern: { w: 1 } })
// Highly durable, slower
db.transactions.insertOne(doc, {
writeConcern: { w: "majority", j: true }
})
π Read Preferences: Where to Read
Read preference determines which replica set member handles read operations -
Trade-off between consistency vs performance/availability
primary (Default)
Behavior: All reads from primary node
Consistency: β Strongest (always latest data)
Performance: π’ Primary can be bottleneck
Use case: Default for most applications
primaryPreferred
Behavior: Primary if available, else secondary
Consistency: β Good (slight lag on failover)
Performance: β‘ Better availability
Use case: High availability with consistency preference
secondary
Behavior: All reads from secondary nodes
Consistency: β οΈ Eventual (may be stale)
Performance: β‘ Distributes read load
Use case: Analytics, reporting, search (stale OK)
secondaryPreferred
Behavior: Secondary if available, else primary
Consistency: β οΈ Eventual (may be stale)
Performance: β‘ Best read distribution
Use case: Read-heavy apps, analytics dashboards
nearest
Behavior: Read from closest node (lowest latency)
Consistency: β οΈ Eventual (may be stale)
Performance: β‘ Lowest latency
Use case: Geo-distributed apps, multi-region
π‘ Configuration Example:
// Read from primary (default)
db.users.find().readPref("primary")
// Read from secondaries (analytics)
db.logs.find().readPref("secondary")
// Read from nearest (geo-distributed)
db.products.find().readPref("nearest")
π Performance Optimizations
π 1. Indexes
Without index: Collection scan (slow) π’
With index: Direct lookup (fast) β‘
// Create index on email field
db.users.createIndex({ email: 1 })
// Query now uses index
db.users.find({ email: "john@example.com" })
// ~0.1ms instead of 100ms!
π 2. Connection Pooling
Reuse connections instead of creating new ones for each request
// Connection pool configuration
const client = new MongoClient(uri, {
maxPoolSize: 100, // Max connections
minPoolSize: 10 // Min connections
});
π― 3. Projection (Select Only Needed Fields)
Don't fetch entire document if you only need specific fields
// Bad: Fetch entire document
db.users.find({ age: { $gt: 25 } })
// Good: Fetch only name and email
db.users.find(
{ age: { $gt: 25 } },
{ name: 1, email: 1, _id: 0 }
)
π¦ 4. Batch Operations
Insert multiple documents in one call instead of many individual calls
// Bad: 1000 individual inserts
for (let i = 0; i < 1000; i++) {
db.users.insertOne(docs[i]) // 1000ms
}
// Good: One bulk insert
db.users.insertMany(docs) // 10ms!
β Interview Questions & Answers
Answer:
Write Path Flow:
- Application (0ms): Issues insert/update command via MongoDB driver
- MongoDB Driver (~0.5ms): Sends request to mongod process over network
- mongod Process (~1ms): Parses query and routes to storage engine
- WiredTiger Cache (~1ms): Writes to in-memory cache (RAM) - Write acknowledged here!
- Journal (~50ms): Operation logged to write-ahead log (WAL) on disk for crash recovery
- Checkpoint (~60 seconds): Dirty pages flushed from cache to .wt data files on disk
Key Points:
- Write is acknowledged after step 4 (~1ms) for speed
- Journal ensures durability - on crash, replay journal to recover uncommitted writes
- Checkpoint creates consistent snapshot every 60 seconds
- Trade-off: Speed (cache) vs Durability (journal + checkpoints)
Answer:
Cache Hit (Fast β‘):
- Data found in WiredTiger Cache (RAM)
- Return immediately without disk access
- Latency: ~100 microseconds (0.1ms)
- Example: Reading a user profile that was recently accessed
Cache Miss (Slower π’):
- Data NOT in cache β must read from disk (.wt files)
- After reading from disk, load into cache for future reads
- Latency: ~10-50 milliseconds (100-500x slower than cache hit)
- Example: Reading a document that hasn't been accessed in hours
Why Cache Hit Ratio Matters:
- Goal: Keep cache hit ratio >90%
- 90% hit rate = 10x faster average reads than 50% hit rate
- Increase cache size (more RAM) to improve hit ratio
Answer:
Write concern controls when MongoDB acknowledges a write operation. It's a trade-off between speed and durability.
w: 1 (Default - Acknowledged):
- Behavior: Acknowledged when written to primary node's cache
- Speed: Fast (~1ms)
- Durability: Good - journal ensures recovery on crash
- Risk: If primary fails before replicating, write could be lost
- Use case: Most applications, balanced approach
w: "majority" (Replica Set):
- Behavior: Acknowledged when written to majority of nodes (e.g., 2 out of 3)
- Speed: Slower (~10-50ms depending on network)
- Durability: Excellent - survives single node failures
- Risk: Very low - write is durable even if primary fails
- Use case: Critical data (banking, healthcare, financial transactions)
Example Configuration:
// Fast, default
db.users.insertOne(doc, { writeConcern: { w: 1 } })
// Highly durable, slower
db.transactions.insertOne(doc, {
writeConcern: { w: "majority", j: true }
})
Answer:
Read preference determines which replica set member handles read operations. Trade-off between consistency and performance.
primary (Default):
- Behavior: All reads from primary node
- Consistency: Strongest - always latest data
- Performance: Primary can become bottleneck
- Use case: When you need guaranteed latest data (user profile updates, inventory)
secondary:
- Behavior: All reads from secondary nodes
- Consistency: Eventual - may be stale (lag 0-few seconds)
- Performance: Distributes read load across secondaries
- Use case: Analytics, reporting, search, dashboards (where stale data is acceptable)
When to use secondary:
- Read-heavy workloads that don't need latest data
- Reporting and analytics queries
- Search functionality
- Background batch processing
When to use primary:
- Transactional data requiring latest state
- Read-your-own-writes scenarios
- Critical operations (money transfers, inventory checks)
Example:
// Primary for critical reads
db.inventory.findOne({ sku: "ABC123" }).readPref("primary")
// Secondary for analytics
db.orders.aggregate([...]).readPref("secondary")
Answer:
MongoDB uses multiple mechanisms to ensure data durability:
1. Journal (Write-Ahead Log):
- All write operations logged to journal before applying
- Journal synced to disk every 50ms (configurable)
- On crash: Replay journal entries to recover uncommitted writes
- Location:
dbPath/journal/directory
2. Checkpoints:
- Every 60 seconds, flush WiredTiger cache to disk
- Creates consistent snapshot of all data
- After checkpoint, journal entries before that point can be deleted
3. Replica Sets (w: "majority"):
- Write acknowledged only after majority of nodes have it
- Survives single node failures
- Example: 3-node cluster needs 2 nodes to acknowledge
Crash Recovery Process:
1. MongoDB crashes at 10:30:45 2. Last checkpoint: 10:30:00 3. On restart: - Load checkpoint (consistent state at 10:30:00) - Replay journal (10:30:00 to 10:30:45) - All committed writes recovered! β
Trade-offs:
- More durability = Higher latency (journal sync, replication)
- Less durability = Lower latency (cache-only writes)
- Configure via writeConcern based on use case
Answer:
Read Optimizations:
- Indexes: Create indexes on frequently queried fields
db.users.createIndex({ email: 1 }) // 100x faster lookups - Projections: Fetch only needed fields
db.users.find({}, { name: 1, email: 1 }) // Less data transferred - Covered Queries: Query + projection covered entirely by index (no document fetch)
db.users.createIndex({ email: 1, name: 1 }) db.users.find({ email: "x" }, { name: 1, _id: 0 }) // Covered! - Increase Cache Size: More RAM = higher cache hit ratio
wiredTiger.cacheSizeGB: 16 // Larger cache
- Read from Secondaries: Distribute load for analytics/reporting
db.logs.find().readPref("secondary")
Write Optimizations:
- Batch Operations: Insert many docs at once
db.users.insertMany(docs) // 100x faster than individual inserts
- Lower Write Concern: For non-critical writes
db.logs.insertOne(doc, { w: 1 }) // Faster than w: "majority" - Reduce Index Count: Each index slows writes
// Remove unused indexes db.users.dropIndex("oldIndex") - Connection Pooling: Reuse connections
const client = new MongoClient(uri, { maxPoolSize: 100 })
General Optimizations:
- Shard for Scale: Distribute data across servers
- Monitor Cache Hit Ratio: Keep >90%
- Use Profiler: Identify slow queries