1. Requirements Clarification

Functional Requirements

  • Upload, download, and delete files up to 50GB
  • Sync files across multiple devices automatically
  • File versioning — restore previous versions (up to 30 days)
  • Folder sharing and collaboration
  • Conflict detection and resolution
  • Offline support — queue operations and sync on reconnect

Non-Functional Requirements

  • Scale: 100 million users; average 10GB storage per user = 1 Exabyte total
  • Upload throughput: Resume interrupted uploads; parallel chunk uploads
  • Sync latency: Changes visible on other devices within 30 seconds
  • Durability: 99.999999999% (S3 11 nines) for stored files

2. High-Level Architecture

  Client (Desktop / Mobile)
       │
       ├─── Metadata API ──▶  Metadata Service ──▶ MySQL (files, versions, chunks)
       │                              │
       │                      Redis (sync state,
       │                       notifications)
       │
       ├─── Upload Chunks ──▶  Block Service ──▶  S3 (raw binary chunks)
       │                              │
       │                     Chunk DB (SHA256 → S3 key)
       │
       └─── Sync Notification ◀── WebSocket / SSE
                                 (notify other devices of changes)

3. Chunked Upload Design

Files are split into 4MB chunks on the client before upload. This enables resumable uploads, deduplication, and parallel transfer.

Upload Flow

  1. Client splits file: Divide file into 4MB chunks; compute SHA256 hash for each chunk
  2. Check which chunks exist: POST /api/chunks/check with list of SHA256 hashes. Server returns which hashes are already stored (dedup benefit).
  3. Upload missing chunks only: For each missing chunk, PUT /api/chunks/{sha256} — upload the binary data
  4. Commit the file: POST /api/files with file path + ordered list of chunk hashes. Server creates the file record and version entry.
  5. Server notifies other devices via WebSocket: "file X updated, fetch new chunk list"
Client pseudocode — chunked upload CHUNK_SIZE = 4 * 1024 * 1024 # 4MB def upload_file(file_path): chunks = split_into_chunks(file_path, CHUNK_SIZE) hashes = [sha256(chunk.data) for chunk in chunks] # Step 1: Check which chunks server already has missing = api.post('/chunks/check', {'hashes': hashes})['missing'] # Step 2: Upload only missing chunks (parallel) with ThreadPoolExecutor(max_workers=4) as pool: futures = [ pool.submit(api.put, f'/chunks/{h}', chunk.data) for h, chunk in zip(hashes, chunks) if h in missing ] wait(futures) # Step 3: Commit file api.post('/files', { 'path': file_path, 'chunk_hashes': hashes, # ordered list 'size': file_size, 'modified_at': file.mtime })

Content-Addressable Storage (CAS)

Storing chunks by their SHA256 hash (not a UUID or sequential ID) is called content-addressable storage. The benefit: if two users store identical chunks (e.g. a popular PDF, a common video intro), the chunk is physically stored only once in S3. Dropbox reported that CAS reduced their S3 storage by ~40% in 2011. The SHA256 hash is both the key and the integrity check — tampering with the content would change the hash.

4. Delta Sync — Only Upload Changed Chunks

When an existing file is modified, only the changed chunks need to be re-uploaded. The client retrieves the current chunk hash list from the server, compares with the new chunk hashes, and uploads only the diff.

ScenarioFile SizeChange SizeWithout Delta SyncWith Delta Sync
Edit 1 line in Word doc200MB~4KBUpload 200MBUpload 1 chunk (4MB max)
Add slide to PowerPoint50MB~2MBUpload 50MBUpload 1 chunk (4MB max)
Rename fileAny0 bytesUpload entire fileUpload 0 bytes (metadata only)
Video file re-encode2GBAll bytes changeUpload 2GBUpload 2GB (no savings)

5. Metadata Database Schema

SQL — files, versions, chunks CREATE TABLE files ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, user_id BIGINT UNSIGNED NOT NULL, parent_id BIGINT UNSIGNED NULL, -- NULL = root folder name VARCHAR(255) NOT NULL, type ENUM('file','folder') NOT NULL, is_deleted TINYINT(1) NOT NULL DEFAULT 0, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id), KEY idx_parent (user_id, parent_id), KEY idx_path_search (user_id, name) ); CREATE TABLE file_versions ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, file_id BIGINT UNSIGNED NOT NULL, version_num INT UNSIGNED NOT NULL, size_bytes BIGINT UNSIGNED NOT NULL, created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_file_version (file_id, version_num) ); CREATE TABLE version_chunks ( version_id BIGINT UNSIGNED NOT NULL, chunk_index INT UNSIGNED NOT NULL, -- ordering within file chunk_hash CHAR(64) NOT NULL, -- SHA256 hex PRIMARY KEY (version_id, chunk_index), KEY idx_chunk_hash (chunk_hash) -- find which versions use a chunk ); CREATE TABLE chunks ( hash CHAR(64) NOT NULL, -- SHA256 hex s3_key VARCHAR(200) NOT NULL, -- S3 object key size_bytes INT UNSIGNED NOT NULL, ref_count INT UNSIGNED NOT NULL DEFAULT 1, -- for GC created_at DATETIME NOT NULL, PRIMARY KEY (hash) );

6. Conflict Resolution

Conflicts occur when two devices modify the same file while one is offline. There are three common strategies:

  • Last-Write-Wins (LWW): The version with the most recent modified_at timestamp wins. Simple but loses data. Dropbox uses this as the base strategy.
  • Conflicted Copy: Both versions are preserved. The "loser" is renamed to filename (Device's conflicted copy YYYY-MM-DD).ext. The user sees both and manually resolves. Dropbox's default behavior.
  • 3-Way Merge: Only viable for plain text. Merge: (your version) + (their version) relative to (common ancestor). Git uses this for code files.

Watch Out — Clock Skew in Last-Write-Wins

Client clocks can be wrong by minutes or hours. If Device A has a clock 5 minutes ahead, its changes will always "win" LWW conflicts — even if Device B made more recent changes by wall-clock time. Solution: use server-assigned timestamps for version ordering, not client-reported modified_at times. The client's timestamp is stored for display purposes only.

7. File Versioning Strategy

Versioning stores the history of changes so users can restore previous versions. Because chunks are content-addressable, versioning is storage-efficient — unchanged chunks between versions are shared automatically.

  • Every save creates a new file_versions row with the full chunk hash list
  • Chunk storage is shared: only changed chunks consume new S3 space
  • Retention policy: keep all versions for 30 days, then delete old versions (but keep chunks if still referenced by other versions)
  • Chunk GC (garbage collection): when a chunk's ref_count drops to 0, schedule deletion from S3 (run GC job nightly)
  • Soft-delete files: set is_deleted=1 and keep for 30 days in Trash before permanent deletion

8. Sync Notification Between Devices

When Alice saves a file on her laptop, her phone needs to download the updated version. This requires a real-time notification channel.

  • After the metadata service commits a new file version, it publishes a file_changed event to Redis Pub/Sub
  • Each device maintains a long-polling connection or WebSocket to the sync service
  • The sync service subscribes to Redis and pushes events to connected devices
  • The event contains: file_id, new version number, changed chunk hashes
  • The device downloads only the changed chunks and reconstructs the file

How We Research and Update This Guide

We test the underlying formula or workflow, compare outputs with reliable references, and revise examples whenever the page content changes.

  • The workflow or formula is tested directly in the tool and compared against independent reference examples.
  • Examples are kept practical so readers can verify the result without hidden assumptions.
  • Pages are revised whenever the interface, calculation flow, or surrounding guidance materially changes.

Frequently Asked Questions — File Storage System Design