Skip to content

Commit fb19bd9

Browse files
committed
added documentation from pganalyze
1 parent 05b487f commit fb19bd9

6 files changed

Lines changed: 445 additions & 1 deletion

File tree

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
name: AWS RDS OpenTofu
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
action:
7+
description: "OpenTofu action"
8+
required: true
9+
type: choice
10+
default: plan
11+
options:
12+
- plan
13+
- apply
14+
- destroy
15+
postgres_version:
16+
description: "Target PostgreSQL version"
17+
required: true
18+
type: choice
19+
default: pg18
20+
options:
21+
- pg15
22+
- pg16
23+
- pg17
24+
- pg18
25+
allowed_cidr_block:
26+
description: "CIDR allowed to connect (example: 203.0.113.10/32). Leave blank to use AWS_ALLOWED_CIDR_BLOCK secret."
27+
required: false
28+
type: string
29+
aws_region:
30+
description: "AWS region"
31+
required: true
32+
type: string
33+
default: us-west-2
34+
35+
concurrency:
36+
group: aws-rds-${{ inputs.postgres_version }}
37+
cancel-in-progress: false
38+
39+
jobs:
40+
opentofu:
41+
name: ${{ inputs.action }} ${{ inputs.postgres_version }}
42+
runs-on: [self-hosted, linux, pgfirstaid-ci]
43+
permissions:
44+
contents: read
45+
id-token: write
46+
defaults:
47+
run:
48+
working-directory: testing/aws/deploy/${{ inputs.postgres_version }}
49+
50+
env:
51+
TF_IN_AUTOMATION: "true"
52+
53+
steps:
54+
- name: Checkout
55+
uses: actions/checkout@v4
56+
with:
57+
clean: false
58+
59+
- name: Setup OpenTofu
60+
uses: opentofu/setup-opentofu@v1
61+
62+
- name: Configure AWS credentials (OIDC role)
63+
if: ${{ secrets.AWS_ROLE_TO_ASSUME != '' }}
64+
uses: aws-actions/configure-aws-credentials@v4
65+
with:
66+
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
67+
aws-region: ${{ inputs.aws_region }}
68+
69+
- name: Configure AWS credentials (access keys)
70+
if: ${{ secrets.AWS_ROLE_TO_ASSUME == '' }}
71+
uses: aws-actions/configure-aws-credentials@v4
72+
with:
73+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
74+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
75+
aws-session-token: ${{ secrets.AWS_SESSION_TOKEN }}
76+
aws-region: ${{ inputs.aws_region }}
77+
78+
- name: Resolve allowed CIDR
79+
shell: bash
80+
run: |
81+
CIDR="${{ inputs.allowed_cidr_block }}"
82+
if [ -z "$CIDR" ]; then
83+
CIDR="${{ secrets.AWS_ALLOWED_CIDR_BLOCK }}"
84+
fi
85+
86+
if [ -z "$CIDR" ]; then
87+
echo "::error::No allowed CIDR provided. Set input 'allowed_cidr_block' or secret 'AWS_ALLOWED_CIDR_BLOCK'."
88+
exit 1
89+
fi
90+
91+
echo "TF_VAR_allowed_cidr_block=$CIDR" >> "$GITHUB_ENV"
92+
93+
- name: OpenTofu init
94+
run: tofu init -input=false
95+
96+
- name: OpenTofu validate
97+
run: tofu validate
98+
99+
- name: OpenTofu plan
100+
if: ${{ inputs.action == 'plan' || inputs.action == 'apply' }}
101+
run: tofu plan -input=false -out=tfplan
102+
103+
- name: OpenTofu apply
104+
if: ${{ inputs.action == 'apply' }}
105+
run: tofu apply -input=false -auto-approve tfplan
106+
107+
- name: Show endpoint
108+
if: ${{ inputs.action == 'apply' }}
109+
run: |
110+
echo "RDS endpoint: $(tofu output -raw endpoint)"
111+
112+
- name: OpenTofu destroy
113+
if: ${{ inputs.action == 'destroy' }}
114+
run: tofu destroy -input=false -auto-approve
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
name: GCP Cloud SQL OpenTofu
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
action:
7+
description: "OpenTofu action"
8+
required: true
9+
type: choice
10+
default: plan
11+
options:
12+
- plan
13+
- apply
14+
- destroy
15+
postgres_version:
16+
description: "Target PostgreSQL version"
17+
required: true
18+
type: choice
19+
default: pg18
20+
options:
21+
- pg15
22+
- pg16
23+
- pg17
24+
- pg18
25+
personal_ip:
26+
description: "IP or CIDR allowed to connect (example: 203.0.113.10/32). Leave blank to use GCP_PERSONAL_IP secret."
27+
required: false
28+
type: string
29+
30+
concurrency:
31+
group: gcp-cloudsql-${{ inputs.postgres_version }}
32+
cancel-in-progress: false
33+
34+
jobs:
35+
opentofu:
36+
name: ${{ inputs.action }} ${{ inputs.postgres_version }}
37+
runs-on: [self-hosted, linux, pgfirstaid-ci]
38+
permissions:
39+
contents: read
40+
id-token: write
41+
defaults:
42+
run:
43+
working-directory: testing/gcp/deploy/${{ inputs.postgres_version }}
44+
45+
env:
46+
TF_IN_AUTOMATION: "true"
47+
48+
steps:
49+
- name: Checkout
50+
uses: actions/checkout@v4
51+
with:
52+
clean: false
53+
54+
- name: Authenticate to Google Cloud (OIDC)
55+
if: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER != '' && secrets.GCP_SERVICE_ACCOUNT != '' }}
56+
uses: google-github-actions/auth@v2
57+
with:
58+
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
59+
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
60+
61+
- name: Authenticate to Google Cloud (service account key)
62+
if: ${{ !(secrets.GCP_WORKLOAD_IDENTITY_PROVIDER != '' && secrets.GCP_SERVICE_ACCOUNT != '') }}
63+
uses: google-github-actions/auth@v2
64+
with:
65+
credentials_json: ${{ secrets.GCP_CREDENTIALS_JSON }}
66+
67+
- name: Setup gcloud CLI
68+
uses: google-github-actions/setup-gcloud@v2
69+
70+
- name: Setup OpenTofu
71+
uses: opentofu/setup-opentofu@v1
72+
73+
- name: Resolve personal IP
74+
shell: bash
75+
run: |
76+
PERSONAL_IP="${{ inputs.personal_ip }}"
77+
if [ -z "$PERSONAL_IP" ]; then
78+
PERSONAL_IP="${{ secrets.GCP_PERSONAL_IP }}"
79+
fi
80+
81+
if [ -z "$PERSONAL_IP" ]; then
82+
echo "::error::No personal IP provided. Set input 'personal_ip' or secret 'GCP_PERSONAL_IP'."
83+
exit 1
84+
fi
85+
86+
echo "TF_VAR_personal_ip=$PERSONAL_IP" >> "$GITHUB_ENV"
87+
88+
- name: OpenTofu init
89+
run: tofu init -input=false
90+
91+
- name: OpenTofu validate
92+
run: tofu validate
93+
94+
- name: OpenTofu plan
95+
if: ${{ inputs.action == 'plan' || inputs.action == 'apply' }}
96+
run: tofu plan -input=false -out=tfplan
97+
98+
- name: OpenTofu apply
99+
if: ${{ inputs.action == 'apply' }}
100+
run: tofu apply -input=false -auto-approve tfplan
101+
102+
- name: Show connection details
103+
if: ${{ inputs.action == 'apply' }}
104+
run: |
105+
echo "Instance: $(tofu output -raw instance_name)"
106+
echo "Connection: $(tofu output -raw instance_connection_name)"
107+
echo "Public IP: $(tofu output -raw public_ip_address)"
108+
109+
- name: OpenTofu destroy
110+
if: ${{ inputs.action == 'destroy' }}
111+
run: tofu destroy -input=false -auto-approve

