Skip to content

Commit b6f42d8

Browse files
feat: default params match tf aurora-cluster [INFRA-82505] (#137)
* feat: default params match tf aurora-cluster [INFRA-82505] * chore: self mutation Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --------- Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 9c973e8 commit b6f42d8

File tree

4 files changed

+213
-18
lines changed

4 files changed

+213
-18
lines changed

API.md

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CLAUDE.md

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
`@time-loop/cdk-aurora` is an opinionated AWS CDK construct library for deploying PostgreSQL Aurora clusters with best practices baked in. It provides a high-level abstraction that includes user management, secrets rotation, RDS Proxy, and database provisioning through custom CloudFormation resources.
8+
9+
## Key Architecture Components
10+
11+
### Main Construct (`src/aurora.ts`)
12+
The `Aurora` class is the primary construct that orchestrates:
13+
- **Aurora PostgreSQL Cluster**: Configured with encryption, CloudWatch logs, and performance insights
14+
- **Three User Types**: Manager (admin), Writer (DML), and Reader (read-only) with separate SecretsManager secrets
15+
- **RDS Proxy**: Optional proxy for connection pooling (default enabled, incompatible with multi-user rotation)
16+
- **Custom Provisioners**: Lambda-backed CloudFormation custom resources for database and user provisioning
17+
- **Activity Stream**: Optional database activity monitoring
18+
19+
### Provisioning System
20+
Custom CloudFormation resources handle runtime database configuration:
21+
- **Database Provisioner** (`src/aurora.provision-database.ts`): Creates databases, schemas, and role grants
22+
- **User Provisioner** (`src/aurora.provision-user.ts`): Creates users, manages passwords, grants roles
23+
24+
### Multi-User Rotation Pattern
25+
Uses AWS SecretsManager multi-user rotation with `_clone` users. **WARNING**: Fundamentally incompatible with RDS Proxy (see README.md proxy section). The proxy immediately blocks the old user when rotation occurs, defeating the "window of opportunity" pattern.
26+
27+
## Common Commands
28+
29+
### Build and Test
30+
```bash
31+
# Full build (compile + test + package)
32+
pnpm build
33+
34+
# Compile TypeScript only
35+
pnpm compile
36+
37+
# Run tests
38+
pnpm test
39+
40+
# Run tests in watch mode
41+
pnpm test:watch
42+
43+
# Lint
44+
pnpm eslint
45+
```
46+
47+
### Running Single Tests
48+
```bash
49+
# Run a specific test file
50+
pnpm test -- test/aurora.test.ts
51+
52+
# Run tests matching a pattern
53+
pnpm test -- -t "provision-database"
54+
```
55+
56+
### Projen Management
57+
This project uses Projen for project configuration. All changes should be made via `.projenrc.ts`:
58+
```bash
59+
# Regenerate project files from .projenrc.ts
60+
pnpm projen
61+
62+
# Update dependencies
63+
pnpm upgrade
64+
```
65+
66+
**Important**: Do NOT manually edit `package.json`, test configs, or other generated files. Edit `.projenrc.ts` and run `pnpm projen` instead.
67+
68+
## Development Patterns
69+
70+
### Testing
71+
- Tests use Jest with `sinon` for mocking
72+
- Database provisioner tests mock `pg` client connections
73+
- CDK snapshot tests verify synthesized CloudFormation templates
74+
- Test files mirror source structure: `test/aurora.*.test.ts` corresponds to `src/aurora.*.ts`
75+
76+
### Lambda Bundling
77+
Lambda functions bundle external AWS SDK clients to avoid runtime issues:
78+
- `bundledDeps`: `@aws-sdk/client-rds`, `@aws-sdk/client-secrets-manager`, `aws-xray-sdk-core`, `pg`, `pg-format`
79+
- These are declared as `externalModules` in `bundling` config (paradoxically - this tells esbuild to bundle them)
80+
- Node modules like `pg` and `pg-format` use `nodeModules` in bundling config
81+
82+
### Security Groups
83+
- Cluster and Proxy can have custom security groups via `securityGroups` and `proxySecurityGroups` props
84+
- Default behavior creates new security groups
85+
- Provisioning lambdas automatically get ingress rules to cluster
86+
87+
### VPC Subnets
88+
- Default: `PRIVATE_WITH_EGRESS` subnets for cluster, proxy, and lambdas
89+
- Configurable via `vpcSubnets` prop
90+
- All components (cluster, proxy, provisioners) use same subnet selection
91+
92+
## Parameter Groups and Parameters
93+
94+
The construct supports two mutually exclusive approaches for cluster configuration:
95+
- `parameterGroup`: Pass an existing `IParameterGroup`
96+
- `parameters`: Pass a map of parameter key-value pairs (auto-creates parameter group)
97+
98+
**Default parameters** (used when neither is specified):
99+
```typescript
100+
{
101+
'rds.logical_replication': '1',
102+
max_replication_slots: '10',
103+
max_wal_senders: '10',
104+
wal_sender_timeout: '0'
105+
}
106+
```
107+
108+
## Important Gotchas
109+
110+
1. **Proxy + Multi-User Rotation**: Do NOT use together. Set `skipAddRotationMultiUser: true` OR `skipProxy: true`.
111+
112+
2. **Provisioning Dependencies**:
113+
- User provisioning depends on database provisioning (roles must exist)
114+
- User provisioning depends on proxy deployment (needs endpoint)
115+
- Use `skipProvisionDatabase` and `skipUserProvisioning` for bootstrapping
116+
117+
3. **Instance Count**: The `instances` prop is TOTAL count. The construct creates 1 writer + (instances - 1) readers.
118+
119+
4. **Secret Naming**: Uses `multi-convention-namer` for consistent naming. Can prefix with `secretPrefix` for multiple clusters in same account.
120+
121+
5. **Removal Policy**: The construct accepts `removalPolicy` prop but applies it differently across resources. Consider implementing the aspect pattern from `AURORA_DELETION_POLICY_IMPLEMENTATION.md` for comprehensive control.
122+
123+
## Troubleshooting Database Issues
124+
125+
See README.md "Troubleshooting" section for manual SQL commands to verify/fix:
126+
- Database connection privileges (`\l`, `GRANT CONNECT`)
127+
- Schema usage (`\dn+`, `GRANT USAGE`)
128+
- Default privileges (`\ddp`, `ALTER DEFAULT PRIVILEGES`)
129+
- Table privileges (`\dp`, `GRANT SELECT/INSERT/UPDATE/DELETE`)
130+
131+
## Connection via JumpBox
132+
133+
The README.md provides a complete script for connecting to Aurora through an SSM-enabled EC2 jump box. Key steps:
134+
1. Install AWS Session Manager plugin
135+
2. Configure SSH to use Session Manager as ProxyCommand
136+
3. Fetch SSH key from SecretsManager
137+
4. Create SSH tunnel to either cluster (manager) or proxy (reader/writer)
138+
5. Use `psql` through tunnel
139+
140+
## File Structure
141+
142+
```
143+
src/
144+
aurora.ts # Main construct
145+
aurora.provision-database.ts # Database provisioner lambda
146+
aurora.provision-user.ts # User provisioner lambda
147+
aurora.activity-stream.ts # Activity stream custom resource
148+
helpers.ts # Shared utilities
149+
index.ts # Public exports
150+
test/
151+
aurora.test.ts # Construct snapshot tests
152+
aurora.provision-*.test.ts # Provisioner unit tests
153+
aurora.activity-stream.*.test.ts # Activity stream tests
154+
```

src/aurora.ts

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -268,16 +268,14 @@ export interface AuroraProps {
268268
* You can only specify parameterGroup or parameters but not both.
269269
* You need to use a versioned engine to auto-generate a DBClusterParameterGroup.
270270
*
271-
* @default - defaultParameters
272-
*
273-
* const defaultParameters = {
274-
* // While these are mentioned in the docs, applying them doesn't work.
275-
* 'rds.logical_replication': '1', // found in the cluster parameters.
276-
* // wal_level: 'logical', // not found in cluster parameters, but implicitly set by rds.logical_replication
277-
* max_replication_slots: '10', // Arbitrary, must be > 1
278-
* max_wal_senders: '10', // Arbitrary, must be > 1
279-
* wal_sender_timeout: '0', // Never time out. Risky, but recommended.
280-
* };
271+
* @default - defaults match tf aurora-cluster module, including:
272+
* - Logical replication enabled (rds.logical_replication: '1')
273+
* - SSL enforcement (rds.force_ssl: '1')
274+
* - Query timeout protection (statement_timeout: 30s, idle_in_transaction: 5s)
275+
* - Enhanced monitoring (log slow queries >200ms, lock waits, DDL statements)
276+
* - Performance tuning (SSD-optimized costs, parallel workers, work_mem: 4MB)
277+
* - Aggressive autovacuum (analyze_scale_factor: 0.01)
278+
* - Extended replication slots (20) and wal senders (20)
281279
*
282280
*/
283281
readonly parameters?: { [key: string]: string };
@@ -386,12 +384,47 @@ export class Aurora extends Construct {
386384
Maybe better to move it to a upstream library.
387385
*/
388386
const defaultParameters = {
389-
// While these are mentioned in the docs, applying them doesn't work.
390-
'rds.logical_replication': '1', // found in the cluster parameters.
387+
// Logical replication
388+
'rds.logical_replication': '1',
391389
// wal_level: 'logical', // not found in cluster parameters, but implicitly set by rds.logical_replication
392-
max_replication_slots: '10', // Arbitrary, must be > 1
393-
max_wal_senders: '10', // Arbitrary, must be > 1
394-
wal_sender_timeout: '0', // Never time out. Risky, but recommended.
390+
391+
// Replication configuration
392+
max_replication_slots: '20',
393+
max_wal_senders: '20',
394+
max_logical_replication_workers: '8',
395+
396+
// Security
397+
'rds.force_ssl': '1',
398+
399+
// Performance monitoring and logging
400+
log_min_duration_statement: '200',
401+
log_lock_waits: '1',
402+
log_statement: 'ddl',
403+
log_temp_files: '1024',
404+
'pg_stat_statements.track': 'ALL',
405+
'pg_stat_statements.track_utility': '0',
406+
'pg_stat_statements.max': '5000',
407+
track_io_timing: '1',
408+
track_activity_query_size: '16384',
409+
track_functions: 'all',
410+
411+
// Query timeout protection
412+
statement_timeout: '30000',
413+
idle_in_transaction_session_timeout: '5000',
414+
415+
// Performance tuning
416+
random_page_cost: '1.1',
417+
work_mem: '4096',
418+
max_parallel_workers_per_gather: '4',
419+
max_parallel_maintenance_workers: '4',
420+
enable_partitionwise_aggregate: '0',
421+
422+
// Autovacuum tuning
423+
autovacuum_analyze_scale_factor: '0.01',
424+
autovacuum_freeze_max_age: '400000000',
425+
426+
// Date formatting
427+
datestyle: 'ISO,YMD',
395428
};
396429
const parameters = props.parameterGroup || props.parameters ? props.parameters : defaultParameters;
397430

test/aurora.test.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@ const createAurora = function (props?: AuroraProps) {
5959
describe('Aurora', () => {
6060
describe('default', () => {
6161
beforeAll(() => {
62-
app = new App();
62+
app = new App({
63+
context: {
64+
'aws:cdk:bundling-stacks': [], // Skip Lambda bundling in tests for performance
65+
},
66+
});
6367
stack = new Stack(app, 'test', { env: { region: 'us-west-2' } }); // We're using determineLatestNodeRuntime() which is region aware.
6468
kmsKey = new Key(stack, 'Key');
6569
vpc = new Vpc(stack, 'TestVpc', {
@@ -230,7 +234,11 @@ describe('Aurora', () => {
230234

231235
describe('options', () => {
232236
beforeEach(() => {
233-
app = new App();
237+
app = new App({
238+
context: {
239+
'aws:cdk:bundling-stacks': [], // Skip Lambda bundling in tests for performance
240+
},
241+
});
234242
stack = new Stack(app, 'test', { env: { region: 'us-west-2' } }); // We're using determineLatestNodeRuntime() which is region aware.
235243
kmsKey = new Key(stack, 'Key');
236244
vpc = new Vpc(stack, 'TestVpc', {

0 commit comments

Comments
 (0)