Products Services BlogAbout Contact
Free Tools
QR Code Generator URL Shortener View all free tools Book a demo
Home  /  Blog  /  Database Design Mistakes
System devUpdated Sep 7, 2026 · 7 min read

Database Design Mistakes That Slow Down Growing Startups

Most schema decisions feel perfectly fine when you have a handful of users and a few thousand rows. Here are the ones that quietly break as a startup scales from dozens of users to thousands.

On this page
  1. No indexes on the columns you actually query
  2. Storing structured data as one big text/JSON blob
  3. No foreign key constraints, "we'll enforce it in the application"
  4. Not planning for soft deletes or audit history early
  5. Premature sharding or over-engineering for scale you don't have yet

No indexes on the columns you actually query

This is the single most common database problem we see, and also the easiest to fix. A table with no indexes works fine when it has 100 rows. Every query, even a bad one, comes back instantly because scanning 100 rows takes no time at all.

The same table at 100,000 rows is a different story. Without an index, the database has to scan every single row to find the ones that match your query. A lookup that took a few milliseconds now takes seconds. Multiply that by every user hitting the page at once, and a slow query becomes a slow app, or a timed-out one.

The fix is usually straightforward. Look at the columns your application filters, sorts, or joins on most often, things like user_id, email, status, or created_at, and add indexes there. Most databases include a query planner or a slow-query log that will point directly at the queries costing you the most time.

  • Index foreign key columns used in joins.
  • Index columns used in WHERE clauses on large tables.
  • Index columns used to sort results, especially with pagination.
  • Don't over-index. Every index speeds up reads but slows down writes, so add them where the query patterns actually justify it.

It's worth running EXPLAIN on your slowest or most frequent queries every few months, not just once at launch. Query patterns change as features get added, and a column that never needed an index in month one can easily need one by month six. Composite indexes, covering more than one column, are also worth learning early if your app regularly filters on two or three fields together, since a single-column index won't help much there.

Storing structured data as one big text/JSON blob

Dumping everything into a single flexible JSON column feels fast to build early on. You skip migrations, you skip deciding on a schema, and you can add new fields whenever you want without touching the database. It's a genuinely appealing shortcut in the first few weeks of a product.

The problem shows up the first time you need to actually query that data in a specific way. Filtering by a field buried in a JSON blob is slower and clumsier than filtering by a real column. Reporting on it means writing awkward extraction logic instead of a simple GROUP BY. Enforcing that a field is required, or that it's a number and not a string, becomes something your application has to police instead of something the database guarantees.

None of this matters if the data really is unstructured, like a settings object nobody ever filters on. It matters a lot once that "flexible" field turns out to be something you filter, sort, or report on every single day.

  • Structured fields you regularly query belong in real columns.
  • JSON columns are fine for genuinely variable, rarely-queried data.
  • If you're already writing code to parse a JSON field on every read, that's a strong signal it should be a column instead.

A common middle ground works well in practice: keep the fields you know you'll query, report on, or validate as real columns, and reserve a single JSON column for the genuinely optional, rarely-touched extras. That gives you the flexibility that made JSON appealing in the first place, without giving up the indexing and integrity guarantees on the data that actually drives your product.

No foreign key constraints, "we'll enforce it in the application"

Relying entirely on application code to keep related data consistent works right up until it doesn't. Every insert and update has to remember to check that the related record exists. That's fine when one person is writing the code. It gets fragile fast once there are multiple developers, a background job, an admin script, and someone doing a manual database edit at 2am, all touching the same tables.

It only takes one of those paths skipping the check to create an orphaned record: an order pointing to a customer that no longer exists, a comment attached to a deleted post. Individually these are small bugs. Collectively they're the reason a "simple" data cleanup script turns into a multi-day investigation.

Foreign key constraints don't slow down normal development. They just make the database refuse to do the one thing that would have created an inconsistent record in the first place.

A foreign key constraint moves that check into the database itself, where it can't be forgotten or skipped. The database rejects the invalid insert immediately, with a clear error, instead of silently letting bad data in that someone has to find and fix months later.

