Start with the show database bloat sample query on the PostgreSQL wiki if you're investigating possible table/index bloat issues.

Also check whether autovacuum is enabled. Some people misguidedly turn it down or off because they see it creating load; they should actually be turning it up in these situations.

Answer from Craig Ringer on Stack Exchange
Top answer
1 of 5
8

Start with the show database bloat sample query on the PostgreSQL wiki if you're investigating possible table/index bloat issues.

Also check whether autovacuum is enabled. Some people misguidedly turn it down or off because they see it creating load; they should actually be turning it up in these situations.

2 of 5
7

If you are administrator/developer of the database, some symptoms are when you have huge (>1m rows) tables with temp data that are regularly deleted (like deleting the entries for the last month) and the database/table size does not change.

I'm using these queries to check the table size:

/***********************
* DB schema sizes
*************************/
SELECT pg_size_pretty(sum(pg_relation_size(pg_class.oid))::bigint), nspname,
CASE pg_class.relkind WHEN 'r' THEN 'table' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 'v' THEN 'view' WHEN 't' THEN 'toast' ELSE pg_class.relkind::text END
FROM pg_class
LEFT OUTER JOIN pg_namespace ON (pg_namespace.oid = pg_class.relnamespace)
GROUP BY pg_class.relkind, nspname
ORDER BY sum(pg_relation_size(pg_class.oid)) DESC;


/***********************
* DB table sizes
*************************/
SELECT pg_size_pretty(pg_relation_size(pg_class.oid)), pg_class.relname,     pg_namespace.nspname,
CASE pg_class.relkind WHEN 'r' THEN 'table' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 'v' THEN 'view' WHEN 't' THEN 'TOAST' ELSE pg_class.relkind::text END
FROM pg_class 
LEFT OUTER JOIN pg_namespace ON (pg_namespace.oid = pg_class.relnamespace)
ORDER BY pg_relation_size(pg_class.oid) DESC;

This is not 100% relevant to your question but can give you a start.

🌐
Datadog
datadoghq.com › blog › postgresql-vacuum-monitoring
PostgreSQL VACUUM Processes: How to Monitor | Datadog
November 28, 2025 - If track_counts is off, the statistics collector won’t update the count of the number of dead rows for each table, which is the value that the autovacuum daemon checks in order to determine when and where it needs to run. You can enable autovacuuming and track_counts by editing these settings in your PostgreSQL configuration file, as described in the documentation. If it looks like the autovacuum process is running in your database, but it hasn’t launched on a table that received a lot of updates that should have triggered a VACUUM, it’s possible that autovacuuming was disabled at some point on the table(s) in question.
Discussions

postgresql - How to find tables that require VACUUM FULL? - Stack Overflow
Before VACUUM FULL I want to know, which table really needs this operation. To check this I found this query: SELECT tuple_percent, dead_tuple_count, dead_tuple_percent, free_space, free_percent FROM More on stackoverflow.com
🌐 stackoverflow.com
PostgreSQL - Query to determine which tables are currently being vacuumed? - Stack Overflow
I've found a query to view when vacuums have run, but not which are currently running. (http://heatware.net/databases/postgres-tables-auto-vacuum-analyze/) Is there a query to accomplish this? I k... More on stackoverflow.com
🌐 stackoverflow.com
Is it common to need to do regular full vacuum on tables?
The short answer is no - it isn't common to need to do a regular full vacuum. If you're finding that this is necessary, then probably you need to tune autovacuum. In my opinion the autovacuum defaults are far too conservative, especially on larger or busier tables, but as in all things this really depends on your workload. Look into the topic of database bloat (probably starting in the PostgreSQL wiki , but there are many blog posts on this topic), and start monitoring your tables to see if this is what's happening to you. If you're seeing bloat increase, then tuning autovacuum may be necessary - although there are other potential causes of bloat as well. More on reddit.com
🌐 r/PostgreSQL
33
8
September 20, 2024
optimization - how to automatically determine which tables need a vacuum / reindex in postgresql - Stack Overflow
i've written a maintenance script for our database and would like to run that script on whichever tables most need vacuuming/reindexing during our down time each day. is there any way to determine ... More on stackoverflow.com
🌐 stackoverflow.com
🌐
Google
docs.cloud.google.com › solutions › optimizing-monitoring-troubleshooting-vacuum-operations-postgresql.pdf pdf
Google Cloud Optimizing, monitoring, and troubleshooting VACUUM
June 20, 2020 - Oracle and Java are registered trademarks of Oracle and/or its affiliates. ... DELETE or UPDATE operations. Ignoring INSERT-only tables can lead to the rapid consumption · of transaction ID space and lead to potential transaction wraparound problems. PostgreSQL-13 enhances statistics considered by the autovacuum process to perform the · VACUUM operation on insert-driven tables as quickly as possible.
🌐
Dbrnd
dbrnd.com › 2018 › 03 › postgresql-script-to-check-the-status-of-autovacuum-for-all-tables
PostgreSQL: Script to check the status of AutoVacuum for all Tables
March 30, 2018 - If you don’t know about the MVCC architecture, you must visit the below article. Because of MVCC, we have to work on dead tuples which are generated by transactions in PostgreSQL. ... You can configure auto-vacuum on tables, but periodically you should check the status of it.
Top answer
1 of 2
7

