In the early stages of a product, creating a bytea (binary data) column in PostgreSQL to store user avatars or product images seems like a quick and dirty solution. It guarantees data integrity (ACID) and makes backups straightforward since everything lives in one place.
However, for Product Managers and TPMs, system design isn't just about making a feature work; it's a calculated decision regarding scalability and performance. Storing images directly in a Relational Database Management System (RDBMS) is a classic anti-pattern that will quickly choke your system as it scales.
To understand why, we need to look down at the physical storage architecture: Page Storage.
The Bottom-Layer Architecture: A Tale of 8KB Pages
Databases like PostgreSQL do not store data as an endless stream of text. Instead, they divide data on the disk into fixed-size blocks called Pages.
In Postgres, the default size of a Page is 8KB.
Whenever the system needs to read or write data, it doesn't read individual bytes; it loads that entire 8KB Page from the disk into the RAM (Buffer Pool). This architecture is highly optimized for structured data (short strings, integers, booleans) because hundreds of rows can fit perfectly within a single Page.
How Do Images Impact Pages?
Let's do some simple math. Suppose you allow a user to upload a 2MB image.
Image size: 2MB = 2,048 KB
Single Page size: 8 KB
Required Pages: 2,048 / 8 = 256 Pages
*(Note: Postgres uses a mechanism called TOAST to chunk large data, but the physical reality remains: allocating hundreds of pages for a single file).*
Instead of a single Page holding hundreds of user records (names, emails, statuses), the system now has to consume 256 Pages just to hold one single image.
The Domino Effect: When the Database Gets "Polluted"
Consuming 256 Pages is not merely a Storage Cost issue. The worst consequences occur in the system's RAM (Shared Buffers / Buffer Pool).
A database operates blazingly fast because it keeps frequently accessed data (hot data) and Indexes in RAM. This RAM is strictly limited. When a query is executed to fetch a list of users along with their images, Postgres is forced to pull thousands of image-heavy Pages from the disk into the RAM.
The Trade-off Rule: Cache memory is a zero-sum game. When 256 Pages of an image are loaded into the Buffer Pool, 256 Pages containing critical Indexes or core transactional data belonging to tens of thousands of other users get pushed out (Cache Eviction).
This phenomenon is called Buffer Pool Pollution. It leads to:
Spiking Cache Miss Rates: The system is constantly forced to perform Disk I/O for simple, everyday queries.
Performance Degradation: The latency of all other APIs sharing the database skyrockets.
Database Bloat & Slow Backups: Write-Ahead Log (WAL) files used for data replication swell massively. Backup processes that used to take minutes can drag on for hours.
The Solution: Why Blob Storage is the Answer
Blob Storage (Binary Large Object Storage) like Amazon S3, Google Cloud Storage, or Azure Blob is designed with an entirely different philosophy. They do not care about strict row structures, Indexes, or Pages. They are flat storage systems heavily optimized for unstructured data at a fraction of the cost of a relational database.
Architectural Separation of Concerns
In a standard, scalable system, we apply a decoupled model:
Database (Postgres): Stores only the metadata. Instead of saving the file, you save a tiny text string representing the path: image_url: "s3://bucket-name/user_123_avatar.jpg". This data costs merely a few dozen bytes (less than 1% of an 8KB Page).
Blob Storage (S3): Handles the physical storage of the 2MB image file.
CDN (Content Delivery Network): Sits in front of the Blob Storage to cache images at edge servers closest to the user's geographic location, completely offloading traffic from the backend system.
The TPM Perspective: Never use an expensive tool optimized for ACID transactions (Database) to do the job of cheap, static storage (Blob Storage). Optimizing costs and decoupling responsibilities is the key to scaling from 10 thousand to 10 million users.
When a system hits its Write limit, Replication becomes useless. Explore Shard Key selection strategies, how to avoid Hotspot disasters, and the expensive dark side of Sharding.