The usual pushback is that constraints add friction, especially around migrations or bulk imports. In practice that friction is small and one-time, while the cost of untangling inconsistent production data with real customers attached to it is not. If you're not using constraints today, adding them to your most important relationships, users, accounts, and billing records, is a good place to start.

Not planning for soft deletes or audit history early

Permanently deleting a row feels simpler. There's less to think about, less to store, and the data is just gone. It feels like the right default when you're moving fast and every table is simple.

Then a support request comes in asking what a record looked like last week, before a customer edited it. Or a compliance requirement shows up needing a history of every change made to a record, not just its current state. If rows are actually deleted rather than marked deleted, that information is simply gone. There's no getting it back.

A soft delete marks a row as deleted with a flag or a timestamp, instead of removing it. Your application filters those rows out of normal views, but the data is still there if you need it. Pairing that with a basic audit log, who changed what and when, is a small amount of extra work early on.

  • Add a deleted_at timestamp instead of hard-deleting important records.
  • Log meaningful changes to sensitive or customer-facing data, even a simple table with old value, new value, and who made the change.
  • Retrofitting this after the fact means you have no history for anything that happened before you added it.

You don't need to log every table on day one. Start with the records that carry real business or customer weight, accounts, subscriptions, permissions, anything a support or legal request might eventually touch. Adding a deleted_at column to a table is a five-minute migration when the table is new. Going back and reconstructing history you never captured is not.

Premature sharding or over-engineering for scale you don't have yet

The opposite mistake is just as common, and just as costly. Some teams design their schema for a scale of users the product may never actually reach: sharding a database that comfortably fits on a single instance, building a complex event-sourcing layer for data that would be perfectly served by a few well-indexed tables, or splitting a simple app into a dozen microservices before there's a second engineer to maintain them.

Every layer of that complexity has a cost. It slows down every feature built afterward, because now a simple change has to account for the sharding logic, or the event replay, or the extra service boundary. It's complexity paid for upfront, for a problem that doesn't exist yet, and might never exist.

The better approach is to design a clean, well-indexed, properly normalized schema that can comfortably handle the next couple of orders of magnitude of growth, and revisit it when you have real evidence you need more. That's usually a much bigger lever than any early architectural bet. If you're not sure where that line is for your product, it's often worth getting a second set of eyes from a team that has seen this tradeoff play out before, which is exactly the kind of thing our software development services help startups get right the first time.

Get your data model right early

We help teams design schemas that hold up as usage grows, without over-engineering for scale you don't have yet.

Talk to us →

FAQ

How do I know if my database needs an index?
Look at the columns you filter, sort, or join on most often, things like user_id, email, status, or created_at. If a query regularly filters on a column and the table has more than a few thousand rows, that column almost certainly needs an index. Most databases also offer a query planner or slow-query log that will point directly at scans that are costing you time.
Is it ever okay to store data as JSON in a relational database?
Yes, for genuinely unstructured or rarely-queried data, like a flexible settings object or a snapshot of a third-party API response. It stops being okay when you start regularly filtering, sorting, or reporting on fields inside that JSON. At that point the data has become structured in practice, even if it isn't in your schema, and it belongs in real columns.
What's a soft delete and why does it matter?
A soft delete marks a row as deleted with a flag or timestamp instead of removing it from the database. The row stays available for support requests, audits, and analytics, while your application simply filters it out of normal views. It matters because permanently deleted data cannot answer questions you didn't know you'd need to ask.
When should a startup actually worry about database scaling?
Almost never as early as founders think. Solid indexing, sane schema design, and a properly sized database instance handle the vast majority of startups well past their first few hundred thousand users. Sharding, read replicas for scale, and multi-region data layers are problems worth solving when you have clear evidence you need them, not before.
Written by the Go4Lead.tech team — we build the tools we write about.

Need software built around your workflow?

This guide is a small taste of what we do. Go4Lead.tech builds custom software, web and mobile apps, and AI automation for businesses.