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.
Surface of change
Section titled “Surface of change”Five surfaces in the infrastructure repo:
src/main/cdk/constructs/storage/aurora-postgres-cluster.ts— widen the construct’sConfigurationinterface with a newdbConfigurationsub-interface; build the parameter group internally; pass instance class through to the writer/reader.src/main/cdk/stacks/purpose/purpose-storage.ts— propagate the new sub-interface from the stack’sConfigurationto the construct.src/main/cdk/apps/Al1x/partition.ts— readpartition.dbConfigurationfrom thePartitionInfoand forward it into the stack.src/main/cdk/platforms.ts— extendPartitionInfo/Partitionto carry an optionaldbConfigurationfield; populate per-partition values insideENVIRONMENTS.- No changes to
amm.sh, the per-instance entry files (Alpha001/prod.ts, etc.), or any chart. The per-instance entries already drive everything throughplatforms.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”1.1 New sub-interface in Configuration
Section titled “1.1 New sub-interface in Configuration”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,);1.4 Backwards-compatibility contract
Section titled “1.4 Backwards-compatibility contract”When dbConfiguration is undefined:
instanceTypedefaults toT3.MEDIUM(today’s value).parameterGroupstaysundefined(today’s value — cluster uses Aurora’s default group).applyImmediatelystaystrue(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.ts — Partition 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.
4.1 Interface + class shape
Section titled “4.1 Interface + class shape”// at the top of the file, alongside the existing importsimport * 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... }}4.2 Per-partition values in ENVIRONMENTS
Section titled “4.2 Per-partition values in ENVIRONMENTS”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).
5. No-op surfaces
Section titled “5. No-op surfaces”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 inplatforms.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).
6. Test expectations
Section titled “6. Test expectations”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):
- Construct-level: snapshot of the synthesized
AWS::RDS::DBClusterParameterGroupresource when adbConfiguration.parameterGroupSettingsblock is provided — asserts theParametersmap contains exactly the keys we expect with the values from the input. - Construct-level: snapshot of
AWS::RDS::DBClusterParameterGroupis absent when noparameterGroupSettingsare provided, and the cluster’sDBClusterParameterGroupNamefalls 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).
7. Verification at synth time
Section titled “7. Verification at synth time”cd /Users/jmp/code/arda/projects/product-slow-responses-worktrees/infrastructurenpm run buildnpm run synth:Alpha001:prod # or whatever the exact app target isThe resulting cdk.out/Alpha001-prod-AuroraDBCluster.template.json
should contain:
AWS::RDS::DBClusterParameterGroupresource withFamily: aurora-postgresql16(matches engine) and the prescribed parameter map (shared_preload_libraries,log_*,pg_stat_statements.track,max_connections=500).- The cluster resource’s
DBClusterParameterGroupNamereferencing the new group. - The writer + reader1
AWS::RDS::DBInstanceresources withDBInstanceClass: db.r7g.large. ApplyImmediately: falseon the instance resources (the prod-only override).
For non-prod synth targets:
- Same parameter group + same parameter map (minus
max_connections). DBInstanceClass: db.t3.mediumpreserved.ApplyImmediately: true.
8. Out of scope (deliberately)
Section titled “8. Out of scope (deliberately)”pg_stat_statements.max— leave at the default 5000 (per Q7). Not exposed on the new sub-interface; can be added later by extendingParameterGroupSettingsif/when needed.- Reader-instance routing for the bitemporal read workload — separate future concern; not part of PDEV-479.
- Aurora Serverless v2 — the construct keeps
serverlessV2settings untouched today; this work doesn’t add serverless support even though the field exists. - Per-cluster
preferredMaintenanceWindow— alreadyundefinedin 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_statementsCREATE EXTENSION — PDEV-498’s job (separate PR #30; under review at time of writing).
9. Cross-references
Section titled “9. Cross-references”_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-DBCREATE EXTENSIONhalf 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).
Copyright: © Arda Systems 2025-2026, All rights reserved