How to get table, index and database sizes in PostgreSQL

The query everyone actually wants — the biggest tables, indexes included — then the three size functions and what each one really measures.

-- Le 20 tabelle più grandi (indici e TOAST inclusi):
SELECT c.oid::regclass AS table_name,
       pg_size_pretty(pg_total_relation_size(c.oid)) AS total,
       pg_size_pretty(pg_table_size(c.oid))          AS table_only,
       pg_size_pretty(pg_indexes_size(c.oid))        AS indexes
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'r'
  AND n.nspname NOT IN ('pg_catalog', 'information_schema')
ORDER BY pg_total_relation_size(c.oid) DESC
LIMIT 20;

The three size functions, disambiguated

FunctionWhat it measures
pg_relation_size('t')The main heap only — no indexes, no TOAST (the out-of-line storage for large values).
pg_table_size('t')The table proper: heap + TOAST + auxiliary maps. Still no indexes.
pg_total_relation_size('t')Everything: table + TOAST + all its indexes. This is "how much disk does this table cost me".

Wrap any of them in pg_size_pretty(...) for human units. A table with long text/JSONB columns can have most of its bytes in TOAST — if pg_table_size dwarfs pg_relation_size, that's where they are.

More one-liners

-- Dimensione di una singola tabella / indice / database:
SELECT pg_size_pretty(pg_total_relation_size('public.orders'));
SELECT pg_size_pretty(pg_relation_size('orders_created_at_idx'));
SELECT pg_size_pretty(pg_database_size(current_database()));

-- Tutti i database del cluster:
SELECT datname, pg_size_pretty(pg_database_size(datname))
FROM pg_database
ORDER BY pg_database_size(datname) DESC;

-- Gli indici più grandi:
SELECT c.oid::regclass AS index_name,
       pg_size_pretty(pg_relation_size(c.oid)) AS size
FROM pg_class c
WHERE c.relkind = 'i'
ORDER BY pg_relation_size(c.oid) DESC
LIMIT 20;

Size on disk ≠ live data

These functions measure allocated disk, which includes bloat: space occupied by dead row versions that vacuum hasn't reclaimed (and that plain vacuum returns to the table, not to the OS). A 50 GB table might hold 20 GB of live rows. If a table looks far too big for its row count, that's a bloat investigation, not a storage upgrade.

Partitioned tables: the parent itself is empty — sum the partitions, e.g. joining pg_inherits, or eyeball them with \d+ parent_name in psql.

🔍 Table suspiciously large? Read VACUUM, autovacuum and bloat. Disk actually running out? There's an error page for No space left on device — check replication slots before buying storage.