The problem: “S3 is down” when S3 is fine
Your pipeline starts throwing 503 SlowDown, 500 Internal Error, or multi-second latency spikes on PUT/LIST. The AWS status page is green. CloudWatch shows elevated 5xxErrors on the bucket. The root cause is almost never “S3 capacity” — it is how you key, list, and parallelize requests.
Partitions, prefixes, and the myth of random hex
S3 scales by automatically splitting prefixes under load. Hot spots still happen when:
- You write millions of objects under a single date prefix like
logs/2026-08-01/with extreme parallelism from one fleet. - You
LISTthe same high-churn prefix continuously (inventory jobs, naive “sync” tools). - You use sequential keynames that concentrate creates (less common than it was, but listing + overwrite patterns still hurt).
Modern guidance: design keys for your query pattern first (partition by customer, then date), and rely on S3’s scaling. Add entropy only when you have measured a hot prefix — not by default.
Multipart upload is not free parallelism
Multipart helps large objects and retries, but each part is an HTTP request with its own retry budget. Anti-patterns:
- Tiny parts (e.g. 5 MB × thousands) for objects that could be a single PUT — you multiply request rate and failure surface.
- Abandoning multipart uploads without abort — incomplete uploads accrue storage charges and clutter.
- Retrying the whole object on a single part failure instead of retrying the failed part with exponential backoff + jitter.
Rule of thumb: part size large enough to keep part count reasonable (often 64–128 MB for multi-GB objects), cap concurrent parts per host, and always run a lifecycle rule to abort incomplete uploads after N days.
LIST is a scalability tax
ListObjectsV2 is consistent and paginated, but using LIST as a database is how batches melt. Prefer:
- S3 Inventory for periodic bulk analysis.
- EventBridge / S3 Event Notifications → SQS/SNS for “object created” driven workflows.
- DynamoDB or Index that stores keys you need to query by non-key attributes.
If you must LIST, paginate with ContinuationToken, bound concurrency, and never LIST inside a tight per-request user path.
Consistency and overwrite races
S3 provides strong read-after-write consistency for new objects and overwrites. That does not mean multi-writer workflows are safe:
- Two writers PUT the same key — last writer wins; no merge.
- Reader may see the latest version, but application logic that “check then write” without versioning/ETags still races.
- Use If-Match / If-None-Match conditional writes, or object versioning + explicit version IDs, when concurrent updates matter.
Reading 503 SlowDown correctly
Treat SlowDown as backpressure:
- Exponential backoff with jitter (full jitter), not fixed 1s retries from 500 workers.
- Reduce client-side concurrency — a “faster” fleet often slows the job.
- Inspect CloudWatch:
FirstByteLatency,TotalRequestLatency,5xxErrors, and per-prefix metrics if enabled. - Check for pathological LIST or HEAD storms from scanners and misconfigured CDNs.
Storage class and request cost traps
Technical correctness also means cost correctness:
- Many small objects in Glacier Instant Retrieval / Flexible Retrieval change the economics of GET patterns.
- S3 Express One Zone is a different durability/API performance profile — do not drop it into a multi-AZ disaster recovery story blindly.
- Cross-Region Replication is asynchronous; RPO is not zero. Design apps that can tolerate lag or use CRR metrics/alarms.
Practical architecture
For log/analytics ingest: land objects with a partition scheme you can prune, drive processing from events not LIST, autoscale consumers on SQS depth, and keep a dead-letter queue for poison messages. For user uploads: presigned PUTs (or POST policies), virus scan asynchronously, then move to a final prefix with a server-side copy — never expose long-lived write credentials to browsers.