Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
b9d01ef
docs: record M1 design decisions
hazeliscoding Sep 26, 2026
087644b
feat(engine): read the server's version, provider and privileges
hazeliscoding Sep 26, 2026
0e01b8a
feat(engine): skip checks that can't run and report errored ones
hazeliscoding Sep 26, 2026
10268dd
test(checks): run fixtures on stock Postgres with directives and decl…
hazeliscoding Sep 26, 2026
21ded50
test(checks): scan every check as a pg_monitor role without errors
hazeliscoding Sep 26, 2026
083fa2b
feat(cli): add explain and grant, and show more in list
hazeliscoding Sep 26, 2026
3596d47
refactor(cli): share report wording between output formats
hazeliscoding Sep 26, 2026
41b959d
feat(cli): add --format json and markdown
hazeliscoding Sep 26, 2026
2962061
docs(roadmap): revise the v0.1 checks after desk research
hazeliscoding Sep 26, 2026
9c5ed49
test(checks): run one check's fixtures with PGCHECKUP_TEST_CHECK
hazeliscoding Sep 26, 2026
679b49a
test(checks): collect rendered findings with PGCHECKUP_TEST_FINDINGS
hazeliscoding Sep 26, 2026
b60af68
feat(checks): add xid-wraparound
hazeliscoding Sep 26, 2026
167aa3c
feat(checks): add multixact-wraparound
hazeliscoding Sep 26, 2026
2f8f778
feat(checks): add long-transaction
hazeliscoding Sep 26, 2026
417f0d5
feat(checks): add idle-transaction-timeout
hazeliscoding Sep 26, 2026
3d8127f
feat(checks): add prepared-transaction-orphaned
hazeliscoding Sep 26, 2026
1b889d6
feat(checks): report replication slots that hold back vacuum
hazeliscoding Sep 27, 2026
c0ff129
feat(checks): add replication-slot-unbounded
hazeliscoding Sep 27, 2026
e0b6c2e
feat(checks): add wal-archiving-failing
hazeliscoding Sep 27, 2026
4108a3d
feat(checks): add connection-saturation
hazeliscoding Sep 27, 2026
42d1612
feat(checks): add dangerous-settings
hazeliscoding Sep 27, 2026
704dd9e
fix(checks): read \n and \t in double-quoted frontmatter as YAML does
hazeliscoding Sep 27, 2026
e8d83ab
feat(checks): add autovacuum-disabled
hazeliscoding Sep 27, 2026
562b4de
feat(checks): add integer-exhaustion
hazeliscoding Sep 27, 2026
94e6fb9
feat(checks): add invalid-index
hazeliscoding Sep 27, 2026
32319c0
feat(checks): add collation-version-mismatch
hazeliscoding Sep 27, 2026
d3b98bb
feat(checks): add postgres-eol
hazeliscoding Sep 27, 2026
23788f8
docs(roadmap): plan for Postgres 14 reaching end of life
hazeliscoding Sep 27, 2026
4bc5b21
docs(readme): describe the v0.1 checks and exit codes
hazeliscoding Sep 27, 2026
cebbb35
fix(cli): escape control characters from the database in reports
hazeliscoding Sep 27, 2026
e0f765a
fix(cli): grant default privileges for the tables' owner
hazeliscoding Sep 27, 2026
9bc1397
test(checks): run fixtures on Debian images to cover glibc collations
hazeliscoding Sep 27, 2026
d1ee7f5
fix(checks): skip dangerous-settings on Neon, which runs with fsync off
hazeliscoding Sep 27, 2026
cac2286
fix(checks): ignore cycling sequences and report narrow sequences beh…
hazeliscoding Sep 27, 2026
7b96d4c
fix(checks): report temporary tables in the wraparound checks
hazeliscoding Sep 27, 2026
2261448
fix(checks): judge archiving failures by how long a segment has waited
hazeliscoding Sep 27, 2026
280d042
fix(checks): report sessions of a dropped role in long-transaction
hazeliscoding Sep 27, 2026
c71144a
docs(json): document null values and fix a check title
hazeliscoding Sep 27, 2026
d829e5c
feat(cli): wrap messages to the terminal width
hazeliscoding Sep 27, 2026
ad1f0b0
docs(readme): re-record the scan with the full catalog
hazeliscoding Sep 27, 2026
ef12d5b
docs(roadmap): tick the v0.1 checks
hazeliscoding Sep 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ These are the working rules for agents in this repo. pgcheckup is a read-only CL
Keep commands cross-platform (`dotnet`, `docker`), because the owner develops on Windows. Avoid bash-only scripts.

