Managed Databases & Data Services
A note on scope: MySQL and DynamoDB already received full, dedicated deep dives in the Databases & Storage Reliability series (Parts 6 and 7). This part focuses on the AWS-specific managed service layer around relational and NoSQL databases — RDS, Aurora, and how AWS operationalizes the concepts already covered there — plus the caching and data-warehousing services that complete AWS's data platform.
Table of Contents#
- Why This Part Doesn't Re-Teach Database Fundamentals
- RDS — Managed Relational Databases
- RDS Multi-AZ — Managed High Availability
- RDS Read Replicas
- RDS Automated Backups and Point-in-Time Recovery
- RDS Parameter Groups and Option Groups
- Aurora — AWS's Own Re-Engineered Database
- Aurora's Storage Architecture — Why It's Actually Different
- Aurora Serverless
- Aurora Global Database
- DynamoDB on AWS — Operational Recap
- ElastiCache — Managed Redis and Memcached
- ElastiCache Redis: Cluster Mode and Replication
- Choosing Between RDS, Aurora, and DynamoDB
- Redshift — Data Warehousing
- A Brief Note on Data Lakes: S3 + Athena + Glue
- Database Migration Service (DMS)
- RDS Proxy — Connection Pooling as a Managed Service
- DynamoDB Accelerator (DAX) — In-Memory Caching for DynamoDB
- Database Security: Encryption and IAM Authentication
- Part 6 CLI Cheat Sheet
- Managed Database Best Practices — The Consolidated Checklist
- A Full Worked Example: Choosing a Data Layer for a New SaaS Product
- Common Mistakes
- Worked Practice Problems
- Summary and What's Next
Why This Part Doesn't Re-Teach Database Fundamentals#
Replication, sharding, backup/recovery, ACID, SQL, and both MySQL and DynamoDB internals already received exhaustive, dedicated treatment in the Databases & Storage Reliability series. Re-explaining replication lag or the CAP theorem here would be pure repetition. This part's actual job is narrower and more practical: how does AWS turn "run your own database with all that theory in mind" into a managed service, and what does that managed layer actually give you (and cost you) compared to self-hosting?
RDS — Managed Relational Databases#
Relational Database Service (RDS) runs a real database engine (MySQL, PostgreSQL, MariaDB, Oracle, SQL Server) on AWS-managed EC2 infrastructure, handling patching, backups, and failover for you.
Diagram
# Launch a Multi-AZ RDS MySQL instance aws rds create-db-instance \ --db-instance-identifier prod-orders-db \ --engine mysql --engine-version 8.0 \ --db-instance-class db.r6i.large \ --allocated-storage 100 --storage-type gp3 \ --master-username admin --manage-master-user-password \ --multi-az \ --vpc-security-group-ids sg-db123 \ --db-subnet-group-name isolated-db-subnets
Why this is directly the Shared Responsibility Model from Part 1, made concrete for databases specifically: everything covered generically in the MySQL In-Depth part of the Databases series (buffer pool tuning, binlog formats, InnoDB internals) still applies — you're still running real MySQL — but AWS takes over the operational burden of patching and infrastructure-level failover, letting you focus on schema design, query performance, and application logic.
RDS Multi-AZ — Managed High Availability#
The AWS-managed, concrete implementation of the primary-replica failover pattern already covered in depth in the Databases series (Part 1).
Diagram
A genuinely important, precise fact worth stating explicitly: the Multi-AZ standby is a SYNCHRONOUS replica used purely for failover — it is NOT readable, and applications should never try to query it directly. This is a deliberate design choice trading the standby's read capacity for the strong durability guarantee synchronous replication provides (directly reusing the sync-vs-async tradeoff already covered in the Databases series, Part 1) — for READ scaling, RDS offers a completely separate feature (Read Replicas, next section).
# Check current Multi-AZ failover status/history aws rds describe-events --source-identifier prod-orders-db --source-type db-instance # Trigger a manual failover test — a genuinely important DR # testing practice, directly connecting to the DR Testing # part of the Disaster Recovery series aws rds reboot-db-instance --db-instance-identifier prod-orders-db --force-failover
RDS Read Replicas#
Unlike the Multi-AZ standby, a Read Replica IS queryable — it's an asynchronous replica intended specifically for scaling read traffic, directly implementing the "Read Replicas — scaling reads separately from writes" concept already covered in the Capacity Planning series (Part 1).
aws rds create-db-instance-read-replica \ --db-instance-identifier prod-orders-db-replica-1 \ --source-db-instance-identifier prod-orders-db # Read Replicas can even live in a DIFFERENT REGION, # combining read-scaling with disaster-recovery positioning aws rds create-db-instance-read-replica \ --db-instance-identifier prod-orders-db-replica-eu \ --source-db-instance-identifier arn:aws:rds:us-east-1:123456789012:db:prod-orders-db \ --region eu-west-1
Why applications must explicitly, deliberately route read traffic to a Read Replica's OWN distinct endpoint, worth stating precisely: RDS does not do this automatically — the application (or a proxy layer, like RDS Proxy) needs its own logic distinguishing "this is a write, goes to the primary endpoint" from "this is a read, can go to a replica endpoint," and needs to accept the real replication-lag risk (Databases series, Part 1) of potentially reading slightly stale data from a replica.
RDS Automated Backups and Point-in-Time Recovery#
Directly implements the point-in-time recovery concept already covered in depth in the Databases series (Part 3) — RDS automates it entirely.
# Automated backups are enabled by default; configure retention aws rds modify-db-instance \ --db-instance-identifier prod-orders-db \ --backup-retention-period 7 \ --preferred-backup-window "03:00-04:00" # Restore to any point within the retention window — # creates a NEW instance, never overwrites the original, # exactly the same safety principle covered for DynamoDB # and MongoDB in the Databases series aws rds restore-db-instance-to-point-in-time \ --source-db-instance-identifier prod-orders-db \ --target-db-instance-identifier prod-orders-db-restored \ --restore-time "2026-08-15T14:30:00Z" # A manual, on-demand snapshot (persists beyond the # automated retention window, until explicitly deleted) aws rds create-db-snapshot \ --db-instance-identifier prod-orders-db \ --db-snapshot-identifier pre-migration-snapshot
Why automated backups rely on the same write-ahead-log mechanism already covered for MySQL specifically in the Databases series, worth stating explicitly: RDS continuously streams transaction logs (the binlog, for MySQL) to S3 behind the scenes, which is exactly what makes restoring to an ARBITRARY point in time (not just a daily snapshot boundary) possible — this is the identical mechanism, just fully managed and automated.
RDS Parameter Groups and Option Groups#
Two AWS-specific configuration concepts worth knowing precisely, since they're the RDS-native way to tune the exact engine internals already covered generically in the Databases series.
# Parameter groups control engine CONFIGURATION — # e.g. the InnoDB buffer pool size already covered # in the MySQL In-Depth part of the Databases series aws rds create-db-parameter-group \ --db-parameter-group-name custom-mysql8 \ --db-parameter-group-family mysql8.0 \ --description "Custom tuning for prod-orders-db" aws rds modify-db-parameter-group \ --db-parameter-group-name custom-mysql8 \ --parameters "ParameterName=innodb_buffer_pool_size,ParameterValue={DBInstanceClassMemory*3/4},ApplyMethod=pending-reboot" aws rds modify-db-instance \ --db-instance-identifier prod-orders-db \ --db-parameter-group-name custom-mysql8
Why this matters precisely for interview purposes: you cannot SSH into an RDS instance to edit my.cnf directly (a real, deliberate limitation of the managed model) — parameter groups are the ONLY sanctioned way to change engine-level configuration on RDS, a genuinely important limitation-vs-Aurora/self-hosted tradeoff to be aware of.
Aurora — AWS's Own Re-Engineered Database#
Aurora is not simply "managed MySQL/PostgreSQL" — it's a genuinely re-architected database engine, MySQL- and PostgreSQL-compatible at the wire protocol/API level, but with a fundamentally different storage layer underneath.
Diagram
Aurora's Storage Architecture — Why It's Actually Different#
The single most important, precise fact to know about Aurora, worth stating exactly: its storage layer is a distributed, log-structured system that automatically replicates data SIX ways across THREE Availability Zones — and this replication happens BELOW the database engine, at the storage layer itself, not via traditional binlog-based replication.
Diagram
Why this matters practically, worth stating explicitly as a genuinely strong interview answer: Aurora can tolerate losing an entire AZ (2 of 6 copies) without any impact on write availability, and can tolerate losing 3 of 6 copies without losing data, because it only needs a QUORUM (4 of 6) to confirm a durable write — a direct, concrete application of the quorum-based consensus concept already covered generically in the Reliability & Architecture Patterns series (Part 3) and the Databases series (Part 1). This also means Aurora replicas (up to 15 of them) share the SAME underlying storage as the primary, so replica lag is typically single-digit milliseconds — dramatically lower than RDS's traditional binlog-based Read Replica lag.
aws rds create-db-cluster \ --db-cluster-identifier prod-orders-aurora \ --engine aurora-mysql --engine-version 8.0.mysql_aurora.3.04.0 \ --master-username admin --manage-master-user-password \ --db-subnet-group-name isolated-db-subnets \ --vpc-security-group-ids sg-db123 # Add a reader instance — shares the SAME storage as the writer, # not a separate replicated copy aws rds create-db-instance \ --db-instance-identifier prod-orders-aurora-reader-1 \ --db-cluster-identifier prod-orders-aurora \ --engine aurora-mysql \ --db-instance-class db.r6g.large
Aurora Serverless#
Aurora Serverless v2 automatically scales database compute capacity up and down based on actual load, without the manual capacity-planning work standard RDS/Aurora provisioned instances require.
aws rds create-db-cluster \ --db-cluster-identifier prod-orders-aurora-serverless \ --engine aurora-mysql --engine-mode provisioned \ --serverless-v2-scaling-configuration MinCapacity=0.5,MaxCapacity=16 \ --master-username admin --manage-master-user-password
Why this is a genuinely strong fit for specific, real workload shapes, worth naming explicitly: intermittent or highly variable workloads (a dev/test database, a SaaS application with wildly different tenant sizes) benefit the most — the same reactive-autoscaling philosophy already covered generically in the Capacity Planning series, applied directly to database compute capacity instead of just EC2 fleet size.
Aurora Global Database#
Extends Aurora's replication across REGIONS (not just AZs), with typical replication lag under 1 second — directly the AWS-native implementation of the multi-region replication concepts from the Databases series (Part 1) and the Disaster Recovery series.
aws rds create-global-cluster \ --global-cluster-identifier prod-orders-global \ --source-db-cluster-identifier prod-orders-aurora # Add a secondary region — read-only, but can be promoted # to a full read/write primary during a regional DR failover aws rds create-db-cluster \ --db-cluster-identifier prod-orders-aurora-eu \ --engine aurora-mysql \ --global-cluster-identifier prod-orders-global \ --region eu-west-1
DynamoDB on AWS — Operational Recap#
DynamoDB received a full, dedicated deep dive in the Databases series (Part 7) — partition keys, hot-partition avoidance, GSIs/LSIs, Streams, Global Tables. Worth a brief, AWS-context-specific note here: DynamoDB is used HEAVILY throughout AWS's own serverless ecosystem, most commonly paired directly with Lambda (Part 7 of this series) as the default data store for serverless applications, precisely because both scale to zero and both bill per-request rather than per-provisioned-capacity — a genuinely natural, common architectural pairing worth recognizing by name in an interview.
ElastiCache — Managed Redis and Memcached#
ElastiCache runs Redis or Memcached as a fully managed, in-memory cache — directly implementing the "caching — the cheapest scaling trick" concept from the Capacity Planning series (Part 1), and the cache-aside/write-through patterns worth knowing from general caching theory.
# Create a Redis cluster (replication group) aws elasticache create-replication-group \ --replication-group-id prod-session-cache \ --replication-group-description "Session cache" \ --engine redis --cache-node-type cache.r6g.large \ --num-cache-clusters 2 \ --automatic-failover-enabled \ --multi-az-enabled
| Engine | Best fit |
|---|---|
| Redis | Needs persistence, replication, pub/sub, complex data structures (sorted sets, etc.) — the modern default choice |
| Memcached | Pure, simple key-value caching, needs multi-threaded scaling across CPU cores on a single node — a narrower, more specialized fit today |
ElastiCache Redis: Cluster Mode and Replication#
Diagram
# Automatic failover (Multi-AZ) promotes a replica to primary # automatically on primary failure — the same pattern already # covered for RDS Multi-AZ earlier in this part aws elasticache describe-replication-groups \ --replication-group-id prod-session-cache \ --query 'ReplicationGroups[0].AutomaticFailover'
Why losing an ElastiCache node is a fundamentally different risk profile than losing a database, worth stating explicitly, a genuinely important interview distinction: a cache is, by definition, a copy of data that has a source of truth elsewhere (typically the primary database) — losing a cache node means a temporary spike in load on that source of truth (cold cache misses) as it repopulates, not permanent data loss, unlike losing a database's actual primary storage.
Choosing Between RDS, Aurora, and DynamoDB#
Directly extending the SQL-vs-NoSQL decision framework already covered in the Databases series (Part 5), now made concrete with actual AWS service names.
Diagram
| RDS | Aurora | DynamoDB | |
|---|---|---|---|
| Engine | Real MySQL/PostgreSQL/etc. | MySQL/PostgreSQL-compatible, custom storage | Proprietary key-value/document |
| Read replica lag | Typically tens to hundreds of ms | Typically single-digit ms (shared storage) | N/A (different model) |
| Max storage | Up to 64 TiB (engine-dependent) | Up to 128 TiB, auto-scaling | Effectively unlimited |
| Multi-region | Cross-region Read Replicas (async) | Aurora Global Database (~1s lag) | Global Tables |
| Pricing model | Provisioned instance + storage | Provisioned (or Serverless v2) + storage | On-demand or provisioned capacity |
Redshift — Data Warehousing#
Amazon Redshift is a fully managed data warehouse, built for large-scale analytical (OLAP) queries across massive datasets — a fundamentally different workload shape than RDS/Aurora/DynamoDB's transactional (OLTP) focus.
Diagram
aws redshift create-cluster \ --cluster-identifier analytics-cluster \ --node-type ra3.xlplus --number-of-nodes 3 \ --master-username admin --master-user-password '...' \ --db-name analytics
Why Redshift's columnar storage is the key architectural difference worth naming, connecting directly to the indexing/query-planning discussion from the Databases series (Part 4): a traditional row-oriented database (RDS/Aurora) stores each row's data together, efficient for fetching one whole record; Redshift stores each COLUMN's data together, dramatically more efficient for analytical queries that scan millions of rows but only touch a few columns (e.g. summing one numeric column across a huge date range) — the same fundamental data-layout tradeoff, just optimized for the opposite access pattern.
A Brief Note on Data Lakes: S3 + Athena + Glue#
Worth knowing this pattern by name, even briefly: rather than loading all data into a data warehouse, many organizations query data directly where it already lives in S3 (Part 5), using Amazon Athena (serverless SQL queries directly against S3 objects) and AWS Glue (a managed ETL/data-catalog service that crawls and indexes S3 data so Athena can query it).
# Query data sitting directly in S3, with plain SQL, # with ZERO infrastructure to manage or provision aws athena start-query-execution \ --query-string "SELECT region, SUM(revenue) FROM sales_data GROUP BY region" \ --query-execution-context Database=analytics_db \ --result-configuration OutputLocation=s3://my-bucket/athena-results/
This "data lake" pattern is genuinely worth distinguishing from Redshift as a decision: Athena/S3 fits ad hoc, less-frequent, schema-flexible analytical queries with zero infrastructure to manage, while Redshift fits sustained, high-concurrency, performance-critical analytical workloads where a dedicated, tuned warehouse justifies its ongoing cost.
Database Migration Service (DMS)#
A genuinely practical, real-world tool worth knowing: AWS DMS migrates data INTO AWS databases from nearly any source (on-premises databases, other clouds, or between AWS database types), with minimal application downtime.
aws dms create-replication-task \ --replication-task-identifier onprem-to-rds-migration \ --source-endpoint-arn arn:aws:dms:us-east-1:123456789012:endpoint:onprem-mysql \ --target-endpoint-arn arn:aws:dms:us-east-1:123456789012:endpoint:rds-target \ --replication-instance-arn arn:aws:dms:us-east-1:123456789012:rep:migration-instance \ --migration-type full-load-and-cdc
Why full-load-and-cdc is the genuinely important migration pattern worth knowing precisely: it performs an initial FULL data copy, then switches to Change Data Capture (CDC) — continuously streaming ongoing changes from the source database's own transaction log (directly reusing the binlog/WAL change-capture concept already covered in the Databases series) — keeping the target database caught up in near-real-time until the actual cutover moment, minimizing the downtime window to just the final switch itself, rather than requiring a single, long, all-at-once migration window.
RDS Proxy — Connection Pooling as a Managed Service#
A genuinely important, practical service worth knowing well, directly connecting to a real, common failure mode. Applications — especially serverless ones (Part 7's Lambda) — can open FAR more database connections than a database instance can actually handle, since each concurrent Lambda invocation may open its own connection.
Diagram
aws rds create-db-proxy \ --db-proxy-name app-db-proxy \ --engine-family MYSQL \ --auth '[{"AuthScheme":"SECRETS","SecretArn":"arn:aws:secretsmanager:us-east-1:123456789012:secret:db-creds"}]' \ --role-arn arn:aws:iam::123456789012:role/RDSProxyRole \ --vpc-subnet-ids subnet-private-1a subnet-private-1b
Why this specifically solves the "serverless connection storm" problem, worth stating explicitly: a database has a hard limit on maximum concurrent connections (directly connects to the connection/thread-handling discussion implicit in the MySQL In-Depth part of the Databases series) — a burst of, say, 500 concurrent Lambda invocations each opening their own direct database connection can exhaust that limit and start failing entirely. RDS Proxy sits between the application and the database, maintaining a much smaller pool of actual database connections and MULTIPLEXING many application-side connections onto them, exactly the same connection-pooling pattern already implicit in application-server connection pools, just implemented as a fully managed AWS service. It also improves failover speed — RDS Proxy handles the reconnection to a newly-promoted primary automatically, faster than each application instance independently reconnecting after a Multi-AZ failover.
DynamoDB Accelerator (DAX) — In-Memory Caching for DynamoDB#
Directly extending both the DynamoDB deep dive (Databases series, Part 7) and the ElastiCache discussion earlier in this part — DAX is a fully managed, DynamoDB-API-compatible in-memory cache, purpose-built specifically for DynamoDB.
Diagram
aws dax create-cluster \ --cluster-name app-dax-cluster \ --node-type dax.r5.large \ --replication-factor 3 \ --iam-role-arn arn:aws:iam::123456789012:role/DAXRole
Why DAX is worth distinguishing precisely from a generic ElastiCache-in-front-of-DynamoDB setup, worth stating explicitly: it's API-compatible with the DynamoDB SDK, meaning an application can often adopt it with minimal code changes, and it specifically understands DynamoDB's item/query model (unlike a generic Redis cache, which would need custom application logic to know what to cache and how to invalidate it) — reducing read latency from single-digit milliseconds to microseconds for cached items, which matters for genuinely latency-critical read paths.
Database Security: Encryption and IAM Authentication#
A focused recap of database-specific security controls, previewing what Part 9 covers in full depth for AWS security services generally.
# Enable encryption at rest for RDS (must be set at CREATION # time — cannot be added to an existing unencrypted instance # without a snapshot-and-restore migration) aws rds create-db-instance \ --db-instance-identifier prod-db --storage-encrypted \ --kms-key-id alias/rds-encryption-key \ # ... other required parameters # IAM database authentication — log into the database using # a temporary IAM-generated auth token instead of a static # password, directly reusing the "prefer temporary credentials" # theme from Part 2 aws rds modify-db-instance \ --db-instance-identifier prod-db --enable-iam-database-authentication aws rds generate-db-auth-token \ --hostname prod-db.abc123.us-east-1.rds.amazonaws.com \ --port 3306 --username app_user
Why "encryption must be set at RDS creation time" is worth knowing precisely, a genuinely common gotcha: unlike EBS's account-wide default-encryption toggle (Part 5), an EXISTING unencrypted RDS instance cannot simply have encryption turned on — enabling it requires creating an encrypted snapshot of the existing instance and restoring a NEW instance from that snapshot, meaning retrofitting encryption onto a production database that was created unencrypted involves real, planned downtime or a careful cutover, not a simple configuration flag flip.
Part 6 CLI Cheat Sheet#
| Area | Command | Purpose |
|---|---|---|
| RDS | aws rds create-db-instance | Launch an RDS instance |
| RDS | aws rds create-db-instance-read-replica | Add a read replica |
| RDS | aws rds restore-db-instance-to-point-in-time | Point-in-time recovery |
| RDS | aws rds reboot-db-instance --force-failover | Test Multi-AZ failover |
| Aurora | aws rds create-db-cluster | Create an Aurora cluster |
| Aurora | aws rds create-global-cluster | Create an Aurora Global Database |
| Proxy | aws rds create-db-proxy | Add managed connection pooling |
| Caching | aws elasticache create-replication-group | Create a Redis replication group |
| Caching | aws dax create-cluster | Create a DAX cluster for DynamoDB |
| Analytics | aws redshift create-cluster | Create a Redshift data warehouse |
| Analytics | aws athena start-query-execution | Query S3 data directly with SQL |
| Migration | aws dms create-replication-task | Start a DMS migration |
Managed Database Best Practices — The Consolidated Checklist#
- Never query the Multi-AZ standby directly — it exists purely for failover; use a dedicated Read Replica for read scaling.
- Use RDS Parameter Groups for all engine-level tuning — there's no shell access to edit configuration files directly on a managed instance.
- Choose Aurora over standard RDS when replica lag or AZ-failure tolerance genuinely matters — its distributed, quorum-based storage layer is a meaningfully different (and stronger) guarantee, not just a rebrand.
- Use RDS Proxy for any serverless (Lambda) workload connecting to RDS/Aurora — prevents connection exhaustion during concurrency spikes.
- Set
storage_encrypted=trueat instance creation time — retrofitting encryption onto an existing unencrypted instance requires a full snapshot-and-restore migration, not a simple flag flip. - Prefer IAM database authentication over static database passwords where the engine supports it, extending the "temporary over long-lived credentials" theme from Part 2 down to the database layer.
- Match the workload shape to the right service — OLTP (RDS/Aurora/DynamoDB) and OLAP (Redshift) are fundamentally different access patterns; using one for the other's job is a common, costly mismatch.
- Use DMS with
full-load-and-cdc, not a single long-downtime cutover, for any non-trivial production database migration.
A Full Worked Example: Choosing a Data Layer for a New SaaS Product#
Bringing this entire part together into one concrete, realistic architectural decision — genuinely worth walking through end to end, since "design the data layer" is an extremely common real interview prompt.
Scenario: a new B2B SaaS product needs: (1) core transactional data (customers, subscriptions, invoices) with complex relational queries and strong consistency; (2) a high-throughput, well-known-access-pattern event log (user activity tracking) at potentially massive scale; (3) a session cache for authentication tokens; (4) monthly analytical reporting across all historical data for the internal BI team.
Diagram
Walking through each choice and its explicit reasoning, directly reusing this part's decision frameworks:
- Core transactional data → Aurora PostgreSQL, not DynamoDB, because it genuinely needs complex relational queries (JOINs across customers/subscriptions/invoices) and strong consistency for billing-related data — exactly the decision framework from earlier in this part favoring a relational engine when SQL and strong schema guarantees matter more than raw horizontal scale.
- Event log → DynamoDB, not Aurora, because the access pattern is well-known in advance (by user, by time range — a natural composite key) and needs to scale to a volume a relational database's write throughput would struggle with — directly the DynamoDB decision framework from the Databases series, Part 7.
- Session cache → ElastiCache Redis, because it's explicitly ephemeral and never the source of truth — losing it means a load spike on Aurora as sessions repopulate, not permanent data loss, exactly the risk-profile distinction already made explicit earlier in this part.
- Monthly reporting → Athena against data exported to S3, rather than a standing Redshift cluster, because the access pattern (monthly, not continuous) doesn't justify paying for always-on warehouse compute — directly the Athena-vs-Redshift decision already worked through in this part's practice problems.
Why explicitly naming EACH workload's distinct requirements before picking its data layer — rather than defaulting to "just use Postgres for everything" or "just use DynamoDB for everything" — is worth practicing as a habit: a genuinely strong answer to "design the data layer" demonstrates that different parts of the SAME system can, and often should, use different, purpose-fit data stores, exactly the "polyglot persistence" concept already introduced in the Databases series (Part 5), now applied concretely with real AWS service names.
Common Mistakes#
| Mistake | Why It's Wrong | Fix |
|---|---|---|
| Querying an RDS Multi-AZ standby directly for read scaling | The Multi-AZ standby is NOT readable — it exists purely for failover | Use a dedicated Read Replica for read scaling instead |
| Trying to SSH into RDS to tune engine configuration files directly | RDS deliberately doesn't allow this — it's a managed service, not a self-managed EC2 instance | Use RDS Parameter Groups for all engine-level tuning |
| Assuming Aurora replication works identically to standard MySQL binlog replication | Aurora's replication happens at the distributed storage layer, below the database engine, giving much lower replica lag | Understand Aurora's storage architecture as genuinely different, not "MySQL with a marketing name" |
| Using Redshift for routine transactional (OLTP) application queries | Redshift's columnar storage is optimized for analytical scans, not fast small-record lookups | Use RDS/Aurora/DynamoDB for OLTP; reserve Redshift for OLAP/analytical workloads |
| Attempting a single, long-downtime cutover for a large production database migration | Unnecessarily long downtime window when a CDC-based approach could minimize it dramatically | Use DMS with full-load-and-cdc to minimize the actual cutover window |
| Sizing ElastiCache Redis cluster mode as "disabled" for a workload with genuinely high write throughput needs | A single primary shard's write capacity becomes the ceiling | Enable cluster mode to shard writes horizontally once single-node capacity is genuinely insufficient |
Worked Practice Problems#
Problem 1: An application experiences read-heavy traffic, and the team configures their RDS Multi-AZ deployment to send read queries directly to the standby instance to offload the primary. Reads start failing entirely. What's the misunderstanding, and what's the correct fix?
Answer: The Multi-AZ standby is not a readable replica at all — it exists purely as a synchronous failover target, and RDS does not expose it for direct querying, which is exactly why the reads are failing outright rather than just being slow or stale. The correct fix is provisioning a dedicated RDS Read Replica (a separate, genuinely queryable, asynchronously-replicating instance) and explicitly routing read traffic to its own distinct endpoint — accepting the real, if usually small, replication lag tradeoff that comes with asynchronous replication, as already covered in the Databases series.
Problem 2: A team migrating from self-hosted MySQL to AWS is deciding between RDS for MySQL and Aurora MySQL, and specifically cares about minimizing read replica lag for a read-heavy reporting dashboard that must reflect near-real-time data. Which would you recommend, and why?
Answer: Aurora MySQL, specifically because of its storage architecture. RDS MySQL Read Replicas use traditional binlog-based replication, where lag is a function of how fast the replica can replay the primary's binlog stream — typically tens to hundreds of milliseconds, sometimes more under load. Aurora replicas share the SAME underlying distributed storage layer as the writer instance, rather than replaying a separate binlog stream, resulting in typically single-digit-millisecond replica lag — a meaningfully better fit for a near-real-time reporting requirement, at the cost of Aurora's marginally different (though wire-compatible) operational model.
Problem 3: An organization wants to run ad hoc analytical queries against several years of historical sales data currently sitting as CSV files in S3, without provisioning any dedicated database infrastructure, since the queries are infrequent (a few times per week). What AWS approach fits this, and why would a Redshift cluster be the wrong choice here?
Answer: Amazon Athena, combined with AWS Glue to catalog the S3 data's schema, fits this need well — Athena runs standard SQL directly against the S3 data with zero infrastructure to provision or manage, and its pay-per-query pricing model matches the infrequent access pattern well. A dedicated Redshift cluster would be the wrong choice here specifically because it requires provisioning and continuously paying for standing compute nodes regardless of how often queries actually run — a poor fit for infrequent, ad hoc access, and the kind of unnecessary fixed cost the FinOps/cost-optimization theme of this series flags as worth avoiding when a serverless, pay-per-use alternative fits the actual usage pattern better.
Problem 4: A serverless application built on Lambda experiences database connection failures during traffic spikes, with RDS reporting it has hit its maximum connection limit, even though the actual query load per connection is light. The team's first instinct is to vertically scale the RDS instance to a larger type specifically to raise the connection limit. Is that the right fix, and what would you recommend instead?
Answer: Vertically scaling primarily to raise the connection limit treats a symptom, not the root cause, and is a genuinely expensive way to solve what's actually a connection-management problem, not a compute-capacity problem — the query load per connection is explicitly described as light, meaning the instance likely has plenty of spare CPU/memory capacity already. The better fix is RDS Proxy, placed between the Lambda functions and the database: it maintains a much smaller, pooled set of actual database connections and multiplexes the many short-lived, concurrent Lambda-originated connections onto that pool, directly addressing the real problem (too many simultaneous direct connections) without needing to pay for a larger database instance whose extra compute capacity wouldn't even be the bottleneck.
Problem 5: A team is migrating a production RDS MySQL database that was created without encryption at rest several years ago, and a new compliance requirement now mandates encryption at rest for all production databases. An engineer proposes simply enabling an encryption setting on the existing instance. What's wrong with that plan, and what's the correct migration path?
Answer: RDS encryption at rest can only be set at instance CREATION time — there is no configuration flag that can be flipped on an existing, already-unencrypted instance to encrypt it in place, unlike EBS's account-wide default-encryption setting which only affects NEW volumes going forward. The correct migration path is: take a snapshot of the existing unencrypted instance, copy that snapshot while specifying a KMS key (snapshot copying is the specific operation that supports adding encryption), then restore a brand-new RDS instance from the now-encrypted snapshot copy — followed by a planned cutover (updating the application's connection endpoint, ideally during a low-traffic maintenance window) from the old unencrypted instance to the new encrypted one, since this is fundamentally a new-instance migration, not an in-place configuration change.
Summary and What's Next#
- RDS applies the Shared Responsibility Model to real database engines (MySQL, PostgreSQL, and others) — AWS handles patching and infrastructure-level failover; you handle schema, queries, and tuning through Parameter Groups.
- RDS Multi-AZ provides a synchronous, non-readable failover standby; RDS Read Replicas provide asynchronous, genuinely queryable read scaling — a critical, frequently-tested distinction.
- Aurora is a re-engineered storage layer (6-way replication across 3 AZs, quorum-based durability), not just "managed MySQL" — resulting in dramatically lower replica lag and stronger AZ-failure tolerance than standard RDS.
- Aurora Serverless v2 and Aurora Global Database extend this further with automatic compute scaling and sub-second cross-region replication, respectively.
- ElastiCache (Redis/Memcached) is the managed caching layer directly implementing the Capacity Planning series' caching concepts — losing a cache node is a load spike, not data loss, since a cache is never the source of truth.
- Redshift (OLAP, columnar) serves a fundamentally different workload shape than RDS/Aurora/DynamoDB (OLTP, row-oriented); Athena + Glue offer a serverless alternative for infrequent, ad hoc analytical queries directly against S3.
- DMS minimizes migration downtime via full-load-and-CDC, streaming ongoing source-database changes until the actual cutover moment.
Continue to Part 7 (07-containers-and-serverless.md) to see how application compute — not just the database layer — runs on AWS beyond raw EC2 instances, through ECS, Fargate, and Lambda.