Skip to content

PDEV-479 — Aurora cluster CDK changes: detailed specification

Detailed specification of the code changes for PDEV-479. Pair with db-plan.md for the execution sequence. The goal of this file is to be readable by an engineer (or a follow-on agent) without having to re-derive the design decisions.

Five surfaces in the infrastructure repo:

  1. src/main/cdk/constructs/storage/aurora-postgres-cluster.ts — widen the construct’s Configuration interface with a new dbConfiguration sub-interface; build the parameter group internally; pass instance class through to the writer/reader.
  2. src/main/cdk/stacks/purpose/purpose-storage.ts — propagate the new sub-interface from the stack’s Configuration to the construct.
  3. src/main/cdk/apps/Al1x/partition.ts — read partition.dbConfiguration from the PartitionInfo and forward it into the stack.
  4. src/main/cdk/platforms.ts — extend PartitionInfo / Partition to carry an optional dbConfiguration field; populate per-partition values inside ENVIRONMENTS.
  5. No changes to amm.sh, the per-instance entry files (Alpha001/prod.ts, etc.), or any chart. The per-instance entries already drive everything through platforms.ENVIRONMENTS; the divergence lives in that single source of truth (per Q15).

A separate Linear comment on PDEV-479 records the instance-class decision (per Q16 (a)). A separate file under documentation/src/content/docs/process/operation-notes/ records each manual operation (per Q16 (b)).

1. aurora-postgres-cluster.ts — widened construct

Section titled “1. aurora-postgres-cluster.ts — widened construct”

Add a DbConfiguration sub-interface (or whatever name the implementer prefers — AuroraTuning, PostgresClusterTuning, …) on the existing Configuration interface. Single field today, designed to grow:

export interface DbConfiguration {
// Writer + reader instance class. If undefined, defaults to T3.MEDIUM
// (today's value), preserving behaviour for partitions that don't
// override.
readonly instanceType?: ec2.InstanceType;
// Parameters that go into a custom DB cluster parameter group built
// inside this construct. If undefined, the cluster uses Aurora's
// default group with none of these set.
readonly parameterGroupSettings?: ParameterGroupSettings;
// Whether instance-level config changes (parameter group reattach,
// instance class swap) apply in the deploy step or queue as
// pending-reboot. Default `true` preserves today's behaviour;
// prod overrides to `false` so the operator triggers the reboot
// explicitly per the operations-notes process.
readonly applyImmediately?: boolean;
}
export interface ParameterGroupSettings {
// Library to preload at server start. Static parameter — instance
// restart required to activate. Set to `pg_stat_statements` for the
// PDEV-479 rollout.
readonly sharedPreloadLibraries?: string;
// Maximum number of concurrent client connections to the cluster.
// Static parameter — instance restart required to activate. Leave
// undefined to use Aurora's instance-class-derived default. Set to
// 500 for prod (per PDEV-479 § 2).
readonly maxConnections?: number;
// Dynamic — propagates within ~1 minute of the parameter group
// attach. Slow-query threshold in ms. Set to 500 for the project
// rollout (per PDEV-479 § 1).
readonly logMinDurationStatementMs?: number;
// Dynamic. Recommended values for the project rollout:
// logStatement = 'ddl'
// logLockWaits = true
// logTempFilesBytes = 0 (log every temp file regardless of size)
// pgStatStatementsTrack = 'all'
readonly logStatement?: "none" | "ddl" | "mod" | "all";
readonly logLockWaits?: boolean;
readonly logTempFilesBytes?: number;
readonly pgStatStatementsTrack?: "none" | "top" | "all";
}

Configuration extends to:

export interface Configuration {
locator: purpose.Locator;
postgresVersion: rds.AuroraPostgresEngineVersion;
dbConfiguration?: DbConfiguration; // new, all fields optional
}
export const defaultConfiguration = {
postgresVersion: rds.AuroraPostgresEngineVersion.VER_16_6,
};

1.2 Parameter-group resource built internally

Section titled “1.2 Parameter-group resource built internally”

