Scalability Patterns and Techniques Questions
Scaling a system to handle growth in traffic and data: horizontal versus vertical scaling, statelessness, sharding and partitioning strategies, read replicas, and connection pooling. Covers capacity estimation, identifying bottlenecks, and the tradeoffs each scaling axis introduces. The general toolkit for taking a design from thousands to millions of users.
For a system that must support high write throughput, heavy reads, and analytical queries all at once, how would you choose between replication, partitioning (sharding), and CQRS? Discuss read/write separation, the latency and consistency trade-offs, isolating the analytical workload, and the operational complexity each approach adds.
Sample Answer
Direct answer
Start from the workload shape, not the pattern name: replication scales reads and adds availability but does nothing for write throughput; partitioning (sharding) scales both reads and writes by splitting data across nodes at the cost of cross-shard operations; CQRS (Command Query Responsibility Segregation) separates the write model from one or more purpose-built read models, which is the only one of the three that cleanly isolates an analytical workload from live transaction processing. Most systems that need all three properties (high write throughput, heavy reads, analytics) end up combining them rather than picking one: shard for write scale, replicate each shard for read scale and availability, and feed a CQRS-style read model or warehouse from a change stream for analytics.
Structured elaboration
Clarify the inputs first. Before choosing, pin down: peak write rate, read queries per second (QPS, queries per second) and their latency service-level objective (SLO, service-level objective), how stale a read is allowed to be, whether analytical queries are ad-hoc (online analytical processing, OLAP, style: broad scans, aggregations) or a handful of known dashboards, and how much operational tooling the team can realistically own.
| Dimension | Replication | Partitioning (sharding) | CQRS |
|---|---|---|---|
| Read/write separation | Reads go to replicas, writes to the primary (or a designated writer) | Both reads and writes spread across shards by key | Explicit: a write model optimized for correctness, separate read model(s) optimized for query shape |
| Write throughput | Not improved; every write still funnels through the primary's replication path | Scales close to linearly with shard count, if the key distributes evenly | Depends on the underlying write store; CQRS itself does not add write capacity, it isolates what happens to reads after the write |
| Latency / consistency | Synchronous replication raises write latency for stronger consistency; asynchronous replication keeps writes fast but reads on replicas can lag | Strong consistency is straightforward within one shard; a transaction that spans shards is slower and harder to keep atomic | Read models are typically eventually consistent, updated asynchronously after the write commits |
| Analytical isolation | Weak: an analytics query on a replica competes with replication apply and any other read traffic on that node | Weak to none: aggregating across shards requires scatter-gather or a separate pipeline | Strong: a dedicated analytical read model or warehouse can be fed from the write path without touching the online transaction processing (OLTP, online transaction processing) system at all |
| Operational cost | Lowest: failover, replication lag monitoring, backup policy | Highest ongoing cost: rebalancing, hot-shard mitigation, cross-shard routing logic | High setup cost: event or change-data-capture (CDC, change-data-capture) pipeline, idempotent consumers, projection rebuild tooling; lower marginal cost once running |
Decision heuristic.
- If the bottleneck is read volume and the dataset still fits comfortably on one primary's write path, replication with read replicas is the cheapest fix. Route analytics away from the primary onto a replica only if you can tolerate its replication lag as staleness; this is a leaky form of isolation, not a real one.
- If write volume or dataset size outgrows a single writer, shard. Pick a partition key that spreads both storage and write load evenly and keeps the transactions your application actually needs (usually single-entity updates) inside one shard.
- If the real requirement is "let analytics run hard queries without ever affecting the live system," reach for CQRS or a streaming pipeline regardless of whether you also shard. This is a workload-isolation problem, not a scaling-throughput problem, and only an explicit read/write split solves it.
- These are not mutually exclusive. A sharded OLTP layer, each shard replicated for local read scale and failover, streaming change events into a CQRS read model or a warehouse, is a common end state for a system that genuinely needs all three properties at once. The pattern briefly touches on how leader election and failover are handled per shard: each shard's own replica set runs a consensus protocol so its nodes agree on which one is the current writable primary (leader election), and if that primary becomes unreachable, the remaining replicas in that shard's set automatically elect a new primary so writes have a single owner again within seconds (failover); the deeper mechanics of that consensus protocol belong to high-availability design and are not repeated here beyond noting that each shard's own replica set needs it.
flowchart LR
C[Client writes] --> P1[Shard 1 primary]
C --> P2[Shard 2 primary]
P1 --> R1[Shard 1 replica]
P2 --> R2[Shard 2 replica]
P1 --> CDC[Change stream]
P2 --> CDC
CDC --> RM[CQRS read model / warehouse]
R1 --> Q[Live read queries]
R2 --> Q
RM --> A[Analytical queries]
Worked example
A payments service currently runs on one primary database and is starting to miss its latency target. Measured: 4,500 writes/sec at peak, 60,000 read QPS at peak, and a finance team that wants ad-hoc OLAP queries over the last 90 days of transactions without ever slowing down checkout.
Assume, as planning inputs rather than measured facts: a single well-provisioned primary sustains around 3,000 sustained writes/sec before latency degrades, and a single read replica comfortably serves about 10,000 QPS.
- Writes: 4,500 / 3,000 = 1.5, so one primary is already past its comfortable ceiling. Add a 40% growth buffer: 4,500 × 1.4 = 6,300 target writes/sec. 6,300 / 3,000 = 2.1, so plan for 3 shards to leave real headroom rather than sitting at the edge of 2.
- Reads: 60,000 / 10,000 = 6 replicas needed at peak with zero slack. Add the same 40% buffer: 60,000 × 1.4 = 84,000; 84,000 / 10,000 = 8.4, so plan for 9 replicas total, distributed across the 3 shards (3 per shard keeps it even).
- Analytics: rather than pointing the finance team's OLAP queries at any of those 9 replicas (which would eat into the buffer just computed), stream committed writes via change-data-capture into a separate read-optimized store. That store's capacity is sized independently and never competes with the 6,300 writes/sec or 84,000 QPS budgeted above.
This is the concrete version of the heuristic above: sharding solved the write ceiling, replication solved the read ceiling, and CQRS-style stream-out solved the isolation requirement, each sized from its own number rather than assumed to fall out of the others.
Trade-offs & pitfalls
- Treating CQRS as a performance pattern rather than an isolation pattern is a common wrong turn: it does not make the write path faster by itself, and adopting it just to "scale" without an actual analytical-isolation or read-shape need adds eventual-consistency complexity for no return.
- Picking a shard key optimized for even distribution but ignored for query patterns creates a system that scales storage and writes fine while every common query becomes a cross-shard scatter-gather.
- Reading from an async replica as a substitute for real analytical isolation looks cheap until the replica's replication lag becomes contended with the analytics load itself, and both the online workload and the analytics workload get worse.
- Under-budgeting the operational tooling is the most common failure in practice: resharding runbooks, projection-repair tooling, and replication-lag alerting are not optional extras, they are what keeps the "solved" architecture solved six months later.
- A senior answer names the concrete headroom numbers and states them as assumptions, rather than presenting a diagram with no sizing behind it.
Explain the basic queueing-theory concepts behind capacity planning: arrival rate, service rate, utilization, and the M/M/1 queue. Walk through a simple numeric example showing how a small increase in utilization can produce a disproportionate increase in average latency.
Sample Answer
Direct answer
Queueing theory formalizes something every engineer has felt intuitively: as a system gets busier, wait time does not grow in proportion to how busy it is, it grows much faster the closer utilization gets to 100%. The core quantities are the arrival rate (how fast work shows up), the service rate (how fast a server can finish work), and utilization (the ratio of the two). The M/M/1 queue is the simplest model that makes this precise, and its formulas show exactly why "we're at 80% capacity, that's fine" and "we're at 95% capacity, that's fine" are very different claims.
Core concepts
- Arrival rate (λ): the average number of requests arriving per second.
- Service rate (μ): the average number of requests a single server can complete per second, equivalently 1 divided by the mean time to handle one request.
- Utilization (ρ): the fraction of capacity in use, ρ=λ/μ for a single server. The system is only stable (queue length stays bounded over time) if ρ<1; at or above ρ=1, work arrives faster than it can be finished and the queue grows without bound.
- M/M/1 queue: a single server where arrivals follow a Poisson process (arrivals are independent and memoryless) and service times are exponentially distributed (also memoryless). "M" stands for "Markovian" (memoryless) on both the arrival and service side; "1" means a single server. This memorylessness is what makes the formulas below solvable in closed form, which is why M/M/1 is the standard first model even though real service-time distributions are rarely exactly exponential.
M/M/1 formulas (steady state)
ρ=μλAverage number of requests in the system (waiting plus being served):
L=1−ρρAverage time a request spends in the system (waiting plus service):
W=μ−λ1The denominator μ−λ is the key structural fact: as λ approaches μ, that denominator approaches zero and W diverges, which is the mathematical reason latency blows up near saturation rather than growing smoothly.
Worked numeric example
Take a single server with μ=100 requests/second.
λAλBλC=50=80=90ρAρBρC=50/100=0.5=80/100=0.8=90/100=0.9WAWBWC=1/(100−50)=0.02s=20ms=1/(100−80)=0.05s=50ms=1/(100−90)=0.10s=100msGoing from 50% to 80% utilization (a 1.6x increase in arrival rate) increases average latency from 20ms to 50ms, a 2.5x increase. Going from 80% to 90% utilization (only a 1.125x increase in arrival rate) still nearly doubles latency again, from 50ms to 100ms, a 2x increase on its own, and 5x relative to the 50% case. A modest-looking rise in utilization near the high end produces a disproportionate jump in latency; the same modest rise near the low end (say, 20% to 30%) would barely move the needle.
How this informs capacity planning
- Do not operate near ρ≈1. Pick a target maximum utilization based on the latency the service level objective (SLO) actually tolerates, commonly somewhere in the 50-70% range for workloads with real variability, leaving room for the nonlinear region above that.
- To hit a target average latency W, the required spare capacity is μ−λ≥1/W; this converts a latency target directly into a minimum service-rate requirement.
- Real service-time distributions are usually not exactly exponential, and real arrivals are usually not exactly Poisson (traffic bursts, batches, and diurnal patterns all violate the memoryless assumption), so treat the M/M/1 formulas as a directionally correct first estimate, then validate with a load test against the real distribution rather than trusting the closed-form number exactly.
- Multiple servers (an M/M/c queue) change the curve's shape but not the underlying lesson: latency still grows nonlinearly as aggregate utilization approaches saturation, it just takes more concurrent servers to keep the whole system away from that region.
Trade-offs and pitfalls
The most common mistake is reading "80% utilization" as "20% headroom, therefore safe," without accounting for how close that already is to the steep part of the latency curve; on this M/M/1 model, the jump from 80% to 90% utilization alone nearly doubles latency. A second pitfall is capacity planning entirely off average utilization while ignoring burstiness: even if average ρ looks comfortable, a service whose real arrivals come in bursts (not the smooth Poisson process the model assumes) can spend meaningful time at effective utilization far above the average, and M/M/1's steady-state numbers say nothing about that transient behavior.
A single incoming request fans out to 50 parallel downstream calls. Each downstream call has a P95 latency of about 100ms, and the downstream system caps out at 1,000 RPS. If your service needs to handle 200 incoming RPS, is the downstream a bottleneck? Show your calculations, then propose architectural changes such as batching, caching, or queueing to reduce the downstream load, and explain the trade-offs.
Sample Answer
Direct answer
Yes, the downstream system is a clear bottleneck, by roughly 10x. At 200 incoming requests per second (RPS) with a fan-out of 50 calls each, the service generates 10,000 downstream calls per second, but the downstream system only accepts 1,000 RPS. Fixing this requires either reducing the number of downstream calls per incoming request (caching, batching, coalescing) or decoupling the response from the downstream work (async processing), not simply adding more capacity on your own side.
The math
Throughput check. Fan-out multiplies the incoming rate directly:
200 RPS×50 calls/request=10,000 downstream calls/s required
required:capacity=10,000:1,000=10:1
Required load is 10 times the downstream cap. That alone confirms a bottleneck: no amount of retrying or connection pooling on your side changes a system that is already saturated at its own ceiling.
Concurrency cross-check (Little's Law). It helps to sanity check the same conclusion from a different angle: how many downstream calls must be in flight simultaneously, not just per second. Little's Law relates throughput X and average time-in-system R to the average number of concurrent items N:
N=X×R
At the P95 latency (the response time that 95% of requests come in faster than) of about 100 ms (0.1 s), the concurrency the fan-out actually demands is:
Nrequired=10,000 RPS×0.1s=1,000 concurrent downstream calls
Whereas the downstream system, operating at its own stated cap with that same latency, is only structured to sustain:
Ncapacity=1,000 RPS×0.1s=100 concurrent downstream calls
Both views agree: you need about 10x the concurrency the downstream system is built to hold. This cross-check matters in an interview because it shows the bottleneck isn't just a rate-limit number on a dashboard, it is a real resource constraint (connections, threads, or queue slots) that a naive retry loop would make worse, not better, by piling on more concurrent attempts against an already-saturated system.
Mitigation options and what each one requires
Different mitigations close the 10x gap in different ways. It is worth deriving the minimum each one needs before choosing, rather than picking whichever sounds most familiar:
| Technique | How it reduces load | Minimum needed to close the gap | Key trade-off |
|---|---|---|---|
| Caching | Cache hits never reach downstream | Hit rate h such that (1−h)×10,000≤1,000⇒h≥0.9 | Staleness, invalidation complexity, only works for cacheable/idempotent reads |
| Batching or aggregation | Combines many logical calls into one downstream request | Batch factor b such that b10,000≤1,000⇒b≥10 | Adds wait-to-accumulate latency; downstream must expose a batch API (application programming interface, an endpoint accepting many items in one call) |
| Request coalescing (in-flight dedup) | Collapses concurrent identical requests into one call | No guaranteed factor; only helps if requests genuinely repeat the same key in a short window | Needs a singleflight-style layer (lets only the first caller for a key actually fetch it, while others waiting on that key reuse its result); zero benefit if requests are for distinct keys |
| Async queue with a worker pool | Decouples the caller's response from when downstream work completes | Does not reduce total required calls; still needs a sustained drain rate at or below 1,000 RPS or the backlog grows without bound over time | Higher end-to-end latency, needs a durable queue, changes the service-level agreement (SLA) from synchronous to eventual |
| Admission control / graceful degradation | Sheds or simplifies requests before they generate 50 downstream calls each | Reduces load by exactly whatever fraction is shed or simplified | Visible feature loss to some fraction of users |
The queueing row is the one candidates most often get wrong: a queue is a shock absorber for bursts, not a source of extra downstream capacity. If the arrival rate into the queue is sustained above the rate downstream can drain (1,000 RPS here), the backlog and its latency grow without bound over time; it only helps if the 10,000 RPS demand is a transient spike layered on top of a steady-state average that downstream can actually absorb.
Worked example: combining two mitigations
A single technique often has to hit an aggressive threshold alone (90% cache hit rate, or a batch factor of 10). Combining two moderate mitigations is usually more realistic. Assume, as an illustrative starting point (not a measured figure), a cache hit rate of 80% and a batch factor of 3 for the remaining traffic:
uncached calls=(1−0.8)×10,000=2,000 RPS
after batching by 3=32,000≈666.7 RPS
666.7 RPS is below the 1,000 RPS cap, with about 33% headroom. This is a useful pattern to point out explicitly: two moderate, individually achievable improvements (an 80% hit rate is realistic for many read-heavy access patterns; batching 3 calls together is a small API change) can beat needing one extreme, harder-to-sustain number from a single technique.
Trade-offs and pitfalls
- Treating "add a queue" as the fix without checking the sustained drain rate. A queue converts an overload into a growing backlog; it does not remove the overload.
- Choosing a hit rate or batch factor that meets the cap with zero margin. Production traffic is bursty and cache hit rates drift, so design to a threshold with headroom, not the exact breakeven point.
- Applying caching or batching uniformly across all 50 downstream calls when only some of them are actually cacheable or batchable in practice; the real achievable reduction is bounded by whichever calls are eligible.
- Retrying failed downstream calls without first fixing the 10x overload. Retries against an already-saturated system amplify load and can turn a slow degradation into a full outage.
- Skipping the concurrency cross-check. Throughput alone can hide a resource-exhaustion story (thread pools, connection limits) that shows up as timeouts before the RPS counter ever looks alarming.
As the lead backend engineer, you must choose between three short-term options to cut P95 latency by 30% within a fixed budget: a vertical database upgrade, adding read replicas, or introducing a caching layer. What metrics and profiling steps would you use to evaluate each option? Describe your experimental rollout (A/B or canary), rollback plan, and the long-term maintainability implications of each choice.
Sample Answer
Direct answer
Under a fixed budget and a 30% 95th-percentile (P95, the latency value below which 95% of requests complete) target, I would profile first to find which resource is actually saturated, then pick the option whose lever matches that bottleneck rather than defaulting to the biggest hammer: a caching layer for read-heavy, repeatable queries; read replicas when the primary is saturated by read volume specifically; and a vertical upgrade only when the workload is genuinely resource-bound with no obvious inefficiency to fix first. Whichever option is chosen, I would roll it out behind a canary with an explicit rollback trigger, because a change aimed at cutting P95 by 30% is exactly the kind of change that can regress tail latency if the assumption behind it is wrong.
Structured elaboration
Metrics and profiling per option
- Vertical database upgrade: check CPU utilization, I/O wait, disk throughput, active connections, and slow-query logs (e.g.,
pg_stat_statements) to confirm the primary is resource-saturated rather than running inefficient queries that a bigger machine won't fix. - Read replicas: check the read-to-write ratio, replication lag under current load, and which endpoints are read-dominated. This option only helps if reads, not writes, are what's driving primary saturation and P95.
- Caching layer: check which endpoints repeat the same query for many requests (cacheability), current origin queries per second (QPS, queries per second) on those endpoints, and how tolerant the data is of a short time-to-live (TTL, the duration a cached value is considered valid before it must be refreshed).
Decision framework
- If profiling shows CPU or I/O saturation on otherwise well-optimized queries: vertical upgrade is the fastest lever, but it has a hard ceiling and doesn't reduce load, it just buys headroom.
- If reads dominate write volume and the primary's read load is the driver: read replicas are the natural fit, since they scale read capacity horizontally instead of scaling one machine up.
- If a meaningful share of requests are repeatable (same query, same or slowly-changing result): caching usually gives the largest P95 improvement per dollar, because it removes load from the database entirely rather than adding capacity to serve it.
Rollout: canary and rollback
Route a small percentage of traffic to the changed path first (a canary), and compare P95, error rate, and (for replicas) replication lag against the unmodified baseline before widening. The specific traffic-shifting mechanics, how you carve off exactly 5% of requests at a load balancer, are the same progressive-delivery machinery used for any staged rollout; the part specific to this decision is what you measure and what threshold triggers a rollback. Define the rollback trigger before starting: for example, P95 regressing beyond baseline, or error rate rising above an agreed service-level objective (SLO, an internal target for how the system should perform). Rollback should be a single reversible action: a feature flag to bypass the cache, a connection-pool switch back to the primary-only read path, or a DNS/config revert to the pre-upgrade instance, not a multi-step manual procedure under pressure.
Worked example
Assume, as a planning input rather than a measured fact, a current P95 of 900 ms. A 30% cut targets:
900×(1−0.30)=630 ms
Now evaluate the caching option concretely. Assume origin traffic on the targeted read endpoints peaks at 5,000 QPS, and the caching layer reaches an 80% hit ratio there (an illustrative target, not a guarantee: it must be validated against real access patterns before committing budget to it). Origin load after caching:
5000×(1−0.80)=1000 QPS reaching the database
That is an 80% reduction in database load on those endpoints, which is the mechanism that produces the P95 win: most of the remaining latency on a cache hit is the cache round-trip, not the database.
Now the read-replica option on the same traffic. Assume 75% of primary traffic is reads and 25% is writes. Moving all cacheable reads to replicas removes up to:
5000×0.75=3750 QPS off the primary
leaving the primary serving roughly 1,250 QPS of writes plus any reads that must stay on the primary for freshness reasons. Both options move a comparable share of load off the critical path here; the real decision hinges on which one the profiling data actually supports, and caching wins on cost when hit rate is achievable, while replicas win when the workload isn't cacheable but is still read-dominated.
Trade-offs & pitfalls
- A vertical upgrade is the easiest to roll back (revert to the old instance size) but has a hard scaling ceiling and doesn't reduce load on the system, it just delays the next capacity conversation.
- Read replicas introduce replication lag: a client that writes and immediately reads its own write from a replica can see stale data unless read-your-writes routing is handled explicitly. That routing logic is new code that has to be maintained.
- A caching layer is usually the biggest win per dollar when hit rate is high, but it adds an invalidation problem: incorrect TTLs or missed invalidation on writes create a second source of truth that can silently diverge from the database.
- Picking the option that matches the profiling data, not the one that is fastest to implement, is what separates a durable fix from a fix that gets undone in the next incident review. All three options are non-exclusive long-term: a mature system typically ends up using all three, sequenced by where the bottleneck actually was.
During a large traffic spike, your cloud autoscaler hit a quota limit and the service breached its SLOs. As the incident commander, what immediate mitigations would you take (manual scaling, throttling), how would you communicate with your cloud provider and stakeholders, and what medium-term fixes (quota monitoring, predictive scaling) would you put in place? How would you update runbooks and alerts to prevent a repeat?
Sample Answer
Direct answer
As incident commander, the first move is to stabilize without waiting on the cloud provider: manually add capacity wherever quota headroom still exists, and shed or queue the traffic you can't serve so the SLO (service level objective, the target you've committed to for availability or latency) breach doesn't get worse. In parallel, escalate to the provider and give stakeholders a clear, honest status. The incident is not resolved by root-causing; it's resolved by capping the damage now and then building the quota monitoring, predictive scaling, and updated runbooks that make sure a known quota ceiling never again gets discovered mid-spike.
Structured elaboration
Immediate mitigations (first 0-30 minutes)
- Manually provision capacity in a region, account, or instance family that still has quota headroom, preferring larger instance types over more instances if the limiting quota is instance count rather than vCPU.
- Throttle non-critical traffic at the edge: return 429 (rate limited) or 503 (unavailable) with a
Retry-Afterheader for low-priority requests (bulk exports, background jobs), preserving capacity for the traffic that actually matters. - Apply admission control instead of dropping traffic uniformly: if only a fraction of demand can be served, let requests in up to capacity (first-come or a fair queue with an ETA) rather than randomly failing a percentage of everyone's requests. This is what separates "everyone gets a slow, unfair experience" from "most users are unaffected and the rest see a clear queue."
- If a queue sits in front of a write-heavy path, let it absorb the burst rather than pushing writes straight through to the database at spike rate.
Communication
- Cloud provider: open a severity-1 support case immediately with the quota metric, current usage, and requested limit; this is not a channel to wait on for immediate relief, so treat it as a parallel track, not the mitigation itself.
- Internal: post a structured status (what's affected, current mitigation, next update time) to a single incident channel on a fixed cadence, not ad hoc.
- Stakeholders and, if customer-facing, a status page: state the actual impact honestly and give a concrete next-update time rather than a resolution promise you can't back.
Medium-term fixes
The underlying gap this incident exposes is that scaling was purely reactive with no advance knowledge of demand. The fix is to design ahead for the traffic patterns you can actually anticipate, which fall into a few recognizable shapes:
- A short, extreme, scheduled burst (for example, a flash sale expected to run around 100,000 requests/second for roughly 10 minutes): because the timing is known in advance, pre-scale capacity ahead of the event rather than relying on the autoscaler to react to it live, put a queue in front of the write path to smooth the burst instead of hitting the database at peak rate, and stage the pre-scale as a monitored, reversible step (canary the added capacity, keep an automatic rollback if error rates rise) rather than a one-way commit.
- A sharp, less-precisely-timed spike (for example, a marketing email that drives a 10x jump within about 30 minutes): the exact start time is fuzzier than a scheduled sale, so the plan needs a short-term component (aggressive reactive scaling plus throttling as a backstop for the first few minutes) and a separate medium-term component (pre-notifying the team before large sends go out, and pre-warming capacity ahead of known send windows so the reactive layer isn't starting from a cold baseline).
- An extreme, unscheduled-feeling spike far above baseline (for example, roughly 100x normal traffic during a flash sale): at that magnitude, no autoscaler reacts fast enough on its own, so the design has to include pre-warmed capacity sized to the expected peak and admission control that treats all waiting users fairly (a first-come or randomized queue with a visible position or ETA) instead of an uncontrolled scramble where whoever's request happens to land first wins and everyone else gets errors.
Across all three shapes, the common fix is the same: stop treating "quota is sufficient" as an assumption and start treating it as a monitored, tested constraint, with permanent quota increases requested ahead of realistic peak-plus-buffer, not discovered during an incident.
Runbook and alert updates
- Add a dedicated quota-exhaustion playbook: exact commands for manual provisioning in each region/account, the throttling levers available, the provider escalation contact path, and a decision matrix for when to degrade vs. scale vs. fail over.
- Add quota-utilization alerts at conservative thresholds (for example, flagged at 60/75/90% of the current limit as an illustrative staging, not a universal standard) so the team requests an increase before hitting the wall, not after.
- Add an alert specifically for "autoscaler issued a scale-out that did not result in additional serving capacity within an expected window," which is a different failure than "no scale-out was attempted" and needs its own signal.
- Schedule runbook drills (tabletop or live) that specifically simulate a quota ceiling being hit, since a runbook that has never been rehearsed against this exact failure mode is unlikely to be followed correctly under real pressure.
Worked example
Assume, as stated planning inputs rather than measured facts: steady-state traffic of 5,000 requests/second (RPS) served by 50 instances, giving a baseline capacity ratio of
50 instances5,000 RPS=100 instanceRPSA flash-sale spike hits 100,000 RPS for about 10 minutes, which is a total request volume of
100,000 sreq×600s=60,000,000 requestsAt 100 RPS/instance, serving the full spike needs 100,000 / 100 = 1,000 instances. If the account's instance quota is capped at 200, the achievable capacity at that ceiling is
200 instances×100 instanceRPS=20,000 RPSwhich is only a fifth of demand, so the fraction of traffic that has to be shed or queued once the quota ceiling is hit is
100,000100,000−20,000=0.80an 80% shortfall. That number is the case for admission control over random shedding: dropping 80% of requests indiscriminately produces a bad experience for everyone, while admitting exactly the 20,000 RPS the fleet can serve and fairly queuing the rest (with a visible wait, not a silent failure) turns the same shortfall into a bounded, predictable degradation instead of a chaotic one. It's also the case for the medium-term fix: a permanent quota request sized to at least the 1,000-instance peak, not the 200-instance historical average, is what prevents this specific ceiling from being hit again.
Trade-offs & pitfalls
- An emergency quota increase request is not instant relief; the mitigation plan cannot depend on the provider responding within the incident window, only the medium-term fix (a pre-approved higher baseline quota) removes that dependency.
- Manually bringing up capacity in a different region can violate data-residency or added-latency assumptions the service normally relies on; that trade-off needs to be made consciously during the incident, not discovered afterward.
- Throttling everyone equally punishes both low- and high-value traffic the same way; admission control that's blind to request importance is only marginally better than dropping randomly.
- Alerting only on "the autoscaler failed to scale" misses the earlier, more useful signal: quota utilization climbing toward its ceiling before a scale-out attempt ever fails.
Unlock Full Question Bank
Get access to all Scalability Patterns and Techniques interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.