High-Performing Databases: Aurora vs RDS, DynamoDB, DAX and ElastiCache
Task 3.3 of SAA-C03 is about choosing and tuning the database layer for performance. That means picking the database type and engine from the access pattern, migrating to that engine, scaling reads with RDS read replicas (and knowing when Multi-AZ is the wrong answer), using what makes Aurora fast (shared storage, low-lag replicas behind a reader endpoint, Global Database, Serverless v2), designing DynamoDB tables and indexes that avoid hot partitions, choosing on-demand or provisioned capacity, and adding a cache (DAX or ElastiCache) with the right population strategy. It sits in the Design High-Performing Architectures domain, and database selection is one of the exam's densest question sources. Questions describe a workload (read-heavy, write-heavy, spiky, global) with a constraint such as lowest cost or least operational overhead, and every option is a real database feature. This lesson shows how to map any requirement phrase to the right engine, replication mode, capacity mode or cache, and why the near-miss options lose.
On this page10 sections
- Pick the database from the access pattern, not the data
- Choosing the engine and migrating to it: homogeneous vs heterogeneous
- RDS read replicas vs Multi-AZ: performance is not availability
- Aurora: why it is fast, and when it beats standard RDS
- DynamoDB capacity: on-demand vs provisioned, and what throttling means
- Partition keys, hot partitions, and GSI vs LSI
- Choosing the cache: DAX vs ElastiCache, Valkey/Redis OSS vs Memcached
- Caching strategies and RDS Proxy: staleness, write cost, and connections
- Capacity planning and the database-selection decision table
- Worked scenarios: reasoning through Task 3.3 questions
- Choose a database type — relational, key-value, in-memory or purpose-built — from the workload's access pattern
- Choose a relational engine (MySQL, PostgreSQL, Aurora) and plan a homogeneous or heterogeneous migration to it
- Decide between read replicas, Multi-AZ DB instance deployments and Multi-AZ DB cluster deployments by separating read scaling from availability
- Explain Aurora's performance architecture and when Aurora replicas, Aurora Global Database or Aurora Serverless v2 beat standard RDS
- Choose DynamoDB on-demand vs provisioned capacity, and design partition keys and secondary indexes that avoid throttling
- Select the right cache — DAX vs ElastiCache, Valkey/Redis OSS vs Memcached — and the right population strategy
- Fix connection exhaustion with RDS Proxy and scale write-intensive workloads by matching the fix to the measured bottleneck
Pick the database from the access pattern, not the data
Pick the database from how the application reads and writes, not from what the data is. SAA-C03 rarely asks what a database service is; it describes an access pattern and asks which database fits. A relational database — Amazon RDS or Aurora — wins when the workload needs ad hoc or unplanned SQL, complex joins across tables, reports that combine data in new ways, or an existing schema-driven application being moved to AWS. A key-value database — DynamoDB — wins when the access patterns are known in advance (get an item by key, query by key and range), traffic must scale to millions of requests with consistent single-digit-millisecond latency, and nobody needs to join data in unplanned ways. An in-memory store — ElastiCache, or DAX in front of DynamoDB — wins when the requirement is microsecond-to-sub-millisecond latency, almost always as a cache in front of another database rather than as the system of record.
Transactions alone do not decide it. DynamoDB supports ACID transactions (TransactWriteItems and TransactGetItems group up to 100 actions across items and tables into one all-or-nothing operation), so "each order updates several records together" does not rule DynamoDB out. What rules it out is the need to query the data in ways you did not design for: DynamoDB has no joins, and every efficient query must follow a key you modelled up front. Unplanned joins and ad hoc SQL are the relational signal.
Beyond those three, the exam expects recognition of the purpose-built engines: Amazon Neptune for graph workloads — social networks, fraud rings, recommendation engines, any scenario stressing "highly connected data" or "relationships between entities"; Amazon Timestream for time-series data — IoT sensor telemetry, metrics analysed over time windows; and Amazon DocumentDB for MongoDB-compatible document workloads — the word "MongoDB" in the stem is the entire signal. You will not be asked to design with these; you will be asked to spot them among relational distractors.
The exam tests this section as elimination: unplanned joins and ad hoc SQL make DynamoDB wrong no matter how attractive its scaling sounds, and a fixed key-based access pattern at massive scale makes RDS the option that falls over first. Match the pattern before you compare features.
Choosing the engine and migrating to it: homogeneous vs heterogeneous
Choose the relational engine by compatibility first: the engine the application, its drivers, its SQL dialect and its team already use is usually the right one, because changing engines turns a move into a conversion project. MySQL is the common choice for web applications and off-the-shelf software built for it. PostgreSQL suits workloads that need its richer SQL and extensibility — advanced data types such as JSONB, extensions such as PostGIS for geospatial queries, and stricter standards compliance. Aurora is not a third dialect: it is AWS's MySQL- or PostgreSQL-compatible engine, so moving from MySQL to Aurora MySQL, or PostgreSQL to Aurora PostgreSQL, keeps the application's SQL unchanged. Commercial engines (Oracle, SQL Server) run on RDS when the application depends on them.
| Homogeneous migration | Heterogeneous migration | |
|---|---|---|
| Meaning | Same engine family at source and target (MySQL to RDS for MySQL or Aurora MySQL) | Different engines (Oracle or SQL Server to Aurora PostgreSQL) |
| Schema and code | Carry over unchanged | Must be converted: tables, data types, stored procedures, triggers, functions |
| Typical tools | Native dump/restore, replication, an Aurora read replica, or AWS DMS | DMS Schema Conversion or AWS SCT for the schema and code, then AWS DMS for the data |
| Effort and risk | Low | Higher — conversion and application testing |
In a heterogeneous migration the two tools do different jobs. DMS Schema Conversion (in the AWS DMS console) or the downloadable AWS Schema Conversion Tool (SCT) converts the source schema and database code — stored procedures, triggers, views — into the target engine's dialect and flags what needs manual work. AWS DMS then moves the data. On its own, DMS creates only basic tables and primary keys; it does not convert secondary indexes, foreign keys or procedural code. DMS supports a full load followed by change data capture (CDC): the source stays live while existing data copies, ongoing changes stream across, and the cutover is a short pause to repoint the application rather than a long outage.
For the common homogeneous move from RDS for MySQL or PostgreSQL to Aurora, the least-effort path is to create an Aurora read replica of the RDS instance. Aurora keeps it in sync; when replica lag reaches zero, stop writes, promote the Aurora cluster to standalone and repoint the application, with only minutes of write downtime. A snapshot restore is simpler to start but misses every write made after the snapshot, so it needs a long outage or a catch-up step. DMS with CDC also works but adds a replication instance and tasks to run — more to manage than a replica that RDS maintains for you.
RDS read replicas vs Multi-AZ: performance is not availability
Read replicas are for performance; Multi-AZ is for availability. These two RDS features are confused by design in exam options, and everything else follows from how each replicates and whether its copies can serve reads.
A read replica is a separate, readable database instance kept in sync by asynchronous replication. Because it is readable, you point read-heavy traffic at it — reporting jobs, dashboards, product-catalog reads — and the primary keeps its capacity for writes. Because replication is asynchronous, a replica can lag the primary, so replicas suit reads that tolerate slight staleness. You can create several replicas per source (up to 15 for RDS for MySQL, MariaDB and PostgreSQL), each can use a different instance class from the source, and replicas can live in another Region for low-latency reads near remote users and as a disaster-recovery copy, since a replica can be manually promoted to a standalone writable database. Applications must send reads to the replica endpoints themselves — RDS does not split traffic for you.
Multi-AZ comes in two deployment types, and the difference matters for reads:
| Multi-AZ DB instance deployment | Multi-AZ DB cluster deployment | |
|---|---|---|
| Standby copies | One standby in another AZ | Two standbys, each in a different AZ (one writer, two readers) |
| Replication | Synchronous | Semi-synchronous (a write commits once at least one reader acknowledges it) |
| Can the standby serve reads? | No — it adds zero read capacity | Yes — through the cluster's reader endpoint |
| Engines | All RDS engines | RDS for MySQL and RDS for PostgreSQL |
| Failover | Automatic; the endpoint DNS repoints | Automatic, typically faster than the instance deployment |
So "the Multi-AZ standby is not readable" is true of the Multi-AZ DB instance deployment, the classic form most exam options mean. Its standby buys survival of an AZ failure or instance crash with no data loss, and nothing else. A Multi-AZ DB cluster deployment does give you two readable standbys, but only for MySQL and PostgreSQL and only within one Region; for many readers, or readers in other Regions, read replicas (or Aurora) are still the tool.
Production designs commonly use both: Multi-AZ for failover plus read replicas for scale. The exam tests whether you keep the purposes straight. "Read performance is degrading" or "offload reporting queries" answered with a Multi-AZ DB instance deployment is wrong — its standby is not readable. "Survive an Availability Zone outage with automatic failover" answered with a read replica is also wrong — replication is asynchronous and promotion is manual. When a question stacks both requirements, the correct option uses each feature for its own job.
Aurora: why it is fast, and when it beats standard RDS
Aurora is faster to scale than standard RDS because it separates compute from storage. Amazon Aurora is a MySQL- and PostgreSQL-compatible relational engine whose data lives in a shared, distributed cluster volume spanning three Availability Zones (six copies of your data). Every instance in the cluster — the writer and all readers — attaches to that same volume, and storage grows automatically, so there is no volume-sizing exercise.
That shared volume is why Aurora replicas behave differently from RDS read replicas. An RDS replica keeps its own copy of the data and replays changes from the source asynchronously, so under heavy write load it can fall seconds or more behind. An Aurora Replica reads the same cluster volume as the writer. The storage writes are synchronous across the six copies, but each replica still refreshes its in-memory cache asynchronously, so replica lag is low rather than zero — typically under 100 milliseconds. A cluster can have up to 15 Aurora Replicas, and Aurora exposes a reader endpoint that load-balances connections across them, so the application uses one writer endpoint and one reader endpoint instead of a list of per-replica hostnames. Replicas double as failover targets: if the writer fails, Aurora promotes a replica, usually within about a minute or less, so one fleet serves both read scaling and high availability.
Know what is not an Aurora differentiator: RDS read replicas can also use a different instance class from the source, can also number up to 15 on MySQL and PostgreSQL, and can also live in another Region. The things only Aurora gives you are low lag from shared storage, the single load-balanced reader endpoint, and fast replica-based failover.
Aurora Global Database serves remote Regions. It replicates a primary cluster to secondary clusters in other Regions at the storage layer, on dedicated infrastructure, with lag typically under one second and without using the writer's compute. Applications in a remote Region read from the secondary cluster's reader endpoint, and a secondary can be promoted for cross-Region recovery in minutes (managed switchover for planned moves, failover for outages). The older alternative for Aurora MySQL — a binlog-based cross-Region read replica — works, but it replays the binary log through the database engine, so it consumes writer resources and lags more under load. When a stem asks for local reads in another Region with minimal lag and no extra load on the primary, Global Database is the fit.
Aurora Serverless v2 scales capacity automatically in fine-grained increments of Aurora capacity units (ACUs) as load changes, fitting variable, spiky or intermittent relational workloads — development and test databases, new applications with unknown demand — where provisioning for peak wastes money. On supported engine versions it can scale to a minimum of 0 ACUs and pause automatically after a period with no connections, so an idle database costs only storage; the first connection after a pause resumes it, with a short wait. The cheaper-looking alternative, stopping and starting instances on a schedule, cuts idle cost too, but the database is unavailable whenever it is stopped and does not react to unexpected demand — and a stopped RDS or Aurora database restarts automatically after seven days. When the requirement is "available whenever someone needs it" with unpredictable use, Serverless v2 beats a schedule.
The exam tests Aurora as a comparison: RDS replicas with growing lag and many hostnames versus Aurora Replicas behind one reader endpoint; "global users, local read latency" pointing at Global Database; "unpredictable or intermittent load, minimize cost and management" pointing at Serverless v2.
DynamoDB capacity: on-demand vs provisioned, and what throttling means
Choose DynamoDB's capacity mode by how predictable the traffic is: provisioned for steady, forecastable load; on-demand for unknown or spiky load. The mode decides both cost and behaviour under load. Provisioned capacity means you declare read capacity units (RCUs) and write capacity units (WCUs) in advance. Requests beyond the provisioned rate are throttled — the application sees throughput-exceeded errors — so provisioned mode demands that you know your traffic. It is the cheaper mode for steady, predictable workloads running at healthy utilisation, and auto scaling eases the planning by adjusting provisioned capacity between a floor and a ceiling as consumption changes. Auto scaling reacts to observed traffic over minutes, so a sudden vertical spike can still throttle before capacity catches up.
On-demand capacity removes the declaration: you pay per request and nothing for idle time. It is not unlimited instant capacity, though. An on-demand table instantly accommodates up to double its previous traffic peak; traffic that grows beyond double that peak within about 30 minutes can be throttled while the table scales further. For a known event — a product launch, a marketing push — you can pre-warm a table by setting its warm throughput, so the read and write rates it can serve instantly are already high before the traffic arrives. On-demand is the answer for unpredictable, spiky or brand-new workloads — "traffic patterns are unknown", "the table sits idle for days", "the team cannot forecast demand" are on-demand signals. The trade-off is unit price: at sustained, well-utilised throughput, provisioned capacity costs less. Tables can switch between modes, so starting on-demand and moving to provisioned once traffic stabilises is a legitimate design, not a trap.
The decision rule: predictable and sustained → provisioned with auto scaling; unpredictable, spiky or idle-then-bursty → on-demand (pre-warmed if the burst date is known). Cost constraints flip between them — the cheapest choice for a steady workload is provisioned, while the cheapest for a mostly idle workload is on-demand, because paying for idle provisioned capacity is the waste.
Expect scenarios with throttling during a flash sale on a provisioned table (fix: on-demand mode, or auto scaling with honest limits) and cost reviews where an over-provisioned table needs right-sizing. When throttling persists even though total capacity looks sufficient, capacity mode is not the culprit — partition key design is, which is the next section.
Partition keys, hot partitions, and GSI vs LSI
A hot partition is one partition key value receiving more traffic than a single partition can serve, and it throttles even when the table has spare capacity. DynamoDB spreads a table's data and throughput across partitions by hashing the partition key, and each partition has a hard ceiling of about 3,000 RCUs and 1,000 WCUs per second. A key with few distinct values, or one where a handful of values dominate — today's date, one celebrity user, one popular game — sends most requests to one partition. The stem signature is the contradiction: "the table is throttling although consumed capacity is well below provisioned capacity."
Adaptive capacity helps but has a limit. DynamoDB automatically shifts unused throughput towards busy partitions and can split a partition so frequently accessed items land on separate partitions, but it cannot push a single key's traffic beyond one partition's ceiling. That is why the fix is key design, not capacity: choose a high-cardinality partition key that traffic naturally spreads across (user ID or order ID rather than status or date), combine attributes when no single one has enough spread, or apply write sharding — append a random or calculated suffix so one logical hot key becomes many physical keys. Buying more RCUs/WCUs or switching to on-demand does not rescue a skewed key: the skew moves with it.
Secondary indexes let you query by attributes other than the table's primary key. A Query on a table or index must supply one exact value of that table's or index's partition key, and can then narrow by its sort key. So the attribute you want to look up by must be the index's partition key: on a support-ticket table keyed by ticket_id, finding every ticket assigned to one engineer needs an index whose partition key is assignee_id. An index keyed on team_id with assignee_id as its sort key cannot answer that on its own: every query against it must first name a team.
| Global secondary index (GSI) | Local secondary index (LSI) | |
|---|---|---|
| Partition key | Any attribute — a completely new key | Same as the table |
| Sort key | Optional, any attribute | A different sort key from the table's |
| When created | Any time, online, on an existing table | Only when the table is created |
| Capacity | Its own (provisioned tables set it separately) | Shares the table's |
| Read consistency | Eventually consistent only | Eventually or strongly consistent |
The practical rule: "query by a different attribute" almost always means a GSI; an LSI is only right when you need an alternative sort order within the same partition key, decided at table-creation time, possibly with strong consistency. A managed GSI also beats hand-building a second table kept in sync by DynamoDB Streams and a Lambda function: that copy works, but you own the code, the error handling and the backfill, where DynamoDB maintains and backfills a GSI for you.
Indexes can cause hot partitions too. A GSI keyed on a low-cardinality attribute — a flag or category with only a handful of values, such as priority = HIGH or LOW — concentrates index writes on a few partitions. Because DynamoDB updates the index as part of each base-table write, throttling on a GSI applies back-pressure and throttles writes to the base table, even when the table itself has spare capacity. The fix is the same as for the table: write-shard the index's partition key (for example, the category value plus a random suffix) and query the shards in parallel. Raising the index's provisioned write capacity helps only when the index as a whole is under-provisioned; it cannot lift the per-partition ceiling that a hot index key hits.
Exam questions here reward diagnosing the hot partition — on the table or on an index — and rejecting the more-capacity distractor.
Choosing the cache: DAX vs ElastiCache, Valkey/Redis OSS vs Memcached
Use DAX to cache DynamoDB and ElastiCache to cache everything else; inside ElastiCache, choose Valkey or Redis OSS for durability and data structures and Memcached for the simplest cache. Two caching decisions repeat across SAA-C03, and each compresses to a table.
DynamoDB Accelerator (DAX) is a purpose-built, write-through cache in front of DynamoDB that is API-compatible with it — the application swaps in the DAX client and keeps its code, and eventually consistent reads that hit the cache return in microseconds. Strongly consistent reads pass straight through to DynamoDB, so DAX helps only reads that can tolerate eventual consistency. DAX exists for one job: accelerating read-heavy or bursty-read DynamoDB workloads, including hot-key read traffic. ElastiCache is the general-purpose in-memory cache: it fronts RDS, Aurora or any backend — including computed results and session state — but the application implements the caching logic itself (check cache, fall back, populate).
| DAX | ElastiCache | |
|---|---|---|
| Works with | DynamoDB only | Any data source (RDS, Aurora, DynamoDB, APIs) |
| Code changes | Minimal — API-compatible client swap | Application implements cache logic |
| Latency goal | Microsecond reads | Sub-millisecond reads |
| Exam trigger | "DynamoDB" + "microseconds" / "read-heavy" / "no application changes" | Caching for relational databases, sessions, computed results |
ElastiCache offers three engines. Valkey (the open-source, Linux Foundation fork of Redis that ElastiCache now offers as its recommended engine) and Redis OSS are the feature-rich choice: persistence via snapshots, replication with Multi-AZ automatic failover, rich data structures — sorted sets (the leaderboard answer), lists, hashes — and pub/sub. Memcached is deliberately simple: multi-threaded, scales horizontally by adding nodes, but has no persistence, no replication and no failover in a node-based cluster — cached data on a lost node is simply gone. (ElastiCache Serverless for Memcached does replicate data across Availability Zones, but it still offers no persistence or rich data structures.)
| Valkey / Redis OSS | Memcached | |
|---|---|---|
| Persistence / durability | Yes (snapshots) | No |
| Replication and failover | Yes — Multi-AZ auto-failover | No |
| Data structures | Sorted sets, lists, hashes, pub/sub | Simple key-value strings/objects |
| Threading model | Single-threaded command execution per shard (newer versions add I/O threads) | Multi-threaded |
| Exam trigger | Leaderboards, session durability, HA cache | "Simplest possible cache", multi-threaded object caching, data loss acceptable |
The exam tests these as one-fact eliminations: "leaderboard" means sorted sets in Valkey or Redis OSS; "cache must survive node failure" means Valkey or Redis OSS with replicas; "simplest cache for query results, data loss acceptable" means Memcached; "microsecond DynamoDB reads without rewriting the application" means DAX, not ElastiCache.
Caching strategies and RDS Proxy: staleness, write cost, and connections
Lazy loading fills the cache on reads and risks stale data; write-through fills it on writes and risks filling memory with data nobody reads; a TTL bounds both. Choosing the cache service is half the decision; the exam also tests how the cache is kept populated.
| Lazy loading (cache-aside) | Write-through | |
|---|---|---|
| When the cache is written | On a read miss: read the database, then store the result | On every database write |
| What ends up cached | Only data that is actually read | Everything written, read or not |
| Staleness | Cached copies go stale after updates until they expire | Cached copies stay current |
| Cost | Miss penalty: three trips (cache, database, cache write) | Every write does double work |
| Node failure | More misses, not an outage | Missing items until rewritten (combine with lazy loading) |
Write-through's hidden cost is cache pollution. When most written data is never read again — log-like records, rarely viewed items — it occupies memory, and when memory is full the cache evicts items to make room, often the frequently read ones. Hit rate falls even though the cache is busy. Lazy loading plus a TTL keeps memory for what is actually read; the TTL bounds how stale lazy-loaded data can get and expires write-through data nobody reads. Real designs typically combine lazy loading, selective write-through for read-critical data, and TTLs everywhere. "Users see outdated data after updates" points at lazy loading without (or with too long) a TTL; "data must be current immediately after writes" points at write-through.
Know what happens when a Valkey or Redis OSS cache runs out of memory. The eviction policy (maxmemory-policy) decides: LRU or LFU policies evict items to make room, while noeviction evicts nothing and makes writes fail with errors once memory is full — the wrong setting for a cache. Adding read replicas adds read capacity and failover, not memory: each replica holds the same data as its primary. To hold more items, use a larger node type or add shards (cluster mode).
The other drag at the database boundary is connection exhaustion. Serverless and highly concurrent applications — Lambda especially — can each open their own database connection, and thousands of them exhaust the database's connection limit even while CPU and memory sit idle. RDS Proxy sits between the application and RDS or Aurora, pooling and multiplexing many client connections over a few database connections. It also shortens failover: clients stay connected to the proxy, which routes to the new writer without waiting for DNS to update — AWS cites failover up to 66% faster for Aurora and RDS Multi-AZ.
Compare it with the near-miss fixes:
- A larger instance class only raises
max_connections(which scales with memory). It costs more and moves the ceiling, but the connection storm still grows to meet it. - Lambda reserved concurrency caps how many functions run, so it caps connections — but excess invocations are throttled, reducing how many requests the application can serve.
- The AWS Advanced drivers (for example the AWS Advanced JDBC Wrapper) add fast failover in the client, but need code and configuration changes, and connections cannot be pooled across separate Lambda execution environments.
"Lambda functions are exhausting database connections" or "too many connections during spikes, CPU low" resolves to RDS Proxy.
Capacity planning and the database-selection decision table
Capacity planning starts with one question: is the workload read-intensive or write-intensive? Reads scale by adding copies and caches: read replicas (or Aurora's reader fleet) absorb query load, and a cache — ElastiCache or DAX — absorbs repeated reads before they reach the database at all. Writes are harder, because every write must reach the system of record; caches do not help. Write-intensive designs scale by distributing writes — DynamoDB with a well-spread partition key is the canonical horizontally-scaling write store — by scaling the writer up (bigger instance class, or Provisioned IOPS storage when the bottleneck is disk I/O rather than CPU), or by buffering: putting an SQS queue in front of the database so a consumer writes at a steady, sustainable rate while spikes wait in the queue rather than overwhelming the writer.
Know the planning units at recognition level. For RDS and Aurora, capacity is the instance class (CPU and memory — memory-optimized classes suit large working sets and heavy query engines) plus the storage's IOPS; when a scenario says queries are slow and metrics show the disk is saturated, the answer is Provisioned IOPS storage, not a cache. For DynamoDB, capacity is RCUs and WCUs — and reads cost double when they are strongly consistent, so a stem that relaxes consistency to eventual is quietly halving read cost. Matching the fix to the measured bottleneck — CPU, memory, disk I/O, connections, or a single hot partition — is the skill; upgrading the wrong dimension is the standing distractor.
With that framing, most Task 3.3 questions reduce to a lookup from requirement phrase to feature:
| Requirement phrase in the stem | Answer |
|---|---|
| Complex joins, ad hoc or unplanned SQL | RDS or Aurora (relational) |
| Same engine, move to AWS with least effort | Homogeneous migration (RDS to Aurora: Aurora read replica, then promote) |
| Change engines (e.g. Oracle to PostgreSQL) | DMS Schema Conversion or AWS SCT for schema and code, AWS DMS full load + CDC for data |
| Key-value access, single-digit milliseconds at any scale | DynamoDB |
| Offload read/reporting traffic from RDS | Read replicas |
| Automatic failover, survive an AZ outage | Multi-AZ DB instance deployment (standby not readable) |
| Failover plus readable standbys, MySQL/PostgreSQL | Multi-AZ DB cluster deployment |
| Many low-lag replicas, one load-balanced read endpoint | Aurora reader endpoint (up to 15 replicas) |
| Global users need local reads; cross-Region DR | Aurora Global Database |
| Variable or intermittent relational load, idle periods, available on demand | Aurora Serverless v2 (scales to 0 ACUs with auto-pause) |
| Unpredictable or spiky DynamoDB traffic | On-demand capacity (warm throughput for a known launch) |
| DynamoDB throttles with spare capacity | Hot partition: high-cardinality key or write sharding |
| Query DynamoDB by another attribute on an existing table | GSI with that attribute as its partition key |
| Steady, predictable DynamoDB traffic at lowest cost | Provisioned capacity + auto scaling |
| Microsecond DynamoDB reads, no code rewrite | DAX |
| Cache for RDS/Aurora, sessions, leaderboards | ElastiCache (Valkey or Redis OSS for durability/structures) |
| Lambda exhausting database connections; faster failover | RDS Proxy |
| Highly connected data, relationships | Neptune |
| Time-series / IoT telemetry over time | Timestream |
| MongoDB-compatible document workload | DocumentDB |
| Database disk I/O is the bottleneck on RDS | Provisioned IOPS storage |
| Write spikes overwhelming the database | SQS buffer in front of the writer |
The exam tests this table with qualifiers: two rows will both "work," and MOST cost-effective, LEAST operational overhead, or a latency number picks between them. Anchor on the latency and consistency words first — they eliminate fastest.
Worked scenarios: reasoning through Task 3.3 questions
Scenario 1. An e-commerce application on RDS for MySQL is slowing down: the reporting team runs heavy analytical queries against the production database every hour, and page loads degrade during each run. The database must also remain available during maintenance. Options include enabling Multi-AZ, adding a read replica, or adding ElastiCache. What improves performance MOST directly?
Reasoning: Multi-AZ is eliminated first — its standby serves no reads, so it cannot relieve query load; it answers the availability sentence, not the performance one. Between a replica and a cache, look at the workload's shape: analytical reporting queries are heavy, varied, and not repeated identically, so a cache would miss constantly — caches pay off for repeated reads of the same data. A read replica is the answer: point the reporting tool at the replica endpoint and production reads never compete with reports again. Slight replica lag is acceptable for hourly reports. If the question had instead described the same product pages being read thousands of times per minute, ElastiCache with lazy loading and a TTL would win — repeated identical reads are exactly what caches absorb. Both features offload reads; the access pattern picks between them.
Scenario 2. A gaming backend stores player scores in a DynamoDB table with provisioned capacity, partition key game_id. During tournaments for a popular game, write requests are throttled — yet CloudWatch shows consumed capacity well below what is provisioned. What should the architect fix?
Reasoning: Throttling with headroom to spare is the hot-partition signature. All tournament writes carry the same game_id, so they hash to one partition, and that partition's share of the throughput is exhausted while the rest of the table idles. Each partition tops out at about 1,000 WCUs, and adaptive capacity cannot lift one key past that, so adding capacity or switching to on-demand moves the table's ceiling but not the skew — the correct fix is partition key design: shard the hot key by appending a suffix (for example, game_id#1 through game_id#10) so writes spread across partitions, or re-key on a higher-cardinality attribute such as player_id.
Scenario 3. A startup is launching a new application on DynamoDB. Traffic is impossible to forecast and may spike by orders of magnitude after press coverage. Which capacity mode minimizes both throttling risk and idle cost?
Reasoning: Provisioned capacity requires a forecast the team does not have: set it high and pay for idle capacity, set it low and throttle during the spike, and auto scaling reacts too slowly for a sudden press-driven surge. On-demand is the designed answer — pay per request, no idle cost, and instant headroom up to double the previous peak while the table scales further. If the coverage date is known, setting the table's warm throughput beforehand removes the remaining risk of the first surge. Once traffic settles into a steady pattern, switching to provisioned with auto scaling becomes the cost optimisation, which is often the follow-up question.
Tip. SAA-C03 tests this task with workload-shaped scenarios plus a constraint: a read-heavy, write-heavy, spiky, idle or global workload and lowest cost, least operational overhead, no code change or a hard latency word (milliseconds vs microseconds). Classic shapes: relational vs DynamoDB decided by ad hoc SQL rather than transactions; homogeneous vs heterogeneous migration (schema conversion vs DMS data movement, Aurora read replica promotion); the read-offload choice (read replicas vs Multi-AZ DB instance vs cache); the Aurora upgrade (low-lag replicas, reader endpoint, Global Database vs binlog replica, Serverless v2 vs a stop/start schedule); DynamoDB capacity mode (on-demand headroom, warm throughput); hot partitions on tables and GSIs; GSI key choice; DAX vs ElastiCache; Valkey/Redis OSS vs Memcached; cache population and eviction; and RDS Proxy vs a bigger instance or reserved concurrency. Trap patterns: Multi-AZ offered for read scaling, more capacity offered for a hot partition, a GSI keyed on the wrong attribute, and a fix that works but violates a stated constraint. Read the constraint words before the options.
- Pick relational (RDS/Aurora) for ad hoc SQL and unplanned joins; DynamoDB supports ACID transactions, so transactions alone don't rule it out — unplanned query patterns do.
- Homogeneous migrations keep the engine; heterogeneous ones need DMS Schema Conversion or AWS SCT for schema and code, plus AWS DMS (full load + CDC) for the data. RDS to Aurora: create an Aurora read replica, then promote.
- Read replicas scale reads (asynchronous, readable, cross-Region capable); a Multi-AZ DB instance standby cannot serve reads, while a Multi-AZ DB cluster (MySQL/PostgreSQL) has two readable standbys.
- Aurora Replicas share the cluster volume, so lag is typically under 100 ms, behind one reader endpoint; Global Database replicates at the storage layer for sub-second cross-Region reads; Serverless v2 can scale to 0 ACUs and pause.
- DynamoDB: provisioned + auto scaling for steady traffic, on-demand for unpredictable or idle-then-bursty traffic — on-demand instantly handles up to double the previous peak, and warm throughput pre-warms for a known launch.
- Throttling with capacity to spare is a hot partition (each partition caps at about 3,000 RCU / 1,000 WCU); fix the key with high cardinality or write sharding. A throttled GSI also throttles base-table writes.
- A Query needs the index's partition key value, so the attribute you look up by must be the GSI's partition key; GSIs can be added any time, LSIs only at table creation.
- DAX is the microsecond, API-compatible cache for DynamoDB; ElastiCache fronts everything else — Valkey/Redis OSS for persistence, failover and sorted sets, Memcached for the simplest multi-threaded cache.
- Lazy loading + TTL caches only what is read; write-through keeps data fresh but can fill memory with unread items. RDS Proxy pools connections and speeds failover — unlike a bigger instance or reserved concurrency.
Frequently asked questions
What is the difference between Amazon Aurora and Amazon RDS?
RDS is AWS's managed service for standard database engines — MySQL, PostgreSQL, MariaDB, SQL Server, Oracle — where each instance and replica keeps its own copy of the data. Aurora is AWS's own MySQL- and PostgreSQL-compatible engine built on a shared, distributed storage volume spanning three Availability Zones. That architecture is why Aurora supports up to 15 read replicas with minimal lag behind a load-balanced reader endpoint, fails over faster, and delivers higher throughput than the equivalent community engines on RDS. Choose Aurora when a relational workload needs serious read scaling, fast failover, or global replicas; standard RDS remains fine for modest workloads or engines Aurora does not offer.
When should I use DynamoDB on-demand vs provisioned capacity?
Use on-demand when traffic is unpredictable, spiky, or unknown — new applications, workloads that idle then surge — because you pay per request with no capacity planning and no throttling from under-provisioning. Use provisioned capacity, usually with auto scaling, when traffic is steady and predictable, because sustained throughput costs less per request than on-demand. A common lifecycle is to launch on-demand, observe real traffic, then switch to provisioned once the pattern stabilizes. Note that neither mode fixes a hot partition: if requests concentrate on one partition key value, that partition throttles regardless of mode.
What is the difference between DAX and ElastiCache?
DynamoDB Accelerator (DAX) is a write-through cache purpose-built for DynamoDB: it is API-compatible, so the application swaps its client with minimal code change, and eventually consistent reads that hit the cache return in microseconds. It only works with DynamoDB. ElastiCache is a general-purpose in-memory cache (Valkey, Redis OSS or Memcached) that can front any data source — RDS, Aurora, external APIs, session state — but the application must implement the caching logic itself. On the exam, "DynamoDB" plus "microsecond reads" or "minimal application changes" means DAX; caching for a relational database or storing sessions and leaderboards means ElastiCache.
Should I choose Valkey/Redis OSS or Memcached for ElastiCache?
Choose Valkey or Redis OSS when you need any of: data persistence through snapshots, replication with Multi-AZ automatic failover, rich data structures such as sorted sets for leaderboards, lists and hashes, or pub/sub messaging. Valkey is the open-source fork of Redis that ElastiCache now offers alongside Redis OSS, with the same feature set for these purposes. Choose Memcached when you need the simplest possible cache and can afford to lose cached data: it is multi-threaded, scales horizontally by adding nodes, and offers no persistence, replication or failover. Valkey or Redis OSS answers most scenarios; Memcached fits only when simplicity, multi-threaded plain object caching and tolerance for data loss are the stated needs.
Do read replicas or Multi-AZ improve RDS performance?
Read replicas improve read performance; a Multi-AZ DB instance deployment does not. A read replica is a readable copy fed by asynchronous replication, so you can route reporting, analytics and read-heavy traffic to it and free the primary for writes. A Multi-AZ DB instance deployment keeps one synchronous standby in another Availability Zone purely for automatic failover; the standby serves no traffic. The newer Multi-AZ DB cluster deployment, available for RDS for MySQL and PostgreSQL, is different: it has two standbys that can serve reads through a reader endpoint. For many readers or readers in other Regions, read replicas (or Aurora) remain the tool.
When do I need RDS Proxy?
Use RDS Proxy when many short-lived or highly concurrent connections are overwhelming an RDS or Aurora database — the classic case is AWS Lambda, where each concurrent execution environment opens its own connection and exhausts the connection limit while CPU stays low. RDS Proxy pools and multiplexes those client connections over a small number of database connections. It also speeds failover: clients stay connected to the proxy, which routes to the new writer without waiting for DNS changes. A larger instance class only raises max_connections at higher cost, and Lambda reserved concurrency caps connections by throttling requests, so neither fixes the problem without a trade-off.
What is the difference between lazy loading and write-through caching?
Lazy loading populates the cache only when data is requested: on a miss the application reads the database and stores the result. It caches only what is actually read, but misses are slower and cached data can go stale after database updates. Write-through updates the cache on every database write, so cached data stays current — at the cost of extra work on every write and memory spent on data that may never be read, which can evict frequently read items. Most designs combine lazy loading, selective write-through and a TTL on every cached item to bound staleness and free unused memory.
What is the difference between a homogeneous and a heterogeneous database migration?
A homogeneous migration keeps the same database engine family, for example on-premises MySQL to Amazon RDS for MySQL or Aurora MySQL, so the schema and database code carry over unchanged and native tools, replication or AWS DMS can move the data. A heterogeneous migration changes engines, for example Oracle to Aurora PostgreSQL, so the schema and code such as stored procedures and triggers must first be converted with DMS Schema Conversion or the AWS Schema Conversion Tool. AWS DMS then moves the data, using a full load plus change data capture to keep the source live and make the final cutover short.
Source
This lesson covers the "Design High-Performing Architectures" domain of the official SAA-C03 exam guide. Vendors revise their guides — check the source for the current version.
- AWS Certified Solutions Architect – Associate (SAA-C03) exam guide — Amazon Web Services
Sign up free to mark lessons complete, bookmark topics and track your exam readiness.