This problem can easily be solved with system catalogs. I suggest to join on pg_locks since autovacuum acquires a ShareUpdateExclusiveLock lock on the table it is working on, to avoid some manual parsing of the query from pg_stat_activity.

The following query lists the tables being auto-vacuumed, solving the pg_toast reference if a toast table is being vacuumed, as explained in Postgres pg_toast in autovacuum - which table? question linked to by @Zeki.

SELECT n.nspname || '.' || c.relname
    FROM pg_namespace n, pg_stat_activity a, pg_locks l, pg_class c
    WHERE
        a.query LIKE 'autovacuum: %'
        AND l.pid = a.pid
        AND l.mode = 'ShareUpdateExclusiveLock'
        AND (c.oid = l.relation OR c.reltoastrelid = l.relation)
        AND n.oid = c.relnamespace
        AND n.nspname <> 'pg_toast';

Please note that while pg_stat_activity and pg_locks catalogs are shared across databases, this query will only list the tables being auto-vacuumed in the current database as pg_relation is not a shared catalog.

2 of 2
2

In instead of finding if the tables are being vacuumed turn auto vacuum off for the involved tables:

alter table table_name_pattern 
set (
    autovacuum_enabled = false,
    toast.autovacuum_enabled = false
);

table pattern is a glob pattern like tbl*. At the end of the query turn auto vacuum back on

alter table table_name_pattern 
set (
    autovacuum_enabled = true,
    toast.autovacuum_enabled = true
);

Edit in response to the commentaries:

The query to find if the involved tables are being vacuumed is unnecessary and useless. If it is known that one or more of the involved tables are being vacuumed what is it supposed to be done? Wait and keep repeating the inquiring query until none is being vacuumed? And when none is then start the long query just to discover after a while that auto vacuum was just kicked off again? There is no point. Why not just turn auto vacuum off and avoid all the hassle?

There is no ethical superiority in doing it the hard way, especially if the hard way is going to give worse results than the simple one. Simpler code is simpler to use and understand but it is not necessarily easier to build. Many times it is the opposite, requiring more intellectual effort or preparedness then the complex one.


If the autovacuum setting is altered inside a transaction and that transaction is rolled back the setting will be back to whatever it was before the transaction start

drop table if exists t;
create table t (id int);

begin;

alter table t
set (
    autovacuum_enabled = false,
    toast.autovacuum_enabled = false
);

\d+ t
                          Table "public.t"
 Column |  Type   | Modifiers | Storage | Stats target | Description 
--------+---------+-----------+---------+--------------+-------------
 id     | integer |           | plain   |              | 
Has OIDs: no
Options: autovacuum_enabled=false

rollback;

\d+ t
                          Table "public.t"
 Column |  Type   | Modifiers | Storage | Stats target | Description 
--------+---------+-----------+---------+--------------+-------------
 id     | integer |           | plain   |              | 
Has OIDs: no

But that setting inside the transaction will not be seen outside of the transaction so I guess autovacuum will still run. If this is true than the setting must be done outside of the transaction and controlled by a job that will turn it back regardless of what happens with the long running query.

🌐
PostgreSQL
postgresql.org › docs › 9.5 › sql-vacuum.html
PostgreSQL: Documentation: 9.5: VACUUM
February 11, 2021 - The parenthesized syntax was added in PostgreSQL 9.0; the unparenthesized syntax is deprecated. ... Selects "full" vacuum, which can reclaim more space, but takes much longer and exclusively locks the table. This method also requires extra disk space, since it writes a new copy of the table and doesn't release the old copy until the operation is complete. Usually this should only be used when a significant amount of space needs to be reclaimed from within the table.
🌐
PostgreSQL
postgresql.org › docs › 9.5 › routine-vacuuming.html
PostgreSQL: Documentation: 9.5: Routine Vacuuming
February 11, 2021 - Whole-table vacuum scans will also occur progressively for all tables, starting with those that have the oldest multixact-age, if the amount of used member storage space exceeds the amount 50% of the addressable storage space. Both of these kinds of whole-table scans will occur even if autovacuum is nominally disabled. PostgreSQL has an optional but highly recommended feature called autovacuum, whose purpose is to automate the execution of VACUUM and ANALYZE commands.
Find elsewhere
🌐
Reddit
reddit.com › r/postgresql › is it common to need to do regular full vacuum on tables?
r/PostgreSQL on Reddit: Is it common to need to do regular full vacuum on tables?
September 20, 2024 -

