Design a File Storage Service (Dropbox / Google Drive)
Fast recall
Quick revision
Core answer
Chunk-based file representation, deduplication by content hash, metadata vs blob storage separation, sync protocol for clients, and pre-signed URL pattern.
Storing files as blobs in PostgreSQL, or uploading whole-file on every change (no chunk diffing).
Discuss content-addressed storage for cross-user dedup, the file change journal for incremental sync, end-to-end encryption trade-offs, and consistent-prefix sharing semantics.
Interview-ready guide
Interview answer
Clarifying questions I'd ask first
- Folder hierarchy depth, sharing model (read-only links, per-user, group folders)?
- Versioning — keep how many versions, time-bounded?
- Conflict resolution — last-write-wins or fork to conflict copy?
- Selective sync, trash, public links?
- End-to-end encryption?
Assumptions for this design
500M users, 100M DAU, average file 1 MB with long tail to GBs, 100 GB free quota, 4 MB fixed chunks. 2 PB/day growth. Sub-2-second sync visibility for small files. Pre-signed S3 URLs for direct transfer. Conflict forking. No E2EE in v1.
Quick capacity check
- 5 PB/day uploads, ~30% net new after dedup = ~175 GB/sec to S3
- 25 EB raw before dedup, ~17 EB after
- Metadata: 500B files, ~250 TB across sharded PostgreSQL (256 shards at ~1 TB each)
- Sync fan-out: ~2M push notifications/sec
- Critical: bytes go directly client ↔ S3, never through our app tier
The central decisions
- 1Chunk files into 4 MB pieces — enables cross-user dedup, diff-sync, resumable uploads, parallel transfer
- 2Address chunks by SHA-256 of bytes — same content always hashes to same key, so dedup is automatic
- 3Separate metadata (sharded PostgreSQL) from blobs (S3)
- 4Pre-signed S3 URLs for direct client transfer — bytes never traverse app servers
Why 4 MB chunks
- Smaller (64KB): too many chunks per file, huge metadata overhead
- Larger (64MB): hurts partial-edit dedup
- 4 MB is the industry sweet spot (Dropbox uses ~4 MB)
- Variable-size content-defined chunking (Rabin) is better for edited text but more complex; defer to v2
Upload flow — step by step
- 1Client computes SHA-256 of each 4 MB chunk locally
- 2Client POSTs the hash list to Upload Service
- 3Server checks
chunkstable; returns pre-signed PUT URLs only for missing chunks - 4Client uploads missing chunks directly to S3 in parallel
- 5Client commits; server creates new file_version row, increments chunk ref counts, publishes change event
Download flow
- Server returns ordered chunk list with pre-signed GET URLs
- Client downloads chunks in parallel from S3
- App tier handles only metadata; bandwidth scales via S3
Sync flow — long-poll with per-user journal
- File change → Kafka → SyncService writes to
sync_eventsfor affected users (owner + collaborators) - Client maintains a cursor (last-seen seq); calls
GET /sync/changes?cursor=N&timeout=30s - Server returns immediately if events exist; otherwise holds connection up to 30s, signals on new event
- Long-poll over WebSocket because mobile clients reconnect frequently and stateless server is simpler
- Push notification fires for offline devices
Conflict resolution
- Two devices edit the same file offline; both upload
- Optimistic version check: second commit detects parent version mismatch, creates a fork as
(conflict copy from device X).docx - User manually merges — same as Dropbox model
- Google Docs-style operational transformation is a separate beast; out of scope here
Data model
files(sharded by owner_id) — file tree, current_version pointerfile_versions— ordered chunk_hashes per versionchunks— the dedup table; chunk_hash PK, s3_key, ref_countsync_events— per-user append-only journalsharesandpublic_links— access control
LLD — clean abstractions
ChunkRegistryinterface — PostgreSQL impl, could swap to DynamoDB for higher hash-lookup scalePresignedUrlFactoryinterface — S3 impl, could swap to GCS or AzureUploadSessionserver-side state tracks claimed chunksSyncServicewith a waiter registry for long-poll efficiency- Garbage collector with 7-day grace runs separately
Storage choice rationale
- S3 for chunks: infinite capacity, 11-nines durability, lifecycle policies for cold tier
- PostgreSQL for metadata: ACID for tree mutations, JOINs for shares/versions, sharded by owner_id
- Redis for upload sessions, dedup bloom filter, share permission cache
- Rejected files-in-PostgreSQL — doesn't scale past low-hundred-MB; backups impossible
Reference-counted chunks
- Each commit increments ref_count for chunks; each delete decrements
- Chunks with ref_count = 0 for > 7 days are GC'd
- 7-day grace prevents race with in-flight uploads
Failure handling
- S3 down: cross-region failover; metadata still works
- DB shard down: read replicas serve stale browse; multi-AZ failover ~30s
- Upload session expires: client retries from scratch (chunks already uploaded skip dedup)
- Conflict at commit: 409 Conflict; client forks locally
- Mobile offline: client queues changes, replays on reconnect
Production extras
- TLS everywhere; OAuth/OIDC for clients
- Pre-signed URLs short-lived (15 min) and bound to specific S3 key
- S3 server-side encryption (KMS); per-customer keys for enterprise
- Async virus scan with quarantine on flag
- Audit log for enterprise compliance
- Lifecycle policies move cold files to Standard-IA / Glacier
At 10× scale
- Geo-distributed metadata with CRDTs for shared folders
- Multi-region S3 with nearest-region client routing
- Variable-size content-defined chunking for better text/code dedup
- Edge POP chunk caches for popular downloads
- WebSocket sync for active sessions
Recommendation
4 MB content-addressed chunks deduped at the upload boundary, sharded PostgreSQL metadata + S3 blobs with pre-signed URLs for direct client transfer, per-user long-poll sync journal, reference-counted chunks with grace-period GC, and CDN-cached public shares — the same shape Dropbox, OneDrive, and Box run in production.
Design a File Storage Service (Dropbox / Google Drive)
Direct Answer
Split files into fixed-size chunks (4 MB), upload them to object storage (S3) addressed by content hash (deduplication), and store metadata (file tree, versions, sharing) in a sharded PostgreSQL cluster. Use a chunk-level diffing algorithm so only changed chunks are transferred on update. Sync clients use a long-poll/push channel plus per-user change journal for incremental sync. Generate pre-signed S3 URLs for direct client upload/download to bypass the application tier. CDN-cache read-only public-share downloads.
Common Mistakes & Pitfalls
Storing files as blobs in PostgreSQL, or uploading whole-file on every change (no chunk diffing).
Check your understanding
Answer these without looking back at the article.
- How would you implement collaborative editing of a shared document (Google Docs style) on top of this chunk-based storage?
- Walk through what happens when a user uploads a 50 GB file with intermittent connectivity — step by step.
- How does the design change if you add end-to-end encryption (zero-knowledge)? What features do you lose?
- Design the search index that lets users find files by name and content across their drive.
Related interview prep categories
1Want a guided curriculum order?
These modules are also sequenced in our flagship backend interview track — open it when you prefer an ordered path instead of browsing by topic.