Back to System design

Design Dropbox / Google Drive

hard
Scale: 500M users; 10K-100K uploads/s Storage: EB-scale; dedup saves 20-50% depending on policy Dropbox, Google, Microsoft
Case StudyStorageSync

You edit a file on your laptop. A minute later it is on your phone. Someone else on the shared folder sees it too.

Scale500M users; 10K-100K uploads/s
StorageEB-scale; dedup saves 20-50% depending on policy

Key Concepts

1
That sounds like uploading and downloading. What makes it hard is that the same file exists on several devices that are rarely online at the same time, and people expect a one-word edit in a large file to sync instantly.
2
Split every file into blocks. Cut files into fixed pieces — 4 MB is common — and name each piece after a hash of its contents. This one decision buys three things at once.
Split every file into blocks.
3
Edit one slide in a 200 MB presentation and only the changed block is uploaded: 4 MB instead of 200 MB.
4
If two people upload the same file, the blocks have the same names, so it is stored once. That is how the storage bill stays sane.
5
And an interrupted upload restarts at the last complete block instead of at the beginning.
6
The file list is the real database. Blocks go into cheap storage and are never modified. What needs careful handling is everything else: folder structure, file names, which blocks make up which version of which file, and which device has seen which version.
The file list is the real database.
7
A user with 500,000 small files barely transfers any data and creates an enormous amount of this bookkeeping. Candidates usually design the block storage carefully and wave at the rest, and that is where interviewers push.
8
Syncing is asking what changed. Each device remembers a marker for the last update it saw. On reconnect it asks "what has changed since this marker?" and gets a list. It then downloads only the blocks it does not already have.
Syncing is asking what changed.
9
Use a counter for that marker, not a timestamp. Two servers have two clocks that disagree, and a clock can go backwards.
10
Conflicts are not resolved, they are kept. Two people edit the same file while offline. There is no correct way to merge two versions of a spreadsheet automatically — merging the bytes produces a corrupt file.
Conflicts are not resolved, they are kept.
11
So both are kept, and the second becomes "document (conflicted copy from Priya's laptop)". Saying "last write wins" here means quietly destroying someone's work, and the interviewer will ask what happened to it.
12
Deleting does not delete. Removing a file removes it from your file list. The blocks stay until no version of any file refers to them. That is what makes undelete and version history possible.
Deleting does not delete.
13
What the interviewer is probing.
What the interviewer is probing.
14
1. "Design Dropbox." Probing: whether you reach blocks early. Stalls: "Upload the file to S3 and save a row." Moves up: content-named blocks, and the three things that follow from it.
1. "Design Dropbox."
15
2. "Someone edits one paragraph of a 200 MB file." Probing: whether your design actually helps. Stalls: re-upload the file. Moves up: one block moves; the rest are already stored.
2. "Someone edits one paragraph of a 200 MB file."
16
3. "Two devices edit the same file offline." Probing: honesty about merging. Stalls: "Last write wins." Moves up: there is no correct automatic merge for binary files; keep both and let the person choose.
3. "Two devices edit the same file offline."
17
4. "A user has 500,000 tiny files." Probing: data versus bookkeeping. Stalls: "That is only a few GB." Moves up: same data, hundreds of times the metadata, and that is what gets slow.
4. "A user has 500,000 tiny files."
18
5. "How does a device know something changed?" Probing: the sync mechanism. Stalls: "Poll every 30 seconds." Moves up: a notification for speed, a stored marker for correctness, and losing a notification costs time rather than data.
5. "How does a device know something changed?"

High-level design

Client: chunker + sync engine + local SQLite metadata cache.
Upload: chunk file → hash → query server for missing → upload missing chunks → commit metadata.
Download: poll/listen for namespace changes → fetch metadata → download missing chunks.
Sharing: ACL on namespace; commits emit events to all members.
Versioning: old metadata revisions retained; chunks reference-counted.
Search: async indexer reads metadata + extracts content for indexable file types.

Components

  • Client SDK (chunker, sync engine, conflict resolver).
  • Metadata service (sharded by namespace/user_id); strong consistency.
  • Block store (S3-class object storage with replication + erasure coding).
  • Block index (chunk hash → blob location + reference count).
  • Notification service (WebSocket / long poll) for change events.
  • Search indexer for content search.
  • Audit + versioning store.
  • ACL service for sharing permissions.
  • Sync conflict detector.

Sizing, worked

  Users                                         100,000,000
  Average stored per user                              10 GB
  Block size                                             4 MB
  ---------------------------------------------------------------
  Logical data      100M x 10 GB        =               1 EB
  After dedup and compression (assume 30% saving)
                                        =             700 PB
  Blocks per user   10 GB / 4 MB        =         2,560 blocks
  Metadata row per block
    block hash 32 B + offset 8 B + version id 8 B + flags 4 B
                                        =            ~52 bytes
  Per user          2,560 x 52 B        =              133 KB
  All users         100M x 133 KB       =             13.3 TB
                                        (metadata alone, before
                                         indexes and versions)
  The small-file case that breaks naive designs
    A user with 500,000 files of 20 KB
    Data                                =               10 GB
    Blocks (one partial block per file) =             500,000
    Metadata rows                       =             500,000
    -> same data volume as the average user, 200x the metadata.

Consequences:
- Dedup pays for itself at this scale; the same OS installer, the same
company deck, stored once.
- 13 TB of metadata is a sharded database problem with real indexes, not
an afterthought attached to the block store.
- Per-user sharding of metadata keeps a single user's listing fast, which
is what they actually notice.

Sync protocol, step by step

  Client state
    last_seen_version (a cursor, not a timestamp -- clocks disagree)
  On reconnect
    1. GET /delta?cursor=<last_seen_version>
    2. Server returns metadata changes since the cursor, plus a new cursor.
    3. Client diffs locally: which block hashes does it already have?
    4. Client fetches only missing blocks from object storage (signed URL,
       so the bytes never pass through the application tier).
    5. Client commits locally and advances the cursor.
  On local change
    1. Chunk the file, hash each block.
    2. Ask the server which hashes are unknown (this is where dedup pays:
       usually most are known).
    3. Upload the unknown blocks.
    4. Commit a new version referencing the full block list.
  While online
    A notification channel pushes "something changed" so step 1 happens
    immediately rather than on a timer. The cursor makes the notification
    advisory -- losing one costs latency, not correctness.
  Why a cursor rather than a timestamp
    Two servers writing metadata have different clocks. A monotonic version
    per user is unambiguous and survives clock skew, leap seconds and
    backwards NTP steps.

Failure modes in production

Rename storm
Symptom: renaming a folder with 50,000 files saturates sync for hours.
Cause: rename modelled as delete + create per file.
Fix: model the move as a single metadata operation on the subtree;
blocks never move.

  Partial-block churn
    Symptom: an append-only log file re-uploads its last block constantly.
    Cause: fixed block boundaries meeting appends.
    Fix: content-defined chunking (rolling hash) for files that change by
    insertion, which keeps boundaries stable.
  Dedup across tenants leaking existence
    Symptom: an attacker learns whether a file exists by timing the "do you
    already have this hash?" call.
    Cause: global dedup exposed to the client.
    Fix: dedup per tenant, or require proof of possession before accepting
    a hash match.
  Reference count drift
    Symptom: storage grows despite deletions; orphaned blocks accumulate.
    Cause: counts updated in a separate transaction from version commits.
    Fix: treat reclamation as a periodic mark-and-sweep over metadata
    rather than trusting incremental counts.