File Storage & Sync — Architecture¶
9. Architecture¶
The system splits cleanly along the metadata/data boundary, with a sync client doing real work on the device and a notification service closing the real-time loop.
Client sync engine (per device)
- watch filesystem, chunk + hash changed files
- reconcile local vs server via cursor
- upload missing chunks / download new chunks
│
│ 1. status(hashes) 2. PUT blocks 3. commit(metadata) 4. GET /sync
▼ ▼ ▼ ▲
┌───────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ Block Service │ │ Block Store │ │ Metadata Service │ │
│ status / GC │───────▶│ (object store│ │ file tree, │ │
│ refcounting │ │ S3-class, │ │ versions, │ │
└───────────────┘ │ content- │ │ chunk lists, │ │
▲ │ addressed) │ │ ACLs, cursor │ │
│ CDN (immutable └──────────────┘ │ (sharded by ns, │ │
│ blocks by hash) │ strongly consistent) │
download path └────────┬─────────┘ │
│ on commit: │
▼ "namespace changed"│
┌──────────────────┐ │
│ Notification Svc │─────────┘
│ (long-poll / WS │ push "pull!" to
│ per device) │ user's OTHER devices
└──────────────────┘
Every stage justified:
- Client sync engine — the workhorse: watches the local filesystem, chunks + hashes changed files, computes what
differs from the server, and drives the upload/commit/download protocol. Real intelligence lives on the client.
- Metadata service — the source of truth for the file tree, versions, per-file chunk lists, ACLs, and the sync
cursor. Strongly consistent, sharded by namespace; the commit is its atomic transaction. The real DB problem.
- Block service + block store — status (which hashes are missing → dedup), block PUT/GET, and refcount/GC.
Bytes live in an S3-class content-addressed object store; downloads are CDN-fronted (blocks are immutable).
- Notification service — holds a lightweight long-poll/WebSocket per
device; on a commit it pushes a signal ("namespace X changed") to the user's other devices, which then pull the
delta via /sync. Cheap push + efficient pull.
10. Request / data flow¶
Upload / edit (the core write path)¶
1. Client detects a changed file → splits into chunks → hashes each → [h1..h4]
2. Client → Block Service: status([h1..h4]) → missing:[h2,h4] (dedup: h1,h3 already exist)
3. Client → PUT /blocks/h2, /blocks/h4 (idempotent, resumable; server verifies hash)
4. Client → Metadata: commit(path, chunks:[h1..h4], baseVersion:7)
→ metadata TX: check baseVersion (conflict?) → write version 8 → bump refcounts → advance cursor
→ 201 {version:8} (this is the atomic "the file now exists at v8")
5. Metadata → Notification Service: "namespace changed"
6. Other devices receive the nudge → GET /sync?cursor → get {report.pdf v8, chunks} → download missing chunks → apply
Download / new device¶
1. Client → GET /sync?cursor=0 → full tree (or selective) as changes
2. For each file → GET metadata chunk list → GET /blocks/{hash} for chunks it lacks (CDN) → reassemble
Conflict (two devices edit the same file offline)¶
Device A commits report.pdf baseVersion:7 → becomes v8. ✔
Device B (also edited from v7) commits baseVersion:7 → server sees current=8 ≠ 7 → 409 Conflict
→ server keeps BOTH: creates "report (conflicted copy, Device B).pdf" → both sync everywhere. No data lost.
Failure path¶
Upload interrupted → re-run status() → upload only still-missing chunks (resumable). No commit yet = nothing visible.
Commit fails / times out → client retries (idempotent on (path, chunks, baseVersion)); orphan blocks GC'd by refcount.
Notification missed → client's periodic /sync poll catches up anyway (notification is an optimization, not truth).
11. Bottlenecks (ranked)¶
| Rank | Bottleneck | Why | Detect via | Fix |
|---|---|---|---|---|
| 1 | Metadata service (QPS + consistency) | high-QPS transactional commits + tree reads must be consistent | commit latency, shard hot spots | shard by namespace; cache hot reads; keep commits single-shard |
| 2 | Upload bandwidth | ingress of new/changed bytes | ingress rate, upload latency | delta sync + dedup (move only new chunks); resumable |
| 3 | Download bandwidth | read-heavy; popular files | egress, CDN hit rate | CDN immutable blocks; client-side chunk cache |
| 4 | Notification fan-out | shared folders → notify many devices | notify lag, connection count | lightweight signal + pull; reuse real-time patterns |
| 5 | Block GC / refcounting | trillions of chunks; safe deletion | orphan-block growth | async refcount + mark-and-sweep GC |
The signature problems: a consistent, sharded metadata service and moving as few bytes as possible — not the blob storage, which the object store handles.
12. Scaling each bottleneck¶
- Metadata → shard by
namespace_idso each user/shared-folder's tree + commits + sync stream are co-located and commits are single-shard transactions; cache hot tree reads (caching); read replicas for listing-heavy load. This is the crux of the design. - Upload BW → delta sync (chunk +
status+ upload-missing) and dedup mean you move only genuinely new content; resumable uploads survive flaky networks; compress chunks. - Download BW → CDN the content-addressed (immutable) blocks — cache by hash forever, no invalidation; clients keep a local chunk cache and only fetch what they lack.
- Notification → the push channel carries a signal, not data; shared-folder fan-out reuses
real-time fan-out routing;
a periodic
/syncpoll is the backstop if a nudge is missed. - GC → reference counting on chunks (++ on commit, -- on version expiry) + async mark-and-sweep to reclaim orphan blocks safely; never delete on the hot path.
20. Evolution with scale¶
| Stage | Shape | What forced the jump |
|---|---|---|
| 1 · Blob upload | POST whole file to a server → local disk / one object store; download it back | no sync; re-uploads whole files; one box; no dedup |
| 2 · Metadata + block split + chunking | separate metadata DB (file tree/versions) + object store for chunks; chunk + hash; dedup; resumable uploads | large files; wasted bandwidth; need history + dedup |
| 3 · Sync + notifications + delta (base target) | cursor-based /sync, notification service for near-real-time multi-device, delta up/download, sharded metadata, CDN downloads | multi-device users; "it must feel live"; bandwidth cost |
| 4 · Global scale | metadata sharded + geo-replicated, cross-region block storage, cold tiering, conflict handling at scale, selective/smart sync, optional client-side E2EE | exabytes, global users, storage cost, offline conflicts, privacy |
Transitions are driven by bandwidth waste (→ chunking/delta/dedup), multi-device real-time (→ sync + notify), and metadata QPS/consistency (→ sharding) — not by the difficulty of storing bytes.
→ Next: Deep Dives