- Build: `dotnet build pgcheckup.slnx`. A broken check folder fails the build with its file and line.
- Test: `dotnet test --project tests/Pgcheckup.Tests`. It needs Docker, and uses Postgres 18 unless `PGCHECKUP_TEST_POSTGRES` names another major (14 to 17). CI runs all five.
- Test: `dotnet test --project tests/Pgcheckup.Tests`. It needs Docker, and uses Postgres 18 unless `PGCHECKUP_TEST_POSTGRES` names another major (14 to 17). CI runs all five. `PGCHECKUP_TEST_CHECK=<id>` runs only that check's fixtures, which is quicker while writing a check.
- Publish: `dotnet publish src/Pgcheckup -c Release -r win-x64 -o out` (`linux-x64` on Linux). Trim and AOT warnings fail it.
- Test the published binary: set `PGCHECKUP_BINARY` to it, then run `dotnet test --project tests/Pgcheckup.Tests -- --filter-class Pgcheckup.Tests.Cli.NativeBinaryTests`.
- NativeAOT publish on this Windows machine fails with `'vswhere.exe' is not recognized` unless the VS Installer folder is on PATH. Run it as `$env:PATH = "C:\Program Files (x86)\Microsoft Visual Studio\Installer;$env:PATH"; dotnet publish …`. That is an environment problem, not an AOT warning.
Expand All @@ -24,7 +24,7 @@ The product is only as good as these rules. Never break them, not even in debug

- **Read-only, always.** A check is one `SELECT` against catalogs and statistics views. No DDL or DML, and no functions with side effects: `pg_terminate_backend`, `pg_cancel_backend`, `pg_reload_conf`, `pg_stat_reset*`, `pg_switch_wal`, `pg_create_*`, `pg_drop_*`, `pg_advisory_*`, `nextval`, `setval`, `set_config`, `txid_current`. Every statement pgcheckup sends runs inside `BEGIN READ ONLY` with `SET LOCAL statement_timeout`, `lock_timeout` and `search_path = pg_catalog, pg_temp`, then rolls back. Never set anything for the whole session, because behind a transaction pooler it reaches the app's connections. Never weaken or bypass these guards.
- **Fixes are text.** pgcheckup prints fix SQL and never executes it.
- **Least privilege.** No check needs more than `pg_monitor`. Never require superuser or `rds_superuser`. If the role lacks a privilege, the check is skipped with the reason. It is never an error.
- **Least privilege.** No check needs more than `pg_monitor`, except `integer-exhaustion`, which needs SELECT on sequences (counters only, never rows). Never require superuser or `rds_superuser`. If the role lacks a privilege, the check is skipped with the reason. It is never an error.
- **No network beyond the Postgres connection.** No telemetry, update checks, crash reporting or remote lookups. Data such as end-of-life dates ships inside the release.
- **No secrets or data in output.** Never print or log a password or a full connection string, query text (`pg_stat_activity.query`, `pg_stat_statements.query`), row data or client addresses. Findings name objects, settings, process IDs and durations only.
- **Placeholders everywhere.** Docs, fixtures, tests and issues use `db.example.com`, `app` and `checkup`, never real hosts or credentials.
Expand All @@ -36,10 +36,10 @@ The product is only as good as these rules. Never break them, not even in debug
- `check.sql` is one read-only query that returns values, never prose. The wording lives in the `message` and `fix` templates in `check.md`. Thresholds come in as `@name` parameters and are never hard-coded. The search path is `pg_catalog` only, so qualify anything in another schema.
- Compute ages and durations in SQL from the server's `now()`, not the client's clock.
- `check.md` has **What breaks**, **Fix** and **Seen in** sections. Every check has at least one **Seen in** link to a public incident or the Postgres docs. Never cite anything a reader can't open.
- Both fixtures are required. `fires.sql` is the positive control, so a check without one isn't done. A fixture may lower a threshold (`-- threshold name = value`) when the real condition can't be reproduced at scale.
- Both fixtures are required. `fires.sql` is the positive control, so a check without one isn't done. A fixture may lower a threshold (`-- threshold name = value`) when the real condition can't be reproduced at scale, start Postgres with a setting that needs a restart (`-- server name = value`), and let its next statement fail (`-- expect error`).
- Check ids are kebab-case and stable, because baselines and ignore lists depend on them. Renaming one is a breaking change that needs a decision in `ROADMAP.md`.
- Severity: `critical` can take the database down or lose data soon. `warning` is heading there, or removes a safety net. `info` is housekeeping. Don't inflate severity.
- A check declares its minimum Postgres version and the providers where it is skipped. Every check is tested on every supported version.
- A check declares its minimum Postgres version, the privileges it needs and the providers where it is skipped. Every check is tested on every supported version, and its `fires` fixture is tested as a role with exactly its declared privileges.

