Everything in this module was a piece. This is the whole walk, on one brief, in the order you would actually do it, whether you are at a whiteboard or writing the design doc that a team will build from.
The brief. A link shortener for a mid-sized company. Employees create short links; anyone on the internet resolves them. Marketing wants click analytics. That is all the specification you get, which is normal.

Step 1: Turn the product into traffic
Ask for the two numbers that generate everything else, and if nobody has them, state an assumption out loud and move on.
- 2,000 employees, each creating about 5 links a day: 10,000 writes per day.
- Each link is clicked an average of 500 times over its life, and roughly half of a link's traffic arrives in its first week.
Writes: 10,000 per day is 0.12 per second, effectively zero. Say that plainly, because it settles several arguments at once: no queue, no sharding, nothing beyond an index.
Reads: 5 million redirects per day is about 58 per second average, and with a 3x peak, 175 per second.
The ratio is 500:1, and that is the most important output of the step: this is a read path with a small write path attached, so every later decision optimizes for reads.
Step 2: Turn traffic into bytes
A stored link is small: short code, target URL, owner, timestamps, maybe a flag. Call it 500 bytes with index overhead.
- 10,000 links per day at 500 bytes is 5MB per day, 1.8GB per year, about 9GB over five years.
- Click events are the real storage: 5 million per day. At 100 bytes each that is 500MB per day, 180GB per year.
First real design decision, and it came from arithmetic rather than opinion: the links table is trivial and the analytics table is 36 times larger than everything else combined. Keeping every click row forever to answer "how many clicks did this get" is a bad trade, so either aggregate into counters with a daily rollup, or give the raw rows a retention window and partition them so old days can be dropped.
Bandwidth barely registers: a few hundred bytes per redirect at 175 per second is under 1 Mbps. Worth saying out loud, because it removes a whole category of concern.
Step 3: Turn traffic into instances
Now use Little's Law and a measured latency, not a guess about "requests per second per server".
A redirect is one cache or index lookup plus one 301 response. Assume 5ms of service time, which is generous for a keyed lookup.
L = λ × W = 175 req/s × 0.005 s = 0.875 concurrent requestsNot quite one request in flight at peak, so instance count is driven by availability, not load: two instances in separate zones because one is a single point of failure, and a third to survive losing a zone at peak while staying under the knee from the capacity lesson.
The database is the same story: 175 keyed reads per second against a few million rows is one Postgres instance, with a 9GB working set that fits in memory. A cache is optional, and "we do not need one yet" is a stronger answer than reflexively adding Redis.
Step 4: State the SLO, then the headroom
Redirects are on the critical path of somebody else's marketing campaign. Analytics are not.
Redirect path
- Availability: 99.95% (22 minutes a month)
- Latency: 99% of redirects under 100ms, measured at the edge
- Failure mode: cannot serve a redirect, which is a broken link in public
- Consequence: this is the path that earns the headroom and the second zone
Analytics path
- Freshness: 99% of clicks visible in the dashboard within 5 minutes
- Availability: 99.5% is plenty
- Failure mode: counts lag or are briefly wrong
- Consequence: buffer, retry, backfill; never block a redirect on it
That split is the design's spine: the click write is never synchronous with the redirect. The redirect returns, the event goes to a buffer to be counted later. Same conclusion as the background-job question, reached from a latency budget instead of from taste.
Headroom follows from step 3: losing a zone at peak leaves 117 requests per second across two instances, nowhere near the knee. At this scale headroom is free, and saying why beats quoting a percentage.
Step 5: Name what breaks first
Every design has a first constraint to fail. Naming it unprompted is the difference between an estimate and an engineering judgment.
- Analytics writesfirst5M/day today; a viral link makes this bursty first
- Analytics table size180GB/year forces retention or rollups
- Hot-key readsone link taking 90% of traffic needs a cache, not a bigger DB
- Redirect QPSlast to break; would need a 100x traffic change
Note what is not on that list: the write path, still trivial after ten years, and the redirect service, which was never the constraint. A design review that spends its time on the short-code algorithm is optimizing the part that cannot break.
The walk, portable to any brief
- Users and behavior to QPS, split into reads and writes, and name the ratio.
- Bytes per operation to storage per year, and find which table dominates.
- Little's Law to concurrency, then ask whether load or availability sets the instance count.
- An SLO per path, because paths have different consequences, and let that split dictate what is synchronous.
- Headroom from the failure domain, and then name the constraint that breaks first.
Every number carries the assumption it came from, so people challenge the input instead of arguing with the output. That is the point of the module: not that these numbers are right, but that a design conversation with numbers in it converges, and one without them does not.
Module 2 picks up where step 4 leaves off: the networking and API decisions that make the redirect path fast, cacheable and versionable. Meanwhile the System Design deck drills these numbers in question form, and the database deck goes deeper on the storage side.
Key takeaways
- Always start from users and behavior, and always split reads from writes. The ratio between them shapes the design more than either number.
- Find the table that dominates storage. It is rarely the one the feature is named after.
- Use Little's Law for concurrency, then ask whether load or availability is setting your instance count. At small scale it is almost always availability.
- Give each path its own SLO. Different consequences mean different targets, and that split decides what may be synchronous.
- Derive headroom from the failure you must survive at peak, not from a habitual percentage.
- Name the constraint that breaks first, unprompted. It shows you know which numbers you are defending.
Checkpoint · lesson 6 of 6
You can now:
- Run one repeatable sizing walk from a product brief to an instance count
- State the assumption behind every number so it can be challenged
- Name the constraint that breaks first, before anyone asks