Find what's big first, don't guess:
select relname, pg_size_pretty(pg_total_relation_size(relid)) as size
from pg_catalog.pg_statio_user_tables
order by pg_total_relation_size(relid) desc
limit 10;My bet for an expense tracker: receipt photos stored as base64 text in a table column. That's the usual way ChatGPT "saves an image" when nobody tells it about Storage. Move them to a Storage bucket and keep only the path in the table.
On the delete not helping: plain VACUUM marks space as reusable but doesn't give it back to the disk. VACUUM FULL table_name rewrites the table and shrinks it, but locks the table while it runs, so do it at a quiet moment.