Blob storage
0-day streakReview

Your progress

Level 1

0 XP100 XP to level 2
0day streak
0best streak
0topics started
0cards reviewed
    Review cards

    Progress is stored only in this browser. No account is required.

    Layer 4 · Full breakdown · 20 min · 5 figures

    Blob storage (S3)

    What an interviewer expects you to know about blob storage such as Amazon S3: buckets, flat keys and whole objects, strong consistency and conditional writes, per-prefix request limits, presigned and multipart uploads, storage classes and the real bill.

    Blob storage, also called object storage, keeps arbitrary bytes (photos, videos, backups, logs, model checkpoints) under a key in a bucket, behind a small HTTP API: PUT, GET, DELETE and LIST. Amazon S3 is the reference design; Google Cloud Storage and Azure Blob Storage work the same way. In an interview it is the default home for anything over a few hundred kilobytes, provided you can say how bytes get in and out without touching your servers, what is consistent, and what the bill looks like.

    Picture it first: a railway cloakroom

    A big railway junction has a cloakroom. You hand over a sealed bag; the clerk writes a token such as PLT4/2026-10-27/0815 and shelves the bag unopened. The slashes mean nothing to him: his register is one list sorted by token, and if you ask for everything starting PLT4/, he reads it out a page at a time. To add a shirt you take the bag out, repack it and deposit it again under the same token. A tag on the handle says whose it is.

    A signed chit, valid until 6 pm, lets a friend collect the bag without your ID. Bags left a month move to a cheaper back room; after a year they go to a warehouse outside town, and fetching one takes half a day.

    Map it back: the cloakroom is a bucket in a region, the token is the key, the bag is the object, the tag is metadata, repacking is a whole-object PUT, reading the register by prefix is LIST, the chit is a presigned URL, and the back room and warehouse are storage classes reached by lifecycle rules.

    Data model: buckets, keys and objects

    Bucket. A named container in one region, fixed at creation. Names are 3–63 characters and by default unique across all AWS accounts (an account-regional namespace is now an option). The bucket is the unit for access policy, encryption, versioning, lifecycle rules and billing.

    Key. Up to 1,024 bytes of UTF-8. The namespace is flat: photos/u42/p9.jpg is one string, not a file in two folders. LIST prefix=photos/ delimiter=/ returns matching keys and rolls deeper ones into “common prefixes”, which is how consoles draw folders. There is no rename; moving a “folder” is a copy and a delete per object.

    Object. The bytes plus three kinds of metadata:

    • System metadata: size, Content-Type, ETag, last-modified time, storage class, checksums.
    • User metadata: x-amz-meta-* headers, at most 2 KB and fixed at upload; changing it means copying the object.
    • Tags: up to 10 key-value pairs, editable, usable in lifecycle rules and policies.

    No partial updates. A PUT replaces the whole object: you cannot append a log line or patch byte 400 of a video. Reads can take any slice with a ranged GET (Range: bytes=0-1048575). The exception is S3 Express One Zone, whose directory buckets accept appends, up to 10,000 appends per object.

    Versioning. Per bucket. Each PUT adds a version instead of overwriting, and a plain DELETE adds a delete marker: GET returns 404 while older versions stay readable by version ID, and billed. Object Lock adds write-once-read-many retention; in compliance mode nobody, root included, can delete a locked version early.

    One bucket, one flat sorted list of keys, and the anatomy of a single object in a versioned bucket.Notice “folders” are computed from the key text at LIST time, and that a delete in a versioned bucket only stacks a marker on top of versions you keep paying for.

    Guarantees, precisely

    Strong read-after-write, per key

    Since 1 December 2020, S3 is strongly consistent for every object in every region, at no extra cost. Once a PUT (new or overwrite) succeeds, any GET, HEAD or LIST that starts afterwards sees it; once a DELETE succeeds, GET returns 404 and LIST omits the key. Before that, overwrites and listings could briefly be stale, which is why Hadoop users ran S3Guard. Prep material calling S3 “eventually consistent” is out of date.

    What it still does not give you:

    • No multi-key atomicity. A data file and its manifest, or a photo and its thumbnail, are separate writes; a reader can see one without the other.
    • Concurrent writers: last writer wins, by timestamp, and the loser is not told. Each PUT is atomic: readers see the old or the new object, never a mix.
    • Bucket configuration is eventually consistent. After enabling versioning, wait 15 minutes before writing.

    How S3 built this on a cached metadata layer belongs to designing an object store like S3; the general trade-off is in strong vs eventual consistency.

    Conditional writes: compare-and-set on one key

    Since 2024 a write can carry a precondition:

    • If-None-Match: * (August 2024): create only if the key does not exist. Racing creators: the first to finish wins, the rest get 412 Precondition Failed.
    • If-Match: <ETag> (November 2024): replace only if the object is still the version you read. This is optimistic locking on one object.

    Both work on PutObject, CopyObject and CompleteMultipartUpload and can be enforced by bucket policy; a concurrent DELETE can surface as 409 Conflict, so retry. S3 becomes a small coordination store: exactly one job writes a _SUCCESS marker, or a table format commits manifest 124 only if nobody else did:

    PUT /tables/orders/_commits/000124.json HTTP/1.1
    Host: lake-prod.s3.ap-south-1.amazonaws.com
    If-None-Match: *
    
    HTTP/1.1 412 Precondition Failed      <- another writer committed 124 first

    Durability and availability

    S3 Standard is designed for 99.999999999% (eleven nines) annual durability across at least three availability zones. With 10 million objects, expected loss is 10⁷ × 10⁻¹¹ = 10⁻⁴ objects a year: one object every 10,000 years. That covers failing disks and zones, not your own DELETE.

    Availability is lower and varies by class: Standard is designed for 99.99% with a 99.9% monthly SLA; Standard-IA 99.9% (SLA 99%); One Zone-IA 99.5%; Express One Zone 99.95%. Both One Zone classes lose data if their zone is destroyed. See availability and the nines.

    What stays asynchronous

    • Event notifications (to SQS, SNS, Lambda or EventBridge) are delivered at least once, usually within seconds but sometimes after a minute or more, and not in order. PUT and DELETE events carry a sequencer that orders events for one key only.
    • Replication (same- or cross-region) is asynchronous. With Replication Time Control, 99.9% of objects arrive within 15 minutes; without it there is no bound. It is a disaster recovery tool with a non-zero RPO, not a synchronous copy.

    How it scales

    Request rate per prefix

    S3 supports at least 3,500 PUT/COPY/POST/DELETE and 5,500 GET/HEAD requests per second per partitioned prefix, with no limit on prefixes. A prefix is any leading part of the key; S3 splits the sorted key space into partitions by traffic. Ten evenly loaded prefixes give 10 × 5,500 = 55,000 reads/s.

    Scaling is gradual. When a key range suddenly takes more than its partition can serve, S3 returns 503 Slow Down while it splits the range; the SDKs retry with exponential backoff, and hand-written clients must too (backoff with jitter).

    The trap is a key layout that aims all new traffic at one range. Date-first keys (logs/2026-10-27/...) sort a whole day’s writes together, and tomorrow the hot spot moves to a fresh range. A hash prefix in front spreads them. A log pipeline writing 9,000 objects/s needs 9,000 ÷ 3,500 ≈ 2.6, so at least 3 partitions’ worth; with a two-hex-digit prefix (00/ to ff/) and four partitions, each carries 2,250 writes/s. Better still, batch: fewer, larger objects cut requests and cost together.

    The same 9,000 PUT/s with two key layouts. Bars show writes per second landing on each partition; the dashed line is the per-prefix guideline of 3,500 writes per second.Notice Capacity on yesterday’s partitions sits idle while today’s range throttles: the fix is a key layout that spreads load, not a bigger bucket.

    Design keys for the access pattern, not for humans

    Put the evenly spread part first (a hash, user ID or shard number) and time later. Anything you query by, such as owner, time or status, belongs in a database row, not in key text you plan to LIST.

    Throughput, latency and the fast tier

    • Parallelism, not faster requests. Treat S3 as a fleet, not a server: many connections let one EC2 instance reach 100 Gb/s. Download big objects as parallel ranged GETs aligned to the upload’s parts (GET ?partNumber=N).
    • Latency. First bytes take roughly 100–200 ms from S3 Standard. AWS suggests retrying a small request still running after 2 seconds; the retry usually takes a faster path.
    • Express One Zone gives single-digit-millisecond access and hundreds of thousands of requests per second in one zone you choose, at about 5× the storage price ($0.11 vs $0.023 per GB-month). Use it for scratch data next to compute, such as training shards, not as the system of record.
    • A CDN serves hot reads from edges; Transfer Acceleration speeds long-distance uploads through them.

    Uploads and downloads that skip your servers

    Never stream user files through your API tier: a 2 GB video ties up a server for minutes. Clients talk to S3 directly; your servers only decide who may write what, where.

    Presigned URLs

    A presigned URL is one S3 request (method, bucket, key, expiry) signed with your service’s credentials. Whoever holds it can make that request until it expires; it is a bearer token:

    • Expiry. Up to 7 days when signed with an IAM user’s keys under SigV4; with temporary credentials it dies when they do (about 6 hours for EC2 role credentials). Use minutes. Expiry is checked when a request starts, so a download in flight is not cut off.
    • Reuse. It works any number of times until expiry, and a presigned PUT overwrites an existing key, so the server chooses the key (uploads/u42/<uuid>.jpg), never the client.
    • Size. A presigned PUT does not cap size; a browser POST policy can (content-length-range), or check after upload.

    This is the flow the Instagram and YouTube designs use:

    Direct upload with a presigned URL. The API creates a pending metadata row and signs a URL; the phone sends bytes straight to S3; an event drives a worker that finishes the job.Notice No file bytes touch the API servers, and that the row exists before the object does, so nothing is ever stored that the database cannot find.

    Writing the row before the upload is the detail interviewers listen for: every object has an owner row, and anything stuck in pending is easy to clean up. Write it after, and a crash in between leaves bytes nothing points to. Many designs also let the phone call “done” for speed and keep the event as the backstop.

    Multipart upload

    Above about 100 MB, and always above 5 GB (the single-PUT limit), upload in parts:

    1. CreateMultipartUpload returns an upload ID.
    2. UploadPart sends parts numbered 1 to 10,000, each 5 MiB to 5 GiB (the last may be smaller), in any order and in parallel. Each returns an ETag; a failed part is retried alone.
    3. CompleteMultipartUpload lists the part numbers and ETags; the object appears atomically. Until then nothing is visible at the key.

    The maximum object size is 50 TB (48.8 TiB = 10,000 × 5 GiB), raised from 5 TB in December 2025. The part count sets a floor: part size ≥ object size ÷ 10,000, so a 200 GB file needs parts of at least 20 MB. To resume, presign each part; after a drop the client calls ListParts and resends only what is missing. The client side is in large file uploads.

    A 5 GB file uploaded as 50 parts of 100 MB, three at a time, and read back with parallel ranged GETs.Notice A dropped connection costs one 100 MB part, not the whole file, and that the object does not exist at its key until Complete names every part.

    Checksums. AWS SDKs send a checksum with each upload (CRC64NVME by default; CRC32C, SHA-256 and others are supported), and S3 recomputes it and rejects a mismatch. A multipart ETag is not the object’s MD5; to deduplicate by content, store your own SHA-256 (checksums).

    Parts you never completed are still billed

    An upload never completed or aborted keeps its parts: billed, and invisible to LIST. Every bucket that takes multipart uploads needs a lifecycle rule with AbortIncompleteMultipartUpload after a few days.

    Storage classes, lifecycle and the bill

    Every object has a storage class; cheaper storage always costs slower or pricier reads and a minimum billed stay. Prices are us-east-1 list prices, October 2026.

    Class Zones Designed availability Minimum stay First byte $/GB-month
    Standard ≥ 3 99.99% None Milliseconds 0.023 (first 50 TB)
    Intelligent-Tiering ≥ 3 99.9% None Milliseconds; opt-in archive tiers take hours 0.023 → 0.0125 after 30 idle days → 0.004 after 90, plus $0.0025 per 1,000 objects monitored
    Standard-IA ≥ 3 99.9% 30 days; 128 KB minimum size Milliseconds, $0.01/GB read fee 0.0125
    One Zone-IA 1 99.5% 30 days; 128 KB Milliseconds, $0.01/GB 0.01
    Express One Zone 1 99.95% None Single-digit ms 0.11
    Glacier Instant Retrieval ≥ 3 99.9% 90 days; 128 KB Milliseconds, $0.03/GB 0.004
    Glacier Flexible Retrieval ≥ 3 99.99% once restored 90 days Restore: 1–5 min expedited, 3–5 h standard, 5–12 h bulk 0.0036
    Glacier Deep Archive ≥ 3 99.99% once restored 180 days Restore within 12 h, or 48 h bulk 0.00099

    Lifecycle rules, filtered by prefix, tag or size, move objects down this table on a schedule and expire them at the end. Three things to know:

    • Minimum stays are billed. Delete a Standard-IA object on day 10 and you pay for 30 days.
    • Small objects don’t tier well. IA classes bill at least 128 KB per object, lifecycle rules skip objects under 128 KB by default (since September 2024), and each archived object carries 40 KB of extra metadata. Archive bundles, not millions of thumbnails.
    • Transitions only go down. Leaving Deep Archive means a restore and a copy. If access is unpredictable, Intelligent-Tiering moves objects for you, with no retrieval fees.
    One lifecycle rule for raw video originals: Standard for a month, Standard-IA until day 90, Glacier Flexible Retrieval until day 180, Deep Archive until deletion at seven years. Bars show storage price per GB-month in us-east-1, October 2026.Notice Storage gets about 23 times cheaper from Standard to Deep Archive, while the first byte goes from milliseconds to up to 12 hours, and every step down adds a minimum billed stay.

    Requests and egress usually beat storage

    At the same prices, PUTs cost $0.005 per 1,000, GETs $0.0004 per 1,000, and internet egress $0.09/GB for the first 10 TB a month, falling to $0.05/GB above 150 TB.

    • Tiny objects. A pipeline writes 1 billion 4 KB events a day as separate objects. PUTs: 1,000,000,000 ÷ 1,000 × $0.005 = $5,000 a day, while storing that day’s 4 TB costs about 4,000 GB × $0.023 ≈ $92 a month. Batched into 64 MB files, a day is 4 TB ÷ 64 MB = 62,500 PUTs: $0.31.
    • Hot images. A shopping app in a Flipkart-style sale month serves 1 billion product-image views of 200 KB straight from S3: 200 TB out. GETs cost 1,000,000 × $0.0004 = $400. Egress through the tiers is 10,000 × 0.09 + 40,000 × 0.085 + 100,000 × 0.07 + 50,000 × 0.05 ≈ $13,800, against about $2,250 to store 100 TB of images. Egress is six times the storage bill. Put a CDN in front: S3-to-CloudFront transfer is free, and the edge absorbs most requests.

    Uploads and same-region transfers to other AWS services are free. More in cost-aware design.

    Limits and numbers

    From the S3 User Guide, October 2026.

    What Number Notes
    Object size 50 TB (48.8 TiB) via multipart; 5 GB per single PUT Raised from 5 TB in December 2025
    Multipart parts 1–10,000 parts, 5 MiB–5 GiB each; last part any size Part size ≥ object size ÷ 10,000
    Key 1,024 bytes of UTF-8 Prefix and delimiters included
    User metadata 2 KB (whole PUT header 8 KB) Change it only by copying the object
    LIST page 1,000 keys Paginate with a continuation token
    Request rate ≥ 3,500 writes and 5,500 reads per second per partitioned prefix Scales up gradually; 503 Slow Down meanwhile
    Latency About 100–200 ms first byte (Standard); single-digit ms (Express One Zone) Retry slow small requests after 2 s
    Durability, availability 11 nines designed; Standard 99.99% designed, 99.9% SLA One Zone classes: one zone
    Presigned URL ≤ 7 days (IAM user, SigV4) Shorter if signed with temporary credentials
    Consistency Strong read-after-write since 1 December 2020 Bucket configuration is eventual
    Conditional writes If-None-Match since August 2024; If-Match since November 2024 412 when the condition fails
    Events At least once; usually seconds, sometimes a minute or more Unordered; sequencer orders one key

    When to pick it, and when not

    Object storage (S3, GCS, Azure Blob) Block storage (EBS) File storage (EFS, NFS) Blob in a database CDN cache
    Interface HTTP API, whole objects A disk on one VM Shared POSIX files A column in a row HTTP cache by URL
    Partial writes No Any block Any byte, with locks Rewrite the value No
    Reached by Any client over HTTPS One instance, one zone Many hosts in a network Database clients Everyone, at the edge
    Latency 100–200 ms first byte Sub-ms to ms Milliseconds A row read Ms on a hit
    Consistency Strong per key; no multi-key transactions One writer File-system semantics Transactional with the row Stale until TTL or purge
    Cost shape Cheapest per GB; pay per request and egress Provisioned size, used or not More per GB than objects Priciest per GB; bloats backups Per GB served
    Limits Per-prefix request rates One zone Throughput per configuration DynamoDB item 400 KB; PostgreSQL field 1 GB Misses hit the origin
    Pick it for Media, backups, logs, data lakes, ML data Databases, boot volumes Legacy apps needing shared files Small values committed with the row Hot public content
    Where to put bytes. Rows depend on configuration; the last row is the honest reason to choose each.

    Pick object storage when data is large, written once and read often, reached by clients or many services, or kept cheaply for years. It is also where other systems spill big payloads: Kafka messages and DynamoDB items carry a key that points into a bucket.

    Pick something else when you update in place or append often (a database or log); you need millisecond reads of small values (Redis or a database); you query by attributes (keep them in PostgreSQL and store only the key); a few-kilobyte value must commit with its row; or an old application needs a POSIX file system.

    The same idea on other clouds

    • Google Cloud Storage: strongly consistent object writes, deletes and listings, even in multi-region buckets; access changes take about a minute. Compose joins up to 32 objects into one, for parallel uploads and appends.
    • Azure Blob Storage: account → container → blob. Block blobs reach about 190.7 TiB, append blobs suit logs, page blobs back VM disks. Tiers: hot, cool (30-day minimum), cold (90) and archive (180, hours to rehydrate).
    • Cloudflare R2: S3-compatible, with no egress fees and $0.015 per GB-month storage.
    • Self-hosted: MinIO or Ceph’s RADOS Gateway speak the S3 API on your disks; you own durability and repair.

    Common interview usage

    Design problem Role of blob storage Key layout The follow-up to expect
    YouTube, Netflix Raw upload → transcode → HLS or DASH segments served through a CDN raw/<video_id>/original, hls/<video_id>/720p/seg-00042.ts Resuming a 20 GB upload; lifecycle for originals nobody re-reads
    Instagram Originals and resized variants; the database holds owner, caption and keys <hash>/<photo_id>/orig.jpg, .../w1080.jpg What happens if the thumbnail event is lost or arrives twice
    Dropbox / Google Drive Immutable, content-addressed blocks; the database maps files and versions to block lists blocks/<sha256> Dedupe across users; garbage-collecting blocks no version references
    URL shortener and Pastebin Paste bodies; the database maps code → key pastes/<code> Why not keep 10 MB pastes in the database; expiry by lifecycle rule
    Logging, data lake Batched, compressed files (Parquet, gzip) read by query engines clicks/dt=2026-10-27/hr=14/part-0001.parquet The small-files problem; moving old partitions to Glacier
    ML training data, RAG documents Source files, training shards, checkpoints datasets/v7/shard-00042.tar Keeping GPUs fed: parallel ranged reads, Express One Zone, local caches
    Static website and app assets HTML, JavaScript and images behind a CDN assets/app.3f9c2a.js Content-hashed names instead of cache purges

    The data lake key is date-first, yet fine: big, batched files mean a few hundred PUTs a second, and the date lets query engines skip whole partitions.

    In the interview

    Tell the storage story in one breath: “Photos go to S3 through presigned URLs, never through our servers; the database stores key, owner, size, checksum and status; reads go through the CDN; a lifecycle rule moves originals to Glacier after 90 days.” That pre-empts the first three follow-ups.

    Going deeper: failure modes

    Using LIST as a database

    LIST returns at most 1,000 keys per call, in key order, filtered by prefix and nothing else. Finding “user 42’s photos from last week” among 100 million objects means 100,000 calls; at an assumed 50 ms each, one after another, that is 5,000 s, about 83 minutes. Keep the index in a database keyed by owner and time; for audits, use S3 Inventory reports rather than crawling.

    Hot prefixes and hot objects

    Beyond a burst on one key range, a single viral object read more than about 5,000 times a second can draw 503s too. Put a CDN or cache in front of hot reads, ramp traffic gradually, and watch 5xx errors per prefix.

    Bytes stored, row missing (and the reverse)

    S3 and your database cannot share a transaction, so every upload is a dual write. The safe order is the presigned flow’s: pending row first, then bytes, then an idempotent update, plus a sweeper for anything left pending. Deletion runs the other way: mark the row deleted, then remove the object asynchronously. The general patterns are the transactional outbox and idempotent consumers.

    Bytes you forgot you were paying for

    Incomplete uploads, old versions behind delete markers and replicas in another region bill silently; with versioning on, a DELETE frees nothing. Versioned buckets need lifecycle rules that expire noncurrent versions after N days and abort stale uploads. Millions of tiny objects are the other silent cost: chatty I/O with a price tag.

    Public buckets and leaked URLs

    Since April 2023, new buckets block public access and disable ACLs by default, and objects are encrypted with S3-managed keys. Keep it that way: public content through a CDN that alone can read the bucket, private content through short-lived presigned URLs, since a leaked one works for anyone until it expires. See encryption and key management.

    Torn ranged reads

    Parallel range GETs during an overwrite can stitch together halves of two objects; Google’s documentation warns about exactly this. Pin each range to one version with If-Match and the first response’s ETag, or a version ID. Better, never overwrite: write new versions under new keys and move the pointer in the database.

    Late, duplicate and missing events

    Notifications arrive at least once, out of order, sometimes late, and a bad filter or failing consumer makes them look lost. Make handlers idempotent, compare sequencer values per key, and reconcile against the database on a schedule. Beware recursion: a function that writes thumbnails into the prefix that triggers it calls itself forever.

    Eleven nines does not cover you

    Durability protects against S3 losing data, not against you deleting it. A wrong lifecycle rule, a script with the wrong prefix or stolen credentials can empty a bucket in minutes. For data you cannot recreate, use versioning, Object Lock where regulation demands it, and a replica in another account.

    Cheat sheet

    Concept Remember it as
    Model Bucket (one region) → flat keys up to 1,024 bytes → whole objects up to 50 TB
    Folders Prefix plus delimiter at LIST time; nothing is stored
    Updates Whole-object PUT; ranged GET for reads; appends only in Express One Zone
    Consistency Strong read-after-write per key since December 2020; no multi-key transactions
    Concurrent writers Last writer wins, silently; use unique keys or If-None-Match / If-Match
    Durability, availability 11 nines designed across ≥ 3 zones; 99.99% designed, 99.9% SLA for Standard
    Request rate ≥ 3,500 writes, 5,500 reads per second per prefix; 503 Slow Down while scaling
    Throughput Many connections, parallel ranged GETs, CDN for hot reads
    Uploads Presigned URL (≤ 7 days, use minutes); multipart above ~100 MB, required above 5 GB
    Multipart 5 MiB–5 GiB parts, ≤ 10,000; abort stale uploads by lifecycle
    Metadata Lives in a database; LIST is 1,000 keys a page and is not an index
    Events, replication At least once, unordered, can lag; RTC: 99.9% in 15 minutes
    Cost Storage cheap; requests, small objects and egress expensive; tier down with lifecycle

    How to say it in the interview

    “Media goes to S3, never through our app servers. The API writes a pending row and returns a presigned PUT URL valid for 15 minutes; big videos use multipart with presigned part URLs, so a dropped connection costs one part. An at-least-once ObjectCreated event triggers the transcoder, which marks the row ready idempotently. Keys start with a hash to spread partitions, lookups go through the database rather than LIST, and reads go through a CDN because egress would dominate the bill. Lifecycle rules move originals to Standard-IA at 30 days and Glacier at 90.”

    Is S3 eventually consistent? Not since December 2020: PUTs, overwrites, DELETEs and LISTs are strongly consistent per key. Events, replication and bucket configuration are still asynchronous, and there are no transactions across keys.

    How do you upload a 10 GB video from a phone? Multipart, since a single PUT stops at 5 GB. With 16 MiB parts that is 10 GiB ÷ 16 MiB = 640 parts, each with a presigned URL, a few in flight. After a drop the app asks which parts arrived, sends the rest and calls Complete; a lifecycle rule aborts abandoned uploads.

    Two clients write the same key at the same time. What happens? Last writer wins; the other write vanishes without an error. Avoid it with unique keys (a UUID or content hash), or detect it with If-None-Match: * to create once and If-Match to update only an unchanged object.

    Your uploads start failing with 503 Slow Down. Why? One key range is above 3,500 writes a second while S3 splits it, usually because keys start with a date or a constant. Back off with jitter, put a hash at the front of the key, and batch small objects.

    Why not store the images in PostgreSQL? Fine for a few kilobytes, but large blobs bloat the table, the WAL, backups and every replica. Object storage costs a fraction per GB, needs no capacity planning and sits behind a CDN; the database keeps the key and metadata.

    Where to go next

    References and further reading

    1. Amazon S3 User Guide: What is Amazon S3? (with the data consistency model)docs.aws.amazon.comBucket types and namespaces, keys and version IDs, strong read-after-write for PUT, DELETE and LIST, atomic single-key updates, last writer wins, and eventually consistent bucket configuration.
    2. Amazon S3 Update: Strong Read-After-Write Consistency (AWS News Blog, 1 December 2020)aws.amazon.comThe date and scope of the change (all objects, all Regions, no extra cost) and the S3Guard and EMRFS workarounds it retired.
    3. Best practices design patterns: optimizing Amazon S3 performancedocs.aws.amazon.comAt least 3,500 write and 5,500 read requests per second per partitioned prefix, gradual scaling with 503 Slow Down, up to 100 Gb/s per instance and 100–200 ms small-object latency.
    4. Amazon S3 multipart upload limitsdocs.aws.amazon.comUp to 10,000 parts of 5 MiB to 5 GiB, a 48.8 TiB (50 TB) maximum object size, and multipart from about 100 MB.
    5. Download and upload objects with presigned URLsdocs.aws.amazon.comSeven-day maximum with IAM user SigV4 credentials, shorter lives with temporary credentials, reuse until expiry, overwrites, and the signatureAge policy key.
    6. How to prevent object overwrites with conditional writesdocs.aws.amazon.comIf-None-Match and If-Match on PutObject, CopyObject and CompleteMultipartUpload, and the 412, 409 and 404 outcomes under concurrency.
    7. Understanding and managing Amazon S3 storage classesdocs.aws.amazon.comDesigned durability and availability, Availability Zones, minimum storage durations and minimum billable sizes for every class.
    8. Amazon S3 Event Notificationsdocs.aws.amazon.comAt-least-once delivery that usually takes seconds and sometimes a minute or more, destinations, and the recursive-trigger warning.
    9. Amazon S3 pricingaws.amazon.comUS East (N. Virginia) list prices published in October 2026: storage per class, PUT and GET prices, Intelligent-Tiering monitoring, internet egress tiers and free transfer to CloudFront.
    10. Google Cloud Storage: consistencycloud.google.comStrong global consistency for object writes, deletes and listings, eventually consistent access changes, and torn ranged reads without a generation precondition.