CMD Guide
HomeSystem DesignSystem Design Problems

Designing an S3-like Object Storage — Erasure Coding, Multipart & Versioning, Traced

Not a filesystem, and that is the point

Object storage looks like a filesystem and deliberately is not one. The differences are the design:

Scope

The architectural split that makes it work

Separate metadata from data:

These have completely different requirements — one needs consistency and indexing, the other needs throughput and cheap bulk capacity — and splitting them lets each be built correctly. It also explains why LIST is the awkward operation: it is a metadata range scan over a flat keyspace, which is why listing a bucket with a billion objects is paginated and comparatively slow while GET is fast.

A comparison for one 6 MB object. Three-times replication stores three full 6 MB copies for 18 MB total, a 3.0x overhead, surviving two losses, with cheap repair since one surviving replica is copied and cheap reads since any single copy serves the whole object. Reed-Solomon 6,3 splits the object into six 1 MB data chunks plus three 1 MB parity chunks for 9 MB total, a 1.5x overhead, surviving three losses, where any six of the nine chunks rebuild the object. Its repair is expensive: losing one 1 MB chunk requires reading six chunks from six machines and recomputing, a six times read amplification with network cost on every repair. The conclusion is to erasure-code the cold bulk and replicate the hot, small and latency-critical data.
A comparison for one 6 MB object. Three-times replication stores three full 6 MB copies for 18 MB total, a 3.0x overhead, surviving two losses, with cheap repair since one surviving replica is copied and cheap reads since any single copy serves the whole object. Reed-Solomon 6,3 splits the object into six 1 MB data chunks plus three 1 MB parity chunks for 9 MB total, a 1.5x overhead, surviving three losses, where any six of the nine chunks rebuild the object. Its repair is expensive: losing one 1 MB chunk requires reading six chunks from six machines and recomputing, a six times read amplification with network cost on every repair. The conclusion is to erasure-code the cold bulk and replicate the hot, small and latency-critical data.

Durability: replication versus erasure coding

Replication stores N full copies. Simple, fast to repair (copy a surviving replica), and fast to read (any copy serves the request). At 3×, you pay 200% overhead and survive 2 simultaneous losses.

Erasure coding splits an object into k data chunks and computes m parity chunks, such that any k of the k+m chunks reconstruct the object. With Reed–Solomon(6,3): 6 data + 3 parity = 9 chunks at 1.5× overhead, surviving 3 losses. Compare directly: erasure coding gives better fault tolerance for half the storage.

Which raises the obvious question — why replicate anything? Three real costs:

Hence the rule: erasure-code the cold, large bulk; replicate the hot, small and latency-critical. Most production systems do both and move objects between schemes as access patterns reveal themselves.

Failure domains are what turn the arithmetic into real durability. Chunks must be placed so that no single correlated failure — a rack, a power circuit, an availability zone — takes out more than m chunks. Nine chunks spread across nine racks survives a rack loss; nine chunks that happen to land in one rack survives nothing, while reporting the same replication factor. Durability is a property of placement, not of the coding scheme.

Multipart upload, and why it exists

Uploading a 5 TB object as one HTTP request is untenable: a failure at 99% loses everything, one connection caps throughput, and no proxy tolerates the duration. Multipart upload fixes it in three phases:

  1. Initiate — the client asks to start an upload and receives an upload ID.
  2. Upload parts — parts are uploaded independently, in parallel, in any order, each returning an ETag. A failed part is retried alone.
  3. Complete — the client sends the list of part numbers and ETags; the service assembles them and then makes the object visible.

The critical property is that the object becomes visible atomically at completion — readers never observe a half-uploaded object. Note the operational consequence: an abandoned multipart upload leaves parts consuming storage that no object references and no LIST reveals. Every real deployment needs a lifecycle rule to abort incomplete uploads, and forgetting it is a classic silent cost leak.

Integrity: checksums all the way down

At sufficient scale, disks return wrong data rather than errors — bit rot, firmware bugs, torn writes. So every chunk carries a checksum, verified on read and periodically by a background scrubber that walks stored data looking for corruption and repairs from parity or replicas. Without scrubbing, corruption is discovered only when someone reads the object, which for archival data could be years — by which time the redundancy protecting it may also have degraded.

