galena
Architecture

Data stores

What lives in Aurora, DynamoDB, S3 and SSM, and why each is where it is.

StoreHoldsWritten byRead by
Aurora Serverless v2 (Postgres 16)The domain: everything people create, and every confirmed state changeThe API, the workersThe API, the workers
DynamoDB telemetryCheck results and detection stateThe evaluatorThe evaluator, the API (for the dashboard)
S3 config bucketmonitors.jsonThe workersThe probes
S3 page bucketsThe published status pageThe workers (primary); S3 replication (replica)CloudFront
SSM Parameter StoreSecrets and resource identifiersYou, and CDKThe API, CDK, you

Aurora

Aurora is the source of truth. It runs with a minimum of 0 capacity units and pauses after five idle minutes; the maximum is set per stage (auroraMaxAcu). It sits in isolated subnets and is reached only through the Data API, authorised by IAM. RDS keeps the master password in Secrets Manager and rotates it every 7 days.

The page's data model Groups hold components; components have monitors, daily uptime, and many incidents and maintenance windows through join tables; monitors keep each confirmed state change; incidents keep their updates. incident_component maintenance_component component_group # id name position component # id → group_id name manual_status monitor # id → component_id state, state_seq incident # id status, impact visibility started, resolved uptime_daily → component_id date minutes monitor_state_change → monitor_id seq, state at incident_update → incident_id status body maintenance # id status starts_at, ends_at 1 n 1 n 1 n 1 n 1 n
AreaTables
Identityuser, session, account, verification, two_factor, rate_limit
Workspaceworkspace, member, audit_log
Pagepage, component_group, component, page_component
Monitorsmonitor, monitor_state_change, uptime_daily
Incidentsincident, incident_update, incident_component, timeline_event
Maintenancemaintenance, maintenance_component
Notificationssubscriber, webhook_endpoint, delivery
Eventsoutbox

Conventions:

  • ids are UUID v7, generated in the application;
  • columns are snake_case, with created_at and updated_at on every table;
  • every table but the identity tables carries workspace_id;
  • incidents and maintenance windows are soft-deleted, for the record;
  • monitor holds each monitor's last confirmed state and its sequence number, which only moves forward; monitor_state_change keeps each transition once, unique on monitor and sequence;
  • uptime_daily holds the minutes spent in each status, per component per UTC day.

Migrations live in packages/db/migrations and run on every deploy from a Lambda in the API stack.

DynamoDB

One table, telemetry, provisioned inside the always-free tier and without point-in-time recovery: everything in it can be rebuilt by checking again.

ItemPartition keySort keyHolds
Check resultMON#<monitorId>R#<scheduledAt>#<region>Status, latency, phases, error; expires after 90 days
Monitor stateMON#<monitorId>STATEThe detection state, a version every write is conditional on, the last minute applied per region, and transitions not yet confirmed as triggered
Region healthREGION#<region>HEALTHWhether the region is excluded, and its run of canary passes

S3

BucketNotes
ConfigPrivate. One file, monitors.json: enabled monitors and their unfinished maintenance windows, sorted so the same state always writes the same bytes.
Page primaryPrivate, read by CloudFront. Files under pages/<slug>/. Versioned for replication; old versions expire after a day.
Page replicaThe same, in a second region, filled by replication.
DashboardPrivate, read by CloudFront. The Next.js static export.

SSM parameters

Under /galena/<stage>/:

ParameterKindSet by
auth-secret, app-key, trigger-secret-keySecureStringYou, once
public-urlStringThe web stack: the dashboard's address
database-cluster-arn, database-secret-arn, telemetry-table, check-results-queue-url, check-results-queue-arn, config-bucket, kms-key-arnStringThe foundation stack

On this page