Storage Basics
Every project eventually has to put its data somewhere. Nearly always the choice is between block storage and object storage. From a distance they look like the same thing with different billing; once you start writing to them they behave nothing alike.
Block Storage
Block storage gives you a disk. Not a metaphor for a disk, an actual block device: a fixed-size range of addresses carved into chunks of 512 bytes or 4 KB, with no concept of a file. Something else has to supply that concept, and that something is a filesystem. You run mkfs once, ext4 or XFS or APFS writes its own bookkeeping into the first blocks, and from then on the kernel translates /data/results.csv into “blocks 88,412 through 88,530.”
On AWS this is EBS. On GCP it’s Persistent Disk. In a machine room it’s whatever the SAN hands out over iSCSI. The laptop you’re reading this on has one soldered to the board.
The detail that drives everything else is attachment. A block volume connects to one machine, the way an external drive does. That machine mounts it and treats it as a normal path. Detach it and you have an inert volume sitting in a single availability zone, reachable by nothing.
What it’s good at
- Speed. The volume sits on the host or one hop away on a dedicated storage network. A gp3 volume starts at 3,000 IOPS and 125 MB/s and can be dialed up to 16,000 IOPS. Latency is measured in microseconds and low milliseconds, not round trips.
- Editing in place. Change four bytes in the middle of a 40 GB file and the drive rewrites one block. Everything transactional depends on this. Postgres writes 8 KB pages and flushes a write-ahead log on every commit; if each of those commits meant rewriting the whole database file, the database would not work.
- Programs already expect it. File locks,
seek(),mmap, atomic renames, permissions, symlinks. SQLite needs locking. Git needs renames. If a tool has ever asked you for a path, it wants a block device underneath. - Cheap snapshots, because the service only stores the blocks that changed since the last one.
Where it hurts
- One host at a time. Two machines that both need the data means running NFS or EFS or Lustre on top, which is a second system to operate.
- The size is a decision you make up front. Growing a volume means expanding the volume and then running
resize2fsorxfs_growfsto tell the filesystem about the new room. Shrinking usually isn’t possible at all. - You’re billed for what you allocated. A 1 TB volume holding 4 GB of data costs exactly what a full one costs.
- It lives in one availability zone. If the zone goes, so does the volume, unless you’ve been taking snapshots.
- Roughly $0.08 per GB-month for gp3 in us-east-1, against $0.023 for S3 Standard. Check current prices before you quote those, but the ratio holds.
- Everything about it is yours to run: filesystem choice, backups, fsck after a bad shutdown, and the alert at 3 a.m. saying the disk is full.
Object Storage
Object storage drops the disk idea entirely. There’s no device to attach and no filesystem to create. You get a bucket, and into it you put objects: a blob of bytes, a key that identifies it, and whatever metadata you attach. All of it moves over HTTPS with PUT, GET, and DELETE.
The namespace is flat. aws s3 ls s3://my-bucket/raw/2026/ prints something that looks like a directory listing, but nothing is nested; the service is matching a string prefix against every key in the bucket, and the slashes are ordinary characters that the console draws as folders because people find that easier to read.
Objects are also immutable. There’s no seek-and-write, no appending. Change one byte in a 4 GB file and you upload 4 GB.
Why everyone copied S3
S3 shipped in March 2006, before EC2, and the API turned out to be small enough that competitors implemented it rather than inventing their own. That is why MinIO, Cloudflare R2, Backblaze B2, Wasabi, Ceph, and Google Cloud Storage all answer S3 calls today, and why boto3, rclone, DuckDB, Spark, and pandas can read from any of them after you point them at a different endpoint. Time spent learning the S3 API is not time spent learning one vendor’s product. The lock-in is in the egress bill, not the interface.
What it’s good at
- You never size it. There is nothing to provision and nothing to grow. A bucket holding 3 KB and a bucket holding 3 PB are configured identically. The only ceiling worth knowing is 5 TB per object, and anything over 5 GB goes up as a multipart upload, which the CLI and the SDKs handle for you.
- It survives things. Every object is written to several separate facilities before the
PUTreturns. AWS quotes eleven nines of annual durability for S3 Standard, and you get that number without configuring replication, RAID, or backups. - Nothing is attached to it. The bucket has no host. A hundred machines across three regions can read the same object at the same time, and the data keeps existing after the cluster that produced it is torn down. That property is the reason data lakes are built this way.
- Storing things is cheap enough to stop thinking about. Around $0.023 per GB-month for Standard, plus fractions of a cent per thousand requests.
- Lifecycle rules move old data down the price ladder to Glacier and its colder tiers on a schedule you set once.
- Every object has a URL, so the same bucket can serve a static site, hand out a dataset, or accept uploads through presigned links.
Where it hurts
- Latency is an HTTP round trip, tens to hundreds of milliseconds. Reading one 200 MB Parquet file is fine. Reading 200,000 tiny files is miserable, and the fix is always the same: fewer, larger files.
- Immutability shapes your whole pipeline. You don’t append a day of records to yesterday’s file, you write today’s file next to it and let the query engine read both.
- It is not a filesystem and won’t pretend convincingly. No locks, no
seek(), and a rename is a copy followed by a delete, which is why “moving” a large prefix takes real time and real money.s3fsand Mountpoint will mount a bucket for you, and the seams show under load. - S3 has been strongly read-after-write consistent since 2020, but plenty of S3-compatible services still aren’t. Verify before you build on it.
- Requests and egress are where the surprise bills come from. A job issuing millions of small
GETs can spend more on requests than on storage, and data leaving the cloud runs about $0.09 per GB. - Buckets are easy to expose. Public-by-accident buckets have leaked more records than most attacks have.
Picking one
| Situation | Use |
|---|---|
| A database, or anything with a hot write path | Block |
| Software that expects an ordinary file path | Block |
| Heavy random reads and writes inside large files | Block |
| Raw data feeding analytics, a data lake, a warehouse staging area | Object |
| Backups, archives, logs, model checkpoints | Object |
| Anything many machines or many people read at once | Object |
| Data that has to outlive the machine that made it | Object |
Most real systems use both, in a specific arrangement: the bucket holds the authoritative copy, and the volume holds a fast scratch copy. A training run pulls its dataset from S3 onto a local disk, hammers that disk with random reads for six hours, writes checkpoints back to S3, and then the instance and its volume are deleted. Nothing of value lived on the volume.
If you’re editing bytes where they sit, you want block. If you’re writing whole files once and reading them many times, you want object.
Going further
- Amazon S3 documentation
- Amazon EBS documentation
- MinIO runs an S3-compatible server on your own machine, which is the cheapest way to practice the API without an AWS bill.