A team's single relational database is becoming a bottleneck. Walk through how you'd decide, in order, whether to reach for vertical scaling, read replicas with read/write splitting, connection pooling fixes, denormalization/materialized views, or functional federation - and under what conditions would you deliberately avoid read/write splitting even though it's technically available?
answer
- diagnose before scaling: read vs write, index/pool fixes first
- vertical scaling = cheapest, zero app changes
- read/write split only helps read-heavy load
- avoid splitting where staleness is ever unacceptable
- federation costs cross-domain transactions permanently - reserve for org-independent domains
basics
~30 sBefore adding complexity, check the cheap fixes first: is the current server sized right, are connections pooled well, are slow queries fixed. Only after that would you add read replicas, pre-computed views, or split the database by feature - because each of those adds real complexity that isn't worth paying until simpler fixes run out, and some of them (like read/write splitting) should be skipped entirely if the workload is write-heavy or can never tolerate stale reads.
solid answer
~60 sInvestigate before scaling: confirm the bottleneck (read-heavy vs write-heavy, CPU vs IO vs lock contention), check connection pool sizing and query/index quality first since those are often the actual problem and are cheap to fix. If genuinely resource-bound, vertical scaling is the cheapest next step with zero application changes. If read volume specifically is the driver and headroom is exhausted, add read replicas with read/write splitting - but this introduces replication lag and staleness that the application must handle, so it should be deliberately avoided when the workload is write-heavy (replicas won't help), when the system can't tolerate any read staleness anywhere (e.g., financial authorization checks), or when the team lacks the operational maturity to run lag monitoring and routing correctly, since a broken read/write split going unnoticed is worse than staying on one instance. Denormalization/materialized views target specific expensive read patterns cheaply and compose with any of the above. Functional federation is reserved for genuinely independent domains that also want independent team/service ownership, since it costs cross-domain transactions permanently.
go deeper
Not expected to reason across the full decision tree; credit for knowing that adding complexity should be justified by an actual measured problem.
Should recognize that cheaper fixes (indexing, pool sizing, vertical scaling) generally come before replicas or federation.
Should be able to sequence the techniques with reasoning and identify at least one concrete condition under which read/write splitting is the wrong call.
Should reason fluently across the full trade-off space, including organizational/team-ownership considerations for federation and the operational-maturity cost of running read/write splitting correctly, and justify a decision under ambiguity.
## Diagnose before reaching for a technique The right first move when a single database looks like a bottleneck is almost never to reach for the most powerful scaling technique - it's to confirm what's actually bottlenecked, because the answer changes which of the available techniques (vertical scaling, connection pooling fixes, read replicas with read/write splitting, denormalization/materialized views, functional federation) is worth its cost. A database that's slow because of missing indexes, a handful of pathological N+1 query patterns, or a badly sized connection pool will not be meaningfully helped by adding read replicas - it'll just be slow across multiple machines instead of one, while the team has taken on real new operational complexity for nothing. So the first step is **diagnosis**: - is the load read-heavy or write-heavy, - is the constraint CPU, I/O, memory, or lock contention, - and are connections queueing because the pool is undersized or because queries are genuinely slow. Query and index tuning plus correct connection pool sizing routinely buy back an order of magnitude of headroom for a fraction of the engineering cost of any of the structural changes, and skipping this step is one of the most common real-world mistakes teams make - reaching for architecture when the fix was an index. ## Vertical scaling is the next cheapest lever Once genuine resource exhaustion is confirmed, vertical scaling is the next cheapest lever precisely because it requires zero application changes: it's a configuration change (bigger instance) with no new failure modes to reason about, no new consistency model, no new code paths. It should be exhausted, or judged clearly insufficient for the growth trajectory, before anything else - both because it's cheap to try and because it buys time to design the next step properly rather than under pressure. It stops being sufficient when - the workload has outgrown the largest available instance, - the cost curve of continuing to scale up outpaces the value delivered, - or the actual need is availability (a single instance is a single point of failure) rather than raw capacity, which vertical scaling never addresses. ## Read replicas, and when to refuse them If, after that, the specific driver is read volume - the common case, since most OLTP systems are read-heavy - read replicas with read/write splitting are the natural next step, and they're worth their complexity in that specific case: read capacity scales roughly linearly with replica count, and replicas double as failover targets. But this is exactly the point at which a principal-level judgment call matters: read/write splitting should be deliberately avoided, or scoped very narrowly, in a few concrete situations. 1. **A genuinely write-heavy workload.** First, if the workload is genuinely write-heavy rather than read-heavy, replicas don't address the bottleneck at all - the single primary is still the ceiling for writes, and the team would be adding staleness risk and operational surface area for no throughput gain. 2. **Domains where staleness is unacceptable.** Second, if the system has domains where staleness is unacceptable at any scale - authorizing a financial transaction against a balance, checking real-time inventory before confirming a sale - then those specific read paths must stay pinned to the primary regardless of how the rest of the system is split, and if a large fraction of the system's reads fall into that category, the overall benefit of splitting shrinks close to zero while the complexity remains. 3. **Missing operational discipline.** Third, and often underweighted: read/write splitting requires ongoing operational discipline - lag monitoring, routing logic, and a team that understands and tests the failure modes - and a team that adds it without that maturity tends to end up with silent staleness bugs that are far worse for user trust than a database that's simply a bit slower under load. In that situation, staying on vertical scaling longer, or investing in caching at the application layer instead, is often the more defensible call even if it looks less sophisticated. ## Denormalization and materialized views sit orthogonally Denormalization and materialized views sit orthogonally to this decision: they're targeted fixes for specific expensive read patterns (an expensive join, an expensive aggregation) and are worth reaching for whenever such a pattern exists, independent of whether the team has also added replicas - they reduce the cost of the query itself rather than the number of machines answering it, so they compose with any of the other techniques. ## Federation carries the highest one-way cost Functional federation is the technique with the highest one-way cost, since it permanently removes cross-domain joins and transactions, so it should be reserved for cases where the domains being split are also organizationally independent - separate teams that want to own their data model and deployment lifecycle separately, not merely a database that's technically too large. Federating purely for capacity when the domains are still tightly coupled in the business logic (frequent cross-domain transactions, frequent joined reads) tends to produce a worse outcome than just scaling the single database vertically or via replicas, because the team pays the coordination cost of distributed consistency without gaining the organizational benefit that would have justified it. ## The overarching principle The overarching principle across all of these: each technique should be adopted because a specific, diagnosed problem needs exactly what it buys, not because it's the next item on a scaling checklist - complexity taken on prematurely is a cost paid indefinitely, for a problem that may never actually arrive.
- How would you tell, from production metrics alone, whether a database bottleneck is read-heavy or write-heavy before deciding on a scaling approach?Look at the ratio of SELECT to INSERT/UPDATE/DELETE statements in query logs or the database's own statistics views, alongside where CPU/IO time is actually being spent - a read-heavy profile shows most time in SELECT execution and would benefit from read replicas, while a write-heavy profile shows most time in the write path (log flushing, index maintenance) and wouldn't be helped by adding read capacity at all.
- Why might a team choose to invest in application-layer caching (e.g., Redis) instead of read replicas for a read-heavy workload?Caching can serve extremely hot, repeatedly-requested data with far lower latency than even a local read replica and takes load off the database entirely rather than just spreading it, which is often a better fit when the read pattern is highly skewed toward a small set of frequently accessed items. It comes with its own staleness/invalidation complexity, so the choice between caching and replicas depends on the read pattern's shape, not just its volume.
- What's a warning sign that a team adopted functional federation prematurely?Frequent cross-domain queries or transactions being awkwardly stitched together in application code, or a proliferation of synchronous service-to-service calls just to reassemble what used to be one join - both indicate the domains weren't actually independent enough to justify paying the permanent cost of splitting them.
It's like triaging a traffic jam: before building a new highway (federation) or opening extra lanes (replicas), you check whether the real problem is a broken traffic light (a missing index) or too few toll booths open (a bad connection pool) - fixing those first is cheaper and often makes the extra lanes unnecessary.
saying these in an interview costs you the question
- Jumps straight to the most complex technique (federation/sharding) without diagnosing the actual bottleneck
- Treats every scaling technique as strictly additive with no trade-off
- Doesn't mention checking indexes/query performance/pool sizing first
- Can't articulate a concrete scenario where read/write splitting should be avoided
- Assumes federation is purely a capacity decision with no organizational dimension