Skip to content

🔄 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:

  1. Amazon RDS for PostgreSQL — a traditional managed single-instance database
  2. 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:

  1. Create an Aurora cluster from an RDS snapshot (aws rds create-db-cluster --source-db-instance-identifier ...)
  2. Update the DATABASE_URL to point to the Aurora cluster endpoint
  3. 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.