Inside the AuroraPostgresCluster constructor, before instantiating the rds.DatabaseCluster, build the parameter group resource only if dbConfiguration.parameterGroupSettings is provided:

const pgSettings = props.dbConfiguration?.parameterGroupSettings;
const parameterGroup: rds.IParameterGroup | undefined = pgSettings
? new rds.ParameterGroup(this, "ClusterParameterGroup", {
engine: rds.DatabaseClusterEngine.auroraPostgres({
version: props.postgresVersion,
}),
parameters: buildParameterMap(pgSettings),
})
: undefined;

buildParameterMap is a small local helper that converts the typed settings into the {[key:string]: string} shape rds.ParameterGroup expects. Suggested implementation:

function buildParameterMap(
pg: ParameterGroupSettings,
): { [key: string]: string } {
const map: { [key: string]: string } = {};
if (pg.sharedPreloadLibraries !== undefined) {
map.shared_preload_libraries = pg.sharedPreloadLibraries;
}
if (pg.maxConnections !== undefined) {
map.max_connections = String(pg.maxConnections);
}
if (pg.logMinDurationStatementMs !== undefined) {
map.log_min_duration_statement = String(pg.logMinDurationStatementMs);
}
if (pg.logStatement !== undefined) {
map.log_statement = pg.logStatement;
}
if (pg.logLockWaits !== undefined) {
map.log_lock_waits = pg.logLockWaits ? "1" : "0";
}
if (pg.logTempFilesBytes !== undefined) {
map.log_temp_files = String(pg.logTempFilesBytes);
}
if (pg.pgStatStatementsTrack !== undefined) {
map["pg_stat_statements.track"] = pg.pgStatStatementsTrack;
}
return map;
}

Note the {} for pg_stat_statements.track — CDK’s parameters object key needs the literal Postgres parameter name including the dot. TypeScript object-key syntax allows this when the property name is a string literal.

1.3 Pass parameter group + instance type into defaultDbProps

Section titled “1.3 Pass parameter group + instance type into defaultDbProps”

defaultDbProps() currently hardcodes T3.MEDIUM for both writer and reader and passes parameterGroup: undefined. Change the function to accept the resolved values and weave them in:

export function defaultDbProps(
namePrefix: string,
props: Props,
parameterGroup: rds.IParameterGroup | undefined,
): rds.DatabaseClusterProps {
const instanceType =
props.dbConfiguration?.instanceType ??
ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MEDIUM);
const applyImmediately = props.dbConfiguration?.applyImmediately ?? true;
const defaultInstanceConfig = {
autoMinorVersionUpgrade: true,
enablePerformanceInsights: true,
performanceInsightRetention: rds.PerformanceInsightRetention.DEFAULT,
performanceInsightEncryptionKey: undefined,
publiclyAccessible: false,
availabilityZone: undefined,
preferredMaintenanceWindow: undefined,
parameters: undefined,
allowMajorVersionUpgrade: false,
parameterGroup: undefined, // cluster-level pg lives on the cluster, not per-instance
isFromLegacyInstanceProps: false,
caCertificate: undefined,
applyImmediately, // <— now driven by dbConfiguration
instanceType, // <— now driven by dbConfiguration
};
return {
// ...all existing fields unchanged...
parameterGroup, // <— cluster-level parameter group
writer: rds.ClusterInstance.provisioned("writer", {
...defaultInstanceConfig,
instanceIdentifier: `${namePrefix}-AuroraClusterWriter`,
promotionTier: 0,
}),
readers: [
rds.ClusterInstance.provisioned("reader1", {
...defaultInstanceConfig,
instanceIdentifier: `${namePrefix}-AuroraClusterReader1`,
promotionTier: 0,
}),
],
// ...rest unchanged...
};
}

The construct constructor passes the just-built parameterGroup into defaultDbProps:

const rdsProps: rds.DatabaseClusterProps = defaultDbProps(
fqn,
props,
parameterGroup,
);

When dbConfiguration is undefined:

  • instanceType defaults to T3.MEDIUM (today’s value).
  • parameterGroup stays undefined (today’s value — cluster uses Aurora’s default group).
  • applyImmediately stays true (today’s value).