pgFirstAid.sql

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,74 @@ select
421421
3 as severity_order
422422
from
423423
bq;
424+
-- MEDIUM: Deadlocks since stats reset
425+
insert
426+
into
427+
health_results
428+
select
429+
'MEDIUM' as severity,
430+
'Query Health' as category,
431+
'Deadlocks Since Stats Reset' as check_name,
432+
psd.datname as object_name,
433+
'Deadlocks have been detected since the last PostgreSQL statistics reset' as issue_description,
434+
'Deadlocks: ' || psd.deadlocks || ', Stats reset: ' || psd.stats_reset as current_value,
435+
'Capture deadlock details in logs and review transaction ordering to reduce lock cycles' as recommended_action,
436+
'https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-DATABASE-VIEW \
437+
https://pganalyze.com/blog/postgresql-log-monitoring-101-deadlocks-checkpoints-blocked-queries' as documentation_link,
438+
3 as severity_order
439+
from
440+
pg_stat_database psd
441+
where
442+
psd.datname not in ('template0', 'template1')
443+
and psd.deadlocks > 0;
444+
-- MEDIUM: Deadlock rate per hour
445+
insert
446+
into
447+
health_results
448+
select
449+
'MEDIUM' as severity,
450+
'Query Health' as category,
451+
'Deadlock Rate Per Hour' as check_name,
452+
psd.datname as object_name,
453+
'Deadlocks are occurring often relative to time since stats reset' as issue_description,
454+
'Deadlocks/hour: ' || round(
455+
psd.deadlocks / GREATEST(EXTRACT(EPOCH FROM (now() - psd.stats_reset)) / 3600.0, 1),
456+
3
457+
) || ', Deadlocks: ' || psd.deadlocks as current_value,
458+
'Investigate recent lock contention and reduce transaction overlap on the same rows' as recommended_action,
459+
'https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-DATABASE-VIEW \
460+
https://pganalyze.com/blog/postgresql-log-monitoring-101-deadlocks-checkpoints-blocked-queries' as documentation_link,
461+
3 as severity_order
462+
from
463+
pg_stat_database psd
464+
where
465+
psd.datname not in ('template0', 'template1')
466+
and psd.deadlocks > 0;
467+
-- MEDIUM: Deadlocks compared to transaction volume
468+
insert
469+
into
470+
health_results
471+
select
472+
'MEDIUM' as severity,
473+
'Query Health' as category,
474+
'Deadlocks as Percent of Transactions' as check_name,
475+
psd.datname as object_name,
476+
'Deadlocks are impacting a measurable share of transaction volume' as issue_description,
477+
'Deadlocks: ' || psd.deadlocks || ', Total transactions: ' || (psd.xact_commit + psd.xact_rollback) ||
478+
', Deadlock percent: ' || round(
479+
100.0 * psd.deadlocks / NULLIF(psd.xact_commit + psd.xact_rollback, 0),
480+
4
481+
) || '%' as current_value,
482+
'Review application retry logic and lock acquisition patterns for frequently conflicting transactions' as recommended_action,
483+
'https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-DATABASE-VIEW \
484+
https://pganalyze.com/blog/postgresql-log-monitoring-101-deadlocks-checkpoints-blocked-queries' as documentation_link,
485+
3 as severity_order
486+
from
487+
pg_stat_database psd
488+
where
489+
psd.datname not in ('template0', 'template1')
490+
and psd.deadlocks > 0
491+
and (psd.xact_commit + psd.xact_rollback) > 0;
424492
-- MEDIUM: Tables with outdated statistics
425493
insert
426494
into

