Practical database scaling approaches from indexing and query optimization to read replicas, partitioning, and sharding strategies.
Nine times out of ten, "we need to scale our database" actually means "we need to fix our queries." Before investing in replication, sharding, or a new database engine, exhaust the optimization opportunities in your current setup.
This guide walks through database scaling strategies in order of complexity, from changes you can make today to architectural decisions that take months.
The most common cause of slow queries is missing or incorrect indexes. Start here.
Finding problem queries:
-- MySQL slow query log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
-- PostgreSQL pg_stat_statements
SELECT query, mean_exec_time, calls
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 20;
Indexing rules of thumb:
(status, created_at) works for queries filtering on status alone or status AND created_at, but not for created_at alonepg_stat_user_indexes (PostgreSQL) or sys.schema_unused_indexes (MySQL) to find dead indexesN+1 queries. The most common performance killer in ORM-based applications. If you load 100 orders and each order lazy-loads its customer, you execute 101 queries instead of 2. Use eager loading consistently.
*SELECT . Only select columns you need. Wide rows with large text columns waste I/O when you only need the ID and name.
Unoptimized pagination. OFFSET 50000 LIMIT 25 forces the database to scan and discard 50,000 rows. Use keyset pagination instead: WHERE id > :last_seen_id ORDER BY id LIMIT 25.
Missing LIMIT on aggregations. Counting millions of rows is expensive. If you only need "is there more data?" check for LIMIT + 1 instead of counting everything.
Each database connection consumes memory on the server (typically 5-10 MB per connection in PostgreSQL). When your application runs on multiple servers with multiple processes each, connection counts add up fast.
Use a connection pooler like PgBouncer (PostgreSQL) or ProxySQL (MySQL) to multiplex hundreds of application connections onto a smaller pool of actual database connections.
Cache expensive query results in Redis or Memcached:
$topProducts = Cache::remember('top-products', 3600, function () {
return Product::query()
->withCount('orders')
->orderByDesc('orders_count')
->limit(50)
->get();
});
Cache invalidation rules:
The hardest part of caching is not the implementation but deciding what staleness is acceptable for each piece of data.
When your read load significantly exceeds your write load (common for most web applications), read replicas distribute query load across multiple database servers.
How it works:
Laravel configuration:
'mysql' => [
'read' => [
'host' => ['replica-1.db.internal', 'replica-2.db.internal'],
],
'write' => [
'host' => 'primary.db.internal',
],
],
Replication lag is the trade-off. Replicas are typically milliseconds behind the primary. This means a user who creates a record might not see it immediately if the next request hits a replica. Handle this by:
When individual tables grow beyond what a single server handles efficiently (typically hundreds of millions of rows), partitioning splits tables into smaller, more manageable pieces.
Horizontal partitioning (range-based):
CREATE TABLE orders (
id BIGINT,
created_at TIMESTAMP,
...
) PARTITION BY RANGE (YEAR(created_at)) (
PARTITION p2024 VALUES LESS THAN (2025),
PARTITION p2025 VALUES LESS THAN (2026),
PARTITION p2026 VALUES LESS THAN (2027)
);
Queries that filter on the partition key only scan relevant partitions. Historical data in old partitions can be archived or moved to cold storage independently.
When to partition:
Sharding distributes data across multiple independent database servers. Unlike partitioning (which splits data within one server), sharding splits data across servers.
Sharding strategies:
Sharding is a last resort. It introduces enormous complexity: cross-shard queries, distributed transactions, rebalancing when shards become uneven, and operational overhead of managing many database instances. Exhaust all other strategies first.
Most applications will never need sharding. The vast majority of scaling problems are solved by proper indexing, query optimization, caching, and read replicas. The order in this article is deliberate: start at the top and move down only when the simpler approaches are not enough.
Whether you're modernizing your infrastructure, navigating compliance, or building new software - we can help.
Book a 30-min Call