Software EngineeringMarch 5, 20268 min read

Database Scaling Patterns Before You Shard

Sharding is expensive, permanent and hard to reverse. Here are the scaling patterns to exhaust first, in the order we reach for them on real production systems.

By Innovation T Team


Sharding gets talked about like a rite of passage, the moment a database finally "grows up." In practice it is one of the most expensive, least reversible decisions you can make, and most teams reach for it years before they need to. This is the order we work through when a database starts to hurt, and why each rung buys you time before you commit to the hard one.

Why sharding is the last resort

Sharding means splitting your data across multiple independent database instances so no single machine holds the whole set. It sounds like more capacity, and it is. It is also a tax on almost everything you build afterward.

Once data lives on separate shards, cross-shard joins stop being free, transactions that span shards need application-level coordination, and every query has to know which shard to talk to. Rebalancing a hot shard is a project, not a config change. Analytics that used to be a single query become a scatter-gather across nodes. In our experience, teams that shard early spend more engineering time working around their own topology than they ever spent on the performance problem that triggered it.

So the goal of this article is not "how to shard." It is "how to not need to yet." Every pattern below is cheaper to adopt and cheaper to undo.

Measure before you move

Before touching architecture, get honest numbers. Scaling the wrong layer is how teams add read replicas to fix a problem that was really a missing index.

Collect at minimum:

  • Slow query logs with actual execution plans, not guesses about which queries are slow.
  • p95 and p99 latency per endpoint, not averages. Averages hide the queries that make users leave.
  • Connection counts over time, including idle and waiting connections.
  • Read/write ratio. A 95% read workload and a 60% read workload call for different rungs.
  • Table and index sizes, plus row growth rate per month.

If you cannot see these today, that is your first task. You cannot scale what you cannot measure, and half the time the data reveals a fix that costs an afternoon instead of a quarter.

The scaling ladder

Here is the order we climb, from cheapest and safest to most invasive. Work top to bottom and stop the moment your numbers are healthy again.

  1. Fix queries and indexes.
  2. Right-size and pool your connections.
  3. Add caching where the read pattern allows it.
  4. Offload reads to replicas.
  5. Scale the primary vertically.
  6. Partition large tables inside one database.
  7. Split by domain (separate stores or CQRS).
  8. Only then, shard.

1. Queries and indexes first

The single highest-return work is almost never architectural. It is a missing index on a WHERE or JOIN column, an N+1 query firing hundreds of times per request, or a SELECT * pulling wide rows when three columns would do.

Run EXPLAIN ANALYZE (Postgres) or the equivalent on your slowest queries. Look for sequential scans on large tables, sorts spilling to disk, and nested loops over big row counts. Add covering indexes so the database answers straight from the index. Watch out for over-indexing though: every index you add slows writes and consumes memory, so index for the queries you actually run, then drop the ones that never get used.

This rung regularly cuts p99 latency by a large margin for close to zero infrastructure cost. Do it before anything else.

2. Connection pooling and limits

Databases handle far fewer concurrent connections well than most people assume. A Postgres box that is happy at 100 active connections can fall over at 500, because each connection carries real memory and scheduling overhead. Serverless and autoscaling app tiers make this worse: every new instance opens its own pool, and connection counts explode.

