Stop Stuffing Everything Into a BLOB Column

I watched a team spend three weeks debugging a query that was literally impossible to optimize because every piece of user metadata had been jammed into a single JSON blob column. They thought they were being agile. They were being lazy, and the database made them pay for it. A BLOB is a Binary Large Object. In practice, most people use this term to describe storing semi-structured data—JSON, XML, serialized objects—as an opaque chunk inside a relational table instead of breaking it into proper columns. The pattern gets called "The Blob That Ate Everyone" because once you start treating your database like a document dropbox, normalization goes out the window and everything underneath it becomes harder to work with. Every team I have ever seen do this starts with good intentions. They tell themselves they will refactor later. They never do.

The Blob That Ate Everyone: What It Actually Is

The concept is straightforward. Instead of designing a schema with separate columns and proper relationships, you create one wide column meant to hold arbitrary data. Postgres calls it JSONB. MySQL calls it JSON. SQL Server calls it NVARCHAR(MAX) or VARBINARY. The name changes. The problem does not. Here is what happens in practice. You ship the feature fast. Queries that need only a slice of that data still read the entire blob. You cannot index into specific keys reliably without vendor-specific functions. When you need to run a report, you are writing JSON extraction logic instead of a simple WHERE clause. And when the schema evolves, you are manually migrating tens of thousands of rows because the blob gives you zero structural guarantees. I encountered this directly on a project where we stored device telemetry inside a JSONB field on a Postgres table with roughly eight million rows. The application needed to filter by a nested timestamp and a sensor ID, both buried three levels deep inside the blob. The query planner could use a GIN index on the JSONB column, but only for top-level key existence checks. Filtering on nested values required a function call per row, which turned a sub-second query into something that took forty-seven seconds. I ended up writing a materialized view that extracted the hot fields into real columns, updated it on a schedule, and pointed the reporting layer at that instead. It cut the query time to under two hundred milliseconds. The fix was not elegant, but it was necessary.

Why Teams Reach for a Blob Instead of a Proper Schema

There are legitimate reasons. Rapid prototyping benefits from flexible storage. Event sourcing and audit logs often store payloads that genuinely vary in shape. Some domains simply do not map cleanly to tabular form. These are real cases. The problem is scope creep. A JSON column meant for an audit log slowly starts receiving application state. A blob meant for optional preferences gets used for core business data because it is easier to insert than to alter a table. Before you notice it, half your important data is hiding inside opaque text fields and nobody remembers why.

Get the Full Details

The Blob That Ate Everyone (Classic Goosebumps #28) | Scholastic Canada
The Blob That Ate Everyone (Classic Goosebumps #28) | Scholastic Canada

When a Blob Is Actually the Right Choice

Use a BLOB or JSON column when the data is truly variable and you do not need to query inside it regularly. Store it when it is a payload, not a model. Examples include: Do not use a blob when you need to filter, sort, aggregate, or join on the data inside it. If your application queries a specific field more than once a week, it deserves its own column. The first cost is performance. BLOB storage forces full row reads even when you only need one field. Second is migration pain. Altering a table with real columns is straightforward. Migrating JSON data between versions requires custom scripts, and you will lose type safety in the process. Third is tooling. Most ORM generators do not handle embedded JSON well. Most reporting tools cannot touch it. You end up writing custom wrappers just to make standard tools work.

There is also a constraint most teams ignore until it is too late. Database-level constraints do not apply inside a blob. You can accidentally store missing required fields, wrong types, or duplicate keys, and the database will accept it without complaint. Application-level validation is the only thing stopping bad data from entering the system, and application-level validation is always weaker than database-level validation.

How to Use Blobs Without Destroying Your Database

Be intentional about what goes into a blob and what stays structured. Define a contract. If you are using JSONB in Postgres, use check constraints with JSON path expressions to enforce structure at the database level. This is faster than application validation and catches bad inserts before they reach your code. For Postgres, a constraint like this keeps a nested value from going missing without forcing you to create a separate column: ALTER TABLE events ADD CONSTRAINT valid_payload CHECK (jsonb_typeof(payload -> 'user_id') = 'number');

the blob that ate everyone on myCast - Fan Casting Your Favorite Stories
the blob that ate everyone on myCast - Fan Casting Your Favorite Stories

Index selectively. GIN indexes help with containment queries, but they do not solve deep nested filtering. If you find yourself writing the same extraction path repeatedly, pull that path into a generated column and index the generated column instead. Generated columns give you the flexibility of a blob with the query performance of a real column. Version your blob schemas. Treat the JSON structure inside the blob like code. Maintain a migration history for payload changes, and write backward-compatible readers. I have seen teams lose months of data because they upgraded a schema version and broke ten thousand existing rows that no one had tested against the new structure.

When to Walk Away Completely

If your blob is growing faster than your normalized tables, you have a design problem, not a storage problem. Start moving hot fields out. Identify the top five queries that touch the blob and create dedicated columns for the data they need. This is never a one-day task on a large table. Plan for weekends or scheduled maintenance windows. For read-heavy workloads built around blob data, consider moving the payload to a document store like MongoDB or Elasticsearch while keeping the relational schema clean. The right tool for the job matters more than keeping everything in one database. I have seen teams force a single Postgres database to serve as both a transactional store and a document repository, and it performed worse than both would have separately. Blobs are not evil. Uncontrolled blobs are. Use them where they fit, constrain them where you must, and pull data out into proper columns the moment it stops behaving like a payload. The team that suffers later is the one that waited too long to decide.