robovac reads a statistics snapshot of one table and tells you three things: what autovacuum is doing today, what it should do at your write rate, and the exact ALTER TABLE that closes the gap. Every term in the report links to a page that explains it with a demo you can drag.
No account, no agent required, no write access. It never connects to your database, you bring the numbers.
SELECT
current_database() AS db,
s.schemaname AS schema_name,
s.relname AS table_name,
c.relpages::bigint AS relpages,
c.relallvisible::bigint AS relallvisible,
c.reltuples::bigint AS reltuples,
s.n_live_tup AS n_live_tup,
s.n_dead_tup AS n_dead_tup,
s.n_tup_ins AS n_tup_ins,
s.n_tup_upd AS n_tup_upd,
s.n_tup_del AS n_tup_del,
s.n_tup_hot_upd AS n_tup_hot_upd,
s.last_autovacuum::text AS last_autovacuum,
s.last_vacuum::text AS last_vacuum,
s.n_mod_since_analyze AS n_mod_since_analyze,
age(c.relfrozenxid) AS xid_age,
mxid_age(c.relminmxid) AS mxid_age,
(SELECT count(*) FROM pg_index i WHERE i.indrelid = c.oid) AS index_count,
c.reloptions AS reloptions,
c.relispartition AS is_partition,
(c.reltoastrelid <> 0) AS has_toast,
current_setting('server_version_num')::int AS version_num,
pg_is_in_recovery() AS is_replica,
-- A platform that already meters vacuum against live load makes the cost
-- advice redundant. missing_ok = true returns NULL on vanilla Postgres,
-- so this probes a capability without a vendor list.
current_setting('enable_google_adaptive_autovacuum', true) AS adaptive_vacuum,
-- xmax is the next xid to be assigned. Its delta over the two runs is the
-- xid consumption rate. (xmin is the oldest running transaction, so using
-- it here would read the horizon's movement instead, and would report a
-- rate near zero exactly when a long transaction pins it.)
pg_snapshot_xmax(pg_current_snapshot())::text::numeric AS xid_now,
-- How far behind the oldest snapshot sits, in xids. This is what makes
-- the dead-but-not-removable floor computable.
(pg_snapshot_xmax(pg_current_snapshot())::text::numeric
- pg_snapshot_xmin(pg_current_snapshot())::text::numeric) AS horizon_xids,
-- The ProcArray snapshot above misses replication slots, and a stuck slot
-- is the classic cause of a horizon that never advances.
(SELECT max(GREATEST(age(s.xmin), age(s.catalog_xmin)))
FROM pg_replication_slots s) AS slot_horizon_xids,
(SELECT jsonb_object_agg(name, setting) FROM pg_settings
WHERE name IN ('autovacuum_vacuum_scale_factor', 'autovacuum_vacuum_threshold', 'autovacuum_vacuum_insert_scale_factor', 'autovacuum_vacuum_insert_threshold', 'autovacuum_analyze_scale_factor', 'autovacuum_analyze_threshold', 'autovacuum_vacuum_cost_delay', 'autovacuum_vacuum_cost_limit', 'vacuum_cost_page_hit', 'vacuum_cost_page_miss', 'vacuum_cost_page_dirty', 'vacuum_freeze_min_age', 'vacuum_freeze_table_age', 'autovacuum_freeze_max_age', 'autovacuum_multixact_freeze_max_age', 'vacuum_cost_limit', 'vacuum_cost_delay', 'vacuum_failsafe_age')) AS global_settings,
now()::text AS captured_at
FROM pg_stat_user_tables s
JOIN pg_class c ON c.oid = s.relid
WHERE s.schemaname = 'schema' AND s.relname = 'table'Register the MCP server once and ask in plain language. If your agent already reaches Postgres, it runs the query itself and hands you back a link.
Your trigger threshold against your write rate, in days, not percentages. Most large tables discover here that the answer is "every three weeks".
Table age against autovacuum_freeze_max_age and the wraparound limit, with the date the cluster would stop accepting writes.
Duration and throughput under your cost settings. A 20 ms delay on a 14 GB table is 44 minutes of throttled I/O per run.
An ALTER TABLE … SET (…) that mirrors the sliders, listing only what you changed. You run it; robovac cannot.