Your files are cut into small numbered pieces. The cloud keeps each unique piece once, plus a list of which pieces make each file. When you edit a file, only the changed pieces are uploaded, and your other devices fetch just those pieces.
Design a cloud file storage and sync service like Google Drive or Dropbox: users store files in the cloud, see the same folder on all their devices, and changes made on one device appear on the others. The interesting parts are efficient uploads of large files, keeping devices in sync, and handling conflicting edits.
POST /v1/files/commit
{ path|file_id, parent_version, blocks: [h1, h2, h3, ...], size }
-> 200 { file_id, version } (all blocks present)
-> 409 { conflict: true, current_version } (someone else changed it)
-> 412 { missing_blocks: [h2] } (upload these first)
PUT /v1/blocks/{hash} (or pre-signed URL) body: block bytes
GET /v1/blocks/{hash}
GET /v1/changes?cursor=<c> -> { entries changed since c, next_cursor } (long-poll / push)
desktop/mobile client
- watcher detects local changes
- chunker splits files into blocks, hashes them
- local DB of synced state
| |
| metadata (small, consistent) | block bytes (big)
v v
Metadata service <--> Metadata DB Block service -> object storage
(namespace, versions, (relational, (dedup by hash; blocks
ACLs, change log) sharded by are immutable)
| user/namespace)
v
Notification service --(long-poll / WebSocket)--> other devices:
"namespace changed" -> they call /changes and download missing blocks
file -> blocks of ~4 MB (fixed size; content-defined chunking survives
insertions better) -> hash each block
upload:
client sends the block list to commit
server replies "missing: [h2]" (h1, h3 already stored, maybe from another user)
client uploads only h2, then commits again -> new version = [h1, h2', h3]
A one-byte edit in a 1 GB file uploads ~4 MB, not 1 GB.
Identical files across users are stored once (dedup by hash).
entries(entry_id PK, namespace_id, parent_id, name, type, latest_version, deleted)
UNIQUE(namespace_id, parent_id, name)
versions(file_id, version, block_hashes[], size, modified_by, created_at,
PRIMARY KEY(file_id, version))
changes(namespace_id, seq, entry_id, op) -- per-namespace, monotonically increasing
blocks(hash PK, storage_key, size, refcount)
Metadata needs transactions (a commit updates the entry, adds a version and appends to the change log atomically), so it lives in a relational database sharded by namespace (a user's or shared folder's tree). Blocks live in object storage.
parent_version (optimistic concurrency). If two devices edit offline and both commit, the second gets 409; the client keeps both, saving its copy as "file (conflicted copy from Laptop).docx". No silent data loss.