Large files and small rows want different homes. A database is built for many small rows; files of a megabyte or more fill its network and its disks. Object storage is built for files, and moving them there takes the weight off every server they used to pass through.
Files don't belong in the database
Every picture served out of the database leaves through the database's network card, then through an app server's. At a megabyte a picture, a few hundred views a second fill a medium server's card, and everything else on those servers waits behind them. The files also bloat the database itself: its disks fill, its backups crawl, and its memory caches pictures instead of the rows that make pages fast.
| Data | Where it belongs |
|---|---|
| Notes, users, prices: small rows read and changed often | a database |
| Pictures, attachments, exports: large files written once, read many times | object storage |
Object storage
Object storage keeps files, called objects, in buckets, each under its own name. There's nothing to size: no tiers, replicas or partitions, and it serves as many requests and as many bytes as you send it, up to a limit per key prefix (on S3, about 3 500 writes and 5 500 reads a second for each prefix). It's durable, too, so a stored file stays stored.
The price is speed. Each request takes tens of milliseconds, and the slowest few in a hundred 100 to 200 ms, slower than a typical indexed database lookup.
Off the servers
Instead of passing a file on, the app hands the client a link to fetch it from storage. The database keeps only a small row for each file: its key in the bucket and metadata such as its size and owner.
Paying per request and per byte
Object storage bills every request: about $0.40 per million reads and $5 per million writes. It also bills every byte it sends to readers, its egress, at about $0.09 per GB. A thousand views a second of 1 MB pictures is 3.6 million reads an hour, about $1.50, and 3 600 GB of egress, over $300. The egress is the bill. A CDN in front answers the popular files from its edges, so only its misses reach storage, but it bills every request it answers, about $1 per million, and every byte it sends, about $0.085 per GB.
Uploads
The client asks the app for a presigned URL, a short-lived signed link for one upload, then sends the file straight to the bucket. Private files are read the same way, through a signed link for one download. On the canvas, connect writers to both the app and the storage: the app issues the presigned URL, so without a connection to the servers, uploads are refused.
Big files fail midway. A multipart upload sends a file in parts, so a lost part can be sent again on its own. The lab sends one in three parts and loses the second. Compare starting over with sending only what is missing.
In the wild
The ideas in this chapter, and where you'll meet them in real systems and interviews:
| Idea | Where you'll meet it |
|---|---|
| Object storage | Amazon S3, Google Cloud Storage, Azure Blob Storage |
| Presigned URLs | S3 presigned URLs, Google Cloud Storage signed URLs |
| A CDN in front | Amazon CloudFront in front of S3 |
Your practice
Inkwell's pictures are everywhere now, about 1 MB each and a thousand views a second, and they're served out of the database, whose network has been full all morning. Notes fail along with them, and the database won't get bigger. Give the pictures a home of their own, so pages are fast and nothing fails.