· nervico-team · cloud-architecture · 10 min read
AWS Databases: RDS, Aurora, and DynamoDB Compared
Technical comparison of RDS, Aurora, and DynamoDB on AWS: performance, real costs, usage patterns, limitations, and clear criteria for choosing the right database.
AWS offers 15 different database services. But for 90% of applications, the decision comes down to three: RDS, Aurora, and DynamoDB. Each serves a different use case, and choosing the wrong one costs money, performance, and engineering time.
RDS is the conservative option: managed relational databases with familiar engines. Aurora is the evolution of RDS: compatible with MySQL and PostgreSQL but with a storage engine redesigned by AWS. DynamoDB is the NoSQL option: single-digit millisecond latency at any scale, but with a data model that demands different thinking.
This article compares the three services with verified data, real costs, and concrete scenarios. There is no universally better database. There is a correct database for each problem.
RDS: Managed Relational Databases
What RDS Is
RDS (Relational Database Service) is a managed service that runs standard relational database engines on AWS infrastructure. AWS handles provisioning, OS patches, backups, and replication. Your team handles the schema, queries, and optimization.
Available engines:
- PostgreSQL: Versions 12 through 16. The most versatile engine for general applications.
- MySQL: Versions 5.7 and 8.0. The most popular database engine in the world.
- MariaDB: Versions 10.5 through 10.11. MySQL fork with performance improvements.
- Oracle: Standard Edition and Enterprise Edition. For legacy applications requiring Oracle.
- SQL Server: Express, Web, Standard, and Enterprise. For Microsoft ecosystems.
How It Works Internally
RDS runs the database on dedicated EC2 instances with EBS storage. But you do not have SSH access to the instance: AWS manages the operating system and database engine. Your access point is the connection endpoint (host:port).
Storage: RDS uses EBS volumes with three options:
| Type | IOPS | Recommended Use | Cost (eu-west-1) |
|---|---|---|---|
| gp3 | 3,000 base + up to 16,000 | General workloads | $0.0952/GB/month |
| io2 | Up to 256,000 | I/O-intensive workloads | $0.131/GB/month + $0.066/IOPS |
| Magnetic | Variable | Legacy only, do not use | $0.116/GB/month |
Recommendation: gp3 for 95% of cases. io2 only if you need more than 16,000 consistent IOPS.
High Availability with Multi-AZ
Multi-AZ creates a synchronous replica of your database in another availability zone. If the primary instance fails, AWS performs automatic failover in 60-120 seconds. The connection endpoint does not change.
# Create RDS instance with Multi-AZ
aws rds create-db-instance \
--db-instance-identifier my-database \
--db-instance-class db.r6g.large \
--engine postgres \
--engine-version 16.1 \
--master-username admin \
--master-user-password "SecurePassword123!" \
--allocated-storage 100 \
--storage-type gp3 \
--multi-az \
--vpc-security-group-ids sg-12345678 \
--db-subnet-group-name my-subnet-group \
--backup-retention-period 7 \
--storage-encrypted \
--kms-key-id alias/rds-encryption-keyMulti-AZ cost: It doubles the instance cost. A db.r6g.large goes from $0.252/hour to $0.504/hour. Not cheap, but for production it is mandatory.
Read Replicas for Scalable Reads
If your application is read-intensive (dashboards, reports, analytical queries), you can create up to 15 Read Replicas. Replicas are asynchronous: there is a delay (lag) of milliseconds to seconds between writing to the primary and availability on the replica.
# Terraform: Read Replica
resource "aws_db_instance" "read_replica" {
replicate_source_db = aws_db_instance.primary.identifier
instance_class = "db.r6g.large"
publicly_accessible = false
tags = {
Name = "read-replica-1"
Role = "read"
}
}Usage pattern: The application sends writes to the primary instance and reads to the replicas. ORM frameworks like SQLAlchemy, Sequelize, or TypeORM support this configuration natively.
RDS Costs
| Instance | vCPU | RAM | On-Demand Cost (eu-west-1) | Reserved 1 Year |
|---|---|---|---|---|
| db.t4g.micro | 2 | 1 GB | $12.41/month | $8.03/month |
| db.t4g.medium | 2 | 4 GB | $49.64/month | $32.12/month |
| db.r6g.large | 2 | 16 GB | $183.96/month | $116.07/month |
| db.r6g.xlarge | 4 | 32 GB | $367.92/month | $232.14/month |
| db.r6g.2xlarge | 8 | 64 GB | $735.84/month | $464.28/month |
Aurora: The Engine Redesigned by AWS
What Makes Aurora Different from RDS
Aurora is compatible with MySQL 8.0 and PostgreSQL (versions 13-16), but uses a completely different storage engine than RDS. Instead of writing data to a local EBS volume, Aurora writes to a distributed storage system that replicates data 6 times across 3 availability zones.
Primary instance -> Storage Layer (6 copies across 3 AZs)
^
Read Replicas (up to 15) |Practical implications:
- Faster failover: Aurora performs failover in under 30 seconds (vs 60-120 in RDS Multi-AZ).
- Read Replicas without lag: Aurora replicas read from the same storage layer as the primary. Lag is milliseconds, not seconds.
- Auto-expanding storage: Starts at 10 GB and grows automatically up to 128 TB. No need to pre-provision.
- Continuous backups: Aurora continuously backs up to S3 with no performance impact.
Aurora Serverless v2
Aurora Serverless v2 automatically scales compute capacity in increments of 0.5 ACU (Aurora Capacity Units). One ACU is approximately 2 GB of RAM.
# Terraform: Aurora Serverless v2
resource "aws_rds_cluster" "aurora_serverless" {
cluster_identifier = "my-aurora-cluster"
engine = "aurora-postgresql"
engine_mode = "provisioned"
engine_version = "16.1"
database_name = "myapp"
master_username = "admin"
master_password = "SecurePassword123!"
storage_encrypted = true
serverlessv2_scaling_configuration {
min_capacity = 0.5
max_capacity = 16
}
}
resource "aws_rds_cluster_instance" "serverless" {
cluster_identifier = aws_rds_cluster.aurora_serverless.id
instance_class = "db.serverless"
engine = aws_rds_cluster.aurora_serverless.engine
engine_version = aws_rds_cluster.aurora_serverless.engine_version
}When to use Serverless v2: Unpredictable workloads. If your application has traffic spikes followed by periods of low activity, Serverless v2 scales automatically and you pay only for consumed capacity.
When not to use it: Constant workloads. If your database uses 8 ACUs 24 hours a day, a provisioned instance (db.r6g.xlarge) is significantly cheaper.
Aurora vs RDS: Direct Comparison
| Feature | RDS PostgreSQL | Aurora PostgreSQL |
|---|---|---|
| Storage | EBS (gp3 or io2) | Distributed, 6 copies across 3 AZs |
| Max storage | 64 TB | 128 TB |
| Read Replicas | 15 (async, seconds of lag) | 15 (same storage, ms of lag) |
| Failover | 60-120 seconds | Under 30 seconds |
| Backup | Daily snapshots | Continuous to S3 |
| Storage scaling | Manual (resize EBS) | Automatic |
| Cost (db.r6g.large equiv.) | $183.96/month | $219.00/month |
| Storage cost | $0.0952/GB/month (gp3) | $0.10/GB/month |
| I/O cost | Included in gp3 | $0.20 per million requests |
Verdict: Aurora costs 15-20% more than RDS for equivalent workloads, but the difference is justified when you need fast failover, lag-free read replicas, or auto-scaling storage. For applications that can tolerate 2-minute failovers and seconds of replication lag, RDS is the more economical choice.
DynamoDB: NoSQL for Extreme Scale
Data Model
DynamoDB is not a relational database. It does not have tables with fixed schemas, does not support JOINs, and does not use SQL. It is a key-value and document store that offers single-digit millisecond latency at any scale.
Fundamental concepts:
- Table: The organizational unit. Conceptually equivalent to a relational table, but without a fixed schema.
- Item: A record in the table. Equivalent to a row. Each item can have different attributes.
- Partition Key (PK): The attribute DynamoDB uses to distribute data. Determines which physical partition stores each item.
- Sort Key (SK): Optional attribute that, combined with PK, allows storing multiple items with the same PK ordered by SK.
- GSI (Global Secondary Index): An index that enables querying the table by an attribute different from the original PK/SK.
Table Design: The Right Mindset
In a relational database, the design follows this process:
- Identify entities (Users, Orders, Products)
- Normalize (3NF)
- Create tables and relationships
- Write queries when you need them
In DynamoDB, the process is reversed:
- Identify all access patterns (queries your application needs)
- Design the table to support those patterns
- Denormalize aggressively
Example access patterns:
1. Get user by ID
2. Get all orders for a user
3. Get order by ID
4. Get orders for a user in a date range
5. Get top-selling products
Table design:
PK | SK | Attributes
------------------|----------------------|------------------
USER#u001 | PROFILE | name, email, plan
USER#u001 | ORDER#2024-01-15#o01 | total, status
USER#u001 | ORDER#2024-02-20#o02 | total, status
ORDER#o01 | METADATA | user_id, items, total
PRODUCT#p001 | METADATA | name, price, sales_countCapacity Modes
On-Demand: You pay per read and write performed. No need to plan capacity.
- Read: $0.25 per million read request units
- Write: $1.25 per million write request units
- Ideal for unpredictable workloads or new applications
Provisioned: You define the capacity units you need. Cheaper for predictable workloads.
- Read: $0.00013 per RCU per hour
- Write: $0.00065 per WCU per hour
- Auto Scaling adjusts capacity automatically within defined limits
Cost comparison for 100 million reads and 20 million writes per month:
| Mode | Read Cost | Write Cost | Total/Month |
|---|---|---|---|
| On-Demand | $25.00 | $25.00 | $50.00 |
| Provisioned (no reservation) | $14.04 | $14.04 | $28.08 |
| Provisioned (Reserved 1 year) | $8.42 | $8.42 | $16.84 |
Global Tables: Multi-Region Replication
DynamoDB Global Tables replicates data between AWS regions with eventual consistency. Each region is active for both reads and writes (active-active).
# Create table with global replication
aws dynamodb create-table \
--table-name MyGlobalTable \
--attribute-definitions \
AttributeName=pk,AttributeType=S \
AttributeName=sk,AttributeType=S \
--key-schema \
AttributeName=pk,KeyType=HASH \
AttributeName=sk,KeyType=RANGE \
--billing-mode PAY_PER_REQUEST \
--region eu-west-1
# Add replica in us-east-1
aws dynamodb update-table \
--table-name MyGlobalTable \
--replica-updates \
'[{"Create": {"RegionName": "us-east-1"}}]' \
--region eu-west-1Additional cost: Replicated writes are billed as additional writes in each destination region. If you write 1 million items in eu-west-1 and have a replica in us-east-1, you pay for 1 million writes in each region.
When to Use Each Service
Use RDS When
- Your application needs complex SQL queries with JOINs
- The team has relational database experience and there is no time to learn DynamoDB
- Access patterns are unclear and may change (SQL is flexible)
- You need standard ACID transactions
- Data volume is under 1 TB and traffic is predictable
Use Aurora When
- Everything above applies, but you also need fast failover and lag-free read replicas
- Data volume may grow significantly (up to 128 TB)
- You have read-intensive workloads requiring multiple replicas
- You want to leverage Aurora Serverless v2 for variable workloads
Use DynamoDB When
- You need single-digit millisecond latency at any scale
- Access patterns are well-defined and stable
- Data volume grows continuously (logs, events, IoT)
- You need global multi-region replication (Global Tables)
- Your architecture is serverless (Lambda integrates natively with DynamoDB)
Do Not Use DynamoDB When
- You need frequent ad-hoc queries (reports, exploratory analysis)
- Access patterns change frequently
- You need complex JOINs between entities
- Your team has no experience with NoSQL modeling and there is no time to learn
Combinations That Work
The decision is not always exclusive. Many architectures combine services:
Aurora for transactional data + DynamoDB for sessions and cache:
User -> API -> Aurora (orders, users, products)
DynamoDB (sessions, cart, preferences)RDS for the main application + DynamoDB for events and logs:
Application -> RDS (business data)
-> DynamoDB (audit log, events, activity)Aurora as primary database + ElastiCache for frequent queries:
Application -> ElastiCache (Redis) -> Cache hit: immediate response
-> Cache miss: query AuroraCommon Mistakes
Mistake 1: Choosing DynamoDB Because It Is Trendy
DynamoDB is an engineering tool, not a trend. If your application has unpredictable access patterns and needs flexible queries, PostgreSQL on RDS or Aurora is the right choice. We have seen teams choose DynamoDB for a simple CRUD and end up building a custom ORM that poorly reimplements what PostgreSQL does natively.
Mistake 2: Not Enabling Multi-AZ in Production
A single-AZ RDS instance has a 99.5% availability SLA (43 hours of potential downtime per year). With Multi-AZ, the SLA rises to 99.95% (4.38 hours). The cost difference is 2x. The business impact difference when your database goes down during peak hours is incalculable.
Mistake 3: Not Monitoring Replication Lag
If your application reads from Read Replicas and assumes data is up to date, a 5-second replication lag can cause user-visible inconsistencies. Monitor lag with CloudWatch and configure alerts if it exceeds 100ms.
Mistake 4: Over-Provisioning DynamoDB in Provisioned Mode
If you provision 10,000 WCU but use 500, you are paying 20 times more than necessary. Use Auto Scaling with reasonable limits or start with On-Demand until you understand your traffic patterns.
Conclusion
There is no universal database on AWS. RDS is the safe choice for teams with relational experience and applications with flexible access patterns. Aurora is improved RDS, justified when you need fast failover, lag-free read replicas, or auto-scaling storage. DynamoDB is the choice for extreme scale, guaranteed latency, and serverless architectures, but it demands rigorous data design that does not allow easy changes afterward.
The right decision depends on your access patterns, your data volume, your budget, and your team’s experience. If you are unsure which to choose, start with PostgreSQL on RDS. It is the most flexible option and the one that penalizes you least if you need to change direction.
If you need help designing your data layer on AWS, our consulting team has implemented these architectures in real products with millions of records. Request a free audit to evaluate your current data architecture.