When Amazon launched S3 in March 2006, it quietly introduced a storage model that would go on to hold exabytes of the world’s data. Nearly two decades later, the S3 API is the closest thing the industry has to a universal interface for object data. Yet implementing object storage well is less about picking a vendor and more about understanding a handful of design properties that shape everything downstream: cost, performance, and how badly things break when they break.
This guide walks through the practical decisions that matter, the mistakes that show up again and again in real deployments, and the parts of “S3 compatibility” that vendors rarely put on the marketing page.
Why objects behave differently from files
The single most important thing to internalize is that objects are immutable. There is no in-place partial write, no random write, no appending to the end of an object. Every “edit” is a full replacement: you PUT a new version of the whole object, or you reassemble it from parts via multipart upload.
That one property cascades into everything else. It’s why object storage scales so cleanly (no locking, no file handles, no inode contention), and it’s why workloads built around databases or shared file systems fit poorly. If a team tries to run a transactional database on top of an object store without a purpose-built layer, they’re fighting the model, not using it.
In practice, the workloads that thrive are the ones that treat objects as write-once, read-many: backups, media assets, logs, ML training sets, static site content, and data lake tables. If you’re comparing where those assets should physically live, the trade-offs between cloud storage and local storage are worth mapping out before you commit to an architecture.
Choosing between managed and self-hosted object storage

