Books Deep-Read · System Design Interview Vol 1 · Ch 15
System Design Interview Vol 1 · Ch 15 · Alex Xu · 2020
You edit a file on your laptop, open your phone, and the new version is already there. This chapter asks: what do you have to build to do that for tens of millions of people? The answer is slightly counter-intuitive — storing the file turns out to be the easy half. The hard half is keeping five devices telling the same story about which version counts right now.
You change one word in a two-hundred-page document, and the service does not re-send those two hundred pages. It sends only the small piece your edit landed in. As far as it is concerned, your file was cut into a stack of small cards long ago; you touched one card, so it replaces that card.
The old way was to re-upload the whole file. Nobody minded while files were small. But once you edit a few-hundred-megabyte design file every day, on a phone hotspot, the cost turns absurd: change one word, wait half an hour. Worse, your devices are never online at the same time — the laptop is shut the moment you finish, the tablet wakes three days later, and both may have been edited in between. The system has to untangle that for you.
① Cut the file into small cards. Before uploading, split it into roughly equal pieces; compress, encrypt and store each one on its own. Later, an edit only replaces the card it touched.
② Keep the goods and the ledger apart. A file-sync service is really two systems: one holds the goods (those pieces), the other keeps the ledger (who owns which file, which version, which pieces it is made of). Everything you actually see — folders, names, version history — lives in the ledger. The goods half was solved by cloud warehouses years ago; almost every remaining hard problem is in the ledger.
③ Whoever reaches the ledger first wins. Two devices edit offline and come back online together. The ledger accepts whichever arrived first; the later one is not quietly overwritten but saved beside it as a "conflicted copy", left in front of you to decide.
Every trade-off collapses into one question: how big should a card be? Too small and the ledger's line count explodes until bookkeeping alone sinks the system. Too big and changing one word means re-sending a lump again. The industry's answer lands at a few megabytes — big enough that the ledger can keep up, small enough that a re-send does not hurt.
The cost is honest: you save bandwidth and pay in ledger. Once someone has a lot of files, the slow part is usually not the transfer — it is working out which pieces need sending at all.
A cloud drive is not a storage system, it is a sync system. The file is chopped into pieces and handed to a cloud warehouse; the real craft is in the ledger that records who has what, at which version, assembled from which pieces. And when two sides disagree, it chooses to show you the conflict rather than decide for you.
Want the mechanisms, the numbers and the diagrams? → Switch to the deep read
You think you are designing a storage system; in fact object storage bought that half of the problem years ago. What this chapter actually designs is a ledger: files are cut into immutable blocks handed to cloud storage, a metadata layer records who owns which file, at which version, assembled from which blocks, delta sync keeps bandwidth down, and a notification service drags every device back onto the same version. Nearly all of the difficulty, the consistency requirement and the cost sit on the metadata side.
4 MB.This is the closing case study of Vol 1 (Ch 16 is a short motivational coda with no design content), and the only question in the book where the client owns half the design: everywhere else the protagonist is a server cluster, here a large slice of the work happens inside the sync agent installed on your machine. It inherits the replication thinking of Ch 6's key-value store and the object storage and multipart upload of Ch 14's YouTube, and maps onto Dropbox, Google Drive, OneDrive and iCloud Drive in the real world.
Start with the scale the book assumes: 50 million signed-up users, 10 million daily actives, 10 GB of free space each, two uploads per user per day at an average of 500 KB, a read:write ratio of 1:1. That gives 50M × 10 GB = 500 PB of storage, while the upload API sees only 10M × 2 ÷ 86400 ≈ 240 QPS, or roughly 480 at peak.
Those two numbers are the whole puzzle: absurdly low QPS, absurdly high volume. One decent machine absorbs 480 QPS. What actually costs engineering is 500 PB of data, a ledger with tens of billions of block rows, and the requirement that one file be current on five devices. What happens if you skip it? The bill and the experience break together: whole-file re-upload burns hundreds of megabytes of uplink every time a large file is touched, and without versions and conflict handling two devices editing at once will silently lose data — and this chapter puts "never lose a file" first among its non-functional requirements. One silent overwrite is enough to lose a user permanently.
The starting point is a single machine: a web server takes the file, writes it into a local directory, and records name and size in MySQL. It is guaranteed to hit a wall — no single disk holds 500 PB. So the file contents move to Amazon S3-style object storage, which buys durability and availability through cross-region replication, and your own machines keep only the ledger.
That single cut splits the system into two independent paths: a data plane (client → block servers → object storage) carrying bytes — high volume, asynchronous, retryable — and a metadata plane (client → API servers → metadata cache and database) carrying the ledger — small, but required to be strongly consistent. Add three more parts and you have the chapter's full skeleton: a notification service (tell other devices a file changed), an offline backup queue (hold changes for devices that are offline until they return), and cold storage (move long-untouched blocks to a cheaper tier).
That split is not this book's invention; it is how large-scale storage is generally built. Google's Colossus keeps file metadata in BigTable and lets data flow directly between clients and disk servers (see §6).
Before upload, the client (or the block servers) cuts the file into blocks capped at 4 MB, compresses each one with an algorithm chosen by file type, encrypts it, and pushes it into object storage. From then on an edit only re-sends the blocks it touched — that is delta sync.
For a sense of scale: take a 300 MB project file and change a small region inside it. Whole-file sync pushes 300 MB up the wire; block-level sync pushes one block, at most 4 MB — about two orders of magnitude less. On a home uplink of a few megabits, that is the difference between usable and unusable.
So why not chunk smaller? Because every block is a row in the metadata database, plus a hash. At 4 KB blocks, 500 PB means on the order of a hundred trillion block rows — the bandwidth you saved is spent again on storing, indexing and querying metadata. Too large, and you are back to whole-file transfer. 4 MB is where those two curves cross; it is not a magic number, it is the compromise between "the ledger can keep up" and "a re-send does not hurt".
One property is easy to skim past and holds the chapter up: a block is never modified once written. Editing a file means writing new blocks and swapping pointers in the metadata. Immutability makes dedup, caching and cross-region replication easy — content never changes, so a cache never goes stale — at the cost of orphaned old blocks, which version policy and garbage collection have to reclaim.
The metadata side is roughly these tables: user, device (used to push notifications), namespace (a user's root directory), file, file_version, and block (which block sits at which offset of which version). A file's content is an ordered list of block ids — that is the central row of the whole ledger.
Upload is not one request but two parallel paths: first POST the file metadata with status pending, and in parallel push the changed blocks to the block servers. Once every block has landed in object storage, a callback reaches the API servers, the metadata flips to uploaded, and only then does the notification service broadcast it.
Why insist on that pending state? Because the two paths are asynchronous: without it the system cannot tell "still uploading" from "upload failed", and another device could read a file row pointing at blocks that do not exist yet, then 404 on download. Pending is the stitch that sews the two asynchronous paths back together.
The metadata side must also be strongly consistent: the same file has to read as the same version on all five devices. So caches are invalidated synchronously on write and the metadata store is a relational database with ACID guarantees. The book flags a trap that is easy to walk into: asynchronous primary–replica replication breaks strong consistency by itself — a replica serves the older version, the user watches a file "go backwards", and in sync semantics going backwards and losing data are the same event.
The requirement is plain: device A changed a file, every other online device should learn about it quickly. The book picks long polling, for two unglamorous reasons. First, the communication is not bidirectional — the server needs to tell the client "something changed"; the client has nothing it needs to say back on that channel. Second, the notifications are sparse — at the scale above, a user generates two or three file changes a day, not several messages a second. WebSocket's bidirectional, real-time capability buys nothing here while costing connection management and protocol complexity.
Reading it against Ch 12's chat system is the most valuable comparison in the book: both are "the server pushes", yet a different load profile gives a different answer — chat must use WebSocket because messages are two-way and frequent, while a cloud drive is fine with long polling. Volunteering that contrast in an interview is worth more than memorising either conclusion.
The cost, stated honestly: long polling also parks an enormous number of suspended connections, and when one notification node dies its clients reconnect at the same instant — a thundering herd of your own making. Changes missed by offline devices still have to be replayed from the offline backup queue when they return.
A 500 PB bill forces three moves. ① Block-level dedup — hash the block contents and store identical blocks once, so a popular installer uploaded by ten thousand people occupies one copy. ② Cap the version count — keep the last N versions so "unlimited history" does not become an unlimited invoice. ③ Tier hot from cold — move blocks untouched for months to a Glacier-class tier, an order of magnitude cheaper per byte, at the cost of slow retrieval and a separate retrieval fee. None of the three changes what the product does; all three change its cost curve.
Table 1 · Sync granularity: whole-file vs block-level delta
| Whole-file sync | Block-level delta sync | |
|---|---|---|
| Uplink for a small edit | the entire file (300 MB) | one block (≤ 4 MB) |
| Metadata volume | one row per file | one row per block — tens of billions |
| Client complexity | almost none | chunking, hashing, diffing, resumable upload |
| Dedup reach | whole files only | across files and across users, per block |
| Fits | small files, early prototypes | large files edited repeatedly — the actual workload |
Table 2 · Choosing the change-notification channel
| Long polling (this chapter) | WebSocket | Client-side periodic polling | |
|---|---|---|---|
| Load it suits | sparse, one-way server notifications | frequent and bidirectional (chat, live cursors) | anything tolerating minutes of lag |
| Latency | seconds | sub-second | whatever the interval is |
| Cost | many suspended connections; node loss causes a reconnect storm | heavier connection and protocol management | wasted empty requests, worse as the interval shrinks |
| The chapter's test | not bidirectional + not frequent → pick this | Ch 12's chat system is where it belongs | stops paying off at scale |
Table 3 · Two devices edited offline — now what
| Strategy | How it works | Honest cost |
|---|---|---|
| First writer wins + conflicted copy (this chapter) | the version processed first becomes current; the later one is kept beside it as a conflicted copy for the user to merge | the decision is pushed onto a human; folders accumulate "conflicted copy" noise |
| Last write wins | whichever write lands last becomes the file | silent data loss — unacceptable for a cloud drive |
| Automatic merge (OT / CRDT) | merge both edits according to document structure | only works on structured documents; general files are opaque bytes with no semantics to merge |
That third row is exactly why the chapter refuses to auto-merge: a cloud drive faces arbitrary binary files and cannot judge whether two edits can coexist. Automatic convergence is available only where editing is modelled as semantic operations.
The book leaves one more trade-off open at the end: should chunking happen on the client or the server? On the client (uploading straight to object storage) it saves a hop, is faster, and spares your servers the bandwidth — at the cost of implementing chunking, compression and encryption once per platform, on code that can be reverse-engineered, exposing the encryption logic. On the server it stays uniform and controllable, but every byte must pass through your machines first.
The value of this question is that it is the only one in the book that forces you to say how much the client should do — answer it as a pure server-side problem and you have answered the wrong question. And the mental model it builds — separate data and metadata planes, immutable content blocks, a state-machine upload flow, a notification channel chosen by load profile — is the generic skeleton of every sync product: backup tools, code hosting, photo streams, offline-first mobile apps all look structurally alike.
11 nines of durability: "we always store data redundantly across multiple availability zones before a write is acknowledged as successful", with checksums verified at rest and mismatches repaired automatically from that redundancy — which is what makes outsourcing file contents to an object store defensible in the first place. Google Cloud Blog, "11 nines of durability", 2021-01 ↗4 MB. Dropbox engineering blog, "Inside the Magic Pocket", 2016 (link not verifiable from this run, so the citation is text-only)4 MB is a compromise rather than an optimum.① In one line: a cloud drive is a sync system, not a storage system; storage can be outsourced to an object store, metadata and sync semantics cannot.
② The scale is the puzzle: 500 PB of storage against a peak of only 480 QPS — the difficulty was never concurrency, it is volume and multi-device agreement.
③ The skeleton is one clean cut: a data plane (block servers → object storage, large and asynchronous) and a metadata plane (API → cache/DB, small and strongly consistent).
④ Files become immutable blocks capped at 4 MB; edits re-send only affected blocks, and block size is the bandwidth-versus-ledger-rows compromise.
⑤ Upload is two parallel paths plus a pending → uploaded state machine; pending is the transaction boundary of the asynchronous pipeline.
⑥ Notifications use long polling, on the test "not bidirectional + not frequent"; chat flips to WebSocket because the load profile flips.
⑦ Conflicts resolve as first writer wins plus a conflicted copy — hand the decision back to the user rather than overwrite silently; auto-merge only holds for structured documents.
⑧ Three space savers: block dedup, capped version counts, hot/cold tiering — all three bend the cost curve without touching the feature set.