Query icon
OutputValues icon
Log icon
If icon
SlackIncomingWebhook icon
Loop icon
Sequential icon
Fail icon
Schedule icon

PostgreSQL Autovacuum Override Guard

Find PostgreSQL tables with autovacuum_enabled off or lax scale factors, alert Slack with dead rows, reset the setting, VACUUM and verify.

Categories
DataInfrastructure

The usual advice for a large bulk load is ALTER TABLE ... SET (autovacuum_enabled = false), then switch it back after. The second step is the one that gets forgotten. From then on:

  • dead rows pile up and the table and its indexes bloat;
  • queries slow down as visibility checks grow;
  • the table moves toward transaction ID wraparound, which ends in a forced shutdown.

The same happens to TOAST tables, set separately with toast.autovacuum_enabled, and to tables given a scale factor so lax that vacuum almost never triggers.

This blueprint finds those tables, with their dead and live row counts, and checks that autovacuum is on for the server. It alerts Slack, and can reset the parameter and run VACUUM (ANALYZE) on each table. It then audits again to verify. Tables you switch off on purpose, such as staging tables you truncate yourself, can be allowlisted.

This blueprint was created by zkasuran.

Findings

Finding Severity Rule Fix
AUTOVACUUM_OFF_SERVER Critical autovacuum is off for the whole server Report only: change it in postgresql.conf or with ALTER SYSTEM
AUTOVACUUM_DISABLED High autovacuum_enabled off on the table, and possibly its TOAST table ALTER TABLE ... RESET (...), then VACUUM (ANALYZE)
TOAST_AUTOVACUUM_DISABLED High Off on the TOAST table only RESET (toast.autovacuum_enabled), then VACUUM (ANALYZE)
LAX_SCALE_FACTOR Medium autovacuum_vacuum_scale_factor above max_scale_factor Report only
ALLOWED_AUTOVACUUM_OFF Info Off, but in allowed_tables None

How it works

  1. audit (io.kestra.plugin.jdbc.postgresql.Query, read-only) reads reloptions of every table and its TOAST table, the dead and live rows from pg_stat_user_tables, and the server autovacuum setting. It returns each finding with its fix, built with regclass.
  2. actionable drops the informational rows. summary logs everything.
  3. route (io.kestra.plugin.core.flow.If):
    • clean: logs it.
    • findings: alert posts to Slack with the dead row counts.
      • remediate_gate runs only when auto_remediate is true and dry_run is false.
      • remediate (io.kestra.plugin.core.flow.Loop) resets the parameter on each table and, with vacuum_after_reset, runs VACUUM (ANALYZE) right away, one table at a time.
      • reaudit and verify_result fail if a table still has autovacuum off. dead_after and remediated log the dead rows now.
  4. errors alerts Slack when the audit cannot run.

What you get

  • Every table that autovacuum is not allowed to clean, with how much dead data it holds.
  • TOAST overrides and lax scale factors, which \d+ does not make obvious.
  • A reset and an immediate vacuum, verified, without touching the tables you allowlist.

Who it's for

  • Teams that bulk load into PostgreSQL and tune autovacuum per table.
  • DBAs chasing table bloat or wraparound warnings.
  • Teams evaluating the PostgreSQL plugin who want an example of a catalog query driving ALTER TABLE and VACUUM.

Why orchestrate this with Kestra

The override is set once and forgotten, because nothing checks it later. Kestra runs the check every day and keeps the allowlist next to the flow. It sends the dead row counts to Slack, resets and vacuums behind auto_remediate and dry_run, verifies the result, and keeps a history of when each table was switched off and back on.

Tested end to end

On Kestra 2.0.5 OSS against PostgreSQL 16, with a local HTTP sink standing in for Slack:

  • events (600,000 rows, 200,000 dead) with autovacuum_enabled=false;
  • order_items (320,000 live, 80,000 dead) off on the table and its TOAST table;
  • shipments with autovacuum_vacuum_scale_factor=0.9;
  • staging_import off and allowlisted.
Run Inputs Result
1 defaults 2 high findings with their dead rows, 1 lax scale factor, the allowlisted table reported as info. Alert sent
2 auto_remediate: true, dry_run: true Same report. events still has {autovacuum_enabled=false}
3 auto_remediate: true, dry_run: false Parameters reset on both tables and the TOAST table, VACUUM (ANALYZE) run, verified. pg_class shows no options left, and pg_stat_user_tables shows 0 dead rows and a recorded vacuum
4 defaults Only the lax scale factor left, report only
5 after resetting the scale factor Clean, with 1 allowlisted table
6 ALTER SYSTEM SET autovacuum = off Critical server finding
7 unreachable database errors alert sent

Prerequisites

  • For the audit, any role can read the catalog and statistics. For remediation, the owner of the tables.
  • Local testing:
    • docker run -d --name pg -p 5432:5432 -e POSTGRES_PASSWORD=pg postgres:16;
    • ALTER TABLE t SET (autovacuum_enabled = false) on a table, then update or delete some rows.

Secrets

  • POSTGRES_USERNAME / POSTGRES_PASSWORD: credentials used by every query.
  • SLACK_WEBHOOK_URL: used by alert and the errors block.
  • In Kestra OSS, provide each one as a base64-encoded environment variable prefixed SECRET_.

Inputs

Input Default Purpose
jdbc_url jdbc:postgresql://localhost:5432/app Database
allowed_tables [public.staging_import] Tables allowed to have autovacuum off
max_scale_factor 0.4 Highest acceptable vacuum scale factor
vacuum_after_reset true Vacuum right after the reset
auto_remediate false Allow ALTER TABLE and VACUUM
dry_run true Must be false for a statement to run

Quick start

  1. Add the POSTGRES_USERNAME, POSTGRES_PASSWORD and SLACK_WEBHOOK_URL secrets.
  2. Import the flow and set jdbc_url and allowed_tables.
  3. Run it once and read the findings.
  4. Run it with auto_remediate: true and dry_run: false outside peak hours, since VACUUM reads the whole table.
  5. Enable the daily trigger, which always runs in safe mode.

How to extend

  • Call the flow at the end of your bulk load pipeline, so the override never outlives the load.
  • Add the transaction ID age of each table (age(relfrozenxid)) to rank the findings by urgency.
  • Loop over a list of JDBC URLs to check every database of a cluster.
  • Also report autovacuum_analyze_scale_factor and autovacuum_vacuum_cost_delay overrides.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.