Database Performance Tuning — Intermediate
Composite indexes: column order is not arbitrary
A composite (multi-column) index is only useful for queries that filter on a left-to-right prefix of its columns — this is the single most misunderstood detail of composite indexing, and getting column order wrong silently makes an index far less useful than it appears:
The practical rule: put the column used in equality filters (=) and with the highest selectivity (fewest matching rows per value) first, and range-filtered columns (>, <, BETWEEN) last — a range condition on a non-final column effectively stops the index from being useful for anything after it in the column order, since the B-tree can no longer maintain a useful sorted position past a range condition.
Covering indexes: avoiding the table lookup entirely
A normal index scan still requires a second step — looking up the actual row in the table (a "heap fetch") to retrieve columns not in the index itself. A covering index includes every column the query needs, letting the database satisfy the entire query from the index alone:
Index Only Scan (versus Index Scan) in the plan output confirms the heap fetch was avoided entirely — a genuinely significant speedup for read-heavy queries against wide tables, at the cost of a larger index (since it now stores more data per entry) — worth reserving for genuinely hot, frequently-run queries rather than applied to every index by default.
Table partitioning: when a single table becomes the actual bottleneck
Once a table grows large enough that even indexed queries and routine maintenance (VACUUM, index rebuilds) become slow simply due to table size, partitioning splits it into smaller physical pieces while keeping one logical table for queries:
The real performance benefit is partition pruning — a query filtering on created_at within a specific month only scans that one partition, not the entire multi-year table, and maintenance operations (VACUUM, reindexing) can run per-partition rather than against the full table at once. This is worth reaching for specifically once a table is large enough that even a well-indexed query's performance is being limited by overall table/index size rather than the index's own efficiency — introducing it prematurely, on a table that isn't actually large enough to need it, adds real complexity (every query needs to consider partition boundaries) without a corresponding benefit yet.
Replication lag: the read-scaling tradeoff that isn't free
Read replicas are a standard way to scale read throughput beyond a single primary — but replication is asynchronous by default in most setups, meaning a read from a replica can return stale data, reflecting the database's state slightly before the current moment:
This becomes a real, user-visible bug in a specific common pattern: a user submits a form (write to primary), the application immediately redirects to a page that reads from a replica, and the just-submitted data appears missing because replication hasn't caught up yet. The fix isn't avoiding read replicas — it's routing reads that must reflect a just-completed write back to the primary (or to the same connection/session that performed the write), while genuinely read-heavy, staleness-tolerant traffic (a dashboard, an analytics view) uses replicas freely.
Query caching: what it actually buys you, and its real limitation
Caching is most valuable for expensive, frequently-repeated queries whose result changes relatively infrequently relative to how often it's requested — a homepage "top products" query recomputed on every single page load, when the underlying data only meaningfully changes every few minutes, is a strong caching candidate. The real limitation worth being explicit about: caching introduces staleness by design (the same tradeoff as replication lag, but deliberately controlled via TTL), and caching a query whose result needs to be immediately consistent with the latest write (an account balance, an inventory count at checkout) can introduce real correctness bugs if applied without carefully considering that specific query's actual freshness requirement.

