Skip to content
System Design

Learn

Design Google Drive

Designing a file sync service: chunked storage with content-addressed dedup, delta sync, the metadata and version model, conflict resolution, and sharing permissions.

3 min readUpdated 2 Sept 2026

#Storage#Sync#Deduplication#Consistency

File sync looks like a storage problem and is actually a metadata and conflict problem. The bytes are easy — blob storage solves that. Keeping many devices' views of a mutable tree consistent is the hard part.

Requirements

Functional

  • Upload, download, delete files; sync across a user's devices.
  • Share files and folders with permissions.
  • Version history and restore.
  • Offline edits that reconcile on reconnect.

Non-functional

  • Never lose a file. Durability outranks everything else here.
  • Sync should feel immediate — seconds, not minutes.
  • Bandwidth-efficient: re-uploading a 2 GB file after a one-line change is unacceptable.
100M users, 10 GB average → 1 EB raw
but dedup + delta sync cut effective storage several-fold
Read:write ≈ 1:1, unusually — this is not a read-heavy system

Chunking and content addressing

The single idea the whole design rests on: **split every file into fixed or content-defined chunks and name each chunk by the hash of its contents.**

v1a1f9c27bde40v2a1freuse9c2reuse5eae40reuseupload1 chunk changed of 425% of the bytes sent
The hash is the storage key, so identical chunks are stored once and an edit uploads only what changed — dedup and delta sync from one decision.

Three properties fall out for free:

  1. Deduplication. Identical chunks are stored once — across versions, and across all

users. Ten thousand copies of the same company handbook cost one copy.

  1. Delta sync. Editing one page changes one chunk; the client uploads that chunk and a

new chunk list. A 2 GB file with a small edit costs a few MB.

  1. Integrity. The hash is the checksum — corruption is detectable by construction.

Fixed-size chunks (say 4 MB) are simple but suffer the insertion problem: adding a byte at the start shifts every boundary and invalidates every chunk. Content-defined chunking (a rolling hash such as Rabin fingerprinting picks boundaries based on content) keeps boundaries stable under insertion. Mention the trade-off; fixed-size is a fine answer if you name its weakness.

Architecture

Client (watcher + chunker + local DB of known state)
   │
   ├─ Metadata service ──→ metadata DB (files, versions, chunk lists, ACLs)
   │        │
   │        └─→ notification service ──→ other devices (long poll / WebSocket)
   │
   └─ Block service ─────→ blob storage (chunks, content-addressed)

Splitting metadata from blocks is the key structural decision. Metadata is small, highly relational (a tree, permissions, versions) and needs transactions — a relational database. Blocks are huge, immutable and need no queries — object storage. Different systems, different scaling.

The sync protocol

Upload:
  1. Client chunks the file, computes hashes
  2. Asks metadata service: which of these hashes do you already have?
  3. Uploads only the missing chunks to blob storage
  4. Commits the new chunk list as a new version (one transaction)
  5. Metadata service notifies the user's other devices

Download:
  1. Device receives "file X changed to version N"
  2. Fetches the new chunk list, diffs against its local list
  3. Downloads only the chunks it lacks, reassembles

Step 2 is what makes uploading an already-known file instantaneous.

Long polling or WebSockets push changes to online devices; a cursor-based change feed ("give me everything after revision 4,318") lets a device that has been offline for a week catch up in one request. Do not have clients poll for the full tree.

Conflicts

Two devices edit the same file while offline. Options:

  • Last-writer-wins — simple, and silently destroys work. Rarely acceptable for files.
  • Keep both — the standard file-sync answer: the loser is saved as

report (conflicted copy, Ana's MacBook).pdf and the user resolves it. Dropbox and Drive both do this.

  • Merge — only possible with structured content, which is what

collaborative editing solves with OT or CRDTs. For opaque binaries it is impossible.

Detect the conflict with a version vector or revision number per file: a client committing version N+1 must state which version it was based on. If that is no longer the head, it is a conflict, not an update.

Sharing and permissions

Permissions are set on a node and inherited down the tree, so a naive check walks to the root on every access. Two standard mitigations: cache the resolved ACL per node, and denormalise inherited permissions onto descendants, recomputed asynchronously when a share changes. Say which you would pick and why — a share change on a huge folder is a big asynchronous job either way.

Follow-ups to expect

  • Encryption — at rest and in transit; note that end-to-end encryption breaks

cross-user dedup, since identical plaintext no longer produces identical ciphertext.

  • Large-file upload — resumable, chunk-parallel, with a session that survives restarts.
  • Trash and retention — soft delete, then garbage-collect chunks no version references.
  • Storage tiering — move cold chunks to cheaper archival storage.
  • Mobile — sync metadata always, download file content lazily on open.
  • Quota — count logical bytes per user, not deduplicated physical bytes.

Track this problem on the System Design sheet.

Frequently asked

How does file sync avoid re-uploading an entire file after a small edit?

By splitting files into chunks addressed by the hash of their contents. An edit changes only the chunks it touches, so the client uploads those and commits a new chunk list. The server can also tell the client which hashes it already has, so uploading a file that exists anywhere in the system costs nothing but the metadata.

How do you resolve sync conflicts?

Detect them with a per-file revision number or version vector — a client must state which version its edit was based on, and if that is no longer the head, it is a conflict. For opaque files the standard resolution is to keep both, saving the loser as a conflicted copy for the user to reconcile. Automatic merging is only possible for structured content.

Why separate the metadata service from the block storage?

They have opposite characteristics. Metadata is small, relational and transactional — a tree with permissions and versions — which suits a relational database. Blocks are large, immutable and never queried, which suits object storage. Splitting them lets each scale on its own terms and keeps huge payloads off the transactional path.

Related