Tools that help
Free tools that fix or explain what a checkup finds. For each: what it does, how to install it, when to run it, and what to watch out for. Before installing anything on a managed provider, check its list of supported extensions, because many don't allow every one.
| Problem | Reach for |
|---|---|
| Table or index bloat | pg_repack, pgstattuple to measure it |
| Slow queries | pg_stat_statements, auto_explain, HypoPG |
| Too many connections | PgBouncer |
| Understanding logs | pgBadger |
| Very large tables | pg_partman |
| Scheduled maintenance | pg_cron |
| Memory and cache questions | pg_buffercache, PGTune |
pg_repack
What it does: rebuilds a bloated table or index while it stays available for reads and writes. VACUUM FULL does the same job but locks the table completely.
When to run it: when a checkup shows heavy bloat (a large share of dead or wasted space) that ordinary VACUUM won't give back, and you can't afford a lock. Run it off-peak. Fix what caused the bloat first (long transactions, autovacuum settings), or it returns.
Install:
# Debian / Ubuntu (PGDG repository), match your Postgres major version
sudo apt install postgresql-17-repack
# RHEL / Rocky / Alma (PGDG)
sudo dnf install pg_repack_17
# From source: needs pg_config on your PATH
git clone https://github.com/reorg/pg_repack && cd pg_repack && make && sudo make install
Then, once per database, as a superuser: create extension pg_repack;. The command-line client and the extension must be the same version.
Use:
# See what it would do first
pg_repack -h db.example.com -U admin -d app --table=public.events --dry-run
# Then run it. --no-kill-backend matters: by default, after the wait timeout it cancels queries that block it.
pg_repack -h db.example.com -U admin -d app --table=public.events --no-kill-backend --wait-timeout=120
# Rebuild only the indexes of a table
pg_repack -h db.example.com -U admin -d app --table=public.events --only-indexes
Watch out for:
- The table needs a primary key or a unique index on non-null columns.
- It needs free disk roughly equal to the table plus its indexes, while it works.
- It takes a brief exclusive lock at the start and end, so a long-running transaction can hold it up.
- If it is interrupted it can leave a temporary schema and trigger behind. Drop the extension and recreate it to clean up.
- Run
analyzeon the table afterwards.
Alternatives: pg_squeeze does the same from a background worker on a schedule (needs wal_level = logical and a preload entry). VACUUM FULL and CLUSTER are built in but lock the table for the whole rewrite.
pgstattuple
What it does: measures real bloat, instead of estimating it.
Install: it ships with Postgres contrib. create extension pgstattuple;
Use: select * from pgstattuple_approx('public.events'); is cheap and safe on big tables (it uses the visibility map). pgstattuple('public.events') scans the whole table, so avoid it in peak hours. Look at dead_tuple_percent and free_percent.
When: a checkup says a table is bloated and you want a number before you rebuild it. pg-checkup uses pgstattuple_approx automatically when it's installed.
pg_stat_statements
What it does: records time, calls, rows and I/O for every distinct query. It is the starting point for almost every performance question.
Install: add it to shared_preload_libraries in postgresql.conf, restart, then create extension pg_stat_statements; in each database. Many managed providers ship it enabled.
Use:
select round(total_exec_time) as total_ms, calls, round(mean_exec_time::numeric, 1) as mean_ms, left(query, 90)
from pg_stat_statements order by total_exec_time desc limit 10;
Reset it with select pg_stat_statements_reset(); after a fix, so you measure the new behaviour.
Watch out for: it's normalised (values replaced by $1), so it shows the shape of a query, not the exact one that ran.
auto_explain
What it does: logs the execution plan of slow queries as they happen, so you don't have to reproduce them.
Install: shared_preload_libraries = 'auto_explain', or per session with load 'auto_explain';. Then:
auto_explain.log_min_duration = '500ms'
auto_explain.log_analyze = on # real timings; adds overhead, so raise the threshold on busy servers
auto_explain.log_buffers = on
When: a query is slow only sometimes, or only in production. Turn it on for a day, then read the plans in the log (pgBadger or the log analyzer helps).
HypoPG
What it does: lets you ask "would this index help?" without building it.
Install: apt install postgresql-17-hypopg, then create extension hypopg;
Use:
select * from hypopg_create_index('create index on events (customer_id, created_at)');
explain select * from events where customer_id = 42 order by created_at desc limit 20; -- the plan now uses the hypothetical index
select hypopg_reset();
When: before adding an index to a large table, where a wrong guess costs hours of build time and write overhead.
PgBouncer
What it does: a connection pooler. Thousands of application connections share a few dozen real ones.
Install: sudo apt install pgbouncer (or use your provider's built-in pooler).
Minimal pgbouncer.ini:
[databases]
app = host=db.internal port=5432 dbname=app
[pgbouncer]
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
default_pool_size = 20
max_client_conn = 2000
server_idle_timeout = 600
When: connections are near max_connections, or many are idle. Size default_pool_size near two to four times the CPU cores, not by traffic.
Watch out for: in transaction mode, session features (SET, advisory locks, LISTEN, and older prepared-statement usage) don't carry across transactions. Point clients at port 6432 and keep max_connections on Postgres well above the sum of your pools.
pgBadger
What it does: turns Postgres logs into an HTML report: slowest queries, errors over time, connections, checkpoints, locks.
Install: sudo apt install pgbadger, or brew install pgbadger.
Use:
pgbadger -f stderr /var/log/postgresql/postgresql-17-main.log -o report.html
It needs useful logging first: log_min_duration_statement = 250, log_checkpoints = on, log_lock_waits = on, log_temp_files = 0, log_connections = on, log_disconnections = on, and a log_line_prefix such as '%m [%p] %q%u@%d '.
When: after an incident, or weekly on a busy system.
pg_partman
What it does: creates and drops time-based partitions automatically.
Install: sudo apt install postgresql-17-partman, then create extension pg_partman;
When: a single table passes roughly 100 GB, or is most of your database, and old data can be dropped or archived. Partitioning lets you drop a month in a second instead of deleting rows for hours. Set it up before the table is huge: converting a very large table is much harder.
pg_cron
What it does: runs SQL on a schedule inside the database.
Install: shared_preload_libraries = 'pg_cron', restart, create extension pg_cron;
Use it for maintenance:
select cron.schedule('nightly-analyze', '0 3 * * *', 'analyze');
select cron.schedule('purge-cron-history', '0 4 * * *', $$delete from cron.job_run_details where end_time < now() - interval '7 days'$$);
Watch out for: it logs every run forever unless you purge (see above), and a failing job fails silently. The checkup flags both.
pg_buffercache
What it does: shows what is in shared_buffers right now.
Install: it ships with contrib. create extension pg_buffercache;
Use:
select c.relname, count(*) as buffers, pg_size_pretty(count(*) * 8192) as size
from pg_buffercache b join pg_class c on b.relfilenode = pg_relation_filenode(c.oid)
group by 1 order by 2 desc limit 10;
When: the cache hit rate is low and you want to know what's occupying memory, or whether the working set could fit with more RAM.
PGTune
What it does: suggests shared_buffers, work_mem, max_wal_size and friends from your RAM, CPU count and workload type. It's a web calculator at pgtune.leopard.in.ua.
When: on a new server, or after resizing one. Treat the output as a starting point: change one group of settings at a time and measure.
Where to go next
- The troubleshooting handbook has step-by-step guides for each symptom.
- When to scale covers what to try before paying for a bigger server.