testing/pgTAP/03_high_tests.sql

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
BEGIN;
2-
SELECT plan(16);
2+
SELECT plan(22);
33

44
SELECT ok(
55
(SELECT count(*) >= 0 FROM pg_firstAid() WHERE check_name = 'Current Blocked/Blocking Queries'),
@@ -10,6 +10,33 @@ SELECT ok(
1010
'View executes Current Blocked/Blocking Queries check'
1111
);
1212

13+
SELECT ok(
14+
(SELECT count(*) >= 0 FROM pg_firstAid() WHERE check_name = 'Deadlocks Since Stats Reset'),
15+
'Function executes Deadlocks Since Stats Reset check'
16+
);
17+
SELECT ok(
18+
(SELECT count(*) >= 0 FROM v_pgfirstaid WHERE check_name = 'Deadlocks Since Stats Reset'),
19+
'View executes Deadlocks Since Stats Reset check'
20+
);
21+
22+
SELECT ok(
23+
(SELECT count(*) >= 0 FROM pg_firstAid() WHERE check_name = 'Deadlock Rate Per Hour'),
24+
'Function executes Deadlock Rate Per Hour check'
25+
);
26+
SELECT ok(
27+
(SELECT count(*) >= 0 FROM v_pgfirstaid WHERE check_name = 'Deadlock Rate Per Hour'),
28+
'View executes Deadlock Rate Per Hour check'
29+
);
30+
31+
SELECT ok(
32+
(SELECT count(*) >= 0 FROM pg_firstAid() WHERE check_name = 'Deadlocks as Percent of Transactions'),
33+
'Function executes Deadlocks as Percent of Transactions check'
34+
);
35+
SELECT ok(
36+
(SELECT count(*) >= 0 FROM v_pgfirstaid WHERE check_name = 'Deadlocks as Percent of Transactions'),
37+
'View executes Deadlocks as Percent of Transactions check'
38+
);
39+
1340
SELECT ok(
1441
(SELECT count(*) >= 0 FROM pg_firstAid() WHERE check_name = 'Outdated Statistics'),
1542
'Function executes Outdated Statistics check'

0 commit comments

Comments
 (0)