After an incident just a week ago with a large table that refused to use an index (full vacuum solved that), ran into a problem where a 130k row table was having drastically different responses based on the value of the query against an indexed column (as long as 2 seconds depending on the value, vs. 002 for others). This time we did a full vacuum on every table and performance is better through.

And now I'm hearing this may be commonly necessary. Is that true? The standard vacuum doesn't help but full locks the table so everything needs to be shut down. Just seems odd to me that a database should require this kind maintenance

🌐
PostgreSQL
postgresql.org › docs › current › sql-vacuum.html
PostgreSQL: Documentation: 18: VACUUM
May 14, 2026 - July 16, 2026: PostgreSQL 19 Beta 2 Released! ... Unsupported versions: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2 / 7.1 · VACUUM — garbage-collect and optionally analyze a database · VACUUM [ ( option [, ...] ) ] [ table_and_columns [, ...] ] where option can be one of: FULL [ boolean ] FREEZE [ boolean ] VERBOSE [ boolean ] ANALYZE [ boolean ] DISABLE_PAGE_SKIPPING [ boolean ] SKIP_LOCKED [ boolean ] INDEX_CLEANUP { AUTO | ON | OFF } PROCESS_MAIN [ boolean ] PROCESS_TOAST [ boolean ] TRUNCATE [ boolean ] PARALLEL integer SKIP_DATABASE_STATS [ boolean ] ONLY_DATABASE_STATS [ boolean ] BUFFER_USAGE_LIMIT size and table_and_columns is: [ ONLY ] table_name [ * ] [ ( column_name [, ...] ) ]
🌐
pganalyze
pganalyze.com › blog › exploring-postgres-vacuum-with-vacuum-simulator
Exploring Postgres VACUUM with the VACUUM Simulator
December 4, 2023 - The autovacuum launcher will periodically check if tables need VACUUM or not, and once it detects that a certain table exceeds the threshold, it’ll trigger an autovacuum worker to run VACUUM on that table.
🌐
Medium
medium.com › gett-engineering › scaling-postgresql-check-your-vacuum-f03092a6399e
Scaling PostgreSQL: Check your Vacuum! | by Stas Panchenko | Gett Tech | Medium
March 28, 2019 - In case of an update, Postgres creates a new row and marks the old one for deletion. The physical deletion of the row is done by the auto vacuum process, which runs whenever it reaches some dead-rows-per-table threshold.
🌐
Carbonblack
community.carbonblack.com › t5 › Knowledge-Base › EDR-How-to-find-Postgres-tables-to-vacuum › ta-p › 92098
Broadcom
July 6, 2020 - Note: Since your browser does not support Javascript, you must press the Continue button once to proceed
🌐
PostgreSQL
postgresql.org › docs › current › routine-vacuuming.html
PostgreSQL: Documentation: 18: 24.1. Routine Vacuuming
May 14, 2026 - Therefore, appropriate vacuum and analyze operations should be performed via session SQL commands. The default thresholds and scale factors are taken from postgresql.conf, but it is possible to override them (and many other autovacuum control parameters) on a per-table basis; see Storage Parameters for more information. If a setting has been changed via a table's storage parameters, that value is used when processing that table; otherwise the global settings are used.
Top answer
1 of 1
5

Firstly, I would suggest using pgstattuple to obtain tuple-level statistics.

pgstattuple returns a relation's physical length, percentage of “dead” tuples, and other info. This may help users to determine whether vacuum is necessary or not.

For example:

create extension pgstattuple ;
create table my_table ( id int , name text);
insert into my_table select a, md5(a::text) from generate_series(1, 1e7)a;

-- size of my_table
 Schema |   Name   | Type  |  Owner   |  Size  | Description
--------+----------+-------+----------+--------+-------------
 public | my_table | table | postgres | 651 MB |

-- dead_tuple_percent = 0, pgstattuple will not lock your table
SELECT tuple_percent, dead_tuple_count, dead_tuple_percent, free_space, free_percent FROM pgstattuple('my_table');

 tuple_percent | dead_tuple_count | dead_tuple_percent | free_space | free_percent
---------------+------------------+--------------------+------------+--------------
         89.35 |                0 |                  0 |     338776 |         0.05

-- let update 50% rows
update my_table set name = name || id where id < 5000000;

-- now, dead_tuple_percent = 28.63%
 tuple_percent | dead_tuple_count | dead_tuple_percent | free_space | free_percent
---------------+------------------+--------------------+------------+--------------
         60.43 |          4999999 |              28.63 |    1834236 |         0.17

-- size of my_table has increased

 Schema |   Name   | Type  |  Owner   |  Size   | Description