The first real fork in the road is whether you run a managed service or host your own. Both are legitimate; they fail in different ways.
| Factor | Managed (e.g., AWS S3) | Self-hosted (e.g., Ceph, MinIO) |
|---|---|---|
| Operational burden | Low; provider handles durability | High; you own disks, rebuilds, upgrades |
| Durability claim | Provider-published (AWS markets 11 nines for S3 Standard) | Depends entirely on your replication or erasure-coding config |
| Cost model | Per-GB storage + request + egress charges | Hardware + people + power |
| Egress | Metered; can dominate bills | Effectively free internally |
| Feature completeness | Full API surface | Often partial; verify feature by feature |
| Consistency | AWS announced strong read-after-write for all S3 operations in December 2020 | Varies; many compatible stores still offer eventual consistency for listings |
A few honest observations from watching these deployments go wrong:
- Egress surprises are the classic managed-storage failure. Teams budget for storage, then get blindsided by data transfer costs when a service starts reading heavily across regions.
- Self-hosted durability is a design exercise, not a checkbox. If you run three-way replication, you’ve tripled your raw storage cost. If you run erasure coding (say k=4, m=2), you cut overhead but raise CPU load and rebuild time.
- “S3-compatible” is a spectrum. Some stores lack Object Lock, Select, or certain ACL semantics. Test the exact features you need before you migrate.
If you’re still weighing providers, a structured comparison of the best cloud storage services helps frame the decision around your actual access patterns rather than sticker price.
Getting the durability math right
Durability and availability are frequently conflated, and they shouldn’t be. Durability is the probability your data survives; availability is the probability you can reach it right now. A system can be highly durable and briefly unavailable.
AWS has long marketed S3 Standard at 99.999999999% (11 nines) durability and a 99.99% availability SLA. Those are provider claims, not independently audited guarantees, and they do not transfer to a self-hosted Ceph or MinIO cluster. Your self-hosted durability is a function of:
- Failure domain design. How many disks, nodes, racks, or sites can fail simultaneously without data loss?
- Redundancy scheme. Replication versus erasure coding, and the specific parameters chosen.
- Rebuild behavior. How long does recovery take, and what’s the risk of a second failure during rebuild?
- Scrub and verification. Are you actively checking object integrity, or waiting for a read to fail?
Erasure coding lowers storage overhead but raises CPU cost and rebuild complexity. Replication is simpler to reason about but expensive at scale. Neither is “better” in the abstract; the right answer depends on your failure domains and your tolerance for rebuild windows.
Large objects and the multipart upload pattern
For anything beyond a few megabytes, multipart upload is the standard mechanism. Clients split the object into parts, upload them in parallel, and the store assembles them. This gives you resumability, parallelism, and the ability to push very large objects without holding them in memory.
Part-size and part-count limits vary by provider and change over time. AWS historically required parts of at least 5 MiB (except the last) with a maximum of 10,000 parts. Always check the provider’s current documentation rather than a blog post, because these numbers drift.
A practical checklist that saves real pain:
- Set part sizes deliberately. Too small means excessive request overhead and cost; too large means less parallelism and worse resume granularity.
- Handle failures mid-upload. Orphaned multipart uploads silently accumulate storage charges. Lifecycle rules to abort incomplete uploads are required.
- Tune concurrency to the client as well as the server. A single-threaded uploader will never saturate object storage throughput.
Performance on object storage scales with parallelism, not single-stream throughput. Many concurrent connections and well-chosen part sizes beat one heroic stream every time.
Consistency: the assumption that quietly breaks code
This is where experienced teams still get burned. AWS announced strong read-after-write consistency for all S3 GET, PUT, and LIST operations in December 2020. That was a significant change, and it’s easy to assume it applies everywhere.
It doesn’t. Many S3-compatible systems still offer only eventual consistency for listings, and behavior varies by vendor and version. Code that writes an object and immediately lists the bucket to confirm it exists may work perfectly on AWS and fail intermittently on a compatible store.
The defensive pattern is simple: don’t build logic that depends on list-after-write. Track object keys yourself. Treat listings as eventually accurate metadata, not as a source of truth.
Security baseline: the settings that prevent headlines
Most object storage breaches aren’t sophisticated. They’re a bucket left public. AWS introduced Block Public Access as an account and bucket setting in 2018 after a series of high-profile misconfiguration leaks, and it remains the single highest-value control to enable.
The baseline that actually works:
- Block public access by default, then explicitly and narrowly open what must be public (typically static assets behind a CDN).
- Enforce TLS for all requests.
- Apply least-privilege bucket policies. Wildcard principals are the enemy.
- Enable access logging and actually review it.
- Use Object Lock where compliance requires WORM behavior. S3 Object Lock became generally available in 2018, supporting retention periods and legal holds.
A common mistake: teams enable versioning for safety but never configure lifecycle rules to expire noncurrent versions. The bucket grows forever, and the cost creeps up quietly.
Versioning and lifecycle: the cost levers that matter
S3 versioning (introduced around 2010) plus lifecycle rules are the two controls that most directly determine your bill. Versioning protects against overwrites and deletes; lifecycle rules move data to colder tiers and expire old versions.
The pattern that works in practice:
- Turn on versioning for anything you can’t afford to lose.
- Immediately add a lifecycle rule to expire noncurrent versions after a defined window.
- Transition to colder storage tiers based on measured access patterns, not guesses.
- Add a rule to abort incomplete multipart uploads after a few days.
Teams that skip the second step are often shocked to find that their “small” bucket is mostly deleted-object versions they never intended to keep.
Data management and finding what you stored
Object storage has no real directory structure; keys with slashes just look like folders. That means discoverability is your problem, not the platform’s. Metadata discipline matters enormously. Tag objects consistently at write time, because retrofitting metadata across millions of objects is painful. (Amazon S3)
If your pipeline ingests user-uploaded media, you’ll eventually need to figure out what a given file actually contains. Techniques for identifying an object from a picture are useful context here, since content-based classification often has to happen after the object lands, not before.
It also helps to understand precisely what cloud storage is and how it works at the infrastructure level, because the abstraction hides real machinery: erasure sets, placement groups, and background scrubbing all influence the latency your application sees.
Testing before you migrate
The migration checklist that separates smooth cutovers from painful ones:
- Verify feature parity, not API names. Test Object Lock, Select, ACL semantics, and lifecycle behavior on the target system.
- Benchmark with your real access pattern. Small-object workloads and large-object workloads stress completely different parts of the stack.
- Test failure modes deliberately. Kill a node during a write. Interrupt a multipart upload. See what your client does.
- Confirm consistency assumptions with a list-after-write test against the target store.
- Model the full cost: requests, egress, and storage per GB.
Conclusion
Object storage rewards teams who respect its model. Immutability, parallelism, and lifecycle discipline aren’t limitations to work around; they’re the properties that make the system scale to exabytes at all. The failures that show up most often are predictable: public buckets, runaway egress, orphaned multipart uploads, and code that assumes strong consistency everywhere. Get those right, verify “compatibility” feature by feature, and design your durability around real failure domains rather than marketing numbers, and object storage becomes one of the most dependable pieces of infrastructure you’ll run.
Frequently Asked Questions
Is S3 compatibility guaranteed across vendors?
No. Compatibility is partial in practice. Many stores implement the core PUT, GET, and LIST operations but lack Object Lock, Select, or certain ACL semantics. Always test the specific features your application depends on against the target system before migrating.
Are all S3-compatible stores strongly consistent?
No. AWS announced strong read-after-write consistency for all S3 operations in December 2020, but that change applies to AWS. Many compatible systems still offer only eventual consistency, particularly for listings. Avoid building logic that depends on list-after-write.
Does the 11 nines durability figure apply to my self-hosted cluster?
No. The 99.999999999% figure is a provider-published claim for managed S3 Standard. Self-hosted durability depends entirely on your replication or erasure-coding configuration, failure domain design, and rebuild behavior. Treat it as an engineering problem you own.
How large should multipart upload parts be?
It depends on the provider’s current limits and your network, but parts in the range of 8 to 64 MiB are common for large objects. Too small adds request overhead and cost; too large reduces parallelism and worsens resume granularity. Always check the provider’s current documentation.
Why do my storage costs keep rising when my data volume looks stable?
The usual culprits are noncurrent object versions accumulating without lifecycle expiration, orphaned multipart uploads that were never aborted, and egress charges from cross-region reads. Audit all three before assuming the storage tier is the problem.
What is the single most important security setting?
Blocking public access by default. Most object storage breaches are misconfigured public buckets rather than sophisticated attacks. Enable it at the account level, then open only the specific paths that must be publicly readable, typically static assets behind a CDN.


