Most application performance problems are database problems. The queries are slow, the indexes are wrong, the connection pool is exhausted, or the schema is modelled in a way that makes the common query patterns expensive. These problems are solvable — but they are much easier to solve during initial design than during an incident with production traffic.
Index for Your Query Patterns, Not Your Schema
Indexes speed up reads and slow down writes. Every index you add is a trade-off. The indexes you need are determined by your most frequent, most performance-sensitive queries — not by the columns in your schema. Identify the top ten queries in your application and build your index strategy around them.
- Index foreign keys — joining on unindexed foreign keys is a common slow query root cause.
- Use composite indexes for queries that filter on multiple columns.
- Use partial indexes for queries that filter on a subset of rows (e.g. WHERE status = 'active').
- Avoid over-indexing write-heavy tables — the write overhead compounds under load.
Normalise to Third Normal Form, Then Denormalise Deliberately
Start with a properly normalised schema. Then, and only then, introduce deliberate denormalisation where query performance demands it — precomputed aggregates, materialised views, or denormalised columns that eliminate expensive joins on hot paths.
Connection Pooling Is Not Optional
Database connections are expensive to establish. Applications that open a new connection per request will exhaust the database's connection limit and suffer severe latency spikes under load. Use a connection pooler — PgBouncer for PostgreSQL, or a connection pool in your ORM — and size it based on your database's max_connections setting.
Read Replicas for Read-Heavy Workloads
If your read-to-write ratio is high (most CRUD applications have 80%+ reads), route read queries to read replicas and writes to the primary. This is a simple architectural change with significant throughput impact. Most managed database services (RDS, Cloud SQL) make provisioning read replicas straightforward.
Plan for Schema Migrations Early
Altering a large table in PostgreSQL takes an exclusive lock that blocks all reads and writes during the operation. On a high-traffic table, this means downtime. Use zero-downtime migration patterns — adding columns as nullable, backfilling in batches, then adding constraints — and tools like pg_repack for table rewrites.
Building a database architecture for scale?
Asquarify designs database schemas and infrastructure that perform under real traffic. We have optimised databases handling millions of queries per day.
Get in touch