Skip to the solution
MediumSystem Design13 min read

How do you approach back-of-the-envelope capacity estimation?

959 views

The problem

Question presented to candidate: "Before you draw any boxes, estimate the scale. Say we are building a Pastebin-style service where people share text snippets. How many requests per second, how much storage over five years, how much bandwidth and cache, and roughly how many servers? Talk me through how you get there."

What a strong answer should cover:

  • Start from stated assumptions (daily active users, actions per user, read:write ratio, object size, retention) and say them out loud before computing
  • Convert per-day volumes to QPS with 86,400 s/day (or the 10^5 shortcut, about 14% low), separate reads and writes, and apply a peak-to-average factor
  • Storage = items per day Γ— size (including metadata) Γ— retention Γ— replication factor; bandwidth = QPS Γ— payload, and keep bits and bytes apart
  • Cache sizing from the working set (a hot fraction of what is read per day), and server count from per-server capacity with headroom; Little's law for concurrency
  • Interpret the result: which resource dominates (here storage, not compute), what that decides in the design, and what breaks first at 10x

Clarifying questions expected:

  • How many daily active users, and what is the read:write ratio?
  • How large is a typical item, and how long must it be kept?
  • Is traffic spiky (launches, time zones) or flat?

Code / implementation expected: No β€” arithmetic on a whiteboard; the example shows the same calculation as a script.

  • estimation

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

13 min read Β· 11 sections

Read the code

Capacity estimate for a Pastebin-style service, step by step
// Back-of-the-envelope estimate for a Pastebin-style text-snippet service.
// Every input is an ASSUMPTION you would say out loud in the interview.
const A = {
  dau: 10e6,              // daily active users
  writesPerUserDay: 0.1,  // 1 in 10 users creates a paste each day
  readWriteRatio: 50,     // reads per write
  pasteBytes: 10e3,       // average paste body: 10 KB
  metaBytes: 500,         // id, owner, timestamps, expiry
  years: 5,               // retention
  replicas: 3,            // copies of every byte
  peakFactor: 3,          // busiest minute vs daily average
  hotFraction: 0.2,       // 20% of the distinct pastes read today get most reads
  serverRps: 2000,        // one app server, from a load test (assumption)
  targetUtil: 0.5,        // run servers at 50% so one failure or spike fits
  latencySec: 0.05,       // average time a request is in flight
};
const DAY = 86400;
const round = (x) => Math.floor(x + 0.5);
const fmt = (n) => String(round(n)).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
function bytes(b) {
  const units = ["B", "KB", "MB", "GB", "TB", "PB"];
  let i = 0;
  while (b >= 1000 && i < units.length - 1) { b /= 1000; i++; }
  return b.toFixed(1) + " " + units[i];
}
const line = (k, v) => console.log("  " + k.padEnd(34) + v);

console.log("1. Traffic");
const writesDay = A.dau * A.writesPerUserDay;
const readsDay = writesDay * A.readWriteRatio;
const writeQps = writesDay / DAY, readQps = readsDay / DAY;
line("writes per day", fmt(writesDay));
line("reads per day", fmt(readsDay));
line("average write QPS", fmt(writeQps));
line("average read QPS", fmt(readQps));
line("peak write QPS (x" + A.peakFactor + ")", fmt(writeQps * A.peakFactor));
line("peak read QPS (x" + A.peakFactor + ")", fmt(readQps * A.peakFactor));
line("shortcut: reads/1e5 per day", fmt(readsDay / 1e5) + " QPS (" + ((DAY / 1e5 - 1) * -100).toFixed(1) + "% low)");

console.log("2. Storage");
const perWrite = A.pasteBytes + A.metaBytes;
const perDay = writesDay * perWrite;
const raw = perDay * 365 * A.years;
line("new data per day", bytes(perDay));
line("raw, " + A.years + " years", bytes(raw));
line("with " + A.replicas + " replicas", bytes(raw * A.replicas));
line("paste count, " + A.years + " years", fmt(writesDay * 365 * A.years));

console.log("3. Bandwidth (average / peak)");
line("ingress", bytes(writeQps * perWrite) + "/s / " + bytes(writeQps * perWrite * A.peakFactor) + "/s");
line("egress", bytes(readQps * A.pasteBytes) + "/s / " + bytes(readQps * A.pasteBytes * A.peakFactor) + "/s");
line("egress in bits at peak", ((readQps * A.pasteBytes * A.peakFactor * 8) / 1e9).toFixed(2) + " Gbit/s");

console.log("4. Cache");
const distinctReadDay = readsDay / 10; // assume each paste read today is read ~10 times
line("distinct pastes read per day", fmt(distinctReadDay));
line("hot set to cache (20%)", bytes(distinctReadDay * A.hotFraction * A.pasteBytes));

console.log("5. Servers and concurrency");
const peakTotal = (writeQps + readQps) * A.peakFactor;
const servers = Math.ceil(peakTotal / (A.serverRps * A.targetUtil));
line("peak total QPS", fmt(peakTotal));
line("app servers at 50% target", servers + " (+1 spare = " + (servers + 1) + ")");
line("in flight (Little: L = QPS x W)", fmt(peakTotal * A.latencySec) + " requests");

console.log("6. What changes at 10x users");
const k = 10;
const peak10 = peakTotal * k;
line("peak total QPS", fmt(peak10));
line("app servers at 50% target", String(Math.ceil(peak10 / (A.serverRps * A.targetUtil))));
line("storage with replicas, 5 years", bytes(raw * A.replicas * k));
line("hot set to cache", bytes(distinctReadDay * A.hotFraction * A.pasteBytes * k));
line("peak write QPS", fmt(writeQps * A.peakFactor * k));

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 13 of 106 in System design

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

Up nextMedium

What is consistent hashing with virtual nodes and when do you need it?

Continue