End to end, the client can send a checksum with the upload so the service verifies the bytes it received match what was sent, catching corruption in transit. (S3's ETag is a related but distinct thing — for single-part uploads it is the MD5 of the object, but for multipart uploads it is a hash of the part hashes plus a part count, so it is not the MD5 of the whole object and cannot be compared as one. That mismatch surprises people writing verification scripts.)

Versioning and deletion

With versioning enabled, a PUT to an existing key creates a new version rather than overwriting, and each version is independently addressable. Because objects are immutable, this is nearly free — you add a metadata row pointing at new chunks and keep the old rows.

Deletion is the subtle part. A DELETE on a versioned object writes a delete marker: a tombstone that becomes the current version, so GET returns 404 while the prior versions still exist and can be restored. Two consequences worth knowing: deletion increases metadata, and deleting data does not free space until a lifecycle policy expires the old versions. A bucket whose storage bill keeps rising while the application "deletes" aggressively is almost always this.

Space is reclaimed asynchronously by a garbage collector that finds chunks no live version references, and by compaction that merges the surviving small objects out of mostly-dead storage regions into fresh ones — the same mechanic as LSM-tree compaction, and for the same reason: reclaiming space in an append-only store means rewriting what is still alive.

Which scheme, when

DecisionOptionChoose whenCost / breaks when
DurabilityErasure coding (6,3)Large, cold, bulk data — the majority by bytesSmall objects, latency-sensitive reads, repair-heavy fleets
Durability3× replicationSmall, hot, latency-critical objectsBulk storage — you pay 2× the disk for less fault tolerance
UploadMultipartAnything above ~100 MBTiny objects — pure overhead; needs abort lifecycle rules
UploadSingle PUTSmall objectsLarge objects — no resume, one connection, timeouts
ConsistencyStrong read-after-writeModern expectation; pipelines depend on itRequires a consistent metadata store — the harder component
ConsistencyEventual for overwritesLegacy designs, geo-replicated readsRead-after-write workflows silently read stale objects
MetadataSharded KV by bucket+keyScales to trillions of objectsLIST across shards needs a merge; prefix scans get hot

The consistency row deserves a note: object storage historically offered eventual consistency for overwrites, and plenty of designs still assume it. Strong read-after-write is now the norm, and it is worth being explicit about which you are designing, because a data pipeline that writes then immediately reads is correct under one model and intermittently broken under the other — with failures that only appear under load.

Pitfalls

Cost model — what dominates the bill

Object storage is the one system here where raw capacity really is the dominant cost — which is precisely why the erasure-coding decision is worth so much money.

Rough BOTE for 100 PB of logical data. At 3× replication you provision 300 PB; with RS(6,3) you provision 150 PB for better fault tolerance. At even $10/TB-month for dense disk, that difference is 150,000 TB × $10 = $1.5 million/month saved. No other decision on this page is worth a fraction of that, and it is why every large object store erasure-codes its bulk tier.

Then the operational costs that surprise people. Repair traffic is continuous: with hundreds of thousands of disks, some fail every day, and each erasure-coded repair reads k chunks across the network. That background rebuild bandwidth must be provisioned, and it competes with user traffic — which is why repair is rate-limited and why a large correlated failure produces a long rebuild queue. Metadata is small per object but enormous in aggregate: a trillion objects at ~1 KB of metadata each is 1 PB of strongly-consistent, indexed, low-latency storage — far more expensive per byte than the bulk tier, and often the harder scaling problem.

Dominant line items: bulk capacity (× the redundancy overhead); then metadata store capacity and IOPS; then egress, which for a public service is typically the largest revenue-relevant line; then repair bandwidth.

Levers: erasure-code everything that tolerates it (by far the biggest); lifecycle-tier cold objects to cheaper media and expire old versions; abort incomplete multipart uploads; and compact small objects so per-object metadata and minimum-block waste do not dominate for the many-tiny-files workload.

Operability: the fingerprints of a sick object store

Storage growing while application-reported data volume is flat has three usual causes and they are distinguishable: orphaned multipart parts (check incomplete-upload age), retained old versions plus delete markers (check version counts per key), and garbage collection falling behind (check unreferenced-chunk backlog). All three bill you for data nobody can read.

Read tail latency degrading while median holds is the erasure-coding signature — a read waits for k chunks, so one slow disk anywhere in the placement group hurts the tail; the fix is hedged requests or reconstructing from parity rather than waiting. Repair queue length growing after a rack failure is expected, but if it does not drain you are rate-limited below your failure rate, which means durability is degrading over time rather than recovering.

The two genuinely dangerous ones: scrubber-detected corruption rate rising, which can indicate a bad firmware batch or a failing hardware generation and is the earliest warning you will get before correlated data loss; and chunk placement skew, where a rebalance or a topology change has quietly co-located chunks of the same object in one failure domain. The second is invisible in every capacity dashboard and is exactly the condition under which a routine rack failure becomes a data-loss incident, so it deserves an explicit audit rather than trust.

Watch also for a hot metadata shard from sequential key prefixes, and LIST latency climbing as a bucket grows, which is inherent to range-scanning a flat keyspace and is a signal to redesign key layout rather than to add capacity. Signals worth having: incomplete-upload count and age, versions and delete markers per key, GC backlog, per-object chunk-placement failure-domain audit, scrub corruption rate by hardware batch, repair queue depth and drain rate, and read latency percentiles split by storage scheme.


Authored for this guide to cover the S3-like object storage design (Alex Xu Vol. 2, ch. 24 — not present in the Vol. 1 PDF); replication-versus-erasure-coding diagram hand-authored as SVG. Complements this guide's Distributed File System topic, "Checksums and ETags", and "Cloud Storage Data Movement" pages.

🤖 Don't fully get this? Learn it with Claude

Stuck on Designing an S3-like Object Storage — Erasure Coding, Multipart & Versioning, Traced? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Designing an S3-like Object Storage — Erasure Coding, Multipart & Versioning, Traced** (System Design) and want to truly understand it. Explain Designing an S3-like Object Storage — Erasure Coding, Multipart & Versioning, Traced from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Designing an S3-like Object Storage — Erasure Coding, Multipart & Versioning, Traced** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Designing an S3-like Object Storage — Erasure Coding, Multipart & Versioning, Traced** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Designing an S3-like Object Storage — Erasure Coding, Multipart & Versioning, Traced** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes