LearnastraTHE DESIGN COURSE

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.

Advanced3h 20m of deliberate study10 questions

Builds on Design a text-sharing service (Pastebin), Strong versus eventual consistency, Redundancy and replication, Polling, long polling, WebSockets, and server-sent events.

By the end, you can
  • 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.
INTERVIEW STEP 1 / 7Clarify the requirements

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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. FR5: Share folders. Give collaborators current read or edit roles. Completing an upload does not grant a read-only user permission to publish it.

  6. 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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. NFR5: Metadata availability. Metadata operations meet 99.9% availability per calendar month. Show synced only after publication succeeds.

  6. 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.

  7. 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.

INTERVIEW STEP 2 / 7Estimate the scale

03Size transfer and metadata independently

  1. **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.

  2. 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.

  3. 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.

  4. **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.

INTERVIEW STEP 3 / 7Define the API

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

  1. hA, hD and hC are readable labels for hashes calculated from chunk contents. The example uses short labels to make the file layout visible.
  2. The service already has hA and hC, so upload only the missing hD bytes.
  3. 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.
  4. 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 409 with 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 returns 403.

Read, catch up and share

  • GET /files/f81/versions/8 returns the ordered chunks of version 8 after checking permission.
  • GET /changes?cursor=103 returns changes after position 103, starting with 104.
  • POST /folders/{id}/members with 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/f81 with a new name and baseVersion renames the file without changing its ID. DELETE /files/f81 with baseVersion saves a deleted-state marker. Both transactions check current permission and that baseVersion is still current; stale requests return 409.
  • POST /files/f81/restore names 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}/snapshot then 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.
INTERVIEW STEP 4 / 7Define the data model

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.

INTERVIEW STEP 5 / 7Draw the high-level design

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.

  1. Record the local edit. The client saves an unsynced journal entry so it can recover after a restart or missed filesystem notification.
  2. 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.
  3. 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.
  4. Transfer and verify. Upload those chunks. The server computes actual lengths and hashes instead of trusting the supplied labels.
  5. 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.
  6. 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

  1. Mira may publish 8 first. Ben's later commit sees that its expected version 7 no longer matches.
  2. Preserve Ben's local bytes as a visible conflict copy or ask him to resolve the conflict.
  3. 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

RetainedAdded / new routeChanged responsibility / route

Start here. Establish atomic publication and explicit conflict behavior before optimizing discovery or byte processing.

Stage 1: durable metadata first, polling for discovery

Stage 1: durable metadata first, polling for discoveryMira 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. Numbered arrows correspond to the connection key below. All connections are directional.Editing deviceEdited version +ordered chunk listMetadata service/ DBPermission, currentversion and historyPrivate chunkstoreVerified immutablebytesSecond deviceResume after lastapplied change12345Stage 1: durable metadata first, polling for discoveryMira 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. Numbered arrows correspond to the connection key below. All connections are directional.Editing deviceEdited version +ordered chunk listMetadata service/ DBPermission, currentversion and historyPrivate chunkstoreVerified immutablebytesSecond deviceResume after lastapplied change12345Stage 1: durable metadata first, polling for discoveryMira 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. Numbered arrows correspond to the connection key below. All connections are directional.Editing deviceEdited version +ordered chunk listMetadata service/ DBPermission, currentversion and historyPrivate chunkstoreVerified immutablebytesSecond deviceResume after lastapplied change12345

Follow the numbered arrows

  1. Editing device → Private chunk storeUpload verified missing chunks
  2. Editing device → Metadata service / DBConditional version commit
  3. Metadata service / DB → Private chunk storeCheck complete authorized references
  4. Second device → Metadata service / DBPoll changes / request manifest
  5. Private chunk store → Second deviceDownload permitted version chunks
The metadata transaction selects the complete visible version. Uploading an object does not change the file’s current-version pointer.
Trace the diagram in words
  1. Mira uploads the new hD chunk; the service reuses her verified hA and hC chunks.
  2. The database publishes complete version 8 only if Mira may still edit and version 7 is still current.
  3. Ben’s device asks for changes after 103, obtains change 104 and applies complete version 8.
What this stage establishes
  • 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.

Read the reasoning for this stage →
Smallest working system
INTERVIEW STEP 6 / 7Go deep

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

  1. A notification says a change may exist. It need not carry the whole version or be the only record of it.
  2. 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.
  3. Two edits delivered to three devices may create six notification attempts, but still only two committed changes.
  4. 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

