In scope
- Add files, e.g. by drag and drop
- Download files
- Sync files across devices: add a file on one, it appears on the others
- See file revisions
- Share files
- Notify when a file is edited, deleted or shared with you
An interactive walk through Chapter 15 of System Design Interview (Vol. 1) by Alex Xu. The chapter designs a file storage and sync service like Google Drive, Dropbox, OneDrive or iCloud. Users store files in the cloud, reach them from any device, share them, and every device stays in sync. Each idea below comes with something to click: estimates, uploads that break mid-way, conflicts, real chunking, compression and encryption, the sync flows, and failures.
Google Drive stores documents, photos, videos and other files in the cloud. You can reach them from a computer, phone or tablet, and share them with friends, family and coworkers. The design has to do this for millions of people without ever losing a file, while keeping every device in sync quickly and cheaply.
Designing all of Drive is too big for an interview, so the candidate asks questions first. The interviewer’s answers fix the scope:
| Question | Answer |
|---|---|
| Most important features? | Upload and download files, file sync, and notifications |
| Mobile app, web app, or both? | Both |
| Supported file formats? | Any file type |
| Encrypted? | Yes, files in storage must be encrypted |
| File size limit? | 10 GB or smaller |
| How many users? | 10M daily active users (DAU) |
Google Docs editing and collaboration, where several people edit the same document at the same time. That needs a different family of techniques, such as differential synchronization (see the chapter’s references).
| Non-functional requirement | Why it matters here |
|---|---|
| Reliability | Data loss is unacceptable for a storage system. |
| Fast sync | If syncing takes too long, users give up on the product. |
| Low bandwidth use | Wasted bandwidth annoys users, especially on mobile data plans. |
| Scalability | Must handle high volumes of traffic. |
| High availability | Keeps working when some servers are offline, slow or hit network errors. |
The book assumes 50 million signed-up users and 10 million DAU, 10 GB of free space each, 2 uploads per user per day averaging 500 KB, and a 1:1 read-to-write ratio. That gives 500 PB of space allocated, an upload rate of about 240 QPS, and a peak of about 480 QPS. Try your own numbers:
Rather than drawing the final diagram first, the chapter starts with everything on one machine and scales it up step by step, revisiting ideas from earlier chapters.
A few hours of setup give us an Apache web server, a MySQL database for metadata (users, logins, file info), and a directory drive/ with 1 TB of space for the files. Inside it, each user gets a directory called a namespace. Files keep their original names, so namespace + relative path uniquely identifies any file or folder (Figure 15-3):
drive/
├── alice/ ← namespace
│ ├── recipes/
│ │ ├── chicken_soup.txt
│ │ └── soup/
│ │ └── best_soup.txt ← alice + /recipes/soup/best_soup.txt
│ └── photos/
└── bob/ ← another namespace
└── notes.txt
All APIs require authentication and use HTTPS, so data is encrypted between client and servers (the book says SSL).
| API | Example | Parameters |
|---|---|---|
| Upload a file | POST /files/upload?uploadType=resumable | uploadType: simple or resumable; data: the local file |
| Download a file | GET /files/download | path, e.g. "/recipes/soup/best_soup.txt" |
| Get file revisions | GET /files/list_revisions | path; limit: most revisions to return, e.g. 20 |
A simple upload sends the whole file in one request, which is fine for small files. A resumable upload is for large files and unreliable networks, in three steps (as in Google Drive’s real API):
Try uploading a 2 GB file with each method on a network that drops every few minutes:
With simple uploads, a file that takes longer to send than the typical gap between drops almost never finishes. Resumable uploads only ever lose the chunk that was in flight.
Eventually the disk fills up. The first fix is to shard: store files on several storage servers, chosen by user_id. That stops the fire, but every file still lives on one machine. On the advice of a friend (“Netflix and Airbnb use S3”), the files move to Amazon S3, which replicates data within a region and across regions. A region is a geographic area where AWS has data centers.
With web servers, metadata database and file storage decoupled, each can scale and fail independently:
With many users and devices, two of them will sometimes modify the same file or folder at the same time. The book’s strategy: the version processed first wins; the one processed later gets a conflict. The losing user is shown both copies, their local one and the latest from the server, and can merge them or keep one. How does the server notice? Each commit says which version it was based on.
Keeping a document synchronized while several people type into it at once is a different and harder problem (differential synchronization, operational transformation, CRDTs). That is Google Docs territory and out of scope.
The design separates two paths: file content flows through block servers into cloud storage, while metadata (who owns what, which version is latest, which blocks make it up) flows through the load balancer and API servers into the metadata database. A notification service ties the devices together.
Click a component to see what it does, or switch to the upload and download paths.
| Component | Role |
|---|---|
| User | Uses the application through a browser or mobile app. |
| Block servers | Upload blocks to cloud storage. A file is split into blocks, each with a unique hash stored in the metadata DB and stored as an independent object in S3. Blocks joined in order rebuild the file. Max block size 4 MB, following Dropbox. |
| Cloud storage | Holds the blocks. |
| Cold storage | Stores inactive data: files not accessed for a long time. |
| Load balancer | Distributes requests evenly among API servers. |
| API servers | Everything except the upload flow: authentication, user profiles, updating file metadata, and so on. |
| Metadata database | Metadata of users, files, blocks, versions. Files themselves live in the cloud. |
| Metadata cache | Some metadata is cached for fast retrieval. |
| Notification service | Publish/subscribe: notifies relevant clients when a file is added, edited or removed elsewhere, so they can pull the latest changes. |
| Offline backup queue | Stores changes for clients that are offline, to be synced when they come back. |
Sending a whole large file on every small change wastes bandwidth. Block servers do the heavy lifting of uploads (Figure 15-11):
Two optimizations follow from working in blocks: delta sync and compression.
When a file is modified, only the modified blocks are synced, not the whole file (Figure 15-12 shows blocks 2 and 5 changing). A block’s hash identifies its content, so comparing the new file’s block hashes with the stored ones reveals what changed. The book points to the rsync algorithm and librsync for how to do this. The method matters more than it looks:
Try Insert a line at the top in each mode. Fixed-size blocks handle in-place edits and appends well, but an insertion shifts every later boundary. rsync’s rolling checksum and content-defined chunking both find unchanged data even after it moves.
Compressing blocks can shrink them a lot, using an algorithm suited to the file type: gzip or bzip2 for text, and different algorithms for images and video. Then each block is encrypted, because files in storage must be encrypted. The order is not arbitrary:
The system needs strong consistency by default: a file must never look different to different clients at the same time. That applies to the metadata cache and the database.
Memory caches are eventually consistent by default: replicas may hold different data. To make them strongly consistent:
For the database itself, relational databases make strong consistency easy because they provide ACID transactions (atomicity, consistency, isolation, durability). NoSQL databases don’t support ACID by default, so those guarantees would have to be built into the sync logic by hand. The design therefore picks a relational database.
A highly simplified version of Figure 15-13, with only the most important tables:
Basic information: username, email, profile photo, and so on.
One row per device; a user can have several. push_id is for sending and receiving mobile push notifications.
The root directory of a user.
Everything about the latest version of a file.
The version history. Existing rows are read-only, to keep the revision history intact.
Everything about a block. Any version of a file can be rebuilt by joining its blocks in order.
Run some queries against example rows to see how the tables fit together:
When a client uploads a file, two requests go out in parallel: one adds the file metadata, the other uploads the content to cloud storage. The metadata’s upload status starts as pending and only becomes uploaded once cloud storage confirms all blocks arrived. Other clients are told at both moments. Editing a file works the same way.
Downloads are triggered when a file is added or edited elsewhere. A client finds out in one of two ways:
Once a client knows something changed, it first fetches the metadata from the API servers, then downloads the blocks and rebuilds the file.
To keep files consistent, every change made on one device must be announced to the others, which also reduces conflicts. The options considered:
The client sends a request that the server holds open until there is news. Dropbox uses it.
A persistent connection where both sides can send at any time.
Both work. The book picks long polling for two reasons: communication is not bi-directional (the server tells the client about changes, never the reverse), and notifications are infrequent, with no bursts of data. WebSocket suits real-time two-way traffic like chat. With long polling, each client keeps a long-poll connection open. When a change is detected the server answers and the connection closes; the client then fetches the latest changes from the metadata (API) servers. After any response, or a timeout, the client immediately sends a new request.
A client that is offline when a change happens can’t receive a notification. The offline backup queue keeps the information so the change is synced when the client comes back online. Switch the client to “offline” in the upload and download simulations above to watch it. (The download section of the book says these changes are “saved to the cache”; the high-level design calls the component the offline backup queue. They are the same idea.)
Keeping a version history and storing copies in several data centers makes storage fill up fast. Three techniques reduce the cost:
Two blocks are identical if they have the same hash, so each distinct block needs to be stored only once. Eliminating redundant blocks at the account level is an easy win: the same file saved twice, or two versions that share most of their blocks, cost almost nothing extra. Use Upload a copy of it and Sync this version in the delta sync lab to see it, and the “Blocks vs objects” query in the schema explorer.
An intelligent backup strategy has two options:
Cold data is data nobody has touched for months or years. Cold storage such as Amazon S3 Glacier is much cheaper than S3. Together, the three techniques add up:
Failures are normal in a large system, and an interviewer may ask how each one is handled:
| Failure | Handling |
|---|---|
| Load balancer | The secondary becomes active and picks up the traffic. Load balancers monitor each other with heartbeats; one that stops sending them for a while is considered failed. |
| Block server | Other servers pick up its unfinished or pending jobs. |
| Cloud storage | S3 buckets are replicated in different regions; if files are unavailable in one, fetch them from another. |
| API server | Stateless: the load balancer redirects traffic to other API servers. |
| Metadata cache | Replicated; read from another node and bring up a new cache server to replace the failed one. |
| Metadata DB | Master down: promote a slave to master and bring up a new slave. Slave down: use another slave for reads and replace the failed one. |
| Notification service | Every online user holds a long-poll connection, and Dropbox (2012) kept over 1 million open per machine. If a server dies, all of its clients must reconnect to other servers, and reconnecting that many is relatively slow. |
| Offline backup queue | Queues are replicated; consumers may need to re-subscribe to the backup queue. |
Fail components and compare a design with redundancy everywhere (the book’s) to one where each component is a single instance:
Why is reconnecting a million clients slow? The surviving servers can only accept new connections (each with a TLS handshake) at a limited rate. If every client retries at once, the flood itself can eat the capacity needed to let anyone in.
The design combines strong consistency, low bandwidth and fast sync. It has two flows, managing file metadata and file sync, plus a notification service that uses long polling to keep clients up to date. No design is perfect: every company has its own constraints, and what matters is knowing the trade-offs of each choice. Two to discuss if time remains:
Clients could upload directly to cloud storage instead of going through block servers. That is faster, because the file is transferred once instead of twice. The book lists two drawbacks: the chunking, compression and encryption logic must be implemented on every platform (iOS, Android, web), which is error-prone and a lot of engineering work, whereas block servers keep it in one place; and clients can easily be hacked or manipulated, so encryption logic on the client is not ideal.
Online/offline tracking currently lives in the notification servers. Moving it into a separate presence service lets other services use it too, for example to show who else is viewing a shared folder.
Points the chapter leaves open, useful for follow-up questions in an interview.
list_folder/longpoll with list_folder/continue). A client returning from a week offline does exactly the same thing, so lost notifications or a dead notification server never lose changes.| Requirement | Design decision |
|---|---|
| Never lose data | S3 replicated across regions; immutable version rows; pending → uploaded commit |
| 10 GB files, flaky networks | Resumable uploads; files split into 4 MB blocks |
| Low bandwidth | Delta sync of changed blocks; per-type compression; de-duplication |
| Fast sync | Long-poll notifications; download only missing blocks |
| Strong consistency | Relational metadata DB (ACID); cache invalidated on write; base-version check on commit |
| Concurrent edits | First processed wins; the later client gets a conflict and both copies |
| Encryption | HTTPS in transit; block servers encrypt every block before storage |
| Scale and availability | Stateless API and block servers behind a load balancer; sharded, replicated metadata; redundancy for every component |
| Storage cost | Dedup, version limits/thinning, cold storage for inactive data |
| Offline devices | Offline backup queue (or change log + cursor); sync on reconnect |