## .NET and NativeAOT

Expand Down
28 changes: 17 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ In February 2019, one of the Postgres shards behind Mailchimp's Mandrill [ran ou

Most of these failures show up in the system catalogs weeks ahead: a table's transaction ID age, a replication slot nobody reads, WAL archiving that failed last night. Teams without a DBA rarely look. pgcheckup looks for them and tells you what to do.

> **Status:** early development. The first check, `replication-slot-inactive`, runs end to end. There is no release to install yet. See [ROADMAP.md](ROADMAP.md).
> **Status:** early development. The 15 checks of v0.1 run end to end, but there is no release to install yet. See [ROADMAP.md](ROADMAP.md).

![pgcheckup scanning a database whose inactive replication slot is holding 1.07 GB of WAL](docs/scan.gif)
![pgcheckup scanning a database: an inactive replication slot holding 1.07 GB of WAL, no WAL limit for slots, and no timeout for idle transactions](docs/scan.gif)

## How it works

Expand All @@ -26,24 +26,30 @@ pgcheckup scan "postgres://checkup@db.example.com:5432/app?sslmode=verify-full"
pgcheckup · app on db.example.com · PostgreSQL 17.6 · Amazon RDS

CRITICAL xid-wraparound
Table orders has used 1.61 billion of its 2.1 billion transaction IDs.
Vacuum can't freeze it while pid 4127 holds a transaction open (6 days).
Fix: end pid 4127, then run VACUUM (FREEZE) orders;
Table public.orders has used 1.61 billion of its 2.1 billion transaction IDs.
Fix: VACUUM (FREEZE, VERBOSE) public.orders;

WARNING long-transaction
Session 4127 (worker on app) has had a transaction open for 6 days and has been idle in it for 6 days.
Fix: if the session is stuck or abandoned, end it:
SELECT pg_terminate_backend(4127);

WARNING replication-slot-inactive
Slot debezium has been inactive for 3 days and is holding 48 GB of WAL.
Fix: restart its consumer, or drop the slot:
SELECT pg_drop_replication_slot('debezium');

11 passed · 1 critical · 1 warning · 1 skipped (wal-archiving-failing: managed by Amazon RDS)
11 passed · 1 critical · 2 warnings · 1 skipped (wal-archiving-failing: managed by Amazon RDS)
```

## What it checks

- **Running out of IDs:** transaction ID and multixact wraparound, and `int4` sequences and identity columns near their limit.
- **Cleanup that can't run:** long transactions, sessions idle inside a transaction, forgotten prepared transactions, and tables with autovacuum turned off.
- **Disks filling with WAL:** inactive replication slots, slots with no WAL limit, and failing WAL archiving.
- **Capacity and settings:** connection saturation, `fsync`, `full_page_writes` or `autovacuum` turned off, invalid indexes, and Postgres versions past end of life.
- **Running out of IDs:** transaction ID and multixact wraparound, and `int4` sequences, identity columns and foreign keys near their limit.
- **Cleanup that can't run:** long transactions and sessions idle inside one, forgotten prepared transactions, no timeout for idle transactions, and autovacuum turned off.
- **Disks filling with WAL:** inactive replication slots, slots with no WAL limit, and WAL archiving that fails or hangs.
- **Capacity and settings:** connection saturation, `fsync` or `full_page_writes` turned off, invalid indexes, collations changed by an OS upgrade, and Postgres versions near or past end of life.

`pgcheckup list` shows every check.

Every finding says what breaks and how to fix it. `pgcheckup explain <check>` prints the full note, with links to incidents where it happened.

Expand All @@ -57,7 +63,7 @@ Every finding says what breaks and how to fix it. `pgcheckup explain <check>` pr

## In CI

`pgcheckup scan` exits 0 when no finding reaches `--fail-on` (default `critical`), 1 when one does, and 2 when the scan couldn't run. `--format json` and `--format markdown` are there for pipelines and pull requests. A baseline, so CI fails only on new findings, comes with v0.2.
`pgcheckup scan` exits 1 when a finding reaches `--fail-on` (default `critical`), otherwise 2 when the scan couldn't run or a check errored, and 0 when neither happened. `--format json` ([its shape](docs/json.md)) and `--format markdown` are there for pipelines and pull requests. A baseline, so CI fails only on new findings, comes with v0.2.

## Prior art

Expand Down
Loading
Loading