Advanced
Storage & Retrieval
25 min

Design Dropbox / Google Drive

A file sync service stores users’ files durably and keeps them consistent across all their devices. The core insight is to split the problem in two: tiny, structured metadata (who owns what, folder tree, versions) lives in a database, while the large, opaque file bytes are chunked and stored in an object store. Sync is then a matter of shipping only the chunks that changed.

Object StorageChunkingDeduplicationSyncMetadata

Design it yourself

Don't just read it — drag components onto a canvas and get Aria's interviewer review.

Requirements

Functional

  • Upload and download files of arbitrary size (up to a few GB)
  • Sync files automatically across all of a user’s devices
  • Share files and folders with other users (view / edit)
  • File versioning — restore a previous version
  • Folder hierarchy, rename, move, delete (with trash)

Non-Functional

  • 100M users, ~1B files, petabytes of data
  • Durability of 99.999999999% (11 nines) — never lose a file
  • Sync latency < a few seconds after an edit on another device
  • Read-heavy for downloads; uploads dominated by a few large files
  • Bandwidth-efficient — only transfer what actually changed

Capacity Estimation

Users100M total, ~10M daily active
Files~1B files, avg 1MB → ~1 PB raw
Chunk size4 MB fixed blocks
Dedup savings~30–50% on shared/duplicate content
Metadata / file~1 KB → ~1 TB metadata total

High-Level Components

Client + Local Sync Agent

A background agent watches the local folder, splits changed files into 4MB chunks, computes each chunk’s hash, and uploads only chunks the server doesn’t already have. It also listens for change notifications to pull remote updates.

API Gateway / Load Balancer

Terminates TLS, authenticates the device token, rate-limits, and routes metadata calls to the Metadata Service and byte transfers to the Block Service.

Metadata Service (relational DB)

Stores the file/folder tree, versions, chunk lists, and sharing ACLs in a sharded PostgreSQL / relational database. Small, highly structured, transactional — the source of truth for "what the file looks like now".

Block / Object Store (S3)

Stores the actual 4MB chunks in Amazon S3 (or equivalent object storage), keyed by content hash. Cheap, effectively infinite, 11-nines durable. Chunks are immutable and content-addressed, which gives free deduplication.

Notification Service

When a file changes, pushes a lightweight "something changed" event to the user’s other online devices over a long-lived connection (WebSocket / long-poll). Offline devices reconcile via a delta query on next launch.

Cache (Redis)

Caches hot metadata (folder listings, recent file versions) and chunk-existence lookups so the common "does this chunk already exist?" check never hits the database.

Architecture Diagram

Rendering diagram…

Deep Dives

Chunking & Content-Addressed Deduplication

Files are split into fixed 4MB blocks. Each block is hashed (SHA-256); the hash is both its storage key and its identity.

Why chunk? A 1GB file that changes one byte only needs the one modified 4MB chunk re-uploaded — not the whole gigabyte. This is the foundation of efficient sync.

Why content-address? If two users upload the same file (or the same chunk appears twice), the hash is identical, so it’s stored once. This deduplication saves 30–50% of storage in practice.

Upload flow: client hashes each chunk → asks the server "which of these hashes do you already have?" → uploads only the missing chunks → commits the new chunk list to metadata.

Client — chunk, hash, upload only what’s missing

// Client-side chunk + dedup check
List<String> hashes = file.chunks(4 * MB).stream()
    .map(chunk -> sha256(chunk))
    .toList();

Set<String> missing = api.checkMissingChunks(hashes); // server answers from Redis/DB
for (Chunk c : file.chunks(4 * MB)) {
    if (missing.contains(sha256(c)))
        api.uploadChunk(sha256(c), c);   // only the new bytes
}
api.commitVersion(fileId, hashes);       // metadata points at the chunk list

Metadata vs. Blocks — Two Very Different Stores

The single most important design decision: do not put file bytes in your database.

Metadata (folder tree, versions, chunk lists, ACLs) is small, structured, and needs transactions — renaming a folder must atomically update many rows. This belongs in a relational database, sharded by user or workspace.

Blocks (the actual bytes) are huge, opaque, and immutable. They belong in an object store (S3) that is built for cheap, durable, infinitely scalable blob storage. The database only ever holds the *list of chunk hashes* that make up a file version — never the bytes themselves.

This separation lets each layer scale independently: metadata QPS scales with the DB, storage volume scales with S3.

Real-time Sync via a Notification Service

When device A saves a change, device B should see it within seconds. Polling every device every few seconds does not scale to 10M devices.

Solution: each online device holds a long-lived connection (WebSocket) to a Notification Service. On a committed change, the Metadata Service publishes an event; the Notification Service pushes a tiny "workspace X changed, cursor Y" message to that user’s other devices. The device then does a delta query ("give me everything after cursor Y") and downloads only the changed chunks.

Offline devices simply run the same delta query on next launch — the cursor makes sync idempotent and resumable.

Delta sync — cursor-based, idempotent, resumable

// Delta sync API — resumable via a monotonic cursor
GET /delta?workspace=123&cursor=98456
→ {
    "changes": [
      { "path": "/reports/q3.xlsx", "version": 12, "chunks": ["a1b2", "c3d4"] }
    ],
    "cursor": 98470     // client stores this; next call resumes here
  }

Sharing & Conflict Resolution

Sharing is an ACL row in metadata: (fileId, granteeUserId, permission). Because blocks are content-addressed and immutable, sharing costs nothing extra — both users’ metadata simply point at the same chunks.

Conflicts happen when two devices edit the same file offline. Dropbox’s pragmatic answer is not to silently merge binary files: it keeps both and creates a "conflicted copy" (e.g. `report (Alice’s conflicted copy).xlsx`). Last-writer-wins is used for metadata like renames. For text, a real merge (like Google Docs’ operational transforms / CRDTs) is a much harder, separate problem.

Key Trade-offs

Where do the file bytes live?

Object store (S3), not the database

Blobs are huge, immutable, and opaque — an object store gives cheap, 11-nines-durable, infinitely scalable storage. The DB only holds the chunk-hash list.

Fixed 4MB chunks vs. whole-file transfer

Fixed chunks + content-addressed dedup

Editing one byte of a 1GB file re-uploads one 4MB chunk, not 1GB. Identical chunks are stored once, saving 30–50% of space.

Metadata store: SQL vs. NoSQL

Sharded relational (SQL)

Folder moves/renames and version commits need multi-row transactions and a consistent tree. Relational integrity is worth more here than raw write scale.

Push notifications vs. polling for sync

Long-lived push + delta query

Polling 10M devices every few seconds is wasteful and slow. A push "something changed" + a cursor-based delta query is efficient and resumable.

Interview Tips

  • 1Lead with the metadata-vs-blocks split — it’s the core insight and interviewers wait for it.
  • 2Explain chunking in terms of the "edit one byte of a huge file" scenario — it makes the 4MB block choice obvious.
  • 3Content-addressed storage (hash = key) gives dedup for free — call this out explicitly.
  • 4For sync, describe the Notification Service + cursor-based delta query rather than polling.
  • 5Be honest about conflicts: binary files get a "conflicted copy"; real merging (CRDTs) is a separate, harder problem.
  • 6Mention durability (11 nines) and how the object store, not your app, provides it.

Discussion

Discussion

Sign in to join the discussion.

Loading discussion…