Skip to content

Design decisions

Each decision says what we chose, why, and what it costs. In an interview, the trade-off is the part that shows judgement. Decisions are added as chapters are written; Parts 0 to 6 are covered so far.

DecisionWhyTrade-offChapter
One account, region us-east-1, names from the stackSimple; a second stack gets its own names with no code changeDev and prod in one account would share a blast radiusAccount and region
No NAT gateway; tasks in public subnets with public IPsSaves about 30+ USD a month for one dev environmentTasks have public IPs; security groups do all the protectingRoute tables
Data in private subnets with only a local routeNo network path from the internet to Postgres or ValkeyThe databases cannot call out (they never need to)VPC and subnets
Security groups reference each other, not CIDRsAllows exactly the tasks that use a group; survives changing IPsA wrong reference fails silently as a timeoutSecurity groups
Separate execution and task roles; web has no task roleThe code never holds the power to read secretsMore roles to manageIAM roles
RDS manages the master passwordThe password never exists in code, config or Pulumi stateRunning tasks keep the old value after a rotationSecrets
Single-AZ RDS, 1 day of backups, no final snapshotCheap dev environmentSwitching data off deletes the dataRDS Postgres
One Valkey node, no backupsEverything in it is short-lived or rebuildableLosing it logs everyone outElastiCache Valkey
Immutable image tags (the git SHA), built once for ARM64Every running task maps to one commit; ARM is cheaperImages must match the ARM64 platformECR and images
One task each on Fargate Spot, circuit breaker onAbout 70% cheaper; failed deploys roll backA Spot reclaim means a short outageECS on Fargate
Service Connect for web ↔ api callsStays inside the VPC; works in CloudFront modeTwo proxy ports and two extra rulesECS on Fargate
ALB health check on /health, not /readyA database blip does not take every task outThe ALB keeps tasks that cannot reach PostgresLoad balancer
DNS at Cloudflare, records added by handThe domain was already thereManual step; a new ALB needs a CNAME updateDNS and certificates
CloudFront built but off (ALB-only mode)The new account needed CloudFront verificationNo edge caching; the ALB serves HTTPS itselfCloudFront
Two locks for CloudFront: prefix list + secret headerOther people’s distributions share CloudFront’s IPsOne more secret to keep in stepCloudFront
DecisionWhyTrade-offChapter
Procedures → services → queries / queuesEach rule lives in one place; services test without a databaseMore files; booking checks break the rule on purposeLayers
Server-side sessions in Valkey, not JWTsInstant revocation, including log out everywhereOne Valkey lookup per requestSessions
Role read from Postgres on each admin requestGranting or removing admin counts at onceOne small query per admin requestRoles in the app
Services throw kinds; one mapper makes HTTP codesServices know no HTTP; internals never leakA plain Error becomes a 500Errors
Three cache layers, cleared on writeFast reads; changes show in millisecondsEvery booking clears every cached listCaching
SELECT … FOR UPDATE plus a CHECK constraintNo oversell; clear 404 / 400 / 409 answersA hot row on very popular eventsRow locks
Idempotency key in the booking rowCommits with the booking; can never disagreeKeys are kept forever (small)Idempotency
Keyset pagination and a trigram indexDeep pages stay cheap; contains-search is indexedNo jumping to page NSearch and pagination
Valkey rate limits (fail closed) and seat gate (fail open)Protects Postgres on sale day; Postgres stays the authorityThe counter can drift and needs repairRate limits
Waiting room, seat holds, autoscaling: explained, not builtNot needed at our trafficA very big sale would need themRate limits
Nonce CSP, Origin check on mutations, HSTS for 1 dayBlocks injected scripts and CSRF; safe start for HSTSHTML cannot be shared-cachedSecurity headers
DecisionWhyTrade-offChapter
Transactional outbox, relay inside the apiThe booking and its message commit together; no extra serviceAt-least-once: a crash can republish a messageThe outbox
SNS topic fanning out to one SQS queue per consumerThe api publishes once; email and tickets fail and retry apartTwo more queues and policies to runSNS fan-out
maxReceiveCount 3, backoff 10 / 40 / 120 s, DLQ kept 14 daysBad messages stop retrying; a person sees each oneA long outage sends healthy messages to the DLQSQS and the DLQ
Lambda outside the VPC, partial batch failures, maximumConcurrencyNo NAT; only failed messages retry; pollers never over-fetchIt cannot reach Postgres, so messages carry all they needThe Lambda worker
Idempotent worker in DynamoDB, keyed by booking, send then recordOne email per booking; a crash never loses oneA rare duplicate email in the crash windowDynamoDB
Private bucket, presigned URLs for 120 sFiles never pass through the api; the bucket stays privateA new link for every page viewS3 and presigned URLs
SES with Easy DKIM, still in the sandboxSigned mail; no production review needed to learnOnly verified recipients receive emailEmail with SES
DecisionWhyTrade-offChapter
CD starts on workflow_run after CI is green on mainOnly tested code can shipCD waits for the slowest CI jobCI
Build once for ARM64, promote the same bytesWhat was scanned is what runsRollback targets must be kept in ECRImages and scans
OIDC with exact sub checks; separate publish and deploy rolesNo stored keys; the code-installing job cannot deployThe deploy role is still broadOIDC
Migration as a one-off Fargate task between two phasesA failed migration leaves the running app untouchedMigrations must stay compatible with the old codeMigrations
Image tag in SSM, switches in the committed configNeither a laptop nor CD rolls back by accidentTwo places to look for “what runs”Two-phase deploy
Deploy guard against the last applied commitAn old approved run can no longer delete newer resourcesA deliberate infra rollback needs a manual flagRollback and the guard
A bootstrap script for the 14 resources CD needs to startCD cannot create the roles it logs in withOne manual, owner-run step per new accountBootstrap
DecisionWhyTrade-offChapter
JSON logs with a correlation id carried into messagesOne query follows a booking across the api and the workerLogs kept only 7 daysJSON logs
Four alarms on customer impact and stuck workFew alarms, each worth waking forSlow-but-working problems raise no alarmAlarms
One dashboard in code, parts follow the switchesFree, reviewed, never driftsNo database metrics on itThe dashboard
Billed layers behind committed switches; a warning-only budgetPay only while working; changes are reviewedSwitching data off deletes the dataCost switches
Runbooks with read-only checks first and no pasteable write commandsCalm, repeatable first minutesWriting and updating them takes timeRunbooks

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