System designDeep dive11 min

File sync & deduplication

A sync service keeps the same folders on many devices without re-sending whole files on every save. It splits files into blocks named by their hash, uploads only blocks the server lacks, stores each unique block once, tells other devices through a journal and a cursor, and keeps both copies when two people change the same file.

1 · The problem

Uploading the whole file on every save wastes almost all the bandwidth

A 30-person architecture firm keeps its project folders in a sync service. Noor, an architect in the office, saves a 40 MB drawing about 20 times a day, usually after changing a few lines. Tariq is at a building site with a laptop and no signal. Every new project starts by copying the firm's 2 GB library of standard details.

Sending whole files would upload 20 × 40 MB = 800 MB a day for Noor's one drawing, then download it again on every device that holds the folder, and it would store each project's copy of the library separately. A sync design has to do better on each of these:

Need

Naive approach fails because

Technique

Send only what changed

Every save re-sends the whole file

Blocks named by hash

Store each byte once

Copies of the library pile up

Deduplication by hash

Survive inserted bytes

Shifted data looks entirely new

Content-defined chunking

Tell other devices fast

Polling every folder is slow and costly

A journal, cursors and long polls

Never lose an edit

Two saves of one file overwrite each other

A merge base and conflicted copies

2 · Blocks

A file is a list of block hashes, and the server asks only for blocks it lacks

The core idea is to describe a file by its content. Dropbox splits every file into 4 MB blocks, hashes each block with SHA-256, and records the file's contents as its blocklist, the ordered list of those hashes. Noor's 40 MB drawing is a list of 10 hashes. The service keeps two kinds of server: a metadata server that knows users, folders and blocklists, and block servers that only map a hash to the stored bytes.

Saving a new version is a negotiation. The client first tries to commit the new blocklist; the metadata server answers with the blocks it doesn't have, the client uploads only those, and commits again:

Noor changed a few lines inside block 7. Nine of the ten hashes are already known, so one save uploads 4 MB instead of 40 MB.

The hashes double as a check. Dropbox's API exposes a content hash per file: the SHA-256 of the concatenated block hashes. A client can compare its local file with the server's copy without downloading anything, and verify an upload arrived intact (Resumable & chunked uploads).

3 · Deduplication

Blocks named by content are stored once, wherever they appear

Because a block's name is its hash, two identical blocks are the same block. When Noor copies the 2 GB library into a new project, the commits list hundreds of hashes the server already has, and nothing is uploaded. Downloads benefit too: before fetching a block, the Dropbox client checks whether it already has it locally, inside existing files or a cache of deleted ones. Storing each unique block once is deduplication.

Two questions follow. Can two different blocks share a hash? Systems built this way assume not: with a strong hash such as SHA-256, an accidental collision is far less likely than a hardware bit error. Older hashes such as SHA-1 can now be collided on purpose, so they're unsafe as names in a store shared across users. And can anyone who knows a hash claim the block? No: the metadata server accepts a blocklist without uploads only if the account has access to those blocks, and the block server checks access before serving them.

Deduplication across users also leaks information. If a file uploads instantly, it already existed somewhere in the service. An attacker who can guess most of a document can test every variant: a standard contract with a salary that is a multiple of $500 between $50,000 and $200,000 has only 301 versions, and the one that "uploads" instantly is the real one. The defenses trade away some savings:

Defense

What it costs

Deduplicate only within one account or firm

No savings across customers

Encrypt with each user's own key before syncing

No cross-user deduplication at all

Upload anyway until a file has several copies, with a random threshold per file

Bandwidth for rare files

4 · Chunking

Fixed blocks fall apart when bytes shift; content-defined chunks survive inserts

Fixed blocks have a weakness. If Noor's drawing program inserts 1 KB near the start of the file, every byte after it moves, every later block boundary cuts in a different place, and every later block gets a new hash. One inserted byte at the start of a large file is enough to make the whole file look new.

Content-defined chunking places boundaries by looking at the data instead of counting bytes. A hash is computed over a small sliding window, 48 bytes in one early system and 64 bytes in the backup tool restic, and wherever the hash matches a chosen pattern, a chunk ends. An insertion changes only the chunks around it, because the boundaries after it are found again where the same content reappears. Minimum and maximum chunk sizes keep chunks in a useful range; restic cuts chunks of 512 KiB to 8 MiB.

Insert, fixed blocks
32 MB
Insert, content chunks
1.7 MB
Overwrite, fixed blocks
4 MB
Overwrite, content chunks
1.7 MB

Measured on 40 MB of random data, inserting or overwriting 1 KB at the 10 MB mark. Fixed blocks re-upload 8 full blocks after an insert; content-defined chunks (64-byte window, 512 KiB to 8 MiB, 27 chunks) re-upload only the one chunk holding the edit.

