Design complete systems / CHAPTER 51
Design a file synchronization service (Dropbox)
Keep complete file versions in sync across devices, resume failed transfers and preserve conflicting offline edits.
Builds on Design a text-sharing service (Pastebin), Strong versus eventual consistency, Redundancy and replication, Polling, long polling, WebSockets, and server-sent events.
- Transfer missing chunks before publishing one complete file version.
- Prevent an offline edit from silently replacing a newer accepted version.
- Recover missed changes from saved server history rather than depending on live notifications.
01Functional requirements
Mira edits a file on her laptop; Ben needs the complete new file on another device. Transfer only the changed pieces where possible, but never show a mixture of the old and new file. Save enough server history for a device to recover after being offline.
FR1: Transfer and resume files. Upload and download files in chunks, retry failed chunks and reuse verified unchanged data. Set account storage quotas so one account cannot consume unlimited space.
FR2: Synchronize file operations. Automatically propagate creates, edits, renames and deletions. A rename preserves the file's identity. Reject a folder move that would put a folder inside itself or one of its descendants.
FR3: Preserve offline edits. Keep a saved local list of edits waiting to upload, called a journal, and rescan files if filesystem notifications were missed. On reconnect, preserve conflicting edits visibly instead of silently replacing another device's newer version.
FR4: Keep version history. Retain complete, unchanging file versions for a stated period. An authorized restore creates a new current version; it does not rewrite past history.
FR5: Share folders. Give collaborators current read or edit roles. Completing an upload does not grant a read-only user permission to publish it.
FR6: Recover device progress. Let a device fetch saved changes after its last applied position. If those changes have expired from history, return the complete folder as it existed at one recorded change number. Before installing it, the device preserves any unsynced local edits in its journal and separate conflict copies. It then requests changes after the snapshot’s number.
Out of scope
- Real-time keystroke collaboration and automatic merging of arbitrary binary formats.
- One global order or transaction across every account and device.
Example: Mira edits plan.txt from version 7 while Ben is offline. Transfer only verified changed chunks, then publish complete version 8 before showing it as synced.
02Nonfunctional requirements
Assume 100,000 daily active users each change 20 MiB per day. Estimate file bytes separately from the database work needed to track versions.
NFR1: Metadata latency. At least 95% of version commits and change-list requests finish within 300 ms at the agreed peak. Chunk upload time is separate.
NFR2: Complete, consistent publication. Within one account or shared folder, save the complete version, change the current-version pointer and append its change record in one transaction. Serialize conflicting renames and moves. Readers see a complete old or new version, never a mixture of chunks from both.
NFR3: Permission and conflict safety. Before publishing, check current edit permission and the version the edit started from. An offline device cannot silently overwrite a newer version or restore a deleted file accidentally.
NFR4: Online discovery. At normal load, at least 95% of committed changes are discovered by other online devices within five seconds. Missed notifications recover from the saved change history.
NFR5: Metadata availability. Metadata operations meet 99.9% availability per calendar month. Show synced only after publication succeeds.
NFR6: Durability and retention. Published versions survive one storage-node failure under the selected storage guarantee. Retain incremental change history for 30 days. Older devices reset through a snapshot. Set a separate policy for retaining historical file bodies.
NFR7: Integrity and resource limits. Verify actual chunk bytes, sizes and hashes, total file size and account quota. Limit simultaneous transfers. Never delete chunks still needed by retained versions or active uploads. Knowing a chunk's hash does not grant access to it.
03Size transfer and metadata independently
**Transfer volume:**100,000 users × 20 MiB changed/day = two million MiB, roughly 2 TiB/day, before retries and retained versions. If 10% arrives in the busiest hour, plan for about 200 GiB that hour before overhead.
Chunk saving: a 100 MiB file divided into 4 MiB chunks has 25 chunks. If one chunk changes without shifting boundaries, upload 4 MiB plus its chunk list instead of 100 MiB.
When fixed chunks lose reuse: inserting bytes near the beginning can shift later boundaries. Many otherwise similar chunks then have different hashes. Measure this frequency before adding chunk boundaries based on content or fine-grained differences from an earlier version.
**Metadata:**100 million file-detail rows × 1 KB ≈ 100 GB before overhead. Count ordered chunk lists, retained versions, indexes and cleanup state separately.
Decision: separate the small database transaction that publishes a complete version from the large transfers that deliver its chunks.
04API Design: Requests, Responses and Retries
Mira's laptop starts from file f81, version 7. Its begin request describes exact bytes and the version edited:
POST /uploads
Idempotency-Key: edit-81
Content-Type: application/json
{"fileId":"f81","baseVersion":7,"size":12582912,"chunks":[{"hash":"hA","length":4194304},{"hash":"hD","length":4194304},{"hash":"hC","length":4194304}]}
{"uploadId":"u81","missing":["hD"],"leaseUntil":"2026-10-24T13:00:00Z"}
Finish this edit
- hA, hD and hC are readable labels for hashes calculated from chunk contents. The example uses short labels to make the file layout visible.
- The service already has hA and hC, so upload only the missing hD bytes.
- Call
POST /uploads/u81/commit. Success returns{"fileId":"f81","version":8,"changeSequence":104}: version 8 is now current, and other devices can discover it at change position 104. - A retry of the same begin or commit request returns the original upload or committed result. Uploading bytes alone does not change the current file.
Reject an unsafe edit
- Reusing a request key with changed content returns
409 Conflict. - If someone has already replaced the version being edited, return
409with currentVersion. The client keeps its local edit so the user can resolve the conflict. - Exceeding the upload quota returns
413. Trying to edit through a read-only share returns403.
Read, catch up and share
GET /files/f81/versions/8returns the ordered chunks of version 8 after checking permission.GET /changes?cursor=103returns changes after position 103, starting with 104.POST /folders/{id}/memberswith an accountId and role of read or edit changes folder sharing after checking the caller’s management permission.DELETE /folders/{id}/members/{accountId}removes that member. These operations change the saved role checked during later reads and commits.- To create a file, begin an upload with a parent folder and name instead of an existing
fileId. Its commit checks current permission and name uniqueness, creates the file ID and first complete version, and appends the change record together. PATCH /files/f81with a new name and baseVersion renames the file without changing its ID.DELETE /files/f81with baseVersion saves a deleted-state marker. Both transactions check current permission and that baseVersion is still current; stale requests return409.POST /files/f81/restorenames a retained version and the expected current version. After checking current permission, it publishes a new version referring to the retained complete bytes. It does not change the historical version.- If the requested change cursor is outside retained history, return an explicit reset-required result.
GET /folders/{id}/snapshotthen returns a consistent folder view and the matching change cursor; the client resumes after that cursor. - A folder move uses
PATCH /folders/{id}with the new parent and expected folder version. Check current permission at the relevant folders and reject a move inside itself or a descendant. Serialize conflicting moves, or check and update the relevant relationships in one transaction, so two moves cannot each pass an old check and together create a cycle.
05Data Model and Storage Access Patterns
Three terms help read the model. A manifest is the ordered list of chunks that makes up a complete version. The head is the file's current version. A cursor is the last change number whose file updates the device installed locally and recorded as complete.
| Record and key | What it tracks |
|---|---|
File, fileId; unique (parentId,name) |
Owner, current version and deleted state; identity survives rename |
Version, (fileId,version) |
Unchanging manifest, total size and the version edited |
Chunk, (ownerBoundary,hash,length) |
Private object location, verification and references from versions |
Change, (accountOrFolderId,sequence) |
Ordered saved changes for a device to catch up |
Upload, (ownerId,requestKey) |
Reserved upload, proposed manifest, deadline and original request result |
Folder member, (folderId,accountId) |
Current read/edit role and its permission version |
Order changes within the account or shared folder that owns them. A device can follow several such logs without requiring one global sequence for all users.
06Publish metadata atomically after bytes
Use a desktop client, an application/database for file details and private object storage for file chunks. Mira is editing plan.txt from version 7. The server calls this file f81; a rename keeps that same file identity.
- Record the local edit. The client saves an unsynced journal entry so it can recover after a restart or missed filesystem notification.
- Describe the bytes. Split the new version into 4 MiB chunks and calculate their hashes. Send the ordered chunk list, total size and base version 7. This ordered list is the manifest.
- Find missing chunks. The application checks which chunks are already verified and may be reused by this account/shared folder. It returns only the missing ones.
- Transfer and verify. Upload those chunks. The server computes actual lengths and hashes instead of trusting the supplied labels.
- Publish one complete version. The database performs the permission check, version comparison and writes as one transaction that excludes a competing publication. It accepts Mira’s edit only if her current role allows editing and 7 is still current. Then it saves the complete new manifest, changes the file’s current version to 8 and appends change record 104 to saved history. A separate earlier check would let another edit slip in before these writes.
- Report synced. Return success only after step 5 commits. A finished byte upload or a notification is not a published file version.
If Ben also edited version 7
- Mira may publish 8 first. Ben's later commit sees that its expected version 7 no longer matches.
- Preserve Ben's local bytes as a visible conflict copy or ask him to resolve the conflict.
- Do not automatically combine arbitrary changed chunks: binary file formats may have dependencies between distant byte ranges.
BUILD THE DESIGN, ONE CHANGE AT A TIME
Watch the architecture evolve.
Advance through the stages in this same diagram. Existing components keep their places; additions fill the available space, then add rows. Highlights show what changed from the previous stage, and numbered arrows match the connection key below.
Stage 1 of 3
Start here. Establish atomic publication and explicit conflict behavior before optimizing discovery or byte processing.
Stage 1: durable metadata first, polling for discovery
Follow the numbered arrows
- Editing device → Private chunk storeUpload verified missing chunks
- Editing device → Metadata service / DBConditional version commit
- Metadata service / DB → Private chunk storeCheck complete authorized references
- Second device → Metadata service / DBPoll changes / request manifest
- Private chunk store → Second deviceDownload permitted version chunks
Trace the diagram in words
- Mira uploads the new hD chunk; the service reuses her verified hA and hC chunks.
- The database publishes complete version 8 only if Mira may still edit and version 7 is still current.
- Ben’s device asks for changes after 103, obtains change 104 and applies complete version 8.
- Upload and verify chunks before the head changes.
- In one transaction, check current edit permission and base version, then save the complete version, current pointer and change record.
The cost: Polling is simple but adds requests and delay; fixed chunks can transfer extra data after shifted edits.
Why change it? The assumed 2 TiB/day and hashing work compete with short metadata operations.
Stage 2: byte workers protect metadata latency
Follow the numbered arrows
- Editing device → Block transfer serviceUpload only missing chunks
- Block transfer service → Private chunk storeVerified byte reads / writes
- Editing device → Metadata service / DBConditional version commit
- Metadata service / DB → Block transfer serviceRequire verified authorized references
- Second device → Metadata service / DBPoll changes / request manifest
- Second device → Block transfer serviceRequest permitted missing chunks
- Block transfer service → Second deviceVerified chunk download
Trace the diagram in words
- The metadata service reserves the upload and identifies hD as the missing chunk.
- The separate byte service receives hD and computes its length and hash without occupying metadata request workers.
- The metadata database checks permission and the expected version in the same transaction that publishes the complete file.
- Extract missing-chunk transfer and validation behind a separate bounded byte service.
- Reroute device bodies through that service; keep publication in the original metadata transaction.
Inspect the connection changes
- Now: Editing device → Block transfer service: Upload only missing chunks
- Now: Block transfer service → Private chunk store: Verified byte reads / writes
- Now: Metadata service / DB → Block transfer service: Require verified authorized references
- Now: Second device → Block transfer service: Request permitted missing chunks
- Now: Block transfer service → Second device: Verified chunk download
- Previously: Editing device → Private chunk store: Upload verified missing chunks
- Previously: Metadata service / DB → Private chunk store: Check complete authorized references
- Previously: Private chunk store → Second device: Download permitted version chunks
All other connections are retained. Response paths are implicit unless drawn.
The cost: The separate transfer service needs restricted credentials and trustworthy verification results. It can scale byte processing without changing how each folder orders metadata updates.
Why change it? Frequent polling by many online devices wastes reads when few files change.
Stage 3: faster discovery without a new source of truth
Follow the numbered arrows
- Editing device → Block transfer serviceUpload only missing chunks
- Block transfer service → Private chunk storeVerified byte reads / writes
- Editing device → Metadata service / DBConditional version commit
- Metadata service / DB → Block transfer serviceRequire verified authorized references
- Second device → Metadata service / DBPoll changes / request manifest
- Second device → Block transfer serviceRequest permitted missing chunks
- Block transfer service → Second deviceVerified chunk download
- Metadata service / DB → Notification relayA newer saved change may exist
- Notification relay → Second deviceWake device; hint may repeat
Trace the diagram in words
- After change 104 commits, the notification worker tells Ben’s device that new changes exist.
- Ben’s device requests changes after the last change number it installed and recorded as complete.
- If the hint is lost, later polling reads the same saved history. If that history expired, preserve local edits, install a folder snapshot and continue after the change number saved with it.
- Add a notification worker that sends wake-up hints through long polling or WebSockets.
- Keep the second device's cursor fetch and snapshot reset as the recovery path.
Inspect the connection changes
- Now: Metadata service / DB → Notification relay: A newer saved change may exist
- Now: Notification relay → Second device: Wake device; hint may repeat
All other connections are retained. Response paths are implicit unless drawn.
The cost: Live delivery adds connection state, slow-consumer policy and duplicate hints; it never replaces the retained change log.
07Compare transfer and synchronization mechanisms
Choose the transfer method
| Method | What the client sends | Cost |
|---|---|---|
| Fixed chunks | Changed fixed-size blocks | Simple; insertions can shift later boundaries |
| Content-defined chunks | Blocks whose boundaries follow byte patterns | Better reuse after shifts; extra scanning and metadata |
| Binary delta | Fine differences from one exact old version | May send less; requires that base and more CPU |
In our three-chunk 12 MiB example, changing the middle chunk sends 4 MiB and saves 8 MiB. Begin with fixed chunks and measure savings before adding either alternative.
Compress or share bytes for different reasons
- Compression represents one set of bytes more compactly. Already compressed images may barely shrink.
- Deduplication reuses one verified identical chunk. Keep a reference from every version that needs it.
- Reuse chunks only among files belonging to the same owner or explicitly shared folder, after checking permission. Revealing that an unrelated account already has a particular hash can disclose that it stored that content.
Recover missed notifications
- A notification says a change may exist. It need not carry the whole version or be the only record of it.
- The device stores the last change number whose updates it installed locally and recorded as complete, called its cursor. After reconnecting, it requests later changes from saved history.
- Two edits delivered to three devices may create six notification attempts, but still only two committed changes.
- If history no longer includes the cursor, get a complete folder snapshot captured at one saved change number. Preserve unsynced local edits before installing the snapshot, then request changes after its saved number. Choosing an unrelated later number could skip changes.
Separate transfer workers when needed
- In stage 2, independent programs verify and serve chunks so upload bandwidth and hashing do not delay metadata commits.
- Give each transfer a temporary credential naming only the chunks it may upload or download. Limit simultaneous transfers so one device cannot occupy every transfer worker.
- Add memory/faster hardware first if sufficient. Later partition metadata by account or shared folder. If a move spans database partitions, one partition’s local transaction cannot change both sides together. Use a supported transaction spanning those partitions or reject that kind of move. The destination must retain the folder’s access rules.
A middle-chunk edit reuses two verified chunks
Follow the numbered arrows
- Version 7 manifest → Local edited fileEdit the middle chunk
- Local edited file → Missing chunkCompare proposed hashes
- Missing chunk → Verified chunk storeVerify new h D bytes
- Verified chunk store → Publish complete versionAll required chunks ready
Trace the diagram in words
- Compare manifests to identify one missing chunk.
- Verify uploaded bytes instead of trusting the hash label.
- Commit the complete new manifest only after every referenced chunk exists.
08Add transfer efficiency and device discovery
- Move bytes off metadata servers. Give devices temporary credentials for their permitted chunk transfers. Limit simultaneous chunks and per-account bandwidth; retry only failed chunks.
- Keep related ordering together. Store an account/shared folder's metadata and change history together where possible. Splitting a huge folder requires explicit coordination for moves spanning partitions.
- Use notifications for speed. Long polling or WebSockets can promptly say “new changes exist.” They do not replace saved history.
- Use history for recovery. Reconnecting devices ask after their last applied cursor. If that history expired, preserve unsynced local edits, install a complete folder snapshot and resume after the change number captured with it.
- Apply complete files locally. Download missing chunks to a temporary file, verify the assembled bytes, then replace the visible local file as one operation.
09Trace edits and offline conflicts
One edit, two devices
- Version 7 of plan.txt contains three 4 MiB chunks:
[hA,hB,hC]. These are readable labels for chunk hashes. Mira edits the middle, producing[hA,hD,hC]. - Her client journals the edit and sends the new manifest against version 7. The server reserves upload
u81for that operation. - The server finds hA and hC verified and permitted for reuse. Only hD's 4 MiB uploads.
- The publication transaction checks edit permission and current version 7, then saves complete version 8 and change 104 together.
- Ben last applied change 103. His device receives a hint or polls, then asks for changes after 103 and obtains version 8's ordered chunk list.
- Ben records pending change 104 with its target file ID, version 8 and expected content hash in his local journal, downloads missing chunks to a temporary file and verifies the complete result before replacing the visible file. He then marks 104 applied and advances his cursor. After a crash, the client compares the visible file with that saved target to check whether replacement already happened, then finishes the journal update without creating a second conflict copy. Repeating change 104 does not create another edit.
Offline edits and deletion
- If Ben edited version 7 offline, keep his bytes and show a conflict; catching up must not overwrite them silently.
- Save a versioned deleted-state marker when a file is deleted. A late offline update cannot accidentally bring it back.
- An authorized restore creates a new current version. It does not rewrite the earlier history.
10Handle cleanup, corruption and cost
- Bad bytes: compare each chunk’s returned bytes with the length and hash listed in the permitted file version. Reject a mismatch and request a replacement copy. A checksum does not identify the uploader or grant download access; also authenticate devices, encrypt transport and restrict credentials.
- Unsafe cleanup: keep chunks needed by current versions, retained history and active uploads. An upload lease reserves temporary bytes until a deadline; a grace period protects publication racing cleanup.
- Stale permissions: check the current edit role during publication. A completed upload or an old replica's permission row cannot authorize a revoked editor.
- Conflicting edits: compare the base version during the transaction and retain rejected local work for deliberate resolution.
- Measure: track conflicts, discovery delay, incomplete uploads, corruption retries, snapshot resets and old unused storage. Retained versions and transferred bytes determine storage and network cost. Fast notifications do not prove that the device installed the right complete file or preserved an offline edit.
OPTIONAL DESIGN EXTENSIONWhere AI Fits in This Design
Use: propose a resolution for conflicting text edits
- Input: with consent, the permitted common base and both edited versions of plan.txt. For unsupported binary formats, keep the ordinary conflict-copy workflow.
- Output: a difference summary and proposed merged preview, saved separately from accepted file history.
- Integration: read fixed version IDs so the proposal describes known inputs. Synchronization never waits for model inference.
- User decision: accepting the preview still creates a normal new version. The publication transaction checks current edit permission and the expected current version. If someone edited again meanwhile, return another conflict.
- Fallback: show the original side-by-side versions when the model fails. The model cannot declare synced, discard another person's bytes or bypass a read-only role.
- Evaluation and limits: measure omitted edits, invented text and later reversions. Limit document length, time and cost. Use approved processing and restricted logs for private files; document content cannot issue application commands.
AWS's prompt-injection guidance explains why application controls must remain around model input.
DRAW. CALCULATE. EXPLAIN.
Resolve two offline edits without losing either
75 minutes · guided practice
Version 7 of plan.txt contains [hA,hB,hC], three 4 MiB chunks. Mira replaces hB with hD and commits version 8. Ben edits version 7 offline. A third device misses its notification, and Ben loses edit permission before reconnecting.
- Calculate how many bytes Mira uploads and how much transfer she saves.
- Show the permission and current-version checks at commit; preserve Ben’s local work when it cannot be published.
- Explain how the third device discovers version 8 without receiving the original notification.
- Show when unused chunks can be collected while keeping retained file history readable.
Compare the worked solution
- Mira uploads only hD’s 4 MiB; hA and hC already exist and may be reused. Compared with 12 MiB, this edit saves 8 MiB of upload.
- Check her current edit permission and expected version 7, then save complete version 8 and change 104 in the same transaction.
- Ben’s later attempt checks both current permission and its old base. Since he lost edit rights, deny publication even if bytes uploaded. Preserve his local edit for export or an explicitly permitted resolution.
- The third device asks for changes after its last applied position, downloads missing chunks for version 8, verifies a temporary complete file and replaces its local file. Record progress only after that action is recoverable.
- If old history expired, obtain a consistent snapshot and its matching resume position.
- Cleanup keeps chunks needed by versions 7/8, retained history or active upload leases. Deleting a current file entry alone does not make those bytes unused.
The checkboxes record your own assessment. The worked solution gives you a way to check the reasoning.
RETRIEVE BEFORE YOU REVEAL
Keep the important ideas.
Say the answer aloud, then turn the card over.
CARD 01What is synchronized atomically?
- Transfer and verify missing chunks.
- In one transaction, check permission and the old version, save the complete new chunk list, advance the current version and append a change record.
- Only then report synced.
CARD 02Why are chunks and deltas different choices?
- Fixed chunks are simple but can lose reuse after insertions shift boundaries.
- Content-defined chunks follow byte patterns.
- A binary delta describes changes from one exact old file version; applying it to different starting bytes can corrupt the result.
- Measure transfer savings before adding complexity.
CARD 03What prevents offline loss and permission bypass?
- Check current permission and the version the edit started from.
- Preserve conflicting offline bytes.
- Recover missed changes from saved history. If it expired, install a complete snapshot and continue after the change number captured with it.
- AI proposals still require normal user approval and version checks.
CHECK THE REASONING
Can you explain the choice?
Answer every question, then finish for an immediate score and an explanation for each answer. Aim for 80%, then revisit any gaps.
CLOSE THE LOOP
Explain it without the page.
Summarise the mechanism, one trade-off, and one failure mode. Then mark this chapter complete.