pg-vacuum-tuning — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited pg-vacuum-tuning (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
This skill guides monitoring, troubleshooting, and tuning of PostgreSQL vacuum operations using pgtuner-mcp tools.
Use this skill when the user:
pgtuner://settings/autovacuum -- Current autovacuum configuration (lightweight)pgtuner://health/bloat -- Quick bloat assessmentpgtuner://table/{schema}/{table_name}/stats -- Per-table dead tuple counts and vacuum timestampsThis skill is related to the `health_check` MCP Prompt (wraparound dimension) and the vacuum aspects of pg-bloat-analysis skill.
DATABASE_URIpg_stat_activity, pg_stat_user_tables, pg_stat_progress_vacuumUser asks about vacuum
|
+--> "Why is autovacuum always running?"
| --> Start Step 1 (progress), then Step 3 (autovacuum config)
| --> May be normal for high-churn tables. Check if it's completing.
|
+--> "Why isn't my table being vacuumed?"
| --> Start Step 2 (needs_vacuum), then Step 3 (autovacuum config)
| --> Check if scale_factor threshold is too high for large tables
|
+--> "Transaction wraparound warning"
| --> URGENT: Start Step 2 (needs_vacuum), check XID age
| --> IF XID age > 1.2B: Go to Emergency Procedures immediately
|
+--> "How long will this vacuum take?"
| --> Start Step 1 (progress) to see current phase and completion %
|
+--> "General vacuum tuning"
--> Follow Steps 1-4 in order, then Tuning RecommendationsTool: monitor_vacuum_progress
Parameters:
action: "progress"What this shows:
Vacuum phases explained:
| Phase | Description | Duration |
|---|---|---|
| initializing | Starting up | Brief |
| scanning heap | Finding dead tuples | Proportional to table size |
| vacuuming indexes | Cleaning index entries | Depends on number of indexes |
| vacuuming heap | Removing dead tuples from heap | Proportional to dead tuples |
| cleaning up indexes | Final index cleanup | Brief |
| truncating heap | Returning pages to OS (end of table only) | Brief |
| performing final cleanup | Updating statistics | Brief |
Tool: monitor_vacuum_progress
Parameters:
action: "needs_vacuum"
schema_name: "public"
min_dead_tuples: 500
include_toast: trueWhat to look for:
| Condition | Risk Level | Action |
|---|---|---|
| Dead tuples > autovacuum threshold | Normal | Autovacuum should pick it up soon |
| Dead tuples >> threshold, no recent vacuum | Warning | Autovacuum may be overwhelmed |
| XID age > 200 million | Warning | Monitor, autovacuum should handle |
| XID age > 1 billion | Critical | Manual VACUUM FREEZE immediately |
| XID age > 1.5 billion | Emergency | Database will shut down at 2 billion |
Transaction wraparound explained: PostgreSQL uses 32-bit transaction IDs (XIDs). After ~2 billion transactions, old XIDs "wrap around" and data could appear to be in the future. PostgreSQL will force a shutdown at 2^31 - 1 million XIDs to prevent data corruption. Autovacuum normally prevents this by freezing old XIDs, but if vacuum can't keep up, manual intervention is needed.
Tool: monitor_vacuum_progress
Parameters:
action: "autovacuum_status"Also check the settings:
Tool: review_settings
Parameters:
category: "autovacuum"Key autovacuum parameters:
| Parameter | Default | Recommended for OLTP | Purpose |
|---|---|---|---|
autovacuum | on | on (never turn off) | Master switch |
autovacuum_max_workers | 3 | 4-6 | Parallel vacuum workers |
autovacuum_naptime | 1min | 15-30s | Time between autovacuum runs |
autovacuum_vacuum_threshold | 50 | 50 | Min dead tuples before vacuum |
autovacuum_vacuum_scale_factor | 0.2 | 0.02-0.05 | Fraction of table that triggers vacuum |
autovacuum_analyze_threshold | 50 | 50 | Min changes before analyze |
autovacuum_analyze_scale_factor | 0.1 | 0.01-0.02 | Fraction of table that triggers analyze |
autovacuum_vacuum_cost_delay | 2ms (PG12+) | 2ms | Pause between cost-limited work |
autovacuum_vacuum_cost_limit | -1 (uses vacuum_cost_limit=200) | 400-1000 | Work limit per cycle |
autovacuum_freeze_max_age | 200M | 200M | Forces vacuum to prevent wraparound |
The scale factor problem: With default autovacuum_vacuum_scale_factor = 0.2, a 100-million-row table needs 20 million dead tuples before autovacuum triggers. This is almost always too high for large tables.
Tool: monitor_vacuum_progress
Parameters:
action: "recent_activity"Categories:
What to investigate:
If vacuum is not making progress, check for long-running transactions:
Tool: get_active_queries
Parameters:
include_idle: true
min_duration_seconds: 60Common vacuum blockers:
idle in transaction sessions hold back the visibility horizonTables with millions of INSERTs/UPDATEs/DELETEs per day:
-- Aggressive per-table settings
ALTER TABLE schema.hot_table SET (
autovacuum_vacuum_scale_factor = 0.01, -- Trigger at 1% dead tuples
autovacuum_vacuum_threshold = 1000, -- Or at least 1000 dead tuples
autovacuum_analyze_scale_factor = 0.005, -- Analyze at 0.5% changes
autovacuum_vacuum_cost_delay = 0, -- No throttling for this table
autovacuum_vacuum_cost_limit = 1000 -- Higher work limit
);Tables that are mostly read with occasional bulk loads:
-- Less frequent but thorough vacuum
ALTER TABLE schema.fact_table SET (
autovacuum_vacuum_scale_factor = 0.05, -- 5% threshold (OK for large read-mostly tables)
autovacuum_vacuum_threshold = 10000,
autovacuum_enabled = true, -- Ensure it's on
autovacuum_freeze_min_age = 100000000, -- Freeze less aggressively
autovacuum_freeze_table_age = 300000000 -- Full-table freeze less often
);Tables where old data is never updated:
-- Minimal vacuum needed since there are few dead tuples
ALTER TABLE schema.log_table SET (
autovacuum_vacuum_scale_factor = 0.1,
autovacuum_vacuum_threshold = 10000,
autovacuum_freeze_min_age = 50000000, -- Freeze sooner (data won't change)
fillfactor = 100 -- Pack pages full (no updates expected)
);-- For servers with SSDs and sufficient I/O capacity
ALTER SYSTEM SET autovacuum_max_workers = 6;
ALTER SYSTEM SET autovacuum_naptime = '15s';
ALTER SYSTEM SET autovacuum_vacuum_cost_delay = '0ms'; -- No throttling on SSD
ALTER SYSTEM SET autovacuum_vacuum_cost_limit = 1000;
ALTER SYSTEM SET vacuum_cost_limit = 1000;
-- For wraparound prevention
ALTER SYSTEM SET autovacuum_freeze_max_age = '300000000'; -- 300M (default 200M)
-- Apply: SELECT pg_reload_conf();| Table | Dead Tuples | XID Age | Last Vacuum | Last Autovacuum | Action |
|---|---|---|---|---|---|
| orders | 2.5M (12%) | 450M | 3 days ago | 2 days ago | Tune scale_factor |
| sessions | 150K (45%) | 1.1B | never | never | VACUUM FREEZE now |
Per-table and global tuning SQL, ordered by priority.
Tables ranked by XID age with time-to-wraparound estimate.
-- 1. Cancel non-essential queries
SELECT pg_cancel_backend(pid) FROM pg_stat_activity
WHERE state != 'idle' AND query NOT LIKE 'autovacuum%'
AND backend_start < now() - interval '1 hour';
-- 2. Run manual VACUUM FREEZE on the affected table
VACUUM (FREEZE, VERBOSE) schema.table_name;
-- 3. Monitor progress
SELECT * FROM pg_stat_progress_vacuum;-- 1. Check for blocking transactions
SELECT pid, state, xact_start, query
FROM pg_stat_activity
WHERE xact_start < now() - interval '1 hour'
ORDER BY xact_start;
-- 2. Terminate long idle-in-transaction sessions (careful!)
SELECT pg_terminate_backend(pid) FROM pg_stat_activity
WHERE state = 'idle in transaction'
AND xact_start < now() - interval '1 hour';
-- 3. Verify autovacuum is enabled
SHOW autovacuum;
-- 4. Force autovacuum to recheck
SELECT pg_reload_conf();autovacuum = off). It prevents transaction wraparound.VACUUM FULL is rarely needed and locks the table. Prefer regular VACUUM or pg_repack.pg_class.reloptions.VACUUM to promptly reclaim space.ALTER SYSTEM require pg_reload_conf() (no restart).ALTER TABLE ... SET take effect on the next autovacuum run.After applying vacuum tuning changes:
monitor_vacuum_progress action: "progress" over the next hours to confirm autovacuum is now running on the target tables.monitor_vacuum_progress action: "needs_vacuum" to confirm dead tuples were cleaned up.get_active_queries with include_idle: true to confirm no more blockers, then re-check vacuum progress.pgtuner://table/{schema}/{table_name}/stats to verify XID age was reset.pg-bloat-analysis skillpg-lock-diagnosis skillpg-connection-analysis skillpg-config-tuning skill| Feature | Version |
|---|---|
pg_stat_progress_vacuum | PG9.6+ |
pg_stat_progress_vacuum.num_dead_item_ids | PG14+ |
VACUUM (PARALLEL n) | PG13+ (parallel index vacuum) |
| Improved autovacuum cost delay default (2ms) | PG12+ |
log_autovacuum_min_duration | PG9.4+ (log slow autovacuum runs) |
monitor_vacuum_progress calls are read-onlyreview_settings is read-onlyget_active_queries is read-onlypg_cancel_backend / pg_terminate_backend calls should only be used with explicit user approvalALTER SYSTEM / ALTER TABLE ... SET does not require a restart~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.