Read Amplification vs Write Amplification
The Hidden Cost of “Optimizations”
Many systems fail not because they’re slow, but because they do too much unnecessary work.
🧠 Definitions
🔹 Read Amplification
One logical read causes multiple physical reads
1 API read → 5 DB / Cache reads
🔹 Write Amplification
One logical write causes multiple physical writes
1 API write → 5 DB / Cache / Index writes
🧩 Why This Matters (Real Systems)
Even if:
Cache hit rate is high
DB is healthy
👉 Amplification silently:
Burns CPU
Increases latency
Kills tail latency (P99)
🧩 Part 1 — Read Amplification
❌ Problem Example: Chat App (Naive)
Scenario
Fetch conversation
For each message → fetch sender
❌ Code
async function getConversation(convId) {
const messages = await db.getMessages(convId);
for (const msg of messages) {
msg.user = await db.getUser(msg.userId); // N reads
}
return messages;
}
Cost
1 conversation
+ 100 messages
= 101 DB reads ❌
✅ Solution A — Batch Reads
async function getConversation(convId) {
const messages = await db.getMessages(convId);
const userIds = [...new Set(messages.map(m => m.userId))];
const users = await db.getUsers(userIds); // 1 read
const userMap = Object.fromEntries(users.map(u => [u.id, u]));
return messages.map(m => ({
...m,
user: userMap[m.userId]
}));
}
Why Better
✔ 101 reads → 2 reads
✔ Lower latency
✔ Predictable load
🧩 Solution B — Denormalization (Tradeoff)
// Message already stores username/avatar
{
message: "Hi",
username: "subhahu"
}
✔ Zero extra reads
❌ Higher write amplification
🧩 Part 2 — Write Amplification
❌ Problem Example: Like a Post
Naive Write Path
async function likePost(postId, userId) {
await db.insertLike(postId, userId);
await redis.del(`post:${postId}`);
await redis.del(`user:${userId}:likes`);
await searchIndex.update(postId);
await analytics.log("like");
}
Cost
1 user action
→ 5 writes ❌
✅ Solution A — Async Writes (Eventual Consistency)
async function likePost(postId, userId) {
await db.insertLike(postId, userId);
queue.publish("post_liked", { postId, userId });
}
Workers:
queue.on("post_liked", async (e) => {
await redis.del(`post:${e.postId}`);
await searchIndex.update(e.postId);
});
Why Better
✔ Fast user response
✔ Load smoothing
❌ Eventual consistency
🧠 Read vs Write Amplification Tradeoff
| Choice | Read Amp | Write Amp |
|---|---|---|
| Normalized | 🔥 High | 🟢 Low |
| Denormalized | 🟢 Low | 🔥 High |
| Index-heavy | 🟢 | 🔥🔥 |
| Caching | 🟢 | 🔥 |
🔥 How This Connects to Previous Topics
Cache stampede → read amplification spike
Hot keys → read amplification concentration
TTL jitter → smooths read amplification
SWR → trades freshness for read efficiency
