Storage & Data
Design Dropbox Sync
01
Requirements
Requirements
- Two-way sync — changes on any device propagate to all others
- Work offline: edit files with no network; sync when reconnected
- Delta sync — only upload/download changed blocks of a file
- File sharing: per-file or per-folder read/write ACLs
- Version history (restore previous version of a file)
- Cross-platform clients: macOS, Windows, Linux, iOS, Android, web
- Cross-user deduplication (if 1000 users have the same PDF, store it once)
- Sync latency < 5 seconds for typical edits on good network
- Handle individual files up to 100 GB; folders with 1M+ files
- No data loss — durability trumps everything else
- Minimal upload bandwidth via block-level diff + dedup
- Scale to 700M users, ~50 PB of unique data, ~exabytes stored (pre-dedup)
- End-to-end integrity — every block verified by hash
02
Scale Estimation
Scale Estimation
03
API Design
API Design
Three logical APIs: metadata (file tree + versions), blocks (raw byte I/O), and notify (push changes to clients). Clients talk HTTPS for metadata + blocks and hold a long-poll / WebSocket for notify.
Client uploads new file metadata: {path, block_list: [sha256_hashes], mtime, parent_rev}. Server checks parent_rev for concurrent edits; returns {rev, conflict?: {…}}. Metadata is lightweight (few KB).
Client sends list of SHA-256 hashes; server returns which ones are missing. Dedup happens here: blocks server already has from any user don't need uploading.
Upload a block. Server verifies the hash of received bytes == URL hash. If mismatch → reject + log. After write, return 201.
Download a block by hash. Served from CDN for hot blocks; from cold tier otherwise. Auth required — client must own a file referencing this hash.
Poll for changes since cursor X. Server returns a stream of {path, rev, action: added|changed|removed} + a new cursor. Efficient way for clients to catch up after being offline.
Long-poll / WebSocket. Server holds the connection; pushes "changes available" pings. Client then calls /metadata/delta to fetch. Keeps clients live without constant polling.
List all historical versions of a file. Each version references its own block list; old blocks retained per retention policy.
04
Architecture
Architecture
Three tiers: block storage (content-addressable; SHA-256 keyed), metadata service (sharded OLTP), and the client sync engine (watches filesystem + talks to server). Notify service is a thin pub/sub on top. Dropbox famously moved off AWS S3 onto their own block storage system ("Magic Pocket") in 2016 for cost.
05
Deep Dive — Sync Loop + Block Dedup
Deep Dive — Sync Loop + Block Dedup
When a user saves a 100 MB photo, the sync loop does NOT upload 100 MB. Here's the exact sequence:
- FS watcher fires. OS-level file-system event (inotify on Linux, FSEvents on macOS, ReadDirectoryChanges on Windows) tells the client "foo.jpg changed."
- Client chunks the file. Split into 4 MB blocks. Compute SHA-256 of each block. Result: 25 hashes for our 100 MB photo.
- Query
/blocks/checkwith 25 hashes. Server looks them up in the block-metadata DB (refcount > 0 = already stored somewhere). Returns which are missing. For a small edit to an existing photo, only the changed blocks are missing — often just 1–2. - Upload only missing blocks. Concurrent
PUT /blocks/{hash}. Server verifies bytes-hash = URL-hash, writes into Magic Pocket (durably replicated). - Commit metadata.
POST /metadata/commitwith new block list, parent_rev. Server CAS on parent_rev — if another device changed meanwhile, returns a conflict for the client to resolve. - Notify other devices. Metadata write emits event; Notify service pushes "something changed" to subscribed clients. They call
/metadata/deltato pull new metadata and (if they have the file open) fetch needed blocks.
Cross-user dedup. The content-addressable store keys blocks by their SHA-256 hash. If user A uploads a block with hash X, and later user B's client asks "do you have X?", the answer is yes — no upload needed. Storage cost drops ~30% from cross-user dedup on typical mixed workloads (much higher for shared software installers, documents templates, stock photos).
sequenceDiagram
participant C as Client
participant BL as Block svc
participant ST as Block storage
participant M as Metadata svc
participant N as Notify
C->>C: chunk file → [h1..h25]
C->>BL: POST /blocks/check [h1..h25]
BL-->>C: missing: [h7, h19]
par concurrent uploads
C->>BL: PUT /blocks/h7
BL->>ST: write (replicated)
ST-->>BL: ok
and
C->>BL: PUT /blocks/h19
BL->>ST: write (replicated)
ST-->>BL: ok
end
C->>M: POST /metadata/commit {blocks, parent_rev}
alt parent_rev matches
M-->>C: rev=42
M->>N: FileChanged event
N-->>C: push to other devices
else stale parent_rev
M-->>C: 409 conflict + server state
Note over C: run conflict resolver
end
Conflict resolution. Two devices edit the same file offline, both come online and commit. Dropbox's choice: both versions are preserved. The second commit creates foo.txt and foo (user's conflicted copy).txt. Simple, predictable, survives years of production use. They explicitly chose not to do OT / CRDT merging — file systems don't have semantic awareness of the content.
Delta sync for big files. For large files (> 100 MB) changed in small ways (e.g., a 2 GB video with re-edited metadata), block-level chunking naturally gives delta — only the block(s) containing the change are uploaded. For rolling changes (inserts in the middle of a file shift all subsequent block boundaries), Dropbox uses rolling hash chunking (Rabin fingerprint) so chunk boundaries follow content, not fixed offsets. A rename or middle-insert only shifts 1–2 blocks, not the whole rest of the file.
"Client chunks files into 4 MB blocks with SHA-256 hashes. Before upload, queries the block service for missing hashes — cross-user dedup means we only upload the actually-new blocks. Metadata commit is CAS-on-rev to detect concurrent edits. Conflicts save both versions (never merge). Notify service pushes metadata changes to other devices via long-poll/WebSocket. For large files with inserts, rolling-hash chunking keeps most boundaries stable. Storage is content-addressed by hash across all users; ~30% typical dedup savings."
06
Tradeoffs & Design Choices
Tradeoffs & Design Choices
- Fixed-size vs rolling-hash chunks. Fixed-size (4 MB) is simple and fast. Rolling hash (Rabin fingerprint) gives better delta ratios on files with mid-content inserts but costs more CPU + breaks cross-user dedup slightly (same logical content can hash differently). Dropbox uses hybrid — fixed by default, rolling for media.
- Block store separate from metadata store. Metadata (tree structure, versions, ACLs) is transactional — OLTP on MySQL. Blocks are immutable byte bags — object storage on Magic Pocket. Conflating them would put transactional constraints on byte-addressed storage = slow.
- Save both on conflict vs merge. Save-both is the right call for generic files. Merging requires understanding file format (text, docx, images) — out of scope for a sync engine. Google Docs has per-character merge because it owns the editor too.
- Magic Pocket vs S3. Dropbox moved off S3 in 2016 because at their volume, running their own storage (with custom erasure coding) was cheaper. The "build vs buy" crossover is specific to storage-heavy businesses.
- Long-poll vs WebSocket for notify. Both work. Long-poll simpler, survives more firewalls, wastes a little compute at the server. WebSocket holds state but more efficient. Dropbox uses long-poll historically; modern clients shifted toward WS.
07
Failure Modes
Failure Modes
GET /blocks/X.08
Interview Tips
Interview Tips
- Lead with chunking + content-addressable storage. This is THE answer to "how do you make this efficient?" Most candidates miss this and just "upload the file to S3."
- Separate metadata from blocks. Metadata is transactional (needs DB); blocks are immutable byte bags (object store). Don't conflate them.
- Conflict resolution is "save both." Not OT/CRDT. Name this explicitly — it shows you understand the scope of a sync engine vs a collab editor.
- Delta sync via chunk hashes. 99% of candidates think "rsync-style diff" — the correct Dropbox answer is "chunked CAS, query missing hashes, upload only those."
- Offline-first is the sync loop's existence. If you don't have a client-side SQLite holding pending ops + a reconcile step, your design assumes online-always.
09
Evolution
Evolution
MVP — Python client, MySQL, S3
Fixed 4 MB chunks, SHA-256 hashes, S3 for blocks, MySQL for metadata. Dropbox's actual architecture for years.
Sharded metadata + notify service
MySQL sharded by user_id. Long-poll notify pushes change events to clients — no polling. Carries to ~100M users.
Magic Pocket — exit S3
Build custom block storage with Reed-Solomon erasure coding; reduce storage cost at scale. ~2016 for Dropbox. Cross-user dedup built in.
Nucleus sync engine rewrite (Rust)
Client sync engine rewritten in Rust (from Python) in 2019–2020. Correctness issues from the decade-old engine solved by redesigning around a proven algorithm. Classic "second-system" that shipped.
Smart Sync + collaboration
"Online-only" files don't occupy local disk until opened. Dropbox Paper + Spaces add collab layers on top of the sync engine. The sync engine stays file-oriented; collab is separate.
Watch and read
References & Videos
Try next
Free to read · better with Enzo
Whiteboard this with Enzo
Enzo runs it as a live system design round on the whiteboard and grades your trade-offs.