Requirements & scope
Problem Statement & Requirements
Design a distributed object storage system that stores and retrieves arbitrary binary objects (files) of any size with high durability, availability, and scalability — similar to Amazon S3, Google Cloud Storage, or Azure Blob Storage.
Functional Requirements
- PUT object — upload objects from bytes to 5 TB in size
- GET object — download objects by key; support range reads (byte-range fetches)
- DELETE object — remove an object permanently
- LIST objects — list objects in a bucket with prefix filtering and pagination
- Multipart upload — upload large objects in parallel parts, then assemble
- Buckets — logical namespace for objects (globally unique name)
- Metadata — per-object user-defined metadata (key-value headers) and system metadata (size, content-type, etag, created_at)
- Versioning — optional per-bucket; retain all historical versions of an object
- Presigned URLs — generate time-limited, signed URLs for unauthenticated upload/download
- Lifecycle policies — auto-transition objects between storage classes or auto-delete after N days
- Storage classes — Standard (hot), Infrequent Access, Archive (cold)
Non-Functional Requirements
- Durability — 99.999999999% (11 nines) — lose < 1 object per 10 billion stored per year
- Availability — 99.99% (< 53 min downtime/year)
- Scalability — store exabytes of data, trillions of objects
- Throughput — handle millions of requests/second globally
- Consistency — strong read-after-write consistency (S3 achieved this in 2020)
- No size limit — individual objects up to 5 TB; buckets unlimited
Out of Scope
- POSIX file system semantics (no rename, no directories, no locks)
- Real-time streaming (this is blob storage, not a streaming platform)
- Block storage (EBS) or file storage (EFS/NFS)
Scale estimations
Scale Estimations
Traffic
| Metric | Value |
|---|---|
| PUT requests / second | 500K RPS |
| GET requests / second | 5M RPS (10:1 read-write ratio) |
| DELETE requests / second | 50K RPS |
| LIST requests / second | 200K RPS |
| Peak GET / second | 15M RPS |
| Total objects stored | 100 trillion |
| Total buckets | 500 million |
Storage
| Metric | Value |
|---|---|
| Average object size | 1 MB (heavily skewed: many small, few huge) |
| Median object size | 64 KB |
| Max object size | 5 TB |
| Total data stored | 100 trillion × 1 MB avg = ~100 EB (exabytes) |
| New data / day | 500K/s × 1 MB × 86,400 = ~43 PB/day |
| Replication factor | 3 (across AZs) |
| Raw storage needed | 100 EB × 3 = ~300 EB |
| Metadata per object | ~500 bytes (key, size, etag, timestamps, ACL, version) |
| Total metadata | 100T × 500 B = ~50 PB of metadata |
Bandwidth
| Metric | Value |
|---|---|
| GET bandwidth (5M × 1 MB avg) | ~5 TB/s |
| PUT bandwidth (500K × 1 MB avg) | ~500 GB/s |
| Cross-AZ replication | ~1 TB/s |
| Total egress | ~6.5 TB/s |
Hardware Estimate
| Component | Spec |
|---|---|
| Storage nodes | 100,000+ (with 100 TB usable each) |
| Disk per node | 12× 20 TB HDD (capacity optimized) + 2× 2 TB NVMe (metadata/journal) |
| RAM per node | 64-128 GB (metadata caching, I/O buffers) |
| Network per node | 25 Gbps NIC |
| Metadata nodes | 5,000+ (SSD-backed, high-IOPS) |
Layered architecture
High-Level Architecture
High-Level ArchitectureExcalidraw diagram · editable shapes · reveal step by stepExplore
Component Breakdown
Component BreakdownExcalidraw diagram · editable shapes · reveal step by stepExplore
API & contracts
API Design
PUT Object
PUT /{bucket}/{key} HTTP/1.1
Host: s3.region.example.com
Content-Type: image/jpeg
Content-Length: 3145728
x-amz-meta-camera: "iPhone 15" # user metadata
x-amz-storage-class: STANDARD
Authorization: AWS4-HMAC-SHA256 Credential=.../s3/aws4_request, ...
<3 MB binary body>
Response (200 OK):
ETag: "d41d8cd98f00b204e9800998ecf8427e"
x-amz-version-id: "v3nR2ql9sa8GhZ1" # if versioning enabledGET Object
GET /{bucket}/{key} HTTP/1.1
Host: s3.region.example.com
Range: bytes=0-1048575 # optional: first 1 MB only
If-None-Match: "d41d8cd98f00b204e9800998ecf8427e" # conditional
Response (200 OK / 206 Partial / 304 Not Modified):
Content-Type: image/jpeg
Content-Length: 3145728
ETag: "d41d8cd98f00b204e9800998ecf8427e"
Last-Modified: Thu, 03 Apr 2026 10:00:00 GMT
x-amz-meta-camera: "iPhone 15"
<binary body>Multipart Upload (Large Objects)
Step 1: Initiate
POST /{bucket}/{key}?uploads HTTP/1.1
Response: { "upload_id": "upl-abc123" }
Step 2: Upload Parts (parallel)
PUT /{bucket}/{key}?partNumber=1&uploadId=upl-abc123
<part 1 body: 100 MB> → ETag: "etag1"
PUT /{bucket}/{key}?partNumber=2&uploadId=upl-abc123
<part 2 body: 100 MB> → ETag: "etag2"
... (up to 10,000 parts, 5 MB - 5 GB each)
Step 3: Complete
POST /{bucket}/{key}?uploadId=upl-abc123
{
"parts": [
{"part_number": 1, "etag": "etag1"},
{"part_number": 2, "etag": "etag2"}
]
}
Response (200 OK):
ETag: "composite-etag-abc"LIST Objects
GET /{bucket}?prefix=photos/2026/&delimiter=/&max-keys=1000&continuation-token=...
Response (200 OK):
{
"name": "my-bucket",
"prefix": "photos/2026/",
"delimiter": "/",
"max_keys": 1000,
"is_truncated": true,
"continuation_token": "next-page-token",
"common_prefixes": ["photos/2026/01/", "photos/2026/02/"], # "folders"
"contents": [
{
"key": "photos/2026/cover.jpg",
"size": 3145728,
"etag": "d41d8cd98f00...",
"last_modified": "2026-04-03T10:00:00Z",
"storage_class": "STANDARD"
}
]
}Presigned URL
# Server-side generation (SDK)
url = s3.generate_presigned_url(
method="GET",
bucket="my-bucket",
key="photos/cat.jpg",
expires_in=3600 # 1 hour
)
# → https://s3.region.example.com/my-bucket/photos/cat.jpg
# ?X-Amz-Expires=3600
# &X-Amz-Signature=a1b2c3...
# &X-Amz-Credential=...
# Client uses URL directly — no SDK or credentials needed
GET https://s3.region.example.com/my-bucket/photos/cat.jpg?X-Amz-Expires=...
→ 200 OK + binary data (if signature valid and not expired)Data model
Data Model
Object Metadata Schema
Object Metadata SchemaExcalidraw diagram · editable shapes · reveal step by stepExplore
Data Chunk Layout
Data Chunk LayoutExcalidraw diagram · editable shapes · reveal step by stepExplore
Bucket Metadata
Bucket MetadataExcalidraw diagram · editable shapes · reveal step by stepExplore
Core design decisions
Core Design Decisions
Decision 1: Data Durability — How to Achieve 11 Nines
This is THE most important decision. Losing even one customer object is unacceptable.
Option A: Replication (3 copies across AZs)
Option A: Replication (3 copies across AZs)Excalidraw diagram · editable shapes · reveal step by stepExplore
| Pros | Cons |
|---|---|
| Simple to implement | 3× storage cost |
| Fast reads (any replica can serve) | 200% overhead for durability |
| Fast writes (parallel replication) | Expensive at exabyte scale |
| Simple repair (copy from surviving replica) |
Option B: Erasure Coding (Reed-Solomon)
Option B: Erasure Coding (Reed-Solomon)Excalidraw diagram · editable shapes · reveal step by stepExplore
| Pros | Cons |
|---|---|
| 1.4× overhead (vs 3× for replication) | Higher CPU for encode/decode |
| Higher durability (more redundancy per byte) | Slower reads (must read from 10 nodes minimum) |
| Massive cost savings at scale | Repair requires reading 10 chunks to reconstruct 1 |
| Mathematically provable durability | Complex implementation |
Recommendation: Hybrid Approach
Recommendation: Hybrid ApproachExcalidraw diagram · editable shapes · reveal step by stepExplore
Decision 2: Metadata Store — The Brain of the System
Decision 2: Metadata Store — The Brain of the SystemExcalidraw diagram · editable shapes · reveal step by stepExplore
Decision 3: Consistency Model
Decision 3: Consistency ModelExcalidraw diagram · editable shapes · reveal step by stepExplore
Request flows
Detailed Flow Diagrams
PUT Object Flow
PUT Object FlowExcalidraw diagram · editable shapes · reveal step by stepExplore
GET Object Flow
GET Object FlowExcalidraw diagram · editable shapes · reveal step by stepExplore
Multipart Upload Flow
Multipart Upload FlowExcalidraw diagram · editable shapes · reveal step by stepExplore
Performance & caching
Garbage Collection & Compaction
The Problem
The ProblemExcalidraw diagram · editable shapes · reveal step by stepExplore
GC Architecture
GC ArchitectureExcalidraw diagram · editable shapes · reveal step by stepExplore
Aggregate File Compaction
Aggregate File CompactionExcalidraw diagram · editable shapes · reveal step by stepExplore
Advanced design
Storage Node Internals
Disk Layout
Disk LayoutExcalidraw diagram · editable shapes · reveal step by stepExplore
Data Integrity — Checksums Everywhere
Data Integrity — Checksums EverywhereExcalidraw diagram · editable shapes · reveal step by stepExplore
Storage Classes & Lifecycle
Storage Classes & LifecycleExcalidraw diagram · editable shapes · reveal step by stepExplore
Edge cases
Handling Edge Cases
Concurrent PUT to Same Key
Problem: Two clients PUT the same key simultaneously
Client A: PUT bucket/key val=A → writes to nodes, commits metadata
Client B: PUT bucket/key val=B → writes to nodes, commits metadata
Resolution:
• Metadata store uses optimistic concurrency (version vector or CAS)
• "Last writer wins" based on metadata commit timestamp
• Both data payloads are written; GC later cleans up the loser's chunks
• With versioning enabled: both versions are preserved
The client that receives 200 OK last is the "winner"
All subsequent GETs return the winner's value
Guaranteed by strong consistency of metadata storePartial Failure During PUT
Problem: Data written to 2 of 3 nodes, then gateway crashes
Scenario 1: Metadata NOT yet committed
→ Object doesn't exist (metadata is source of truth)
→ Orphaned chunks on 2 nodes → GC cleans up in 72h
→ Client gets timeout → retries → new PUT succeeds
Scenario 2: Metadata committed (quorum write succeeded)
→ Object exists with 2 of 3 replicas
→ Repair service detects under-replicated chunks
→ Copies from surviving replica to new 3rd node
→ Full durability restored within minutes
Key insight: Metadata commit is the "point of no return"
Before commit: object doesn't exist, chunks are garbage
After commit: object exists, repair ensures full replicationHot Bucket / Hot Key
Problem: One bucket gets 1M+ requests/sec (e.g., viral content, popular API)
Metadata hotspot:
All requests for bucket X hit the same metadata partition
→ Add request-level caching for metadata (short TTL, 1-5s)
→ Partition metadata by hash(bucket + key) not just bucket
Data hotspot:
One object read 100K times/sec
→ CDN / caching layer in front of object storage
→ Read from any of 3 replicas (load balance)
→ For extreme cases: create temporary read-only replicas
S3 solved this with automatic partition splitting:
Bucket with increasing key prefix "2026/04/03/photo-001"
→ Auto-splits partition at "2026/04/03/photo-500"
→ Each partition served by different nodesLIST Performance at Scale
Problem: Bucket with 10 billion objects, LIST with prefix scan
Naive approach: scan all 10B keys → timeout
Solution: Metadata index designed for prefix scans
Key layout in metadata store:
bucket=my-photos key=photos/2026/01/01/img001.jpg
bucket=my-photos key=photos/2026/01/01/img002.jpg
bucket=my-photos key=photos/2026/01/02/img001.jpg
...
LIST ?prefix=photos/2026/01/01/
→ Range scan from "photos/2026/01/01/" to "photos/2026/01/01/\xff"
→ Returns first 1000 keys + continuation token
→ Continuation token = last key seen (for next page)
With ordered KV store: range scan is O(log N + K) where K = results
→ Fast regardless of total bucket sizeCross-Region Replication
Cross-Region ReplicationExcalidraw diagram · editable shapes · reveal step by stepExplore
Tradeoffs
Tradeoffs & Design Decisions Summary
| Decision | Option A | Option B | Chosen | Why |
|---|---|---|---|---|
| Durability | 3× replication | Erasure coding RS(10,4) | Hybrid | Replication for hot (fast access); EC for warm/cold (cost savings) |
| Consistency | Eventual | Strong read-after-write | Strong | Eliminates entire class of application bugs; 1-2 ms cost is acceptable |
| Metadata store | Sharded MySQL | Ordered KV (FoundationDB) | Ordered KV | Prefix scans for LIST; strong consistency via Raft; horizontal scaling |
| Small object storage | Individual files | Aggregate files | Aggregate | Avoids inode/allocation waste; 4× better disk utilization for small objects |
| Large object chunking | Variable size | Fixed 64 MB chunks | Fixed | Simpler placement, predictable I/O, easier parallelism |
| Write quorum | All 3 replicas | 2 of 3 (majority) | 2 of 3 | Tolerates 1 slow/failed node; repair brings 3rd up async |
| Object versioning | Always on | Opt-in per bucket | Opt-in | Saves storage for buckets that don't need history |
| Multipart assembly | Physical copy/concat | Metadata-only (logical) | Metadata-only | Zero copy overhead; parts stay in place; metadata records order |
| GC strategy | Immediate delete | Mark-sweep with grace period | Mark-sweep (72h) | Grace period prevents race condition data loss during in-flight PUTs |
| Encryption | Client-side only | Server-side (SSE) | Both options | SSE (default, transparent); client-side for maximum security |
Reliability & fault tolerance
Reliability & Fault Tolerance
Single Points of Failure & Mitigations
Single Points of Failure & MitigationsExcalidraw diagram · editable shapes · reveal step by stepExplore
Durability Math (11 Nines in Detail)
Durability Math (11 Nines in Detail)Excalidraw diagram · editable shapes · reveal step by stepExplore
Graceful Degradation
Level 1 (single disk failure):
→ Chunks on that disk read from other 2 replicas. Zero impact.
→ Repair creates new 3rd replica within 1-4 hours.
Level 2 (single node failure):
→ All chunks on that node served from replicas.
→ Repair redistributes replacement replicas across cluster.
→ Brief increase in cross-AZ traffic.
Level 3 (full AZ outage):
→ 1/3 of primary replicas unavailable.
→ Remaining 2 AZs serve all reads (2 of 3 replicas survive).
→ Writes: quorum (2 of 3) still possible across remaining AZs.
→ Metadata: Raft continues with majority of nodes.
Level 4 (metadata store degradation):
→ PUTs and DELETEs may fail (need metadata write).
→ GETs for recently accessed objects served from gateway cache.
→ LIST requests fail until metadata recovers.
→ Data is safe on storage nodes regardless.
Level 5 (region outage):
→ Failover to DR region (if cross-region replication enabled).
→ RPO: seconds to minutes (replication lag).
→ RTO: minutes (DNS update + metadata sync verification).Production architecture
Full System Architecture (Production-Grade)
Full System Architecture (Production-Grade)Excalidraw diagram · editable shapes · reveal step by stepExplore
Further exploration
Workshop note · added for the website’s common reading format
Rebuild Object Storage from memory, then change one assumption: ten times more traffic, a new region, or a stricter consistency requirement. Which component must change first—and which does not?
Compare Metadata service with the same boundary in a related design. Write down one alternative you rejected, what it would simplify, and when you would choose it instead. Follow the source link at the end of this article to explore the original document.
Interview playbook
Interview Tips
Start with the separation of metadata and data — This is the foundational insight. Metadata (where is this object?) is small, needs strong consistency, and lives on SSD. Data (the actual bytes) is large, needs durability, and lives on HDD. They have completely different requirements.
Explain 11 nines of durability concretely — Don't just say "replicate 3 times." Walk through the math: disk failure rate, repair window, probability of correlated failure. Show that fast repair (MTTR) matters more than replica count.
Erasure coding vs replication is the #1 tradeoff — At exabyte scale, 3× replication costs 2× more than erasure coding. Explain RS(10,4): 40% overhead instead of 200%, tolerates any 4 chunk failures. Use replication for hot data (speed), EC for cold (cost).
Multipart upload shows depth — Explain that parts are NOT physically concatenated. Metadata records the ordered list of part locations. GET streams parts in sequence. Zero-copy assembly. This is a non-obvious insight that impresses interviewers.
Address the small object problem — Millions of 1 KB files on disk = inode exhaustion + wasted allocation blocks. Solution: pack into aggregate files (256 MB). Index maps (object_id → file + offset + length). This shows practical systems knowledge.
GC with grace period prevents data loss — Explain the race condition: in-flight PUT writes chunks before metadata commits. If GC deletes "unreferenced" chunks during this window, data is lost. 72-hour grace period eliminates this risk.
Strong consistency is achievable — S3 switched to strong read-after-write in 2020. Explain how: quorum reads/writes on metadata store, linearizable operations via Raft, and why this eliminates an entire class of application bugs.
LIST performance at scale — Naive LIST on a billion-object bucket is a disaster. Explain ordered key-value store with range scans: O(log N + K) where K is result count. Prefix filtering is just a range scan with bounds.
Mention storage classes and lifecycle — Shows you understand the economics. Standard (3× replication, fast) → IA (EC, cheaper) → Glacier (EC, cheapest, slow retrieval). Lifecycle engine transitions automatically.
End with operational concerns — Background scrubbing (detect bit rot before it's needed), repair prioritization (most-at-risk chunks first), capacity planning (predict when to add nodes), and the importance of cross-AZ and cross-region placement for correlated failure resistance.