Existing partitions that don’t yet have a dbConfiguration on their PartitionInfo see no behavioural change.

2. purpose-storage.ts — stack-level propagation

Section titled “2. purpose-storage.ts — stack-level propagation”

PurposeAuroraClusterStack’s Configuration interface gets the same optional field:

export interface Configuration {
readonly locator: purpose.Locator;
readonly postgresVersion: rds.AuroraPostgresEngineVersion;
readonly dbConfiguration?: auroraPostgres.DbConfiguration;
}

Inside the constructor, pass dbConfiguration through to the AuroraPostgresCluster construct invocation:

const cluster = new auroraPostgres.AuroraPostgresCluster(
this,
"AuroraPostgresCluster",
{
locator: props.locator,
postgresVersion: props.postgresVersion,
vpc: props.vpc,
securityGroups: props.securityGroups,
dbConfiguration: props.dbConfiguration,
},
);

No changes to exports / dashboards / outputs.

3. apps/Al1x/partition.ts — app-level wiring

Section titled “3. apps/Al1x/partition.ts — app-level wiring”

buildPartition already receives a partition: Partition parameter. Read partition.dbConfiguration and forward it to the stack:

const dbCluster = new purposeStorage.PurposeAuroraClusterStack(
app,
`${partitionPrefix}-AuroraDBCluster`,
{
locator: partition.locator,
postgresVersion: rds.AuroraPostgresEngineVersion.VER_16_6,
vpc: importedStack.vpc,
securityGroups: sgs,
dbConfiguration: partition.dbConfiguration,
...partialStackProps,
},
);

4. platforms.tsPartition carries the configuration

Section titled “4. platforms.ts — Partition carries the configuration”

The PartitionInfo interface and Partition class learn one new optional field. The ENVIRONMENTS block populates it per partition.

// at the top of the file, alongside the existing imports
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as auroraPostgres from "arda/constructs/storage/aurora-postgres-cluster";
export interface PartitionInfo {
id: string;
signInCaseSensitive: boolean | undefined;
dbConfiguration?: auroraPostgres.DbConfiguration; // new
}
export class Partition implements PartitionInfo {
readonly id: string;
readonly signInCaseSensitive: boolean | undefined;
readonly dbConfiguration?: auroraPostgres.DbConfiguration; // new
// ...
constructor(infra: Infrastructure, cfg: PartitionInfo) {
this.parent = infra;
this.id = cfg.id;
this.signInCaseSensitive = cfg.signInCaseSensitive;
this.dbConfiguration = cfg.dbConfiguration; // new
// ...rest unchanged...
}
}

Add dbConfiguration to each of the four production-facing partitions. Per Q15, all four are explicitly parameterised — even the partitions that keep today’s values — so consistency is visible in one place.

Shared parameter-group settings (identical for all four environments — per db-configuration.md § 1):

const sharedAuroraParameterGroupSettings: auroraPostgres.ParameterGroupSettings = {
sharedPreloadLibraries: "pg_stat_statements",
logMinDurationStatementMs: 500,
logStatement: "ddl",
logLockWaits: true,
logTempFilesBytes: 0,
pgStatStatementsTrack: "all",
// maxConnections deliberately omitted here — set per-partition below
};

Per-partition dbConfiguration values:

// Inside Alpha001 partitions:
{
id: "prod",
signInCaseSensitive: undefined,
dbConfiguration: {
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.R7G,
ec2.InstanceSize.LARGE,
),
applyImmediately: false, // operator reboots explicitly
parameterGroupSettings: {
...sharedAuroraParameterGroupSettings,
maxConnections: 500, // prod-only
},
},
},
{
id: "demo",
signInCaseSensitive: undefined,
dbConfiguration: {
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3,
ec2.InstanceSize.MEDIUM,
),
applyImmediately: true,
parameterGroupSettings: sharedAuroraParameterGroupSettings,
},
},
// Inside Alpha002 partitions:
{
id: "dev",
signInCaseSensitive: undefined,
dbConfiguration: {
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3,
ec2.InstanceSize.MEDIUM,
),
applyImmediately: true,
parameterGroupSettings: sharedAuroraParameterGroupSettings,
},
},
{
id: "stage",
signInCaseSensitive: undefined,
dbConfiguration: {
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3,
ec2.InstanceSize.MEDIUM,
),
applyImmediately: true,
parameterGroupSettings: sharedAuroraParameterGroupSettings,
},
},

