System designLoading progress…

Functional scope

Turn a product requirement into a workload model, measurable objectives and a justified initial capacity estimate.

From a wish to operations

"Build a social photo app" describes a feeling, not a system. You cannot size servers, pick a database or write a test for a feeling. A system is defined by the operations its users perform and by what counts as success for each one. An operation has an actor, an input, a result and a read or write nature. Our case study, a made-up service called Pixelfold, turns the wish into things like:

Vague:     "Users share photos with friends"
Concrete:  Upload a photo     -> stored durably, on my profile within 5 s, I get its id
           Load my feed       -> newest 20 photos from accounts I follow, newest first
           Open a photo       -> full resolution for anyone allowed to see it
           Follow a user      -> their new photos show in my feed, and I can undo it

The split into reads and writes matters early, because they scale differently. Reads can often be copied and cached. Writes must be ordered, stored and made durable.

Scope is load

Every operation you promise brings an access rate with it. Switch operations on and off below and watch the read and write rates change. Adding direct messages is not a free checkbox: it adds the busiest write path in the table.

Tick the operations you promise to build. Each one carries an assumed number of actions per daily active user per day, so scope turns directly into load and storage work.

In scopeOperationKindper user per dayavg req/s at 200k DAU
Upload a photowrite0.51.16
Load my feed (recent photos from people I follow)read2557.87
Open a single photoread613.89
Follow another userwrite0.20.46
Like a photowrite818.52
Comment on a photowrite1.53.47
Search for users and tagsread36.94
Post a 24-hour storywrite0.81.85
Upload short videoswrite0.20.46
Send direct messageswrite49.26
Reads57.9/s
Writes1.2/s
Read to write ratio50.0 : 1

The per-user counts are assumptions for this made-up service. The point is the chain: a feature is an operation, an operation has a rate, and a rate is load.

Rates use assumed actions per daily active user per day. Change the scope and see what the backend inherits.

Three kinds of statements

A requirements list mixes three different things and they should be kept apart. Functional statements say what users can do. Quality statements say how well, with a number: speed, availability, durability. Constraints are limits you did not choose: budget, people, existing platform, deadline. Mixing them hides decisions. "Fast and reliable" is neither functional nor measurable.

Predict first. Which statement specifies a read or write operation and its result?

The common mistake: a feature list with no success criteria

"The server returns 200" is a status, not success. An upload that returns 200 and then loses the photo is a failed upload. Write the criterion in terms the user would verify: the photo is stored, visible, and the id comes back.

Do

Critical first: name the smallest set of operations without which the product is pointless, then add success criteria you can measure.

Don't

Ship every idea in the first release. Each extra operation brings storage, tests and on-call work, as the table above shows.

Chapter context

System design starts with what the system must do and the conditions under which it must do it. A component diagram is only useful when its choices can be traced back to access patterns, data volume, latency targets and failure expectations.

Chapter reference: math, worked example & code

This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.

Math and notation

For U daily active users and a requests per user per day:

Ravg=Ua/86400,Rpeak=pRavg,n=⌈Rpeak/(cu)⌉.R_{avg}=Ua/86400,\quad R_{peak}=pR_{avg},\quad n=\lceil R_{peak}/(cu)\rceil.

Here p is a peak multiplier, c measured capacity per instance and u a chosen usable fraction. The multiplier and capacity are workload assumptions, not universal constants.

Worked example

Assume 100,000 daily users, 20 requests each and a peak multiplier of 10. Average demand is 2,000,000/86,400≈23.15 requests/s and estimated peak is 231.48 requests/s. An instance sustaining 100 requests/s with usable fraction 0.6 contributes 60 requests/s, so four instances cover the estimated peak. Five are needed to retain that same usable capacity after losing one instance.

Reference code

The following example uses the Python standard library and does not call a remote service.

from math import ceil,isclose
average=100_000*20/86400
peak=10*average
usable=100*.6
assert isclose(average,23.14814814814815)
assert ceil(peak/usable) == 4
assert (5-1)*usable >= peak
assert (4-1)*usable < peak

Python examples are provided for study and local execution; this page does not execute Python.

Common mistakes

Average traffic is not peak traffic. A headroom assumption is not measured performance. Durability, correctness and availability need separate objectives.