Skip to content

Commit b12d893

Browse files
committed
feat: add health check custom resource for pgbouncer
1 parent 87d07d0 commit b12d893

File tree

6 files changed

+2736
-274
lines changed

6 files changed

+2736
-274
lines changed

lib/database/PgBouncer.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
aws_lambda as lambda,
55
aws_secretsmanager as secretsmanager,
66
CustomResource,
7+
Duration,
78
Stack,
89
} from "aws-cdk-lib";
910
import { Construct } from "constructs";
@@ -66,6 +67,7 @@ export class PgBouncer extends Construct {
6667
public readonly pgbouncerSecret: secretsmanager.Secret;
6768
public readonly securityGroup: ec2.SecurityGroup;
6869
public readonly secretUpdateComplete: CustomResource;
70+
public readonly healthCheck: CustomResource;
6971

7072
// The max_connections parameter in PgBouncer determines the maximum number of
7173
// connections to open on the actual database instance. We want that number to
@@ -220,6 +222,47 @@ export class PgBouncer extends Construct {
220222
},
221223
}
222224
);
225+
226+
// Add health check custom resource
227+
const healthCheckFunction = new lambda.Function(
228+
this,
229+
"HealthCheckFunction",
230+
{
231+
runtime: lambda.Runtime.NODEJS_20_X,
232+
handler: "index.handler",
233+
timeout: Duration.minutes(10),
234+
code: lambda.Code.fromAsset(
235+
path.join(__dirname, "lambda/pgbouncer-health-check")
236+
),
237+
description: "PgBouncer health check function",
238+
}
239+
);
240+
241+
// Grant SSM permissions for health check
242+
healthCheckFunction.addToRolePolicy(
243+
new iam.PolicyStatement({
244+
actions: [
245+
"ssm:SendCommand",
246+
"ssm:GetCommandInvocation",
247+
"ssm:DescribeInstanceInformation",
248+
"ssm:ListCommandInvocations",
249+
],
250+
resources: ["*"],
251+
})
252+
);
253+
254+
this.healthCheck = new CustomResource(this, "PgBouncerHealthCheck", {
255+
serviceToken: healthCheckFunction.functionArn,
256+
properties: {
257+
InstanceId: this.instance.instanceId,
258+
// Add timestamp to force re-execution on stack updates
259+
Timestamp: new Date().toISOString(),
260+
},
261+
});
262+
263+
// Ensure health check runs after instance is created but before secret update
264+
this.healthCheck.node.addDependency(this.instance);
265+
this.secretUpdateComplete.node.addDependency(this.healthCheck);
223266
}
224267

225268
private loadUserDataScript(

lib/database/index.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,47 @@ function hasVpc(
2727
}
2828

2929
/**
30-
* An RDS instance with pgSTAC installed. This is a wrapper around the
31-
* `rds.DatabaseInstance` higher-level construct making use
32-
* of the BootstrapPgStac construct.
30+
* An RDS instance with pgSTAC installed and PgBouncer connection pooling.
31+
*
32+
* This construct creates an optimized pgSTAC database setup that includes:
33+
* - RDS PostgreSQL instance with pgSTAC extension
34+
* - PgBouncer connection pooler (enabled by default)
35+
* - Automated health monitoring system
36+
* - Optimized database parameters for the selected instance type
37+
*
38+
* ## Connection Pooling with PgBouncer
39+
*
40+
* By default, this construct deploys PgBouncer as a connection pooler running on
41+
* a dedicated EC2 instance. PgBouncer provides several benefits:
42+
*
43+
* - **Connection Management**: Pools and reuses database connections to reduce overhead
44+
* - **Performance**: Optimizes connection handling for high-traffic applications
45+
* - **Scalability**: Allows more concurrent connections than the RDS instance alone
46+
* - **Health Monitoring**: Includes comprehensive health checks to ensure availability
47+
*
48+
* ### PgBouncer Configuration
49+
* - Pool mode: Transaction-level pooling (default)
50+
* - Maximum client connections: 1000
51+
* - Default pool size: 20 connections per database/user combination
52+
* - Instance type: t3.micro EC2 instance
53+
*
54+
* ### Health Check System
55+
* The construct includes an automated health check system that validates:
56+
* - PgBouncer service is running and listening on port 5432
57+
* - Connection tests to ensure accessibility
58+
* - Cloud-init setup completion before validation
59+
* - Detailed diagnostics for troubleshooting
60+
*
61+
* ### Connection Details
62+
* When PgBouncer is enabled, applications connect through the PgBouncer instance
63+
* rather than directly to RDS. The `pgstacSecret` contains connection information
64+
* pointing to PgBouncer, and the `connectionTarget` property refers to the
65+
* PgBouncer EC2 instance.
66+
*
67+
* To disable PgBouncer and connect directly to RDS, set `addPgbouncer: false`.
68+
*
69+
* This is a wrapper around the `rds.DatabaseInstance` higher-level construct
70+
* making use of the BootstrapPgStac construct.
3371
*/
3472
export class PgStacDatabase extends Construct {
3573
db: rds.DatabaseInstance;
@@ -40,6 +78,7 @@ export class PgStacDatabase extends Construct {
4078
public readonly connectionTarget: rds.IDatabaseInstance | ec2.Instance;
4179
public readonly securityGroup?: ec2.SecurityGroup;
4280
public readonly secretBootstrapper?: CustomResource;
81+
public readonly pgbouncerHealthCheck?: CustomResource;
4382

4483
constructor(scope: Construct, id: string, props: PgStacDatabaseProps) {
4584
super(scope, id);
@@ -186,6 +225,7 @@ export class PgStacDatabase extends Construct {
186225
this.connectionTarget = this._pgBouncerServer.instance;
187226
this.securityGroup = this._pgBouncerServer.securityGroup;
188227
this.secretBootstrapper = this._pgBouncerServer.secretUpdateComplete;
228+
this.pgbouncerHealthCheck = this._pgBouncerServer.healthCheck;
189229
} else {
190230
this.connectionTarget = this.db;
191231
}

0 commit comments

Comments
 (0)