SandboxKyle002:kyle keeps no dbConfiguration (the partition is deprecated per PDEV-438; leaving it untouched preserves today’s behaviour).

  • amm.sh — no change. CDK-only delta.
  • Alpha001/prod.ts, Alpha001/demo.ts, Alpha002/dev.ts, Alpha002/stage.ts — no change. Per Q15, divergence lives in platforms.ts, not in the per-instance entry files.
  • Operations chart — no change. HikariCP config (connectionTimeout=30000, validationTimeout=1000) rides out the failover (verified per Q13).

The infrastructure repo uses Jest snapshot tests under src/test/cdk/. Two new assertions to add (paths approximate; align with the existing snapshot-test convention as discovered during implementation):

  1. Construct-level: snapshot of the synthesized AWS::RDS::DBClusterParameterGroup resource when a dbConfiguration.parameterGroupSettings block is provided — asserts the Parameters map contains exactly the keys we expect with the values from the input.
  2. Construct-level: snapshot of AWS::RDS::DBClusterParameterGroup is absent when no parameterGroupSettings are provided, and the cluster’s DBClusterParameterGroupName falls back to the engine default.

Stack-level snapshot updates from per-instance values are picked up by existing snapshot tests; expect deliberate snapshot updates for Alpha001-prod (instance class change), Alpha001-demo, Alpha002-dev, Alpha002-stage (parameter group attach but no instance-class change).

Terminal window
cd /Users/jmp/code/arda/projects/product-slow-responses-worktrees/infrastructure
npm run build
npm run synth:Alpha001:prod # or whatever the exact app target is

The resulting cdk.out/Alpha001-prod-AuroraDBCluster.template.json should contain:

  • AWS::RDS::DBClusterParameterGroup resource with Family: aurora-postgresql16 (matches engine) and the prescribed parameter map (shared_preload_libraries, log_*, pg_stat_statements.track, max_connections=500).
  • The cluster resource’s DBClusterParameterGroupName referencing the new group.
  • The writer + reader1 AWS::RDS::DBInstance resources with DBInstanceClass: db.r7g.large.
  • ApplyImmediately: false on the instance resources (the prod-only override).

For non-prod synth targets:

  • Same parameter group + same parameter map (minus max_connections).
  • DBInstanceClass: db.t3.medium preserved.
  • ApplyImmediately: true.
  • pg_stat_statements.max — leave at the default 5000 (per Q7). Not exposed on the new sub-interface; can be added later by extending ParameterGroupSettings if/when needed.
  • Reader-instance routing for the bitemporal read workload — separate future concern; not part of PDEV-479.
  • Aurora Serverless v2 — the construct keeps serverlessV2 settings untouched today; this work doesn’t add serverless support even though the field exists.
  • Per-cluster preferredMaintenanceWindow — already undefined in the construct (system-chosen). Operator-driven reboots (db-configuration.md § “Deploy and activation sequence” step 2) are how this work activates static parameters, not the maintenance window. Leave the window untouched.
  • Multi-region / DR cluster — out of scope.
  • pg_stat_statements CREATE EXTENSION — PDEV-498’s job (separate PR #30; under review at time of writing).
  • _docs/analysis/db-configuration.md — the goal/spec for the ticket; this file is the implementation-facing companion.
  • _docs/analysis/db-init.md — PDEV-498 (sub-issue) implementation proposal — the per-DB CREATE EXTENSION half of the observability story.
  • _docs/analysis/infrastructure-improvements.md § 2 + § 3 — the umbrella scope narrative for the parameter group + sizing.
  • _docs/pdev-479/implementation/infrastructure/db-plan.md — execution sequence (CDK PR, per-env rollout, operations-notes templates, decision-log).