A middle-chunk edit reuses two verified chunksCompare 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. Numbered arrows correspond to the connection key below. All connections are directional.Version 7manifesth A | h B | h CLocal edited fileh A | h D | h CMissing chunkUpload only hD: 4 MiBVerified chunkstoreh A, h C alreadypresentPublish completeversionVersion 8 + change1041234A middle-chunk edit reuses two verified chunksCompare 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. Numbered arrows correspond to the connection key below. All connections are directional.Version 7manifesth A | h B | h CLocal edited fileh A | h D | h CMissing chunkUpload only hD: 4 MiBVerified chunkstoreh A, h C alreadypresentPublish completeversionVersion 8 + change1041234A middle-chunk edit reuses two verified chunksCompare 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. Numbered arrows correspond to the connection key below. All connections are directional.Version 7manifesth A | h B | h CLocal edited fileh A | h D | h CMissing chunkUpload only hD: 4 MiBVerified chunkstoreh A, h C alreadypresentPublish completeversionVersion 8 + change1041234

Follow the numbered arrows

  1. Version 7 manifest → Local edited fileEdit the middle chunk
  2. Local edited file → Missing chunkCompare proposed hashes
  3. Missing chunk → Verified chunk storeVerify new h D bytes
  4. Verified chunk store → Publish complete versionAll required chunks ready
The byte transfer is partial, but the published file version remains whole.
Trace the diagram in words
  1. Compare manifests to identify one missing chunk.
  2. Verify uploaded bytes instead of trusting the hash label.
  3. Commit the complete new manifest only after every referenced chunk exists.
See stage 2: Independent chunk transfer ↗

08Add transfer efficiency and device discovery

  1. 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.
  2. 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.
  3. Use notifications for speed. Long polling or WebSockets can promptly say “new changes exist.” They do not replace saved history.
  4. 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.
  5. Apply complete files locally. Download missing chunks to a temporary file, verify the assembled bytes, then replace the visible local file as one operation.
See stage 3: Fast change discovery ↗

09Trace edits and offline conflicts

One edit, two devices

  1. 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].
  2. Her client journals the edit and sends the new manifest against version 7. The server reserves upload u81 for that operation.
  3. The server finds hA and hC verified and permitted for reuse. Only hD's 4 MiB uploads.
  4. The publication transaction checks edit permission and current version 7, then saves complete version 8 and change 104 together.
  5. 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.
  6. 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.
INTERVIEW STEP 7 / 7Find bottlenecks and failure points

10Handle cleanup, corruption and cost

  1. 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.
  2. 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.
  3. Stale permissions: check the current edit role during publication. A completed upload or an old replica's permission row cannot authorize a revoked editor.
  4. Conflicting edits: compare the base version during the transaction and retain rejected local work for deliberate resolution.
  5. 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

  1. Input: with consent, the permitted common base and both edited versions of plan.txt. For unsupported binary formats, keep the ordinary conflict-copy workflow.
  2. Output: a difference summary and proposed merged preview, saved separately from accepted file history.
  3. Integration: read fixed version IDs so the proposal describes known inputs. Synchronization never waits for model inference.
  4. 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.
  5. 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.
  6. 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.

  1. Calculate how many bytes Mira uploads and how much transfer she saves.
  2. Show the permission and current-version checks at commit; preserve Ben’s local work when it cannot be published.
  3. Explain how the third device discovers version 8 without receiving the original notification.
  4. Show when unused chunks can be collected while keeping retained file history readable.
Your self-check
Compare the worked solution
  1. 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.
  2. Check her current edit permission and expected version 7, then save complete version 8 and change 104 in the same transaction.
  3. 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.
  4. 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.
  5. If old history expired, obtain a consistent snapshot and its matching resume position.
  6. 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?
  1. Transfer and verify missing chunks.
  2. In one transaction, check permission and the old version, save the complete new chunk list, advance the current version and append a change record.
  3. 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?
  1. Check current permission and the version the edit started from.
  2. Preserve conflicting offline bytes.
  3. Recover missed changes from saved history. If it expired, install a complete snapshot and continue after the change number captured with it.
  4. AI proposals still require normal user approval and version checks.
Practise this topic in the revision deck ↗

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.

1 / 10Version 7 has three 4 MiB chunks. Only the middle chunk changes without shifting boundaries. What must be uploaded if the others remain verified?
2 / 10Mira commits version 8 before Ben submits an edit based on 7. What preserves both edits?
3 / 10Ben's socket drops before change 104 arrives. What is the recovery source?
4 / 10A read-only collaborator finishes uploading bytes. May that upload become the current shared-folder file?
5 / 10Why can a fixed-chunk strategy transfer more after inserting bytes near the beginning?
6 / 10The server receives hD as a claimed hash. What should it verify?
7 / 10Which operations belong in the publication transaction?
8 / 10A device cursor is older than retained change history. What prevents a catch-up gap?
9 / 10Deleting f81 leaves chunks referenced by retained versions. What should collection do?
10 / 10An AI merge preview was made from base 7, but the file head is now 9. Mira accepts the preview. What should happen?
0 of 10 answered

CLOSE THE LOOP

Explain it without the page.

Summarise the mechanism, one trade-off, and one failure mode. Then mark this chapter complete.

Go to the source