A different fix sends a delta instead of blocks. The rsync algorithm has the receiver send a checksum for each fixed block of its old copy; the sender slides over the new file with a cheap rolling checksum, finds those blocks at any offset, and sends only the bytes in between. Dropbox uses rsync together with compression to shrink block transfers.

5 · Change feed

Other devices learn of changes from a journal and a cursor

Once Noor's commit succeeds, the other devices in the firm need to find out. Dropbox records every file version as a row in an append-only journal: the shared folder, the path, the blocklist, and a journal id that only grows within that folder. Each device keeps a cursor, the last journal id it has seen, for every folder it syncs. An idle device holds a long poll open to a notification server that answers when something changes (WebSockets & long polling):

Noor's desktopcommits a new versionFile journalrow 88,412 appendedNotification serverholds long pollsMei's laptopcursor at 88,397Block servershash → bytescommit blocklistfolder changedwake the long pollfetch blocks it lacks

Mei's laptop asks for journal rows after 88,397, receives the 15 newer rows, and fetches only blocks it doesn't already hold.

Google Drive's API has the same shape. A client gets a start page token for the current state, lists changes from that token, and saves the new token returned with the last page for its next check. The pattern is the same as in Chat delivery & sync: devices never ask "what's new?", they ask "what came after this point?", so a laptop that was off for a week gets exactly the changes it missed.

Large files add one more optimization. If other devices wait for the whole upload to finish before downloading, a large file takes nearly twice as long to arrive when upload and download speeds are similar. Dropbox lets other devices prefetch blocks of a file still being uploaded. The gain approaches 2× for large files at equal speeds; in a test with a slower upload than download, a 500 MB file synced in 293 s instead of 383 s.

6 · Conflicts

A merge base tells local edits from remote ones, and a collision keeps both copies

Tariq edits the drawing offline at the site while Noor edits it in the office. When the laptop reconnects, the sync engine sees that its copy and the server's copy differ, but which one changed? Comparing the two can't tell a remote addition from a local deletion. Dropbox's rewritten sync engine keeps three trees: the remote tree (the server now), the local tree (the disk now) and the synced tree, the last state both agreed on. The synced tree is a merge base, as in version control:

Local vs. synced

Remote vs. synced

Meaning

Action

Changed

Same

Edited on this device

Upload

Same

Changed

Edited elsewhere

Download

Missing

Same

Deleted on this device

Delete on the server

Changed

Changed

Edited in both places

Keep both copies

The last row is Tariq's case. Whole files like drawings can't be merged automatically, so sync services keep both and leave the merge to people. Dropbox creates a conflicted copy with the editor's name, "conflicted copy" and the date in the file name; OneDrive keeps the online version under the original name and adds the device name to the local copy. Neither silently discards a version. Real-time co-editing avoids the problem for documents that support it (Collaborative editing).

Files also need a stable identity. Dropbox's original engine represented a move as a delete plus an add, so a network failure between the two could make a moved file vanish from other devices. Its replacement gives files and folders ids that survive moves, and moves are atomic regardless of folder size. Concurrent moves can still combine into a cycle in which each folder ends up inside another. Dropbox's original engine resolved this by duplicating folders; its replacement keeps the original folders, and the final arrangement depends on which move reaches the server first.

7 · Checklist

A sync design is blocks, a journal and a merge base

Each choice below answers one of the needs from the start of the page. Together they let Noor save twenty times a day for the cost of a few blocks, keep one copy of the firm's library, and bring Tariq's laptop back in without losing either drawing:

Decision

Choice for the firm

Unit of transfer

Blocks named by SHA-256; content-defined if files often get inserts

Upload

Commit a blocklist; upload only the blocks the server asks for

Storage

One copy per unique block; metadata and blocks in separate services

Deduplication scope

Within the firm's account, to avoid leaking across customers

Access

Check account access before accepting or serving a block by hash

Change feed

Append-only journal per folder; cursor per device; long polls

Direction

Three trees with a synced merge base

Conflicts

Keep both copies, named so people can merge them

Identity

Stable ids for files and folders; atomic moves

Check yourself

4 questions

1. Noor changes a few lines in a 40 MB drawing stored as ten 4 MB blocks. What does the client upload?
2. Why does inserting 1 KB near the start of a file defeat fixed-size blocks?
3. A file uploads instantly because another customer already stored it. What risk does that create?
4. Tariq's laptop is missing a file that the synced tree and the server both still have. What does the sync engine conclude?

Takeaways

Remember this

  • Describe each file as a list of block hashes, and upload only the blocks the server says it lacks.
  • Blocks named by content are stored once, but cross-user deduplication leaks whether a file exists, so scope it deliberately.
  • Content-defined chunking keeps an insertion from changing every later block; rsync-style deltas are the other fix.
  • Devices learn of changes from an append-only journal through their own cursor, woken by a long poll.
  • A synced merge base tells local changes from remote ones; when both changed, keep both copies and let people merge.