System Design Storage
Blob / Object Storage
A multi-megabyte Instagram photo never travels through every API server on the way to a worldwide carousel. The phone got a short-lived upload path, wrote bytes into , and background workers built thumbnails while a served the public read.
Object storage is the default home for large immutable bytes: photos, videos, backups, and logs that you replace as a whole rather than patch in place. The design craft is , durability, consistency assumptions, cost, and the async path around the blob.
Upload journey
App authorizes a key
Mint identity and destination metadata so the client knows where to write.
The client talks to storage, not your app tier, so gigabytes skip the servers.
Bytes, key, and metadata land durably so later workers can find the object.
Event → workers → CDN
Thumbnails, scans, and public reads follow once the object exists.
What actually happens
The Instagram path above is the default whenever the payload is large and the client is untrusted. Your API authorizes an and mints a ; the phone writes straight to a ; an object-created event wakes workers that write domain state to a metadata database (DB); public viewers hit a nearby edge instead of the origin.
An object is body bytes, that unique key, and (size, checksums, timestamps, plus small user-defined headers). Requests hit an Application Programming Interface (API) tier. A metadata plane maps keys to chunk locations. A data plane spreads those chunks across disks, racks, and Availability Zones (AZs), often with instead of three full copies.
Large files use so a dropped network retries one part, not the whole video. Aging data follows a into colder tiers. Dollar figures later are illustrative: confirm current regional pricing before you commit an estimate in an interview or a budget review.
Try it: walk a multipart object upload
Large blobs upload as numbered parts under one session ID. Parallel PUTs improve throughput on good networks, while per-part retry contains failure blast radius on flaky ones.
- Session
- None
- Phase
- idle
- Uploaded volume
- 0 MB
- Parts pending / failed
- 5 / 0
Client
presigned PUTHolds upload ID, tracks completed part numbers, and sends CompleteMultipartUpload when the ordered list is ready.
Part buffers
parallel PUT- Part 1 · 128 MBpending
- Part 2 · 128 MBpending
- Part 3 · 128 MBpending
- Part 4 · 128 MBpending
- Part 5 · 128 MBpending
Final object
after completeReaders still see the previous version until complete succeeds.
Start a multipart session, upload numbered parts in any order, then complete with the ordered ETag list. A failed part retries alone instead of restarting the whole object.
Simulate a dropped connection. Initiate, upload two parts, fail the third, then retry only that part. Notice how earlier parts remain billable storage but do not need re-upload.
Complete vs abort. Finish all parts and complete to flip the object visible for readers. Abort instead when the user cancels; production buckets also need lifecycle rules to reap stale multipart sessions after a timeout.
How to choose object storage patterns
The path above only fits some bytes. Three questions decide whether is the right home, and which upload and tier pattern to pair with it.
1. Are the bytes mostly replace-as-a-whole? Photos, videos, backups, and log files fit object storage. Databases that need in-place 4 KB writes usually want block volumes instead. Shared path semantics belong on a file system.
2. Who holds the credentials? Prefer a so app servers stay thin and Identity and Access Management (IAM) keys never sit on the device. Use when files are huge or networks drop mid-flight.
3. How hot is yesterday's data? Keep interactive media hot; automate a for aging logs and old originals. Measure retrieval patterns before locking deep archive. Put ownership and access-control lists (ACLs) in a metadata DB: stores bytes, not product state.
Simple rule of thumb
Put unstructured blobs in object storage with IAM-scoped , client-side uploads, lifecycle tiers, and a for read-heavy media. Use events for async processing. Add cross-region copies only where Disaster Recovery (DR) or residency justifies the bill. At very large spend you can evaluate specialized stacks, but most teams win with managed object storage and a clear retention policy first.
From problem to mechanism
Here is the Instagram upload again, slowed down so you can see where a naive proxy fails and what the storage split actually changes.
Problem. A multi-megabyte photo (and later a video) must land durably, sprout thumbnails, and be readable worldwide, without turning every API server into a bandwidth pipe.
Naive system. The app accepts the file body, writes it to disk or a volume, and serves reads from the same tier. There is no , no , and no event that tells workers the object exists. Credentials either sit on the client or every byte transits your autoscalers.
Break it. The app tier proxies gigabytes and saturates under peak uploads. A dropped mobile upload restarts the whole file. A miss burst hits origin request limits. Ownership lives only beside the bytes, so a metadata outage still looks like "the photo is gone."
Mechanism. The API authorizes a key and signs a constrained request. The client PUTs (often in parts) straight to . A metadata plane maps the to chunk locations; a data plane spreads chunks across disks, racks, and AZs, often with . An object-created event fans out to thumbnail, scan, and transcode workers that write the source of truth to a metadata DB. Public GETs stop at the edge.
The invariant is simple: large bytes never need your app process, and product state never lives only in the bucket. The diagrams below are that same split drawn as planes and as the upload-once, process-async loop.
photos/2024/img.jpg is one string, not implicit nesting.Upload playbook and numbers to know
The mechanism says "keep large bytes off the application servers." This is the loop that does it: , direct , then events into a metadata DB and a for reads. Later sections reuse these figures instead of restating them.
Presign loop
Client or mobile app
App API
Mint presigned URL
Back to client
Direct PUT multipart upload
Object bucket
Async path
Object-created event
Queue or bus
Thumbnail scan transcode workers
Metadata DB
Read path
CDN for public reads
How multipart and presigned URLs differ
A presigned request delegates a narrowly scoped operation, such as uploading to one chosen key before expiry; it does not mean the uploaded object is trustworthy. Record the expected owner, size policy, and processing state in application metadata. After completion, verify the resulting object and checksum where supported before marking the upload ready. Keep pending and rejected objects separate from content the application exposes publicly.
They solve different problems. Multipart means the service knows about "part 7 of 42": you retry individual slices and complete when all succeed. Presigned URLs are short-lived signed HTTPS requests: your backend vouches once; the browser or app uploads or downloads straight to object-storage endpoints.
Interview soundbite: we avoid proxying gigabytes through app servers. Multipart is for throughput and resume; a presigned PUT is for trust boundaries and cost.
Multipart upload
Multipart upload creates an upload session, stores individually numbered parts, and assembles the object only after a successful completion request. A client can retry one failed part without retransmitting the rest. Persist enough session information to resume safely, verify completion instead of assuming every part implies a finished object, and abort abandoned sessions so incomplete parts do not accumulate storage charges.
Use when objects exceed comfortable single-PUT limits or when networks drop mid-flight. Exact part-size rules vary by provider (minimum size except last part, maximum parts, and so on). Server-side initiation keeps IAM credentials off the client.
- Initiate. Create a multipart session; receive an upload id and optionally a recommended part size.
- Upload parts. PUT each numbered part in parallel; each returns an ETag (part identifier) you must retain. Retry only failed parts: a massive win versus restarting a multi-GB file.
- Complete or abort. Send the ordered list of parts; the service concatenates into one object key. Abort unfinished uploads to avoid stranded billable storage. can garbage-collect stale multipart ghosts.
- More parts give more concurrency; balance client CPU and API rate limits.
- Use provider checksum headers when correctness of the assembled object matters.
- Persist upload id and completed parts so a reconnect can resume.
Presigned URLs
Your service signs a constrained request (method, bucket/key, headers, expiry). Anyone holding the URL can perform only that operation until it expires, so design the constraints deliberately narrow.
Follow with an event notification, webhook, or client callback so your domain model knows the object landed.
- Minutes to tens of minutes beats never-expires, limiting leaked-link damage.
- Sign exactly one key and verb; optional content-type and length constraints.
- Treat URLs like bearer tokens; never log query strings with secrets.
- Bytes bypass app autoscalers; you still pay PUTs and download egress.
Browser uploads of very large files often use multipart upload initiated server-side, then presigned URLs for each part (or a presigned POST policy for smaller files). You keep IAM credentials on the backend while clients stream slices in parallel.
Numbers to know
Estimate payload bytes separately from and request volume. Small objects can be metadata-heavy; videos are dominated by payload and derived renditions.
1M active users
Profile photos
Profile photos
Metadata DB
Media uploads
Media uploads
Hot tier first 30 days
Warm tier 90 days
Archive or delete by policy
Metadata DB
Analytics and logs
- 0.5–2 KB metadata per object covers key, owner, timestamps, checksum, class, tags, and index rows.
- 1B tiny log objects at 1 KB metadata each is about 1 TB before payload.
- 1M users × (2 MB original + 200 KB thumbnails) ≈ 2.2 TB payload, plus 1–2 GB metadata and versions.
- 100K uploads/day × 50 MB ≈ 5 TB/day raw; three renditions at 30%, 20%, and 10% add ~3 TB/day.
- 1M users × 200 events/day × 1 KB ≈ 200 GB/day raw; retention still reaches multi-TB per month.
- 100K objects/day × 1.5 KB metadata × 2× index/event factor ≈ 300 MB/day catalog growth.
- 50 MB file, 10 MB parts, 100K/day, 5% retry: about 525K part PUTs/day plus complete calls.
- If 20% of 100K videos arrive in one peak hour: 20K/hour, or about 5.6 uploads/second.
- Five parts per video and three parallel parts stay modest on average; size queues for that peak.
Interview template: daily bytes = users x objects/user/day x average size x replica/derivative factor. Then add metadata, request queries per second (QPS), retention days, and lifecycle transitions.
daily_payload = uploads_per_day x average_object_size
derived_payload = daily_payload x derivative_factor
metadata_bytes = object_count x metadata_bytes_per_object x index_factor
tier_bytes = daily_bytes x days_in_tier x compression_or_derivative_factor
Small-object warning: 1B objects of 1 KB payload is only about 1 TB of body data, but 1 KB metadata per object adds another 1 TB before indexes, versions, events, and replication. Object count can dominate listing latency even when payload looks modest. For analytics, compact tiny records into larger files (or columnar layouts) so listing, catalog, and planning work does not dominate reads. Avoid tiny per-event objects without lifecycle and prefix planning.
Retention from those daily bytes
Using the ingest rates above: 5 TB/day of originals for 30 hot days keeps 150 TB hot. Keeping originals for 365 days adds about 1.8 PB total before replication. If derived playback files are 3 TB/day and kept hot for 90 days, add 270 TB. For logs at 200 GB/day compressed 5x, 13 months keeps about 15.6 TB compressed; seven-year archive keeps about 102 TB compressed.
A typical timeline: Day 0–30 Standard (hot); Day 30–90 warm infrequent; Day 90–365 instant archive or cool; Day 365+ deep archive; Year 7+ delete per retention policy. Keep latest photo derivatives hot and expire orphaned previous versions after the restore window. Move upload originals warm after processing if playback uses derived objects, then archive the source after the compliance window. Logs stay hot 7–30 days for debugging, compressed analytics for 13 months, then archive or delete.
| Class | Illustrative $/GB-mo | Retrieval | Typical use |
|---|---|---|---|
| Standard | ~0.02 | Immediate | Hot content, interactive workloads |
| Warm infrequent | Lower storage | Immediate + per-GB retrieval fee | Infrequent but urgent reads |
| Instant archive | Much lower | Milliseconds | Archive that still might spike reads |
| Flexible archive | Lower still | Minutes to hours | Backup / compliance |
| Deep archive | Fraction of a cent | Hours | Cold legal hold |
Cold tiers save storage dollars but add retrieval latency and per-GB egress charges. Measure retrieval patterns before locking deep tiers (see the class table).
Durability and consistency
Once bytes are off the app tier, the next question is how the store keeps them and when a read is allowed to see them. and the provider consistency contract are the two answers; neither is a filesystem.
Replication vs erasure coding
With a 6-data-plus-3-parity layout, nine fragments store the equivalent of six fragments of original data, giving 1.5 times raw storage before metadata. A suitable erasure code can reconstruct from any six surviving fragments. Place fragments across the failure domains the service claims to survive; nine fragments on one failed host provide no protection. Recovery also consumes network and compute, so storage efficiency is only one part of the trade-off.
Triple replication is simple but expensive (~3× raw bytes). Erasure coding splits an object into k data fragments plus m parity fragments so any k of (k+m) can reconstruct it, buying similar fault tolerance with fewer raw bytes. Decode cost favors larger objects; tiny objects sometimes stay on full replication for simplicity. The data plane is engineered for rack and AZ failure, at the price of encode/decode work and fan-out reads.
Storage providers often quote extreme annual durability figures (many "nines"). Treat those as marketing shorthand tied to their replication story. You still want versioning, cross-region copies, and restore drills for important data.
Reed–Solomon style schemes (illustrative)
- Split the object into k data fragments plus m parity fragments.
- Any k of (k+m) fragments can reconstruct the original object.
- Example sketch: 8 data + 3 parity, so any three fragment losses are tolerated.
- Tiny objects sometimes stay on full replication for operational simplicity.
What durability, availability, and recovery each mean
Durability concerns permanently losing the bytes; availability concerns being able to read them now. An archived object can be highly durable while unavailable for immediate playback until restoration completes. A backup also needs usable encryption keys, metadata, and enough restore bandwidth. Test a complete application-level restore rather than only confirming that an object exists in a backup bucket.
- Durability is about not losing bytes: replication, erasure coding, checksums, versioning, backups.
- Availability is about serving requests right now: multi-AZ design and regional failover.
- Recovery still needs restore tests; deletes, bad lifecycle rules, and stolen credentials are yours.
- Choose by access pattern: objects for immutable blobs, block for random writes, file for paths.
Consistency model
Know your storage provider's guarantees for new writes, overwrites, deletes, and listings. Application bugs often come from assuming filesystem or immediate cross-bucket semantics that object APIs do not provide. Confirm the current contract before you rely on a behavior for correctness.
Object-storage semantics vary by provider and operation. Amazon S3 provides strong consistency for object writes, deletes, and listings, so a completed write is not generally hidden from a subsequent S3 LIST. That does not imply atomic updates across several objects or immediate visibility through a separately cached CDN or asynchronous cross-region replica. Traditional flat-key object stores also do not give ordinary filesystem directory-rename semantics; check the exact API and bucket type.
Replication, keys, events, and the CDN
Durability inside one region is not the whole delivery story. Copies, key layout, object-created events, and a decide whether the Instagram carousel feels local and whether workers ever see the upload.
Replication
Same-region copies isolate accounts or aggregate logs. Cross-region async replication aids DR and locality: budget storage twice plus replication API and egress. Optional replication-time Service-Level Agreements (SLAs) exist as paid features; read the fine print. Replication PUTs, cross-region transfer, and destination storage all bill.
Performance patterns
Avoid designing key layouts from an assumed universal prefix limit. Some stores benefit from distributing requests across prefixes, while services such as S3 automatically partition and scale prefixes; verify the current service guidance and measure the workload before randomizing keys. Byte-range requests, multipart transfer, and CDN caching address different bottlenecks. Keep queryable ownership and lifecycle metadata in a database instead of treating object listing as a general-purpose index.
- Prefer
ab/2026/05/user_id/object_idwhereabis a hash prefix, to avoid a thundering herd. - Avoid monotonically increasing prefixes such as
2026/05/16/...for all writes. - Edge ingest onto the provider backbone can help distant uploads; benchmark versus direct PUT.
- Range GET enables parallel partial downloads for video players and big archives.
- Push-down filters over row or column files return fewer bytes than whole objects.
Migrating a bad prefix safely: dual-write new objects to the hashed layout, backfill old keys in batches, keep a redirect map from old key to new key, then expire old keys after readers and caches have moved. That avoids a risky big-bang rename, which is usually copy plus delete for every object.
Event-driven pipelines
An object-created notification means processing can begin, not that the video is already playable. Give each processing job a stable object version and transformation identifier, then make repeated notifications harmless. A worker writes derivatives to new keys and updates metadata to ready only when the required outputs exist. Failed jobs remain visible for retry or repair. Periodic reconciliation can find uploaded objects whose processing event was delayed or whose workflow stalled.
Object notifications fan out to queues or functions for thumbnails, virus scan, or warehouse ingestion, which decouples producers from processors. Pair with dead-letter queues and idempotent handlers: events can duplicate.
CDN in front
Serve public reads from an edge cache with restricted origin access; signed URLs or cookies protect paid streams. The origin stays private.
- High cache hit ratios materially reduce origin GET cost and origin load.
- Origin access policies keep buckets non-public so only the edge can fetch.
- Modern edges can resize or transcode images on the fly.
When the store is fine and the product still fails
Object storage is durable for bits, but product availability still depends on , the , signing, and processing pipelines. Plan what users get when those fail, then drill the restores that marketing "nines" do not cover.
Degraded modes
- Origin or region slow: serve CDN-cached public media, lengthen signed-URL time-to-live (TTL) carefully, and pause non-critical rather than retry storms.
- Metadata DB unavailable: bytes may still exist, but ownership and ACL lookups fail. Degrade to read-known public objects only; block new uploads that need durable metadata commits.
- Processing pipeline down: accept originals into a durable inbox, show "processing" states, and avoid deleting source objects until derivatives succeed.
- Accidental delete or bad : versioning and object-lock windows matter. Without restore drills, "eleven nines" marketing does not save a wrong retention rule.
What to watch and drill
- Quarterly restore: sample objects from version or replica; verify checksum and ACL.
- Cold-cache drill: force a CDN miss burst and confirm origin request limits hold.
- Separate durability of bytes from availability of the metadata and delivery path.
Still fails when…
- A LIST misses a completed PUT, so a pipeline that polls prefixes never starts work.
- A folder rename is copy-plus-delete and leaves half the keys behind mid-failure.
- Billions of 1 KB objects drown the catalog long before disk fills (see Numbers to know).
Trade-offs
- Triple copies are simple; erasure coding saves bytes and costs encode plus fan-out reads.
- Cold tiers cut storage dollars and add retrieval latency plus per-GB restore fees.
- Cross-region copies buy DR and residency, and roughly double storage plus egress.
Wrap-up
You have seen the upload path, the capacity worksheet, and the ways the product still fails when the bucket is healthy. Use these exercises to apply the same numbers, then follow the primary sources.
Exercises
- Photos. Using the 1M-user profile-photo figures in Numbers to know, place ownership in a metadata DB, derive thumbnails async, and put a in front. What do you expire after the restore window?
- Videos. Using the daily ingest and peak-hour rates in Numbers to know, sketch , the processing pipeline, segmented objects, and signed delivery for private playback.
- Logs. Using the event-volume line in Numbers to know, roll events into time partitions, compress for analytics, and apply the 7–30-day / 13-month / seven-year lifecycle already listed.
Topic-specific references
- Amazon S3 User Guide: Objects : object model, multipart, lifecycle.
- Ghemawat et al.: Google File System : large-object distributed storage roots.
- AWS S3: Welcome / consistency model : read-after-write consistency contract.
Key takeaway
Put unstructured blobs in with IAM-scoped buckets, presigned client uploads, lifecycle tiers, and a CDN for read-heavy media. Keep ownership in a metadata DB, process derivatives from events, and treat restore drills as part of durability, not a footnote to the nines.
Practice and further reading
Use these exercises to connect the lesson to a design decision. State your assumptions, trace what happens to one request, and explain the rule that must remain true even during a failure. That rule is the design's correctness invariant.
Focus for this lesson: Design object upload, metadata, multipart cleanup, checksums, versioning, retention, authorization, lifecycle, restore time, and egress cost.
Explain the decision
Why is an S3 ETag not always a content MD5?
Read the reasoning
Multipart uploads and encryption can change ETag semantics; store and verify an explicit supported checksum when integrity matters.
Apply it to a production scenario
Clients complete object upload but the metadata transaction fails. What cleans up?
Read one approach
Record upload intent, finalize metadata idempotently, and lifecycle-abort or garbage-collect unreferenced objects after a safe grace period.
Work through the numbers
Storing 4 PB with three logical copies and 2% monthly restore reads implies what raw copy footprint and restore volume?
Read the worked calculation
12 PB of copy footprint and 80 TB/month restored from logical data, before erasure coding, versioning, and metadata.
Reason through a failure
What must a legal-hold delete path preserve?
Read the recovery reasoning
No actor can bypass retention/object-lock policy; metadata deletion cannot make protected bytes irrecoverable before policy allows it.
Sources and further study
Continue with Data Partitioning.