🔄 Why RDS PostgreSQL over Aurora — Architectural Decision Record¶
This page documents the architectural reasoning behind choosing AWS RDS PostgreSQL over AWS Aurora PostgreSQL for the Portfolio Tracker application.
Context¶
When moving from a local SQLite database to a managed cloud database, two AWS-managed PostgreSQL options were evaluated:
- Amazon RDS for PostgreSQL — a traditional managed single-instance database
- Amazon Aurora PostgreSQL — AWS's cloud-native, re-architected PostgreSQL-compatible engine
Both services are fully managed, support the same PostgreSQL 16 wire protocol, and work identically with psycopg2 from the application's perspective. The decision came down to workload characteristics, cost, and operational simplicity.
Decision¶
Use AWS RDS PostgreSQL (single-instance, db.t3.medium) instead of Aurora PostgreSQL.
Rationale¶
1. Cost — The Dominant Factor¶
Aurora's pricing model is designed for high-throughput production workloads, not single-user portfolio tracking applications.
| Cost Component | RDS PostgreSQL | Aurora PostgreSQL |
|---|---|---|
| Compute | ~$0.068/hr (db.t3.medium) |
~$0.082/hr (db.t3.medium) — 20% more |
| Storage | $0.115/GB/month (gp3) | $0.10/GB/month — but minimum 10 GB billed |
| I/O | Included with gp3 | $0.20 per million I/Os — unpredictable |
| Multi-AZ | Optional | Required for Aurora cluster (writer + reader) |
| Estimated monthly | ~$50–65 | ~$90–150+ |
For a personal project with a single user and <1 GB of data, Aurora's I/O-based billing and cluster overhead would nearly double the cost with no tangible benefit.
2. Workload Profile — Low Concurrency, Low Throughput¶
The Portfolio Tracker has a very specific usage pattern:
| Characteristic | Value | Implication |
|---|---|---|
| Concurrent users | 1 (personal use) | No need for Aurora's connection pooling or read replicas |
| Write frequency | Batch CSV imports (weekly) | No sustained write pressure — Aurora's storage parallelises writes across distributed nodes and reduces I/O ~6× by shipping only WAL records (not full pages), but this only matters under heavy sustained load |
| Read frequency | Dashboard loads (a few times/day) | Single-instance read performance is more than sufficient |
| Data volume | <1 GB total | Aurora's 6-way replicated storage across 3 AZs is extreme overkill |
| Availability requirement | Best-effort (personal tool) | 99.9% SLA of RDS is adequate vs Aurora's 99.99% |
3. Operational Simplicity¶
graph LR
subgraph "RDS PostgreSQL"
RDS_SINGLE["🐘 Single DB Instance<br/>db.t3.medium<br/>One endpoint<br/>One billing line item"]
end
subgraph "Aurora PostgreSQL"
AURORA_W["🐘 Writer Instance<br/>db.t3.medium"]
AURORA_R["🐘 Reader Instance<br/>(optional but recommended)"]
AURORA_S["💾 Aurora Storage<br/>6-way replicated<br/>across 3 AZs"]
AURORA_W --> AURORA_S
AURORA_R --> AURORA_S
end
classDef rds fill:#27AE60,stroke:#1E8449,color:#fff,stroke-width:2px
classDef aurora fill:#E67E22,stroke:#CA6F1E,color:#fff,stroke-width:2px
classDef storage fill:#3498DB,stroke:#2471A3,color:#fff,stroke-width:2px
class RDS_SINGLE rds
class AURORA_W,AURORA_R aurora
class AURORA_S storage
| Aspect | RDS PostgreSQL | Aurora PostgreSQL |
|---|---|---|
| Architecture | Single instance | Cluster (writer + optional readers) |
| Endpoints | 1 (instance endpoint) | 2+ (cluster + reader endpoints) |
| Storage management | Standard EBS (gp3) — predictable | Aurora Storage — auto-scaling but I/O-billed |
| Failover | Optional Multi-AZ (standby) | Built-in (but adds cost) |
| CloudFormation | AWS::RDS::DBInstance |
AWS::RDS::DBCluster + AWS::RDS::DBInstance |
| Backups | Automated snapshots | Continuous backups (nice, but not required here) |
RDS is a single resource in CloudFormation. Aurora requires a cluster definition plus instance definitions — more configuration surface area for a single-user app.
4. Zero Application Code Changes¶
Both RDS PostgreSQL and Aurora PostgreSQL expose the same PostgreSQL endpoint on port 5432. The application connects via:
# db.py — identical for both RDS and Aurora
DATABASE_URL = os.environ.get('DATABASE_URL')
# postgresql://postgres:password@endpoint:5432/portfolio_tracker
conn = psycopg2.connect(DATABASE_URL, cursor_factory=RealDictCursor)
The migration required zero code changes — only the DATABASE_URL endpoint in the .env file changed.
5. Upgrade Path Remains Open¶
If the application grows to support multiple users or requires higher availability, migrating from RDS to Aurora is straightforward:
- Create an Aurora cluster from an RDS snapshot (
aws rds create-db-cluster --source-db-instance-identifier ...) - Update the
DATABASE_URLto point to the Aurora cluster endpoint - No application code changes needed
This makes RDS a safe starting point with a clear escalation path.
When Aurora Would Make Sense¶
Aurora would be the right choice if any of the following became true:
| Trigger | Why Aurora |
|---|---|
| Multiple concurrent users (>10) | Aurora's connection handling and read replicas distribute load |
| High write throughput | Aurora's distributed storage handles sustained writes better |
| Sub-second failover requirement | Aurora's failover is ~30s vs RDS's ~60–120s |
| Large dataset (>100 GB) | Aurora's storage auto-scaling and parallel query shine at scale |
| Read-heavy dashboards with many users | Aurora read replicas serve read traffic separately |
| Regulatory/compliance (financial data SLAs) | Aurora's 99.99% availability SLA |
Decision Summary¶
flowchart TD
START(["Choose PostgreSQL<br/>hosting for Portfolio Tracker"]) --> Q1{"How many<br/>concurrent users?"}
Q1 -->|"1 (personal)"| Q2{"Data volume?"}
Q1 -->|"10+"| AURORA["✅ Aurora PostgreSQL<br/>Read replicas · Auto-scaling"]
Q2 -->|"< 1 GB"| Q3{"Availability SLA?"}
Q2 -->|"> 100 GB"| AURORA
Q3 -->|"Best-effort"| RDS["✅ RDS PostgreSQL<br/>Simple · Predictable · Cost-effective"]
Q3 -->|"99.99%"| AURORA
classDef chosen fill:#27AE60,stroke:#1E8449,color:#fff,stroke-width:2px
classDef alt fill:#E67E22,stroke:#CA6F1E,color:#fff,stroke-width:2px
classDef question fill:#3498DB,stroke:#2471A3,color:#fff,stroke-width:2px
classDef start fill:#9B59B6,stroke:#7D3C98,color:#fff,stroke-width:2px
class RDS chosen
class AURORA alt
class Q1,Q2,Q3 question
class START start
For a single-user personal portfolio tracker with batch data loading and low query volume, RDS PostgreSQL delivers the same PostgreSQL experience at roughly half the cost with simpler operations. Aurora remains the natural upgrade path if the application ever outgrows RDS.
Impact on Infrastructure¶
The CloudFormation stack uses AWS::RDS::DBInstance — a single managed resource:
# From portfolio-tracker-stack.yaml
PostgresDB:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: portfolio-tracker-db
Engine: postgres
EngineVersion: "16"
DBInstanceClass: db.t3.medium
# ... storage, networking, security groups
No cluster management, no reader endpoints, no I/O cost surprises — just a single, predictable database instance in a private subnet.