Put a pooler in front (PgBouncer for Postgres, ProxySQL for MySQL, or your cloud provider's managed equivalent). Use transaction-level pooling so connections are shared aggressively. Cap the pool size deliberately, and size app-tier pools with the database's real limit in mind, not the number your app framework defaults to.

3. Caching where reads repeat

If the same data is read far more often than it changes, a cache in front of the database absorbs the load. Redis or Memcached for hot key-value lookups, HTTP and CDN caching for anything that can be served slightly stale, and materialized views for expensive aggregations that do not need to be real time.

The hard part of caching is invalidation, so be explicit about your strategy: time-based expiry for data that can be a little stale, and event-based invalidation for data that must be fresh on write. Cache the expensive reads, not everything, and always measure your hit rate. A cache running at a 40% hit rate is often adding latency, not removing it.

4. Read replicas

When reads dominate and you have already cached the obvious wins, replicate. A primary handles writes, one or more read replicas handle queries, and the app routes accordingly. Cloud managed databases make this close to a checkbox.

The tradeoff is replication lag. A replica is eventually consistent, so a user who just saved a change might read a replica that has not caught up and see stale data. Route reads that must be immediately consistent (a user reading their own just-submitted form) to the primary, and send everything tolerant of a small delay (dashboards, listings, search) to replicas. Reporting and analytics queries in particular belong on a replica so they never contend with production writes.

5. Vertical scaling

Sometimes the cheapest fix is a bigger machine. Modern hardware is enormous: instances with hundreds of gigabytes of RAM and dozens of cores are routine, and a single well-tuned node can carry workloads teams assume require a cluster. Adding RAM so the working set fits in memory often does more than any topology change.

Vertical scaling has a ceiling and a bill, so it is not the end state. But as a way to buy six to twelve months while you build the next rung properly, it is frequently the right call. Just make sure your provider supports resizing with minimal downtime before you lean on it, and keep an eye on the cost curve. Our cloud cost optimization playbook covers how to keep that spend from quietly running away from you.

6. Partitioning inside one database

Partitioning splits a large table into smaller physical pieces while keeping it a single logical table and staying inside one database. This is not sharding: there is no distributed coordination, and your queries barely change.

The classic win is time-based partitioning on an events, logs or orders table: partition by month, and queries filtered on a date range only scan the relevant partitions (partition pruning). Dropping old data becomes an instant partition drop instead of a slow, bloating DELETE. You can also partition by a key like tenant or region. Partitioning delivers much of the benefit people expect from sharding while keeping the operational simplicity of one database.

7. Split by domain

Before you split one table across many machines, consider splitting your schema across a few. If billing, analytics and the core product all live in one database and compete for the same resources, separating them by bounded context gives each room to breathe and scale on its own terms.

This pairs naturally with service boundaries. If you are already moving in that direction, our guide on going from monolith to microservices walks through drawing those seams without creating a distributed mess. A CQRS split (a normalized write store plus a read-optimized store, often a search engine or a denormalized replica) is the same idea applied to a single high-traffic domain.

When sharding actually makes sense

Sometimes you genuinely need it. The honest signals:

  • A single table has grown past what one node can hold or index efficiently, even after partitioning.
  • Write throughput on the primary is the bottleneck, and replicas do not help because they only scale reads.
  • You have a natural, stable shard key (tenant ID, user ID, region) that keeps almost all queries inside one shard.
  • You have already climbed every rung above and still have a wall in front of you.

If that is you, shard deliberately: pick a shard key that matches your access patterns, plan for rebalancing from day one, and lean on a system that handles the distribution for you (Citus, Vitess, or a natively distributed database) rather than hand-rolling routing in application code.

A pre-shard checklist

Run this before anyone opens a sharding design doc:

  1. Are the slowest 20 queries indexed and free of sequential scans on large tables?
  2. Have N+1 patterns been eliminated in the hottest endpoints?
  3. Is a connection pooler in place with deliberate pool sizes?
  4. Are repeated reads cached, with a hit rate you have actually measured?
  5. Have read-heavy and reporting queries been moved to replicas?
  6. Has vertical scaling been tried or at least costed?
  7. Are large time-series or append-heavy tables partitioned?
  8. Have you split unrelated domains into their own stores?

If you cannot check most of these boxes, sharding will not fix your problem. It will add a distributed-systems problem on top of the one you already have.

How Innovation T can help

Most of the database pain we are called in to fix is not a capacity problem, it is an instrumentation and query problem wearing a capacity costume. At Innovation T, our software and cloud engineering teams start where the numbers point: we profile the real workload, fix the queries and indexes that are actually hurting, then work up the ladder (pooling, caching, replicas, partitioning) so you add complexity only where it earns its keep. When sharding or a distributed database truly is the answer, we design the shard key and migration path with you so it holds up for years, not months.

If your database is starting to feel like the ceiling, take a look at our services or get in touch. We would rather help you avoid a shard than clean up after a rushed one.

#database#scaling#performance#architecture

Ready to build with Innovation T?

Whether it is security, growth or engineering, our team can help you ship it well.