CREATE SCHEMA IF NOT EXISTS zz_utils; GRANT USAGE ON SCHEMA zz_utils TO public; /* zz_utils.get_bloated_tables() - a security workaround wrapper around sql from https://wiki.postgresql.org/wiki/Show_database_bloat */ --drop function if exists zz_utils.get_bloated_tables(boolean, int); DO $OUTER$ DECLARE l_current_db text := current_database(); l_sproc_text text := $SQL$ CREATE OR REPLACE FUNCTION zz_utils.get_bloated_tables( IN p_order_by_bloat_factor boolean DEFAULT FALSE, IN p_limit int DEFAULT 100, OUT schema_name text, OUT table_name text, OUT bloat_factor numeric, OUT wasted_bytes numeric, OUT wasted_bytes_pretty text) RETURNS SETOF record AS $$ SELECT schemaname::text, tblname::text, round(bloat_ratio::numeric, 1), bloat_size::numeric, pg_size_pretty(bloat_size::int8) FROM ( SELECT current_database(), schemaname, tblname, bs*tblpages AS real_size, (tblpages-est_num_pages)*bs AS bloat_size, CASE WHEN tblpages - est_num_pages > 0 THEN round((100 * (tblpages - est_num_pages)/tblpages::float)::numeric, 1) ELSE 0 END AS bloat_ratio FROM ( SELECT ceil(reltuples/((bs-page_hdr)/tpl_size)) + ceil(toasttuples/4) AS est_num_pages, tblpages, bs, tblid, schemaname, tblname, heappages, toastpages, is_na FROM ( SELECT ( 4 + tpl_hdr_size + tpl_data_size + (2*ma) - CASE WHEN tpl_hdr_size%ma = 0 THEN ma ELSE tpl_hdr_size%ma END - CASE WHEN ceil(tpl_data_size)::int%ma = 0 THEN ma ELSE ceil(tpl_data_size)::int%ma END ) AS tpl_size, bs - page_hdr AS size_per_block, (heappages + toastpages) AS tblpages, heappages, toastpages, reltuples, toasttuples, bs, page_hdr, tblid, schemaname, tblname, is_na FROM ( SELECT tbl.oid AS tblid, ns.nspname AS schemaname, tbl.relname AS tblname, tbl.reltuples, tbl.relpages AS heappages, coalesce(toast.relpages, 0) AS toastpages, coalesce(toast.reltuples, 0) AS toasttuples, current_setting('block_size')::numeric AS bs, CASE WHEN version()~'mingw32' OR version()~'64-bit|x86_64|ppc64|ia64|amd64' THEN 8 ELSE 4 END AS ma, 24 AS page_hdr, 23 + CASE WHEN MAX(coalesce(null_frac,0)) > 0 THEN ( 7 + count(*) ) / 8 ELSE 0::int END + CASE WHEN tbl.relhasoids THEN 4 ELSE 0 END AS tpl_hdr_size, sum( (1-coalesce(s.null_frac, 0)) * coalesce( CASE WHEN t.typlen = -1 THEN CASE WHEN s.avg_width < 127 THEN s.avg_width + 1 ELSE s.avg_width + 4 END WHEN t.typlen = -2 THEN s.avg_width + 1 ELSE t.typlen END , 1024)) AS tpl_data_size, bool_or(att.atttypid = 'pg_catalog.name'::regtype) AS is_na FROM pg_attribute AS att JOIN pg_type AS t ON att.atttypid = t.oid JOIN pg_class AS tbl ON att.attrelid = tbl.oid JOIN pg_namespace AS ns ON ns.oid = tbl.relnamespace JOIN pg_stats AS s ON s.schemaname=ns.nspname AND s.tablename = tbl.relname AND s.inherited=false AND s.attname=att.attname LEFT JOIN pg_class AS toast ON tbl.reltoastrelid = toast.oid WHERE att.attnum > 0 AND NOT att.attisdropped AND tbl.relkind = 'r' GROUP BY 1,2,3,4,5,6,7,8,9,10, tbl.relhasoids ORDER BY 2,3 ) AS s ) AS s2 ) AS s3 WHERE NOT is_na ) AS o ORDER BY CASE WHEN p_order_by_bloat_factor THEN bloat_ratio ELSE bloat_size END DESC LIMIT p_limit $$ LANGUAGE sql VOLATILE SECURITY DEFINER; $SQL$; BEGIN EXECUTE l_sproc_text; EXECUTE 'GRANT EXECUTE ON FUNCTION zz_utils.get_bloated_tables(boolean, int) TO public'; END; $OUTER$; /* zz_utils.get_bloated_indexes() - a security workaround wrapper around sql from https://wiki.postgresql.org/wiki/Show_database_bloat */ --drop function if exists zz_utils.get_bloated_indexes(boolean, int); DO $OUTER$ DECLARE l_current_db text := current_database(); l_sproc_text text := $SQL$ CREATE OR REPLACE FUNCTION zz_utils.get_bloated_indexes( IN p_order_by_bloat_factor boolean DEFAULT FALSE, IN p_limit int DEFAULT 100, OUT schema_name text, OUT table_name text, OUT index_name text, OUT bloat_factor numeric, OUT wasted_bytes numeric, OUT wasted_bytes_pretty text) RETURNS SETOF record AS $$ SELECT schemaname::text, tblname::text, idxname::text, round(bloat_ratio::numeric, 1), bloat_size::numeric, pg_size_pretty(bloat_size::int8) FROM ( SELECT current_database(), nspname AS schemaname, tblname, idxname, bs*(relpages-est_pages)::bigint AS extra_size, 100 * (relpages-est_pages)::float / relpages AS extra_ratio, fillfactor, bs*(relpages-est_pages_ff) AS bloat_size, 100 * (relpages-est_pages_ff)::float / relpages AS bloat_ratio, is_na FROM ( SELECT coalesce( 1 + ceil(reltuples/floor((bs-pageopqdata-pagehdr)/(4+nulldatahdrwidth)::float)) , 0) AS est_pages, coalesce( 1 + ceil(reltuples/floor((bs-pageopqdata-pagehdr)*fillfactor/(100*(4+nulldatahdrwidth)::float))) , 0) AS est_pages_ff, bs, nspname, table_oid, tblname, idxname, relpages, fillfactor, is_na FROM ( SELECT maxalign, bs, nspname, tblname, idxname, reltuples, relpages, relam, table_oid, fillfactor, ( index_tuple_hdr_bm + maxalign - CASE WHEN index_tuple_hdr_bm%maxalign = 0 THEN maxalign ELSE index_tuple_hdr_bm%maxalign END + nulldatawidth + maxalign - CASE WHEN nulldatawidth = 0 THEN 0 WHEN nulldatawidth::integer%maxalign = 0 THEN maxalign ELSE nulldatawidth::integer%maxalign END )::numeric AS nulldatahdrwidth, pagehdr, pageopqdata, is_na FROM ( SELECT i.nspname, i.tblname, i.idxname, i.reltuples, i.relpages, i.relam, a.attrelid AS table_oid, current_setting('block_size')::numeric AS bs, fillfactor, CASE WHEN version() ~ 'mingw32' OR version() ~ '64-bit|x86_64|ppc64|ia64|amd64' THEN 8 ELSE 4 END AS maxalign, 24 AS pagehdr, 16 AS pageopqdata, CASE WHEN max(coalesce(s.null_frac,0)) = 0 THEN 2 ELSE 2 + (( 32 + 8 - 1 ) / 8) END AS index_tuple_hdr_bm, sum( (1-coalesce(s.null_frac, 0)) * coalesce(s.avg_width, 1024)) AS nulldatawidth, max( CASE WHEN a.atttypid = 'pg_catalog.name'::regtype THEN 1 ELSE 0 END ) > 0 AS is_na FROM pg_attribute AS a JOIN ( SELECT nspname, tbl.relname AS tblname, idx.relname AS idxname, idx.reltuples, idx.relpages, idx.relam, indrelid, indexrelid, indkey::smallint[] AS attnum, coalesce(substring( array_to_string(idx.reloptions, ' ') from 'fillfactor=([0-9]+)')::smallint, 90) AS fillfactor FROM pg_index JOIN pg_class idx ON idx.oid=pg_index.indexrelid JOIN pg_class tbl ON tbl.oid=pg_index.indrelid JOIN pg_namespace ON pg_namespace.oid = idx.relnamespace WHERE pg_index.indisvalid AND tbl.relkind = 'r' AND idx.relpages > 0 ) AS i ON a.attrelid = i.indexrelid JOIN pg_stats AS s ON s.schemaname = i.nspname AND ((s.tablename = i.tblname AND s.attname = pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE)) OR (s.tablename = i.idxname AND s.attname = a.attname)) JOIN pg_type AS t ON a.atttypid = t.oid WHERE a.attnum > 0 GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9 ) AS s1 ) AS s2 JOIN pg_am am ON s2.relam = am.oid WHERE am.amname = 'btree' ) AS sub WHERE NOT is_na ) AS o ORDER BY CASE WHEN p_order_by_bloat_factor THEN bloat_ratio ELSE bloat_size END DESC LIMIT p_limit $$ LANGUAGE sql VOLATILE SECURITY DEFINER; $SQL$; BEGIN EXECUTE l_sproc_text; EXECUTE 'GRANT EXECUTE ON FUNCTION zz_utils.get_bloated_indexes(boolean, int) TO public'; END; $OUTER$;