01

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)
02

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)
03

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
04

API & contracts

API Design

PUT Object

EXAMPLE
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 enabled

GET Object

EXAMPLE
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)

EXAMPLE
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

EXAMPLE
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

EXAMPLE
# 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)
05

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
06

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
07

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
08

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
09

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
10

Edge cases

Handling Edge Cases

Concurrent PUT to Same Key

EXAMPLE
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 store

Partial Failure During PUT

EXAMPLE
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 replication

Hot Bucket / Hot Key

EXAMPLE
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 nodes

LIST Performance at Scale

EXAMPLE
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 size

Cross-Region Replication

Cross-Region ReplicationExcalidraw diagram · editable shapes · reveal step by stepExplore
11

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
12

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

EXAMPLE
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).
13

Production architecture

Full System Architecture (Production-Grade)

Full System Architecture (Production-Grade)Excalidraw diagram · editable shapes · reveal step by stepExplore
14

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.

15

Interview playbook

Interview Tips

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

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

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

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

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

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

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

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

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

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