--------+----------+-------+----------+---------+-------------
 public | my_table | table | postgres | 1016 MB |                

-- try to vacuum full
vacuum full my_table;

-- after that, dead_tuple_percent = 0 and size of my_table has reduced
 tuple_percent | dead_tuple_count | dead_tuple_percent | free_space | free_percent
---------------+------------------+--------------------+------------+--------------
         88.92 |                0 |                  0 |    1664780 |         0.23
 Schema |   Name   | Type  |  Owner   |    Size    | Description
--------+----------+-------+----------+------------+-------------
 public | my_table | table | postgres | 691 MB     |

Secondly, if you are in production environment, I would suggest using pg_repack to reclaim disk without locking your table.

pg_repack is a PostgreSQL extension which lets you remove bloat from tables and indexes, and optionally restore the physical order of clustered indexes. Unlike CLUSTER and VACUUM FULL it works online, without holding an exclusive lock on the processed tables during processing. pg_repack is efficient to boot, with performance comparable to using CLUSTER directly.

For instance:

/usr/pgsql-11/bin/pg_repack -d postgres -U postgres -n -t my_table  &
🌐
OneUptime
oneuptime.com › home › blog › how to use vacuum and analyze in postgresql
How to Use Vacuum and Analyze in PostgreSQL
January 25, 2026 - -- VACUUM and ANALYZE together (common pattern) VACUUM ANALYZE mytable; -- For entire database VACUUM ANALYZE; PostgreSQL runs autovacuum automatically, but you may need to tune it. -- Check if autovacuum is enabled SHOW autovacuum; -- View autovacuum settings SELECT name, setting, short_desc FROM pg_settings WHERE name LIKE '%autovacuum%'; -- Check tables with many dead tuples SELECT schemaname, relname, n_dead_tup, n_live_tup, round(100.0 * n_dead_tup / nullif(n_live_tup + n_dead_tup, 0), 2) AS dead_pct, last_vacuum, last_autovacuum FROM pg_stat_user_tables WHERE n_dead_tup > 1000 ORDER BY n_dead_tup DESC;
Top answer
1 of 4
112

PostgreSQL 9.3

Determine if Autovacuum is Running

This is specific to Postgres 9.3 on UNIX. For Windows, see this question.

Query Postgres System Table

SELECT
  schemaname, relname,
  last_vacuum, last_autovacuum,
  vacuum_count, autovacuum_count  -- not available on 9.0 and earlier
FROM pg_stat_user_tables;

Grep System Process Status

$ ps -axww | grep autovacuum
24352 ??  Ss      1:05.33 postgres: autovacuum launcher process  (postgres)    

Grep Postgres Log

# grep autovacuum /var/log/postgresql
LOG:  autovacuum launcher started
LOG:  autovacuum launcher shutting down

If you want to know more about the autovacuum activity, set log_min_messages to DEBUG1..DEBUG5. The SQL command VACUUM VERBOSE will output information at log level INFO.


Regarding the Autovacuum Daemon, the Posgres docs state:

In the default configuration, autovacuuming is enabled and the related configuration parameters are appropriately set.

See Also:

  • http://www.postgresql.org/docs/current/static/routine-vacuuming.html
  • http://www.postgresql.org/docs/current/static/runtime-config-autovacuum.html
2 of 4
19

I'm using:

select count(*) from pg_stat_activity where query like 'autovacuum:%';

in collectd to know how many autovacuum are running concurrently.

You may need to create a security function like this:

CREATE OR REPLACE FUNCTION public.pg_autovacuum_count() RETURNS bigint
AS 'select count(*) from pg_stat_activity where query like ''autovacuum:%'';'
LANGUAGE SQL
STABLE
SECURITY DEFINER;

and call that from collectd.

In earlier Postgres, "query" was "current_query" so change it according to what works.

🌐
Red Gate Software
red-gate.com › home › uncovering the mysteries of postgresql (auto) vacuum
Uncovering the mysteries of PostgreSQL (auto) vacuum | Simple Talk
March 7, 2024 - Tables become bloated (more details on table bloat later in the article), which makes sequential scans slower. Visibility map is not updated, which prevents the usage of index-only scan (PostgreSQL still needs to check the heap to make sure that index does not point to any dead tuples). The reality. The VACUUM process is aborted if it is blocking any write operation.
🌐
PostgreSQL
postgresql.org › docs › 9.1 › routine-vacuuming.html
PostgreSQL: Documentation: 9.1: Routine Vacuuming
October 27, 2016 - Therefore, appropriate vacuum and analyze operations should be performed via session SQL commands. The default thresholds and scale factors are taken from postgresql.conf, but it is possible to override them on a table-by-table basis; see Storage Parameters for more information. If a setting has been changed via storage parameters, that value is used; otherwise the global settings are used.