Designing Dropbox: File Sync with Chunking, Delta Sync, and Conflicts
A user edits one paragraph in a 2GB PowerPoint on a hotel Wi-Fi connection, and three seconds later the change is on their laptop at home. Uploading 2GB obviously did not happen. The entire design of a file sync service falls out of one decision: files are not the unit of storage — chunks are.
Requirements
Functional: sync a folder across devices, share folders between users, version history, offline edits that reconcile later. Non-functional: handle files from 1KB to 50GB, minimize bandwidth (users on metered connections), sync latency of a few seconds, never lose a byte — durability beats availability here.
Split metadata from blocks
The first structural move: two planes with completely different access patterns.
- Block storage — immutable, content-addressed chunks in object storage (S3). Huge volume, dumb operations: put chunk, get chunk.
- Metadata service — the file tree: which files exist, which chunk list makes up each version, sharing ACLs, version history. Small data, strongly consistent, transactional (Dropbox runs this on sharded MySQL).
Clients talk to both: negotiate state with the metadata service, then move only missing chunks to/from block storage.
Chunking: why content-defined beats fixed-size
Naive: split every file into fixed 4MB chunks, hash each (SHA-256), store by hash. Content addressing gives you deduplication for free — a chunk shared by a thousand users is stored once — and integrity checking falls out of the name.
The subtle failure: insert one byte at the front of a file and every fixed-size chunk boundary shifts, so every hash changes, so you re-upload the whole file. The fix is content-defined chunking: slide a rolling hash (Rabin fingerprint) over the bytes and cut a chunk wherever the hash matches a pattern. Boundaries are determined by content, not offsets, so an insertion only changes the chunk it lands in and its immediate neighbors.
def chunks(data, mask=0xFFF): # ~4KB average chunk
start = 0
h = RollingHash()
for i, byte in enumerate(data):
h.roll(byte)
if h.value & mask == 0 or i - start >= MAX_CHUNK:
yield sha256(data[start:i+1]), data[start:i+1]
start = i + 1
if start < len(data):
yield sha256(data[start:]), data[start:]
Delta sync is then just set arithmetic: client computes chunk hashes, asks the metadata service "which of these do you not have," and uploads only those. The 2GB PowerPoint edit becomes a few hundred KB.
The sync protocol
A file version is a manifest: an ordered list of chunk hashes plus metadata. Committing an upload is a metadata transaction: "create version N+1 of file F with this chunk list, parent version N."
That parent version field is the concurrency control. It makes commits compare-and-swap: if two devices both commit against parent N, the first wins and the second gets a conflict — the server never silently overwrites.
1. Client watches FS events, chunks changed file, hashes
2. POST /commit {file_id, parent_version: N, chunks: [h1..hk]}
3. Server: missing chunks? -> 412 + list -> client uploads to block store -> retry
4. Server: parent stale? -> 409 conflict
5. Success: version N+1; notify other devices (long-poll/WebSocket)
6. Other devices pull manifest, fetch missing chunks, rebuild file
Notification is a long-poll or WebSocket channel carrying only "namespace changed, cursor X" — devices then pull the delta since their cursor. Push the signal, pull the data: it keeps fan-out cheap and clients correct after sleep/offline gaps.
Conflicts: do not merge, fork
Two devices edit the same file offline. On reconnect, both commit against parent N; one gets a 409. For arbitrary binary files, automatic merging is impossible — and last-writer-wins destroys someone's work, which for a file product is unforgivable.
The right answer is the boring one Dropbox ships: keep both. The losing commit becomes report (Rishi's conflicted copy 2026-07-13).docx. Both versions preserved, human resolves. In the interview, saying "LWW" here is the fastest way to fail; data loss is the one non-negotiable.
Version history is nearly free: manifests are tiny, chunks are immutable and shared across versions. Old versions cost only the chunks that changed. Garbage collection — deleting chunks no manifest references — needs care: reference-count asynchronously, delete lazily, never inline with user actions.
Scale notes
Metadata shards by namespace (user or shared folder) so one folder's commits serialize on one shard — which is exactly the CAS semantics you want. Block traffic goes straight to object storage with pre-signed URLs so bytes never flow through your API tier. Cold chunks tier down to cheaper storage classes.
| Interview probe | Answer sketch |
|---|---|
| Rename a 10GB file? | Metadata-only op — chunk list unchanged, zero bytes move |
| Dedupe across users? | Content addressing; add per-user encryption keys and you lose it (privacy trade-off, say it explicitly) |
| Millions of tiny files? | Batch commits; pack small chunks; metadata QPS is the bottleneck, not bandwidth |
| Sync loop storm? | Cursor-based deltas + jittered backoff; idempotent commits by content hash |
The one-line summary: content-addressed chunks + CAS commits on a strongly consistent metadata service + conflict copies instead of merges.
Keep reading
Designing Chat Presence: Online, Away, and the Lie of Instant Status
Heartbeat vs subscriptions, fan-out of presence, privacy, and why a boolean online flag does not survive a million concurrent sockets.
Consistency Models Beyond CAP: Linearizability, Causal, and Session Guarantees
'Strong vs eventual' is a cartoon. The real spectrum — linearizable, sequential, causal, session guarantees — and how to pick per operation, not per system.
Designing an API Gateway: The Front Door as a System
Authentication, routing, rate limits, transformations, and a plugin chain — in a tier that must add ~1ms and never be the outage. Envoy/Kong architecture from scratch.
Designing a CDC Pipeline: Change Data Capture from Binlog to Downstream
Dual writes are a lie; the database's own log is the truth. Log-based CDC, the snapshot-plus-stream handoff, schema evolution, and ordering guarantees that survive resharding.
Designing a Content Moderation Pipeline: Trust and Safety at Platform Scale
A billion uploads a day, a legal requirement to act in hours, and irreversible mistakes in both directions: hash matching, classifier tiers, human review queues, and appeals.
Designing a Distributed Rate Limiter: Local Buckets, Global Truth
One user, twenty gateway nodes, one limit of 100 req/s. Where does the counter live? Redis+Lua atomicity, the local-cache compromise, and failing open vs closed.
Newsletter
New posts, straight to your inbox
One email per post. No spam, no tracking pixels, unsubscribe anytime.
Comments
- No comments yet. Be the first.