Database advisor (DBA lints)
pg_advisorRun a DBA lint check across PostgreSQL to detect sequence exhaustion, transaction wraparound risk, missing primary keys, and disabled row-level security.
Instructions
Rolled-up DBA lint pass. One call returns four categories of findings:
sequence_exhaustion: SERIAL / BIGSERIAL / IDENTITY sequences whose
last_valueis aboveseqExhaustionThresholdofmax_value. The classic incident class.wraparound_risk: transaction-ID AND multixact wraparound pressure, the classic pageable incident.
{autovacuum_freeze_max_age, autovacuum_multixact_freeze_max_age, databases[], tables[]}. Those two cluster GUCs are the divisors both lists are measured against (null if unreadable). Multixact IDs are a SEPARATE 32-bit counter, consumed by row-level locking (SELECT ... FOR SHARE/UPDATE, FK checks), so a lock-heavy workload can exhaust them while relfrozenxid stays perfectly healthy -- both counters are checked here.databasesrows:{database, xid_age (age(datfrozenxid)), mxid_age (mxid_age(datminmxid)), pct_of_freeze_max_age, pct_of_multixact_freeze_max_age, triggered_by}-- template databases included, since template0 ages like any other and the cluster horizon is the minimum across all of them.tablesrows:{schema, table, relkind, xid_age (age(relfrozenxid)), freeze_max_age, pct_of_freeze_max_age, mxid_age (mxid_age(relminmxid)), multixact_freeze_max_age, pct_of_multixact_freeze_max_age, triggered_by}, wherefreeze_max_age/multixact_freeze_max_ageare the EFFECTIVE limits -- a per-tableautovacuum_freeze_max_age/autovacuum_multixact_freeze_max_agestorage parameter wins over the GUC. A row is returned when EITHER ratio is at or abovewraparoundThreshold, andtriggered_by('xid' | 'multixact' | 'both') says which one did it: 'xid' means chase freezing/autovacuum, 'multixact' means chase the lock-heavy workload burning members.mxid_ageandpct_of_multixact_freeze_max_ageare null on rows whose minmxid is InvalidMultiXactId (no multixact ever recorded); such rows can only be xid-triggered. At pct_of_freeze_max_age 1.0 autovacuum forces an anti-wraparound VACUUM, and near 2.1 billion xids (or 4.2 billion multixacts) the server stops accepting writes.tablesdeliberately includes pg_catalog and pg_toast relations -- the culprit is more often a TOAST table or a system catalog than a user table. On PG18+ table rows also carrypages/all_frozen_pages/frozen_page_fractionfrompg_class.relallfrozen(visibility-map freeze coverage); those three keys are ABSENT on older servers rather than null.tables_without_primary_key: user tables (plain and partitioned) with no PK defined. Bloat candidates and a sign of design drift; some replication setups also need PKs. Foreign tables are excluded -- PostgreSQL forbids declaring PKs on foreign tables.
public_tables_without_rls: tables in
public(or any schema inrlsSchemas) with row-level security disabled. Useful as a security baseline check. Any category whose query fails (permission-gated catalogs on managed providers) appends to_warningsand returns empty; the other categories still return. Use this as the 'what should I be looking at?' starting point, then drill intopg_unused_indexes,pg_table_bloat,pg_seq_scan_tablesfor the perf side.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max rows per category (default 50). | |
| rlsSchemas | No | Schemas where RLS-missing should be flagged. Defaults to ['public']. | |
| wraparoundThreshold | No | Minimum used-fraction to flag a database or table for wraparound risk (default 0.5 = 50%). Applied to BOTH ratios -- age(frozenxid) / autovacuum_freeze_max_age and mxid_age(minmxid) / autovacuum_multixact_freeze_max_age -- and a row is flagged if either one clears it. 1.0 is where autovacuum starts forcing anti-wraparound VACUUMs. | |
| seqExhaustionThreshold | No | Minimum used-fraction (last_value / max_value) to flag a sequence (default 0.5 = 50%). |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| _warnings | No | ||
| wraparound_risk | Yes | ||
| sequence_exhaustion | Yes | ||
| public_tables_without_rls | Yes | ||
| tables_without_primary_key | Yes | Plain and partitioned tables only; foreign tables cannot have a PK and are excluded. |