Builds on Design a Web Crawler. Next up: Design a Video Streaming Platform like YouTube.
System Design Case StudiesPage 1
Cloud File Storage (Dropbox-style): What This Handbook Covers
1. Requirements and Architecture
Metric
Estimate
Users
50M registered, 10M DAU
Storage
2 GB avg x 50M = 100 PB logical, ~70 PB raw after 30% dedup
Uploads
20M/day = ~230 QPS avg, 700 QPS peak
Sync checks
1B/day = ~12K QPS avg, 40K QPS peak
Chunks
100 PB / 4 MB = 25B chunks
Targets
11 nines durability, 99.95% availability, sync < 5 s
Limits
files up to 50 GB; restore last 30 days; no co-editing or search
Clientwatcher, chunker, SQLite
API gatewayauth, JSON
Metadatasharded SQL, Redis
Block storeS3-like objects
NotifierKafka + sockets
System Design Case StudiesPage 2
Problem Statement, Scope and Requirements (Cloud File Storage)
1. Scope and Functional Requirements
Upload and download files
Sync across a user's devices
Share files and folders
Version history with restore
Real-time co-editing (OT/CRDT)
Full-text search
Media transcoding
System Design Case StudiesPage 3
High-Level Architecture (Cloud File Storage)
1. Components
Client
Desktop / mobile
Local watcher
Chunker
API gateway
Auth, routing
Rate limits
Metadata service
Namespace, versions
Block lists
Block service
System Design Case StudiesPage 4
Chunking and Deduplication Service (Cloud File Storage)
1. Responsibilities
Split each file into blocks
Fingerprint each block with SHA-256
Skip blocks the server already has
Fixed 4 MB
Content-defined (CDC)
Boundaries
every 4 MB
rolling hash picks cut points
System Design Case StudiesPage 5
Block Storage Service (Cloud File Storage)
1. Responsibilities and Design
Durably store immutable 4 MB blocks keyed by hash, and serve them fast
Immutable means no update, so there is no cache invalidation
A retry is safe: PUT of the same hash is idempotent
Hot blocks are cached on the CDN/edge
Blocks not read for 90 days move to a cheaper cold tier
3x replication
System Design Case StudiesPage 6
Metadata Service and Database (Cloud File Storage)
1. Responsibilities and Design
Owns the namespace: folders, names, paths
Owns the file-to-block mapping, versions, ACLs and device cursors
Source of truth for what exists; about 1 KB per file: small but hot
API gatewaynamespace_id
Redishot listings
Shard primary1 of 64+
2 replicassync copies
System Design Case StudiesPage 7
Upload and Download Flow (Cloud File Storage)
1. Upload Sequence
11. Chunk and hash4 MB chunks, SHA-256
22. POST /uploads/initsend the hash list
33. Presigned URLsonly for missing chunks
44. PUT chunks4-8 parallel streams
55. POST /files/commitversion visible atomically
Bytes go straight to the block store via presigned URLs, never through app servers
Chunks the server already has are skipped, so an edit uploads only the changed chunks
Nothing is visible to other devices until commit succeeds
2. Resumable Upload and Download
System Design Case StudiesPage 8
Sync Engine and Conflict Resolution (Cloud File Storage)
1. Responsibilities and Design
Goal: converge every device to the same tree
Each device stores a cursor = last change-log seq it applied
Sync = pullGET /changes?cursor=N, apply locally, push local edits via commit
PullGET /changes?cursor=N
Apply locallydiff chunk hashes
Push editsPOST /files/commit
Advance cursorto last seq
System Design Case StudiesPage 9
Versioning, Trash and Garbage Collection (Cloud File Storage)
1. What a Version Is
Goal: keep history so any mistake can be undone
Each commit adds a new immutable version row
The row points at a block list (ordered chunk hashes)
Unchanged chunks are shared between versions
Version 11 GB, 256 chunks
Edit 1 chunkcommit
Version 2255 shared + 1 new
Extra costonly 4 MB
2. Retention Policy
System Design Case StudiesPage 10
Sharing and Permissions (Cloud File Storage)
1. Responsibilities
Owners grant files and folders to users, groups or anyone with a link
Check runs on every metadata call and on every block URL issue
No role check = no signed URL, so bytes never leave without an authz pass
Role
Can do
viewer
list, read, download
System Design Case StudiesPage 11
Notification Service for Device Sync (Cloud File Storage)
1. Job and Design
Tell online devices that something changed so they pull now instead of polling
Payload is only namespace_id + new seq: no file data
The client then calls GET /changes?cursor=N to fetch the real changes
Each client holds a long-poll or WebSocket connection to a notification server
10M DAU, about 3M concurrent connections, 50K per server = about 60 servers
Changes publish to a Kafka topic partitioned by namespace_id
System Design Case StudiesPage 12
Scaling, Failure Handling, Trade-offs and Interview Questions (Cloud File Storage)
1. Scaling Cheatsheet
Metadata: shard by namespace_id, 64 to 1K shards, 1 primary + 2 replicas each
API tier: stateless behind a load balancer, add instances freely
Block store: grows linearly, add nodes, no rebalance because blocks are immutable
CDN: absorbs hot downloads so they never reach the block store
Block bandwidth is small: 10M DAU x 4 MB = 40 TB/day, about 3.7 Gbps avg
System Design Case StudiesPage 13
Check yourself
A GC job deletes any chunk whose refcount is 0. A 50 GB upload has sent 600 of 1000 chunks, and the commit has not happened yet. What goes wrong, and what is the fix?
The 600 uploaded chunks have refcount 0, because no committed version points at them yet
GC deletes them, so the resume finds them gone, or the commit is rejected because listed chunks are missing
Fix: delete only after a grace period, which is the 7-day upload session expiry
Also require two checks before deleting: refcount is 0 and no live manifest or session references the chunk
Phone and laptop both hold version 5 of report.docx. The laptop commits with base_version=5. Later the offline phone commits its own edit with base_version=5. What does the server do?
Control plane: metadata and auth through the API, small JSON
Data plane: file bytes go direct to the block store via presigned URLs
Metadata is ~1 KB per file: tiny but hot, so it is the first bottleneck
2. Chunking, Dedup, Block Storage
File = ordered list of SHA-256 chunk hashes (block list); 1 GB = ~256 chunks
Fixed 4 MB is simple; content-defined (min 1 MB, avg 4 MB, max 8 MB) survives mid-file inserts
Dedup is global by hash, saves ~30%; confirm with proof of possession to avoid leaks
Blocks are immutable: put is idempotent, nothing to invalidate
3x replication over 3 AZs, or erasure coding 10+4 (1.4x): 98 PB vs 210 PB
Edit 1 chunk of a 1 GB file: send 4 MB, not 1 GB
Save sequence
1Chunk + hashon the client
2Ask missingPOST /chunks/missing
3Upload missingpresigned PUT
4Commitatomic new version
3. Upload, Sync, Conflicts, Versions, Sharing
Topic
Rule
Upload
4-8 parallel streams; session lives 7 days and resumes only the remaining chunks
Sync
cursor + GET /changes?cursor=N; notifier pushes {namespace_id, seq}, pull in ~1 s
Conflict
commit carries base_version; mismatch = first wins, loser saved as a conflicted copy
Delete vs edit
the edit wins; versions are server-assigned, never wall-clock
Versions
all kept 30 days, then 1 per day until day 120; restore = one metadata write
Sharing
viewer, commenter, editor, owner; ACLs inherit down the tree; 5 min signed block URLs
GC
refcount hits 0, then 7-day grace; orphans deleted after session expiry
4. Scaling, Failures, Interviews
Bottleneck order: metadata DB, then notifier connections, then block bandwidth
Metadata: 64+ shards by namespace_id; stateless API; CDN absorbs hot downloads
AZ loss: RPO 0, RTO < 30 s. Region loss: async replica, RPO ~1 min
Trade-offs: fixed vs CDC chunks, global vs per-user dedup, 3x vs erasure coding, long-poll vs WebSocket
50 GB file, 1 MB edit? Send only the changed 4 MB chunk, commit a new block list
Two devices overwrite?base_version check; the loser becomes a conflicted copy
Dedup without leaks? Proof of possession on a sampled range before skipping upload
GDPR erase with shared blocks? Drop the user's version rows; delete a block only at refcount 0
File bytes through app servers
Trusting client-supplied hashes
Wall-clock time for versions
Deleting a block before verifying refcount
Polling every second instead of notifying
Remember
Bytes go direct to the block store; metadata decides what exists. A version commits only when every chunk is present.
Files up to 50 GB, organised in folders
Resumable transfers after a dropped connection
Offline edits queue locally and sync later
Share by link or by user, with view or edit roles
Restore any version from the last 30 days
2. Non-Functional Targets
Goal
Target
Meaning
Durability
99.999999999% (11 nines)
Stored data is effectively never lost
Availability
99.95%
About 4.4 h downtime per year
Write safety
No acknowledged write is ever lost
Ack only after a durable commit
Sync latency
under 5 s
For small files
Restore window
30 days
Every version in that window
3. Scale Estimates
Load
Math
Avg
Peak
Uploads
10M DAU × 2/day = 20M/day
~230 QPS
~700 QPS
Sync checks
10M DAU × 100/day = 1B/day
~12K QPS
~40K QPS
Storage: from users to raw bytes
1Logical data50M users × 2 GB = 100 PB
2Chunks100 PB / 4 MB = 25B chunks
3Dedupsaves about 30%
4Raw storageabout 70 PB before replication
Read-heavy metadata
Sync checks (~12K QPS) outnumber uploads (~230 QPS) about 50x. The 70 PB figure is before replication or erasure coding.
Presigned URLs
Hash verify
Notification service
Tells devices to pull
Object store
Immutable 4 MB blocks
Metadata DB
Sharded SQL
2. Control Plane vs Data Plane
Control plane
Data plane
Carries
Metadata, auth, block lists (small JSON)
File bytes in 4 MB chunks
Path
Client → API gateway → metadata service
Client → block store via presigned URL
Load
12K QPS avg
Bytes skip app servers
Sync agent keeps a local SQLite DB: file path, hash list, version
Change detection uses OS file events, not scanning
Ordering edits by device wall-clock time (last-writer-wins) silently loses data. Order by the server's version and keep the loser as a conflicted copy.
Using wall-clock time for versions, use server-assigned versions
Deleting a block before verifying refcount and live manifests
Polling every second instead of notifying via long-poll or WebSocket
5. Interview Questions
Question
Short answer
Sync a 50 GB file after a 1 MB edit?
Chunk + hash, send hash list, upload only the 1 changed 4 MB chunk, commit new version
Stop two devices overwriting each other?
Commit carries base_version; mismatch means a conflicted copy, never a silent overwrite
Dedup without leaking other users' files?
Skip upload only after proof of possession on a sampled range of the chunk
Honor GDPR erase when blocks are shared?
Drop the user's versions, decrement refcounts, delete a block only at refcount 0 after the 7-day grace
Remember
Bytes go to the block store, truth lives in metadata. Versions are server-assigned, blocks are immutable, and a block is deleted only after two checks.
The laptop's commit succeeds, so latest_version becomes 6
The phone's commit has latest_version 6 != base_version 5, so the optimistic check fails
Nothing is overwritten: the phone's edit is saved as a conflicted copy next to the original
The check uses server-assigned versions, not wall-clock time, so clock skew on the phone cannot change the outcome
The client uses fixed 4 MB chunks on a 1 GB file (256 chunks). Case A: one byte in the middle is overwritten. Case B: one byte is inserted at the very start. How much is uploaded in each case?
Case A: only the one chunk containing that byte changes, so about 4 MB is uploaded
Case B: every byte shifts by 1, so all 256 chunk boundaries and hashes change, and about 1 GB is uploaded
Content-defined chunking (rolling hash) places boundaries by content, so an insert changes only the chunks near it
The trade-off is that content-defined chunking is more complex than fixed chunking
Alice revokes Bob's access to a shared folder. The permission cache TTL is 60 s and block URLs are signed for 5 min. What is the worst case before Bob is fully locked out?
For up to 60 s the stale cache can still say Bob is allowed, so he can still be issued a new block URL
A URL issued at the last moment stays valid for 5 min
Worst case is about 6 minutes: revocation takes effect within minutes, not instantly
A permanent public URL would never expire, which is why block URLs are short-lived and signed
A notifier server crashes, so its devices miss the push saying namespace 42 reached seq 900. Is data lost, and how do the devices recover?
No data is lost: the push carries only {namespace_id, seq}, and the real changes live in the change log
Each client polls /changes every 5 min as a fallback, using its stored cursor
The pull is idempotent, so the device fetches everything after its cursor and ends up at seq 900
Reconnecting devices use jittered backoff so they do not all hit the notifiers at once