Skip to content

RDS Postgres

2 min read · main part only; folded sections are optional

After this you can answer: Where does the data live, who can reach it, and what happens when it is down?

flowchart TB
API["api task<br/>uses api-sg"] -->|"TCP 5432, TLS"| DB[("minitickets-dev-db<br/>Postgres 17")]
MIG["migration task<br/>uses api-sg"] -->|"TCP 5432"| DB
SM["RDS master secret"] -.->|"DB_USER, DB_PASSWORD"| API
  1. Reach. The database sits in the private subnets. The api tasks reach it inside the VPC through the local route.
  2. Allowed. db-sg allows TCP 5432 only from api-sg.
  3. Password. The api gets the user and password from the RDS master secret at task start.
  4. TLS. RDS forces SSL (rds.force_ssl), and the api checks the server’s certificate (rejectUnauthorized: true), so nobody in the middle can pretend to be the database.

The instance is minitickets-dev-db: Postgres 17 on one db.t4g.micro (ARM, 1 GB RAM), 20 GB gp3 storage, encrypted at rest, publiclyAccessible: false (infra/src/data/postgres.ts → createPostgres).

Dev settings keep it cheap: single-AZ, backups kept for 1 day, no final snapshot and no deletion protection. So switching dataEnabled off deletes the data.

The api’s connection pool holds at most 10 connections, waits 2 s for a free one and stops any statement after 5 s (apps/api/src/db/client.ts). A slow database fails fast instead of piling up requests.

From → toWhat happensWhat allows it
RDS Postgres → Private subnetsonly when dataEnabled = truethe database lives in the private subnets
  • config DB subnet group minitickets-dev-db-subnets (both private subnets), publiclyAccessible: falsecreatePostgres
RDS Postgres → Database security grouponly when dataEnabled = truethe database uses the db security group
API ECS service → RDS Postgresonly when computeEnabled = trueSQL over TLS on port 5432
  • sg-rule db-sg inbound rule minitickets-dev-db-in-5432: TCP 5432 from api-sgcreateSecurityGroups
  • route local route 10.0.0.0/16: a public subnet reaches a private subnet without leaving the VPCcreateNetwork
  • secret DB_USER and DB_PASSWORD from the RDS master secret (<arn>:username::, <arn>:password::)createApiTask
Migration task definition → RDS Postgresonly when computeEnabled = trueruns node dist/migrate.js against the database
API app → RDS Postgresonly when dataEnabled = truequeries/*.ts run the SQL; a booking locks the event row (SELECT … FOR UPDATE)
Transactional outbox → RDS Postgresonly when dataEnabled = truethe outbox is a Postgres table
  • code queries/outbox.ts deliverBatch reads pending rows in a transactiondeliverBatch
See Postgres on the map →

RDS → Databases → minitickets-dev-db → Connectivity & security: Endpoint, VPC security groups, Publicly accessible: No.

SymptomCauseWhere to look
Timeouts on 5432, /ready failsdb-sg does not allow api-sgdb-sg → Inbound rules
Connection refused: no encryptionThe client connected without SSLapps/api/src/config/database.ts
All data gone after switching data on againdataEnabled: false deleted it, no final snapshotThe CD run that switched it off
What does Multi-AZ add?

A standby copy in a second availability zone, kept in sync. If the primary fails, RDS switches the endpoint to the standby in about a minute or two. It roughly doubles the cost, which is why dev is single-AZ.

What do users see when Postgres is down?

The events list may still come from the Valkey cache (served stale on error). Booking, sign-up and log-in fail. /health still answers 200, so the ALB keeps the task, while /ready reports the database down.

Why a relational database and not DynamoDB here?

A booking must check seats and write the booking in one transaction with a row lock. Postgres does that natively. DynamoDB is used where a simple key lookup is enough, like processed jobs.

Advanced: Connections and the pool

Ten connections per task is a deliberate cap: a db.t4g.micro allows only a limited number of connections, and each waiting booking holds one while it waits for the row lock. The SSL settings and the CA bundle are in apps/api/src/config/database.ts → DatabaseEnvSchema.

Answer these without scrolling up. If one feels hard, reread that section.

  1. Which subnets hold the database, and why?
  2. Which rule lets the api in, and who else uses that group?
  3. What two settings make the connection safe from a fake database?
  4. What happens to the data when dataEnabled is switched off?
  5. Why does the pool stop a statement after 5 seconds?

Redraw it from memory: the api and migration tasks connecting to Postgres on 5432, and the secret beside them

Try this: on the map, play 'Why can the api connect to Postgres?' and name the security group rule it uses

Read this book online: https://book.theversehub.in/