Cloud and Managed Database Services Questions
Running databases as managed cloud services rather than self-hosting them: choosing and provisioning managed relational and NoSQL offerings (for example Amazon RDS/Aurora/DynamoDB, Azure SQL Database/Cosmos DB, Google Cloud SQL/Spanner), sizing instances and storage, and designing an architecture (read replicas, connection proxies and pooling for managed or serverless compute) to meet a latency or availability target. It covers comparing provisioned versus serverless/autoscaling pricing and operational models, forecasting capacity and cost, and migrating a database onto or between managed deployments: moving a self-hosted database to a managed service, switching a workload from provisioned to serverless (or back), changing a single-AZ deployment to multi-AZ, and the questions to ask before adopting a new managed vendor. The core question is the split of responsibility and cost between the cloud provider and the engineering team: what the provider takes on (patching, infrastructure-level HA, automated backups) versus what remains the team's job (configuration, capacity and cost tuning, security posture like encryption and key rotation). It does not cover backup and restore mechanics, RPO/RTO planning, replication or sharding internals, or SQL-level query diagnostics.
Describe how you would deploy a managed relational database for a production web application. Cover service selection (RDS/Aurora/Cloud SQL/Managed MySQL), sizing (instance class, storage type), read scaling, encryption, maintenance windows, and routine maintenance practices.
Sample Answer
Deploying a managed relational database for production is five deliberate decisions, not one "create instance" click: which service, what size, whether you need read scaling yet, encryption configured before first write, and a maintenance window you chose rather than accepted by default.
Service selection
- Amazon RDS: the broadest engine choice (MySQL, PostgreSQL, MariaDB, SQL Server, Oracle, Db2), engine-native replication, and the most flexible instance sizing including small burstable classes. The default choice for most web applications unless you have a specific reason to want more.
- Amazon Aurora: MySQL- and PostgreSQL-compatible, with storage-layer replication instead of engine-level replication, which gives materially faster failover (automatically switching to a healthy standby when the primary fails) and a higher write-throughput (the volume of write operations the database can sustain per second) ceiling on the largest instance classes. Worth the higher baseline cost when you expect to outgrow a single well-tuned RDS instance, or when failover speed matters more than for a typical web app.
- Cloud SQL: Google Cloud's equivalent to RDS for MySQL, PostgreSQL, and SQL Server, same category of decision as RDS on AWS.
For a typical production web application starting out, RDS or Cloud SQL on the engine your team already knows is the right default; reach for Aurora specifically when you can point to a concrete scaling reason, not by default.
Sizing: instance class and storage type
Pick the instance class from measured or realistically estimated CPU and memory need with headroom, not a round number picked for comfort. Storage type matters as much as instance class:
- gp3 (general purpose SSD): the right default for most online transaction processing (OLTP) workloads. It decouples provisioned IOPS (input/output operations per second: the count of individual read or write operations the volume can handle each second, distinct from throughput's raw bytes-per-second measure) and throughput from allocated storage size, so you are not forced to over-allocate storage just to get more IOPS.
- io2 / io2 Block Express: for latency-sensitive, very high-IOPS workloads where gp3's ceiling is not enough; costs more per provisioned IOPS.
- Enable storage autoscaling so unplanned data growth raises allocated storage automatically instead of the instance running out of space and rejecting writes.
Read scaling
Add a read replica only when there is a specific, measured reason: reporting or analytics queries competing with transactional traffic for CPU and I/O, or read throughput that a single writer genuinely cannot serve even with caching in front of it. A read replica is a full additional instance (real cost, real operational surface via replication lag, the delay before a write on the primary becomes visible on the replica), not a free performance knob, so route only the traffic that needs it, typically through RDS Proxy or explicit read/write splitting in the application, rather than defaulting every read query to a replica.
Encryption
Enable storage encryption at creation time: RDS cannot add encryption to an existing unencrypted instance in place, only by snapshotting it, copying the snapshot with encryption enabled, and restoring from that copy, so getting this right on day one avoids a disruptive migration later. The AWS-managed key (which AWS automatically rotates every year) is sufficient for most applications; use a customer-managed KMS (Key Management Service) key only when you need explicit control over key policy or rotation cadence, for example a compliance requirement to control who can disable access to the key. Separately, enforce TLS (transport layer security) on connections at the engine level, rds.force_ssl for PostgreSQL or require_secure_transport for MySQL, since encryption at rest alone does not protect credentials and query data in flight between the application and the database.
Maintenance windows and routine maintenance
If you do not set a maintenance window explicitly, RDS assigns a random 30-minute window in your region, which may land during business hours. Choose one deliberately, during genuinely low-traffic hours for your actual user base, and prefer applying non-urgent changes during that window rather than immediately, so patches land predictably instead of mid-peak-traffic. Keep automatic minor-version upgrades on for security patches, but review major version upgrades manually since those can carry breaking changes to the engine's behavior. As routine practice: review the instance's "pending maintenance actions" on a regular cadence rather than being surprised by them, audit the parameter group for drift from intended settings after any manual tuning, and revisit instance rightsizing on a schedule (for example quarterly) instead of only reacting once performance has already degraded.
Worked example: a concrete creation command
aws rds create-db-instance \
--db-instance-identifier prod-webapp-db \
--engine postgres --engine-version 16.4 \
--db-instance-class db.r6g.xlarge \
--allocated-storage 200 --storage-type gp3 --iops 6000 --storage-throughput 250 \
--max-allocated-storage 500 \
--storage-encrypted --kms-key-id alias/prod-db-key \
--preferred-maintenance-window "sun:05:00-sun:06:00" \
--auto-minor-version-upgrade
This encodes the sizing decision (an r6g.xlarge, memory-optimized Graviton instance class, a common default for a moderate production web app), gp3 storage with explicit IOPS and throughput rather than whatever the default happens to be, storage autoscaling up to 500 GiB so growth does not hit a hard wall, encryption specified at creation with a named key, a chosen (not default) maintenance window, and automatic minor-version patching. Multi-AZ and backup retention are deliberately left off this example: they are availability and disaster-recovery decisions with their own trade-offs, not part of the base provisioning choice this question is about.
Trade-offs and pitfalls
- Defaulting to the largest instance class "to be safe" wastes money every hour it runs oversized; defaulting to the smallest "to save money" invites a production incident the first time traffic grows. Size from data, and revisit it, rather than picking once and forgetting.
- Adding a read replica before you have a measured reason adds real operational surface (monitoring replica lag, routing logic) for no benefit, and it is easy to add one reflexively because "that's what scaling a database looks like."
- Forgetting to set storage encryption before the first write is the single most expensive mistake in this list to fix later, since fixing it means a snapshot-copy-restore cycle on a live database rather than a config change.
- Accepting the default maintenance window is a common, easily avoided source of an unplanned mid-day blip; it costs nothing to set deliberately at creation time.
Design an AWS RDS architecture for a read-heavy, customer-facing application that needs high availability across availability zones and horizontal read scaling. Describe your primary and read-replica placement, automated failover behavior, the connection-routing strategy for read/write separation, how you would handle replica lag, and how you would test failover.
Sample Answer
This architecture needs two structurally different mechanisms working together, not one: a Multi-AZ (availability zone) standby for automated failover, and a separate fleet of read replicas for horizontal read scaling. Conflating them is the most common design mistake here, since a Multi-AZ standby never serves read traffic at all; that is what read replicas are for.
flowchart TB
App[Application tier] --> Router[Read/write router, application- or proxy-side]
Router -->|writes| PrimaryEP[Primary instance endpoint, DNS CNAME]
Router -->|reads, chosen by app/proxy - RDS gives no built-in shared reader endpoint| Replica1[(Read replica, AZ-b)]
Router -->|reads, chosen by app/proxy - RDS gives no built-in shared reader endpoint| Replica2[(Read replica, AZ-c)]
PrimaryEP --> Primary[(Primary, AZ-a)]
Primary -.synchronous, HA only.-> Standby[(Multi-AZ standby, AZ-b)]
Primary -.asynchronous.-> Replica1
Primary -.asynchronous.-> Replica2
Primary and standby placement
The primary runs in one AZ; the Multi-AZ standby runs in a different AZ within the same region, kept current by synchronous replication (the primary does not acknowledge a write until the standby has it too). This standby exists purely for failover, it serves no read traffic under normal operation, which is the key fact that separates it from a read replica. The read replicas are placed across additional AZs (they can share an AZ with the standby; there is no requirement to keep them disjoint), sized in count to the actual read throughput need, addressed below.
Automated failover behavior
On primary failure or an AZ-level impairment, RDS promotes the standby and repoints the DB instance's single existing endpoint (its DNS CNAME, canonical name record) at the newly promoted instance rather than requiring any connection-string change. A classic RDS DB instance has exactly one endpoint total, not a separately named "writer endpoint" the way an Aurora cluster does, so failover here is a DNS re-point of that one address, not a switch between two differently-named endpoints. AWS states this happens "as quickly as 60 seconds" for a single-standby Multi-AZ deployment, with most failovers completing within one to two minutes. The part this leaves in the application's hands: a client with an existing connection to the now-dead primary, and a driver or pool that caches the resolved IP rather than re-resolving DNS promptly, can keep retrying a dead address well past the point RDS considers the failover complete. Short connection timeouts and prompt DNS re-resolution in the connection pool (the application's cache of reusable open database connections) are what actually determine how much of that 60-to-120-second window the application experiences as a real outage.
Connection-routing strategy for read/write separation
Two implementation options, and a fact that has to be stated up front: unlike Aurora, plain RDS does not give you a single, AWS-managed, load-balanced endpoint spanning multiple read replicas. Each RDS read replica is provisioned with, and reachable only through, its own individual endpoint, exactly like any other standalone RDS DB instance. Getting a single logical "read" address, if the application wants one, means building it, either in application code or via a proxy in front of the replica fleet; AWS does not hand it to you for free the way it does for Aurora's cluster reader endpoint.
- App-layer routing: hold the primary's own endpoint for writes, and the individual read replica endpoints for reads, and route each query explicitly in application code (round-robin, random choice, or a fixed per-service assignment across the known replica endpoints). Simplest to reason about, and its failure mode (a write accidentally sent to a read replica) is a loud, immediate error, since replicas reject writes, not a silent correctness bug.
- Proxy-layer routing: a pooler or router (for example a network load balancer or HAProxy in front of the replica fleet, or ProxySQL) that inspects statement type and routes automatically, which also gives the application a single logical address for reads instead of managing the replica endpoint list itself, at the cost of another moving part to operate.
For most teams, start with explicit app-layer routing; it is simpler, its failure mode is safe (a hard error, not silent data drift), and a routing proxy is worth adding later specifically once the number of call sites makes manual discipline unreliable, not as a default from day one.
Handling replica lag
Route only traffic that tolerates some staleness to the reader endpoint, browsing and search-style reads. Any read that must reflect the user's own just-completed write (an order confirmation immediately after checkout is the classic case) should either read from the writer directly for that specific call, or carry a short-lived "read your own write" hint that pins that one request to the writer rather than trusting the load-balanced reader endpoint. Monitor the replica lag metric per replica and treat a replica whose lag exceeds a defined threshold as degraded for routing purposes, since a healthy-but-lagged replica can otherwise keep receiving its normal share of read traffic through the load-balanced endpoint even while serving noticeably stale data.
Testing failover, not just designing for it
Design intent is not validation. Rehearse the actual failover, on a non-production copy at minimum, ideally occasionally against production during a planned low-traffic window once confidence is established:
aws rds reboot-db-instance --db-instance-identifier prod-webapp-db --force-failover
Measure the application's own observed error rate and recovery time during that test, not just the RDS-reported failover duration; those two numbers are not the same thing. A failover that completes on RDS's side in under a minute can still produce several minutes of application-visible errors if the connection pool does not detect the broken connection and re-resolve DNS promptly, which is exactly the gap the "automated failover behavior" section above flags as the application's responsibility, and the only way to know whether that gap is actually closed is to force a real failover and watch what the application does.
Worked example: sizing the read-replica count
peak_read_qps = 40_000
per_replica_sustained_qps = 8_000 # measured comfortable sustained capacity per replica
replicas_for_load = -(-peak_read_qps // per_replica_sustained_qps) # ceiling division
replicas_with_n_plus_1 = replicas_for_load + 1 # tolerate one replica down for maintenance or failure
print(f"Replicas needed to cover peak load: {replicas_for_load}")
print(f"Replicas with N+1 redundancy: {replicas_with_n_plus_1}")
print(f"Per-replica load with one of {replicas_with_n_plus_1} down: "
f"{peak_read_qps/(replicas_with_n_plus_1-1):,.0f} QPS")
Replicas needed to cover peak load: 5
Replicas with N+1 redundancy: 6
Per-replica load with one of 6 down: 8,000 QPS
Five replicas exactly cover the stated peak with zero slack, so losing even one to maintenance or a fault pushes the remaining four over their sustained capacity; provisioning a sixth (N+1) means the fleet still exactly meets sustained capacity, not exceeds it, with one replica out, which is the actual bar "can survive a single replica failure without degrading" sets.
Trade-offs and pitfalls
- Treating the Multi-AZ standby as if it also offloads read traffic is a common misunderstanding that leads teams to under-provision real read replicas; the standby's only job is failover.
- Aurora specifically allows a read replica to also serve as a Multi-AZ failover target if placed in a high-priority promotion tier (tiers run 0 to 15, and tier 0, the lowest number, is the highest priority for promotion, not tier 15), which blurs this line further in Aurora's favor (one fewer idle resource), and Aurora also provides a single load-balanced cluster reader endpoint spanning every Aurora Replica automatically. Neither behavior is something plain RDS read replicas provide: plain RDS replicas are each their own standalone endpoint, with no automatic promotion-to-failover-target and no built-in load balancing across them, which is exactly why a team that wants that behavior for free, rather than building it, often picks Aurora specifically for this reason.
- Skipping the forced-failover rehearsal is the single biggest gap between "designed for high availability" and "actually highly available": the DNS-flip mechanism and the AWS-stated failover time are necessary but not sufficient, since the application's own reconnect behavior is what determines real user-facing downtime.
- Load-balancing reads purely on the reader endpoint without a lag-aware exclusion means a badly lagged but technically healthy replica keeps taking its normal share of traffic, quietly serving stale reads to whichever users happen to be routed to it.
Evaluate the trade-offs of using a managed multi-region database service (for example Cloud Spanner, Cosmos DB, or Aurora Global Database) versus running a self-managed sharded cluster on VMs or Kubernetes, for a system handling roughly 10 million QPS reads and 200,000 writes per second. Cover latency, consistency, operational complexity, cost, scaling behavior, and disaster-recovery capabilities, and give your recommendation.
Sample Answer
Direct answer
At 10 million reads per second and 200,000 writes per second, the default recommendation is a managed multi-region service (Spanner-class horizontal partitioning or Cosmos DB-class tunable consistency) over a self-managed sharded cluster, unless the team already operates comparable distributed stateful infrastructure in-house and the consistency requirements are loose enough to accept asynchronous cross-region replication. At this scale the operational surface a managed vendor absorbs, quorum health (whether enough replicas are up and agreeing to keep accepting writes), rebalancing, and cross-region failover (automatically switching traffic to a healthy region if one goes down) across what turns out to be hundreds of nodes, is exactly the class of problem these services are purpose-built for. The worked example and framework below show why, and name what would flip the recommendation.
Framework: six axes
| Axis | Managed multi-region (for example Cloud Spanner, Cosmos DB, Aurora Global Database) | Self-managed sharded cluster (VMs or Kubernetes) |
|---|---|---|
| Latency | In-region reads and writes stay low: Cosmos DB's own documentation guarantees a 99th-percentile (P99) latency under 10 ms for reads and writes within a region, at any consistency level. Cross-region strong consistency is bounded by physics, not engineering: Cosmos DB states cross-region strong-consistency write latency as two times the round-trip time (RTT) between the two farthest regions, plus 10 ms P99. | Fully tunable: keep the write quorum inside one metro region for low latency, and replicate elsewhere asynchronously for disaster recovery (DR) only, if the business can accept that. Requires the team to design and prove this topology correct itself. |
| Consistency | Comes largely built in. Cosmos DB offers five levels (Strong, Bounded Staleness, Session, Consistent Prefix, Eventual). Spanner gives a single strong guarantee, external consistency (global linearizability, meaning all observers agree on the order transactions actually committed in), via TrueTime, a globally synchronized clock with a bounded uncertainty window that every commit waits out before acknowledging. | Owned entirely by the team. Single-shard transactions are ordinary single-node ACID (atomicity, consistency, isolation, durability). Cross-shard transactions need a distributed-transaction layer, for example two-phase commit (a protocol that coordinates an atomic commit across multiple shards), that the team designs, implements, and must prove correct under partition and retry. |
| Operational complexity | Patching, quorum membership, internal rebalancing, and backup mechanics are the vendor's job. The team owns schema design, indexing, provisioned or consumed throughput sizing, and query-level tuning. | Everything: failover automation, connection pooling and proxy layers, monitoring, and on-call runbooks for partition and quorum-loss scenarios are the team's to write and operate, with no vendor site reliability engineering (SRE) team absorbing pages. |
| Cost | Priced at a premium over commodity compute (consumption-based request units, or per-node or per-vCPU list pricing depending on the specific service and tier; check the current pricing page for the exact service and region before budgeting, since list prices change without a fixed schedule). The premium buys engineering time the team does not have to spend building the same capability. | Commodity VM or Kubernetes compute, storage, and network egress rates, but the true cost includes the engineering headcount to build and operate the sharding, failover, and DR tooling a managed service ships by default. That cost does not appear on the cloud bill. |
| Scaling behavior | Elastic and mostly automatic. Aurora Global Database adds up to 10 read-only secondary regions with storage-based replication typically under a second of lag, though only the primary region accepts direct writes (a "write forwarding" feature lets secondaries relay writes to the primary, at the cost of that round trip). Spanner and Cosmos DB partition writes horizontally across internal shards transparently to the application. | Fully within the team's control (shard key choice, resharding strategy, instance sizing), but every reshard is a project the team runs and risks itself, exactly the work automatic rebalancing would otherwise absorb. |
| Disaster recovery | Documented, tested guarantees. A Cloud Spanner multi-region configuration places two read-write regions (two replicas each) plus a witness region (a fifth voting replica that participates in the write quorum but does not serve reads), five voting replicas total; a write quorum needs the leader region's replica plus any two of the other four, so the configuration survives the complete loss of any one region with zero data loss, a recovery point objective (RPO) of zero. Aurora Global Database offers a planned switchover (no data loss) and an unplanned failover after a regional outage (a small, non-zero RPO bounded by the sub-second replication lag (the delay between a write completing in the primary region and arriving in the secondary) at the moment of failure). | Fully bespoke: cross-region async replication, backup and restore, or a warm standby are all buildable, but the recovery time objective (RTO) and RPO the system actually delivers are only as good as the team's own runbook and the disaster tests actually run, not a documented product guarantee. |
Worked example
Whichever path is chosen, the raw infrastructure needed at this scale is a useful reality check: 10 million reads/sec and 200,000 writes/sec is real online transaction processing (OLTP) hardware regardless of who operates it.
import math
# Inputs: the QPS figures come from the question; the rest are stated,
# illustrative capacity-planning assumptions (NOT vendor-measured benchmarks).
read_qps = 10_000_000
write_qps = 200_000
read_capacity_per_node = 50_000 # assumed sustained cached-read throughput of one well-provisioned OLTP node
write_capacity_per_shard = 8_000 # assumed sustained write throughput of one shard's single-writer primary
dr_regions = 3 # regions in the deployment
shards_needed = math.ceil(write_qps / write_capacity_per_shard)
total_read_nodes = math.ceil(read_qps / read_capacity_per_node)
read_nodes_per_shard = math.ceil(total_read_nodes / shards_needed)
nodes_per_shard_group = 1 + read_nodes_per_shard # 1 primary + its read nodes
total_nodes_single_region = shards_needed * nodes_per_shard_group
total_nodes_all_regions = total_nodes_single_region * dr_regions
print(f"shards needed (write-bound): {write_qps:,} / {write_capacity_per_shard:,} -> {shards_needed}")
print(f"read-serving nodes needed (read-bound): {read_qps:,} / {read_capacity_per_node:,} -> {total_read_nodes}")
print(f"read nodes per shard: {total_read_nodes} / {shards_needed} -> {read_nodes_per_shard}")
print(f"nodes per shard group (1 primary + reads): {nodes_per_shard_group}")
print(f"total nodes, single region: {shards_needed} x {nodes_per_shard_group} -> {total_nodes_single_region}")
print(f"total nodes, {dr_regions}-region deployment: {total_nodes_single_region} x {dr_regions} -> {total_nodes_all_regions}")
Output:
shards needed (write-bound): 200,000 / 8,000 -> 25
read-serving nodes needed (read-bound): 10,000,000 / 50,000 -> 200
read nodes per shard: 200 / 25 -> 8
nodes per shard group (1 primary + reads): 9
total nodes, single region: 25 x 9 -> 225
total nodes, 3-region deployment: 225 x 3 -> 675
Roughly 675 stateful nodes across three regions, on these stated assumptions. That number does not change based on who manages it: a managed multi-region service still provisions and rebalances something in that range internally, it simply does not page the team when a node in it fails. The real question is whether the team wants to own the failure domain for 675 stateful nodes directly, given that a self-managed cluster at this size effectively requires building an internal SRE function for exactly that fleet.
flowchart LR
subgraph MG["Managed multi-region service"]
C1["Client"] --> R1["Regional endpoint"]
R1 --> P1[("Primary region")]
P1 -. replicates .-> S1[("Secondary region A")]
P1 -. replicates .-> S2[("Secondary region B")]
end
subgraph SM["Self-managed sharded cluster"]
C2["Client"] --> RT["Shard router"]
RT --> SH1[("Shard 1 primary")]
RT --> SH2[("Shard 2 primary")]
SH1 --> RE1[("Shard 1 replica")]
SH2 --> RE2[("Shard 2 replica")]
end
In the managed topology, the vendor owns everything to the right of the regional endpoint: which replica leads, how replication and quorum work, how a lost region is detected and routed around. In the self-managed topology, the team owns and must operate the shard router, the primary and replica wiring per shard, and the logic for what happens when a shard's primary disappears.
Recommendation and what would flip it
Recommend the managed multi-region path by default at this scale. The deciding factors are the team's existing distributed-systems operational maturity (does it already run something like Vitess- or CockroachDB-class infrastructure at hundreds of nodes today), the actual consistency requirement (does the product need synchronous cross-region strong consistency, or would single-writer-per-shard with asynchronous DR replication genuinely be acceptable), and whether the cost delta at roughly 675 nodes' worth of infrastructure materially matters to the budget, since the managed premium multiplies across a large fleet at this scale rather than a handful of instances.
It flips to self-managed when all three line up: the team already operates comparable stateful infrastructure in-house, the workload tolerates a single-writer-per-shard model with asynchronous cross-region replication instead of synchronous strong consistency, and the cost savings at this node count are large enough to justify paying the engineering-time cost once, building the sharding, failover, and DR tooling, instead of paying the managed-service premium every month indefinitely. Defaulting to "managed is always safer" ignores that at 675 nodes the managed premium is not a rounding error, and a team that already has the operational muscle for a fleet this size may reasonably prefer to keep that premium as engineering budget instead.
Trade-offs and pitfalls
The dominant failure mode on this kind of question is picking a side without naming the deciding variable, or hedging into "it depends" without ever committing. A second common mistake is assuming managed multi-region services give multi-region strong consistency for free: Cosmos DB explicitly does not support strong consistency on a multi-region-write account, and even Spanner's external consistency costs real latency, a bounded commit-wait on every write, it is not free ordering. A third is treating "self-managed" as automatically cheaper: at 675 nodes, the engineering cost of building and operating correct distributed-transaction and failover logic in-house is usually larger, not smaller, than the managed premium, unless that capability already exists in the organization.
Compare provisioned capacity (reserved throughput) with serverless/autoscaling models for DynamoDB and Aurora Serverless v2. Discuss cost predictability, latency characteristics, cold-start behavior, throttling, operational overhead, and what kind of workload pattern each model suits best.
Sample Answer
Provisioned capacity means you reserve a fixed amount of throughput (how many read or write operations, or how much compute, the system can handle per second) up front and pay for it whether you use it or not; serverless/autoscaling means the service adjusts capacity to match load and you pay closer to actual consumption. The two managed products that expose this choice most directly are DynamoDB (provisioned throughput vs. on-demand) and Aurora (a fixed instance class vs. Serverless v2), and the right choice depends almost entirely on how predictable and how bursty the workload is.
DynamoDB: provisioned throughput vs. on-demand
| Provisioned | On-demand | |
|---|---|---|
| Cost predictability | High: you set read capacity units (RCU) and write capacity units (WCU) and pay a flat rate for them | Lower: billed per request, cost tracks traffic directly, which is predictable only if traffic is predictable |
| Latency | Same single-digit-millisecond latency as on-demand; capacity mode does not change per-request latency | Same latency profile as provisioned |
| Throttling behavior | Requests beyond provisioned capacity are throttled (ProvisionedThroughputExceededException) unless application auto scaling is configured, and auto scaling reacts to a CloudWatch alarm on a delay, not instantly | Scales automatically to serve the traffic, but a sudden spike well beyond the table's recent peak can still see brief throttling until DynamoDB provisions more partitions behind the scenes |
| Operational overhead | You own setting and tuning target utilization for auto scaling, and re-tuning it as traffic patterns shift | Effectively none: no capacity numbers to manage |
| Best workload pattern | Steady, forecastable traffic where the flat provisioned rate beats per-request pricing | Spiky, new, or hard-to-forecast traffic, or a small table where operational simplicity matters more than shaving cost |
Aurora: provisioned instance vs. Aurora Serverless v2
| Provisioned | Serverless v2 | |
|---|---|---|
| Cost predictability | High: fixed instance-class price regardless of load | Variable: billed in Aurora capacity unit (ACU) hours, each ACU is roughly 2 gibibytes (GiB) of memory plus corresponding CPU and networking, measured every second |
| Latency characteristics | Constant, matches the chosen instance class | Same engine, same query latency at a given capacity; latency only changes if the workload outgrows the current ACU level faster than it can scale up |
| Cold-start behavior | None: capacity is always provisioned | None for the normal 0.5 to 256 ACU scaling range, since scaling happens in place (typically the same host) without dropping connections; a cold-start-like delay only appears if you opt into "scale to zero" (available on newer engine versions with a minimum of 0 ACUs) and the cluster has to resume from a fully paused state |
| Throttling | N/A: fixed capacity either handles the load or the instance is CPU/memory saturated | Scales up automatically in increments as small as 0.5 ACU as load increases; a very sudden spike can still see brief elevated latency while capacity catches up, but there is no hard throttling wall the way DynamoDB provisioned mode has |
| Operational overhead | Manual: resizing the instance class is a deliberate, brief-outage operation you schedule | Low: you set a minimum and maximum ACU range once and the service scales within it continuously |
| Best workload pattern | Steady-state OLTP (online transaction processing) load where you can predict peak capacity and want the most predictable bill | Spiky or unpredictable workloads (promotions, seasonal traffic, dev/test environments that sit idle most of the day) where paying for idle provisioned capacity would be wasteful |
Worked example: turning a workload into numbers
DynamoDB. Say a product-catalog table needs to sustain a peak of 800 strongly consistent reads/sec (items averaging 3 KB) and 150 writes/sec (items averaging 1.5 KB). DynamoDB charges 1 RCU (read capacity unit) per strongly consistent read of an item up to 4 KB, and 1 WCU (write capacity unit) per write of an item up to 1 KB, rounding each item up to the next chunk boundary:
import math
peak_reads_per_sec = 800
peak_writes_per_sec = 150
read_item_kb = 3
write_item_kb = 1.5
rcu_per_read = math.ceil(read_item_kb / 4) # 3KB fits in one 4KB chunk -> 1 RCU
wcu_per_write = math.ceil(write_item_kb / 1) # 1.5KB needs two 1KB chunks -> 2 WCU
read_rcu_per_sec = peak_reads_per_sec * rcu_per_read
write_wcu_per_sec = peak_writes_per_sec * wcu_per_write
headroom = 1.2 # 20% buffer above measured peak
provisioned_rcu = math.ceil(read_rcu_per_sec * headroom)
provisioned_wcu = math.ceil(write_wcu_per_sec * headroom)
print(f"Peak RCU/sec needed: {read_rcu_per_sec}")
print(f"Peak WCU/sec needed: {write_wcu_per_sec}")
print(f"Provisioned RCU to reserve (20% headroom): {provisioned_rcu}")
print(f"Provisioned WCU to reserve (20% headroom): {provisioned_wcu}")
Peak RCU/sec needed: 800
Peak WCU/sec needed: 300
Provisioned RCU to reserve (20% headroom): 960
Provisioned WCU to reserve (20% headroom): 360
Under provisioned capacity, you reserve and pay for 960 RCU and 360 WCU every hour of the day, including overnight when actual traffic might fall to 50 reads/sec and 10 writes/sec (50 RCU and 20 WCU, under 6% of what is provisioned). Under on-demand, you are billed only for the RCU and WCU each request actually consumes, so that overnight trough costs proportionally less, at a higher per-unit rate for whichever units you do consume. This is the concrete version of the "idle capacity" trade-off in the table above: provisioned wins when the 800/150 peak is close to the round-the-clock average, on-demand wins when it is not.
Aurora Serverless v2. Say a workload idles near the 1 ACU minimum for 20 hours a day and spikes to 8 ACU for a 4-hour business-hours peak. An Aurora capacity unit (ACU) is billed per second, in ACU-hours:
idle_hours, idle_acu = 20, 1
peak_hours, peak_acu = 4, 8
daily_acu_hours = idle_hours * idle_acu + peak_hours * peak_acu
rate_per_acu_hour = 0.12 # AWS's published Aurora Serverless v2 rate, us-east-1 at time of writing; check current pricing for your region
daily_cost = daily_acu_hours * rate_per_acu_hour
monthly_cost = daily_cost * 30
provisioned_daily_acu_hours = 24 * peak_acu # a fixed instance sized for the peak, running all day
provisioned_monthly_cost = provisioned_daily_acu_hours * rate_per_acu_hour * 30
print(f"Serverless v2 ACU-hours/day: {daily_acu_hours}")
print(f"Serverless v2 monthly compute cost: ${monthly_cost:,.2f}")
print(f"Fixed-at-peak monthly compute cost: ${provisioned_monthly_cost:,.2f}")
Serverless v2 ACU-hours/day: 52
Serverless v2 monthly compute cost: $187.20
Fixed-at-peak monthly compute cost: $691.20
For this bursty, mostly-idle shape, Serverless v2 costs about 73% less than a provisioned instance sized to never fall short at the 4-hour daily peak, because the provisioned instance bills for 8-ACU-equivalent capacity for all 24 hours whether the workload needs it or not. A workload that instead sat near 8 ACU around the clock would flip this: Serverless v2 would bill close to the same 192 ACU-hours/day as the provisioned option, at a higher per-ACU-hour rate, making the fixed instance the cheaper choice.
Trade-offs and pitfalls
- Provisioned capacity is not "safer," it is differently risky: undersized provisioned throughput throttles real user traffic with a hard error, while serverless/on-demand's failure mode is a cost spike or brief latency bump, not a hard rejection. Which failure mode is worse depends on whether the business cares more about a predictable bill or never dropping a request.
- On-demand and Serverless v2 both have a per-unit price premium over well-utilized provisioned capacity. The trade only pays for itself when the workload's peak-to-average ratio is high enough that you would otherwise be paying for a lot of idle provisioned headroom; for a genuinely flat, 24/7 workload, provisioned is usually cheaper.
- Aurora Serverless v2's scale-to-zero option is attractive for dev/test but is a different feature from its normal 0.5 to 256 ACU scaling: only enable it where an occasional resume delay is acceptable, not on anything customer-facing.
- Mixing modes inside one system is a legitimate design, not a compromise: for example, a steady OLTP writer on a provisioned Aurora instance with Serverless v2 read replicas that scale independently to absorb bursty reporting traffic, or a DynamoDB table on provisioned capacity for its steady core traffic with on-demand reserved for a table that only exists to catch spiky, unpredictable event ingestion.
A vendor offers a managed distributed SQL database that promises automatic sharding and rebalancing. List 6 operational questions you would ask before adopting it for a latency-sensitive transactional service.
Sample Answer
Direct answer
Six questions cover the ground a "handles sharding and rebalancing automatically" pitch conveniently skips: what latency the vendor will actually guarantee under your transaction shape, whether rebalancing degrades live traffic or pauses it, what the replication and consistency model implies for data loss and recovery time, how visible internal topology changes are to your own monitoring, how the system behaves when it cannot reach a quorum (a majority of replicas agreeing before a write counts as durable), and how hard it would be to leave. Ask all six framed against "latency-sensitive transactional," not as a generic vendor-diligence checklist: an answer that would satisfy an analytics workload can still be a bad fit here.
The six questions
| # | Question | Why it matters for a latency-sensitive transactional workload | Red flag in the vendor's answer |
|---|---|---|---|
| 1. Latency guarantee and measurement basis | "What 99th-percentile (P99) commit latency do you guarantee for a single-row transactional write, under a documented workload similar to mine, and is that backed by a service-level agreement (SLA) with a financial remedy?" | Distributed SQL databases add a consensus round trip (a network exchange where a quorum of replicas must acknowledge a write) on every commit. The number that matters is the tail latency of a small, single-row transaction, not an average and not a number measured on a large analytical batch. | Vendor quotes only mean or median latency, or a benchmark run on multi-row batch writes instead of the small single-row commits typical of a transactional service. |
| 2. Rebalancing impact on live traffic | "When the cluster rebalances automatically (a node joins, a hot shard splits, a failed replica is repaired), does the affected data stay fully read and write available, or does the system pause or throttle traffic on the piece being moved? Can rebalancing be rate-limited or scheduled away from peak hours?" | Rebalancing moves a shard (a contiguous chunk of the table's rows, grouped by key range) from one node to another by streaming a full copy of it across the network. Left unthrottled, that transfer competes with production traffic for network and CPU capacity right when the cluster's internal state is least predictable. This is not hypothetical: even mature distributed SQL engines have shipped rebalancing paths where an ordinary configuration change measurably degraded foreground query latency before the vendor patched it. | Vendor cannot describe a concrete rate-limiting or scheduling mechanism, or only offers "it is usually fine in practice." |
| 3. Replication, consistency, and disaster-recovery implications | "What is the default replication factor and quorum size per shard, what consistency guarantee does a client get by default (strict ordering across all replicas versus eventual convergence), and what recovery point objective (RPO, how much recent data could be lost) and recovery time objective (RTO, how long recovery takes) does that imply if a region fails mid-transaction?" | "Automatic sharding" says nothing about whether each shard's replicas live in one region (fast, but a regional outage can lose recent writes) or are kept in sync across regions by a global consensus protocol (safer, but adds real latency to every commit). | Vendor cannot state RPO or RTO as numbers, or uses "highly available" and "zero data loss" interchangeably. |
| 4. Observability into internal topology changes | "What real-time visibility exists into shard splits, leader elections (which replica currently owns the right to accept writes for a shard), and in-progress rebalancing, and can a latency spike in my own application performance monitoring (APM) tooling be correlated back to a specific internal event?" | If the database's internal reshuffling is invisible, a 2 a.m. latency spike looks identical to an application bug, and an on-call engineer burns an hour ruling out their own code before learning the database moved a shard underneath them. | Vendor offers only an aggregate cluster-health dashboard with no exportable, timestamped events for splits, elections, or rebalances. |
| 5. Failure-mode behavior | "During a network partition or a quorum loss on a shard, does the system fail closed (refuse writes, to stay consistent) or fail open (keep accepting writes on a stale replica)? Does failover for a lost leader happen automatically, or does it require a manual runbook step?" | For a transactional service, a database that fails open during a partition can accept writes that conflict or silently overwrite each other, surfacing as corrupted business state days later. That is worse than a short, honest availability gap. | Vendor cannot describe the failure mode precisely, or the answer implies writes are accepted during a partition "to keep availability high." |
| 6. Data portability and exit strategy | "If the contract ends, is export a native logical dump, a wire-protocol-compatible export another engine can read, or a proprietary snapshot only the vendor's own restore tooling understands? Who controls the encryption keys, and how long would extracting the full current data volume take at the vendor's stated export throughput?" | Automatic sharding and rebalancing are exactly the features that make a vendor's on-disk storage format the least portable. Verifying the exit path before signing is far cheaper than discovering it during a forced migration. | Vendor's export path is undocumented, or only supports restoring into their own product. |
Worked example
Take a payments authorization service: 3,000 transactions per second, a 40 ms P99 commit latency SLA of its own, and a requirement to survive a full region outage, so the team is evaluating a three-region deployment spanning, say, New York and San Francisco.
Question 1 asks the vendor for a P99 commit latency guarantee. Before even hearing their number, it is worth computing the physics floor a synchronous cross-region quorum write cannot beat, because no amount of engineering can get a commit acknowledgment back faster than light travels through fiber:
# Physics floor on cross-region consensus commit latency: a sanity check on any
# vendor SLA number, not a benchmark. All inputs are stated explicitly below.
great_circle_km = 4130 # approx. New York <-> San Francisco great-circle distance
routing_factor = 1.5 # real fiber paths run longer than the great circle
fiber_path_km = great_circle_km * routing_factor
speed_of_light_vacuum_km_s = 299_792
refractive_index_fiber = 1.5 # typical single-mode fiber
speed_in_fiber_km_s = speed_of_light_vacuum_km_s / refractive_index_fiber
one_way_ms = (fiber_path_km / speed_in_fiber_km_s) * 1000
round_trip_ms = one_way_ms * 2
print(f"fiber path length: {great_circle_km} km x {routing_factor} = {fiber_path_km:.0f} km")
print(f"speed in fiber: {speed_of_light_vacuum_km_s} / {refractive_index_fiber} = {speed_in_fiber_km_s:,.0f} km/s")
print(f"one-way propagation time: {fiber_path_km:.0f} km / {speed_in_fiber_km_s:,.0f} km/s = {one_way_ms:.3f} ms")
print(f"round trip (quorum ack floor): {one_way_ms:.3f} ms x 2 = {round_trip_ms:.2f} ms")
Output:
fiber path length: 4130 km x 1.5 = 6195 km
speed in fiber: 299792 / 1.5 = 199,861 km/s
one-way propagation time: 6195 km / 199,861 km/s = 30.996 ms
round trip (quorum ack floor): 30.996 ms x 2 = 61.99 ms
A round trip between New York and San Francisco alone costs about 62 ms, before any disk write, queueing, or server-side processing. If the vendor's answer to question 1 is "P99 commit latency under 40 ms" and question 3 reveals that every commit needs a quorum acknowledgment from a replica in the other region, those two answers contradict each other: the deployment cannot meet its own stated SLA. The honest fix is either to keep the write quorum inside one metro region (propagation delay measured in fractions of a millisecond) and replicate to the far region asynchronously for disaster recovery only, accepting a small, named RPO, or to relax the latency SLA. Either is a legitimate design choice; a vendor who does not surface this tension when questions 1 and 3 are asked together has not actually answered the question.
Trade-offs and pitfalls
The most common wrong turn is accepting a vendor's self-reported questionnaire as verification. A "yes" to all six questions from a sales engineer is not evidence: insist on a proof-of-concept load test that replays the actual read/write mix and transaction size against the vendor's cluster, sized to the real data volume, while watching P99 latency through a real rebalance (add a node, kill a replica, and observe). "Distributed" and "managed" are not synonyms for "safe for latency-critical transactions": consensus overhead, rebalancing behavior, and failure-mode defaults vary enormously between vendors, and that is exactly the corner of the evaluation a generic due-diligence checklist glosses over.
Unlock Full Question Bank
Get access to all 20 Cloud and Managed Database Services interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.