Ecommet
Case study

Design Distributed File Storage (like a shared drive)

Design a Dropbox or Google Drive style service: upload, sync and share files across devices. Covers chunking, content hashing and deduplication, metadata vs block storage, sync with change notifications, versions and conflicts.

Advanced 6 min read 3 quiz questions

Jump to the quiz

In a nutshell

  • Split files into chunks identified by their content hash; upload only chunks the server does not have.
  • Keep metadata (folders, files, versions, chunk lists) in a transactional database; blocks in object storage.
  • Clients sync by asking for changes since a cursor, woken up by a notification channel.
  • Concurrent edits create conflicted copies rather than silently losing one version.

Requirements

  • Functional: upload, download, delete; folders; sync across a user's devices; share with others; version history.
  • Non-functional: never lose data; efficient with bandwidth (resume uploads, send only changes); changes appear on other devices within seconds; files up to many GB.

Estimates

Assume 50 million users with 10 GB each on average: 500 PB before replication, so storage cost dominates and deduplication pays off. If each user changes a few files a day, metadata updates run in the thousands per second, while block uploads move far more bytes.

API

Commit a file version as a list of chunk hashes

Example
POST /files/commit { path: "/docs/plan.pdf", chunks: ["9f2c…", "a71b…", "9f2c…"], base_version: 6 }
  -> 409 { missing: ["a71b…"] }          upload only what is missing:
PUT  /blocks/a71b…  (bytes)
POST /files/commit … -> 200 { version: 7 }
GET  /changes?cursor=…  -> { entries: [...], cursor: "…", has_more: false }

Data model

StoreHolds
Metadata DB (sharded by user or namespace)Folders, files, versions, each version's ordered chunk list, sharing, change journal
Block store (object storage)Chunk bytes keyed by SHA-256 hash, stored once
Notification serviceLong-lived connections that tell clients "something changed"

High-level design

Uploading a change and syncing another device

Uploading a change and syncing another device Laptop → Block store: missing chunks Laptop → Metadata service: commit Metadata service → Metadata DB: new version Metadata service → Notifier: changed Notifier → Phone: wake up Phone → Metadata service: changes since cursor LaptopMetadata serviceMetadata DBBlock storechunks by hashNotifierPhonemissing chunkscommitnew versionchangedwake upchanges since cursor

All steps Only new chunks are uploaded. The metadata commit creates a version; other devices are notified and fetch the change list.

Diagram as text
  1. The laptop chunks the file, hashes the chunks and uploads only those the server is missing.
  2. It commits the new version: the ordered list of chunk hashes.
  3. The metadata service records the change in the user's journal and notifies the user's other devices.
  4. The phone asks for changes since its cursor and downloads only the chunks it lacks.

Deep dive: chunking and deduplication

Splitting a file into chunks (often a few MB) and naming each chunk by its SHA-256 hash gives several wins: uploads resume chunk by chunk; editing one part of a large file re-uploads one chunk; identical chunks (the same file in two folders, or shared by many users) are stored once.

Fixed-size chunking with content hashes, and finding what to upload

Java
import java.security.MessageDigest;
import java.util.*;

class Chunker {
    static String sha256(byte[] data) {
        try {
            return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(data));
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    static List<String> chunkHashes(byte[] file, int chunkSize) {
        List<String> hashes = new ArrayList<>();
        for (int i = 0; i < file.length; i += chunkSize) {
            hashes.add(sha256(Arrays.copyOfRange(file, i, Math.min(file.length, i + chunkSize))));
        }
        return hashes;
    }

    // Chunks the server does not have yet, each listed once, in file order.
    static List<String> missing(List<String> hashes, Set<String> serverHas) {
        Set<String> out = new LinkedHashSet<>();
        for (String h : hashes) {
            if (!serverHas.contains(h)) {
                out.add(h);
            }
        }
        return new ArrayList<>(out);
    }
}
Python
import hashlib

def sha256(data):
    return hashlib.sha256(data).hexdigest()

def chunk_hashes(file_bytes, chunk_size):
    return [sha256(file_bytes[i:i + chunk_size]) for i in range(0, len(file_bytes), chunk_size)]

def missing(hashes, server_has):
    """Chunks the server does not have yet, each listed once, in file order."""
    return [h for h in dict.fromkeys(hashes) if h not in server_has]

Deep dive: sync and conflicts

Each user (or shared folder) has an ordered change journal. A device stores a cursor into it and asks for everything after the cursor; a notification over a long-lived connection simply says "there is something new". Every commit carries the base version it was edited from: if the server's latest version is newer, two devices edited concurrently, and the server keeps both, saving the second as a "conflicted copy" for the user to resolve.

Bottlenecks and trade-offs

  • Metadata is the hard part: it must be strongly consistent and transactional; shard it by user or shared namespace.
  • Garbage collection: a chunk can be deleted only when no file version references it; use reference counts or periodic mark-and-sweep, with a grace period.
  • Cross-user deduplication saves space but can leak whether a file exists elsewhere; many services limit it to within an account.
  • Large shared folders generate many notifications: batch and coalesce them.

The last five minutes

Common mistakes

  • Re-uploading whole files for small edits.
  • Storing file bytes in the metadata database.
  • Last-write-wins on concurrent edits, silently losing work.
  • Deleting chunks still referenced by older versions or other files.

Interview questions

Try answering out loud before you open each one.

A user edits one paragraph of a 2 GB file. What is uploaded?

The client re-chunks the file and hashes the chunks; only chunks whose hashes changed are uploaded (often one or two, especially with content-defined chunking). Then it commits a new version listing all chunk hashes, most of which the server already has.

Check yourself

1. Why name chunks by their content hash?
Show the answer

Answer: Identical chunks are stored once and changes are easy to detect. Same content, same name.

2. Where do file versions and chunk lists live?
Show the answer

Answer: In a transactional metadata database. Metadata must be consistent; bytes go to the block store.

3. Two devices edit the same file offline. What should happen?
Show the answer

Answer: Keep both, saving one as a conflicted copy. No work is lost; the user decides.

Further reading

Your notes

Saved in this browser as you type.

Finished this topic?

Mark it read to track your progress, then carry on with the next one.

Back to the course