@@ -417,7 +417,9 @@ SELECT json_object_agg(
417417 'toast_blks_read', si.toast_blks_read,
418418 'toast_blks_hit', si.toast_blks_hit,
419419 'tidx_blks_read', si.tidx_blks_read,
420- 'tidx_blks_hit', si.tidx_blks_hit
420+ 'tidx_blks_hit', si.tidx_blks_hit,
421+ 'bloat_size', bloat_size,
422+ 'bloat_percent', bloat_pct
421423 )
422424)
423425FROM
@@ -428,6 +430,63 @@ JOIN
428430 pg_catalog.pg_stat_all_tables AS s ON c.oid = s.relid
429431JOIN
430432 pg_catalog.pg_statio_all_tables AS si ON c.oid = si.relid
433+ JOIN
434+ (
435+ SELECT
436+ tblid,
437+ CASE WHEN tblpages - est_tblpages_ff > 0
438+ THEN (tblpages-est_tblpages_ff)*bs
439+ ELSE 0
440+ END AS bloat_size,
441+ CASE WHEN tblpages > 0 AND tblpages - est_tblpages_ff > 0
442+ THEN 100 * (tblpages - est_tblpages_ff)/tblpages::float
443+ ELSE 0
444+ END AS bloat_pct
445+ -- , tpl_hdr_size, tpl_data_size, (pst).free_percent + (pst).dead_tuple_percent AS real_frag -- (DEBUG INFO)
446+ FROM (
447+ SELECT ceil( reltuples / ( (bs-page_hdr)/tpl_size ) ) + ceil( toasttuples / 4 ) AS est_tblpages,
448+ ceil( reltuples / ( (bs-page_hdr)*fillfactor/(tpl_size*100) ) ) + ceil( toasttuples / 4 ) AS est_tblpages_ff,
449+ tblpages, fillfactor, bs, tblid, schemaname, tblname, heappages, toastpages, is_na
450+ -- , tpl_hdr_size, tpl_data_size, pgstattuple(tblid) AS pst -- (DEBUG INFO)
451+ FROM (
452+ SELECT
453+ ( 4 + tpl_hdr_size + tpl_data_size + (2*ma)
454+ - CASE WHEN tpl_hdr_size%ma = 0 THEN ma ELSE tpl_hdr_size%ma END
455+ - CASE WHEN ceil(tpl_data_size)::int%ma = 0 THEN ma ELSE ceil(tpl_data_size)::int%ma END
456+ ) AS tpl_size, bs - page_hdr AS size_per_block, (heappages + toastpages) AS tblpages, heappages,
457+ toastpages, reltuples, toasttuples, bs, page_hdr, tblid, schemaname, tblname, fillfactor, is_na
458+ -- , tpl_hdr_size, tpl_data_size
459+ FROM (
460+ SELECT
461+ tbl.oid AS tblid, ns.nspname AS schemaname, tbl.relname AS tblname, tbl.reltuples,
462+ tbl.relpages AS heappages, coalesce(toast.relpages, 0) AS toastpages,
463+ coalesce(toast.reltuples, 0) AS toasttuples,
464+ coalesce(substring(
465+ array_to_string(tbl.reloptions, ' ')
466+ FROM 'fillfactor=([0-9]+)')::smallint, 100) AS fillfactor,
467+ current_setting('block_size')::numeric AS bs,
468+ CASE WHEN version()~'mingw32' OR version()~'64-bit|x86_64|ppc64|ia64|amd64' THEN 8 ELSE 4 END AS ma,
469+ 24 AS page_hdr,
470+ 23 + CASE WHEN MAX(coalesce(s.null_frac,0)) > 0 THEN ( 7 + count(s.attname) ) / 8 ELSE 0::int END
471+ + CASE WHEN bool_or(att.attname = 'oid' and att.attnum < 0) THEN 4 ELSE 0 END AS tpl_hdr_size,
472+ sum( (1-coalesce(s.null_frac, 0)) * coalesce(s.avg_width, 0) ) AS tpl_data_size,
473+ bool_or(att.atttypid = 'pg_catalog.name'::regtype)
474+ OR sum(CASE WHEN att.attnum > 0 THEN 1 ELSE 0 END) <> count(s.attname) AS is_na
475+ FROM pg_attribute AS att
476+ JOIN pg_class AS tbl ON att.attrelid = tbl.oid
477+ JOIN pg_namespace AS ns ON ns.oid = tbl.relnamespace
478+ LEFT JOIN pg_stats AS s ON s.schemaname=ns.nspname
479+ AND s.tablename = tbl.relname AND s.inherited=false AND s.attname=att.attname
480+ LEFT JOIN pg_class AS toast ON tbl.reltoastrelid = toast.oid
481+ WHERE NOT att.attisdropped
482+ AND tbl.relkind in ('r','m')
483+ GROUP BY 1,2,3,4,5,6,7,8,9,10
484+ ORDER BY 2,3
485+ ) AS s
486+ ) AS s2
487+ ) AS s3
488+ ) a on
489+ a.tblid = s.relid
431490WHERE
432491 n.nspname NOT IN ('pg_catalog', 'information_schema')
433492 AND c.relkind = 'r'
@@ -485,7 +544,9 @@ SELECT json_object_agg(
485544 'idx_tup_read', idx_tup_read,
486545 'idx_tup_fetch', idx_tup_fetch,
487546 'idx_blks_read', idx_blks_read,
488- 'idx_blks_hit', idx_blks_hit
547+ 'idx_blks_hit', idx_blks_hit,
548+ 'bloat_size', bloat_size,
549+ 'bloat_percent', bloat_pct
489550 )
490551)
491552FROM
@@ -496,6 +557,108 @@ JOIN
496557 pg_catalog.pg_stat_all_indexes AS s ON c.oid = s.indexrelid
497558JOIN
498559 pg_catalog.pg_statio_all_indexes AS si ON c.oid = si.indexrelid
560+ JOIN
561+ (
562+ SELECT nspname AS schemaname, tblname, idxname, bs*(relpages)::bigint AS real_size,
563+ bs*(relpages-est_pages)::bigint AS extra_size,
564+ 100 * (relpages-est_pages)::float / relpages AS extra_pct,
565+ fillfactor,
566+ idxoid,
567+ CASE WHEN relpages > est_pages_ff
568+ THEN bs*(relpages-est_pages_ff)
569+ ELSE 0
570+ END AS bloat_size,
571+ 100 * (relpages-est_pages_ff)::float / relpages AS bloat_pct,
572+ is_na
573+ -- , 100-(pst).avg_leaf_density AS pst_avg_bloat, est_pages, index_tuple_hdr_bm, maxalign, pagehdr, nulldatawidth, nulldatahdrwidth, reltuples, relpages -- (DEBUG INFO)
574+ FROM (
575+ SELECT coalesce(1 +
576+ ceil(reltuples/floor((bs-pageopqdata-pagehdr)/(4+nulldatahdrwidth)::float)), 0 -- ItemIdData size + computed avg size of a tuple (nulldatahdrwidth)
577+ ) AS est_pages,
578+ coalesce(1 +
579+ ceil(reltuples/floor((bs-pageopqdata-pagehdr)*fillfactor/(100*(4+nulldatahdrwidth)::float))), 0
580+ ) AS est_pages_ff,
581+ bs, nspname, tblname, idxname, idxoid, relpages, fillfactor, is_na
582+ -- , pgstatindex(idxoid) AS pst, index_tuple_hdr_bm, maxalign, pagehdr, nulldatawidth, nulldatahdrwidth, reltuples -- (DEBUG INFO)
583+ FROM (
584+ SELECT maxalign, bs, nspname, tblname, idxname, reltuples, relpages, idxoid, fillfactor,
585+ ( index_tuple_hdr_bm +
586+ maxalign - CASE -- Add padding to the index tuple header to align on MAXALIGN
587+ WHEN index_tuple_hdr_bm%maxalign = 0 THEN maxalign
588+ ELSE index_tuple_hdr_bm%maxalign
589+ END
590+ + nulldatawidth + maxalign - CASE -- Add padding to the data to align on MAXALIGN
591+ WHEN nulldatawidth = 0 THEN 0
592+ WHEN nulldatawidth::integer%maxalign = 0 THEN maxalign
593+ ELSE nulldatawidth::integer%maxalign
594+ END
595+ )::numeric AS nulldatahdrwidth, pagehdr, pageopqdata, is_na
596+ -- , index_tuple_hdr_bm, nulldatawidth -- (DEBUG INFO)
597+ FROM (
598+ SELECT n.nspname, i.tblname, i.idxname, i.reltuples, i.relpages,
599+ i.idxoid, i.fillfactor, current_setting('block_size')::numeric AS bs,
600+ CASE -- MAXALIGN: 4 on 32bits, 8 on 64bits (and mingw32 ?)
601+ WHEN version() ~ 'mingw32' OR version() ~ '64-bit|x86_64|ppc64|ia64|amd64' THEN 8
602+ ELSE 4
603+ END AS maxalign,
604+ /* per page header, fixed size: 20 for 7.X, 24 for others */
605+ 24 AS pagehdr,
606+ /* per page btree opaque data */
607+ 16 AS pageopqdata,
608+ /* per tuple header: add IndexAttributeBitMapData if some cols are null-able */
609+ CASE WHEN max(coalesce(s.null_frac,0)) = 0
610+ THEN 8 -- IndexTupleData size
611+ ELSE 8 + (( 32 + 8 - 1 ) / 8) -- IndexTupleData size + IndexAttributeBitMapData size ( max num filed per index + 8 - 1 /8)
612+ END AS index_tuple_hdr_bm,
613+ /* data len: we remove null values save space using it fractionnal part from stats */
614+ sum( (1-coalesce(s.null_frac, 0)) * coalesce(s.avg_width, 1024)) AS nulldatawidth,
615+ max( CASE WHEN i.atttypid = 'pg_catalog.name'::regtype THEN 1 ELSE 0 END ) > 0 AS is_na
616+ FROM (
617+ SELECT ct.relname AS tblname, ct.relnamespace, ic.idxname, ic.attpos, ic.indkey, ic.indkey[ic.attpos], ic.reltuples, ic.relpages, ic.tbloid, ic.idxoid, ic.fillfactor,
618+ coalesce(a1.attnum, a2.attnum) AS attnum, coalesce(a1.attname, a2.attname) AS attname, coalesce(a1.atttypid, a2.atttypid) AS atttypid,
619+ CASE WHEN a1.attnum IS NULL
620+ THEN ic.idxname
621+ ELSE ct.relname
622+ END AS attrelname
623+ FROM (
624+ SELECT idxname, reltuples, relpages, tbloid, idxoid, fillfactor, indkey,
625+ pg_catalog.generate_series(1,indnatts) AS attpos
626+ FROM (
627+ SELECT ci.relname AS idxname, ci.reltuples, ci.relpages, i.indrelid AS tbloid,
628+ i.indexrelid AS idxoid,
629+ coalesce(substring(
630+ array_to_string(ci.reloptions, ' ')
631+ from 'fillfactor=([0-9]+)')::smallint, 90) AS fillfactor,
632+ i.indnatts,
633+ pg_catalog.string_to_array(pg_catalog.textin(
634+ pg_catalog.int2vectorout(i.indkey)),' ')::int[] AS indkey
635+ FROM pg_catalog.pg_index i
636+ JOIN pg_catalog.pg_class ci ON ci.oid = i.indexrelid
637+ WHERE ci.relam=(SELECT oid FROM pg_am WHERE amname = 'btree')
638+ AND ci.relpages > 0
639+ ) AS idx_data
640+ ) AS ic
641+ JOIN pg_catalog.pg_class ct ON ct.oid = ic.tbloid
642+ LEFT JOIN pg_catalog.pg_attribute a1 ON
643+ ic.indkey[ic.attpos] <> 0
644+ AND a1.attrelid = ic.tbloid
645+ AND a1.attnum = ic.indkey[ic.attpos]
646+ LEFT JOIN pg_catalog.pg_attribute a2 ON
647+ ic.indkey[ic.attpos] = 0
648+ AND a2.attrelid = ic.idxoid
649+ AND a2.attnum = ic.attpos
650+ ) i
651+ JOIN pg_catalog.pg_namespace n ON n.oid = i.relnamespace
652+ JOIN pg_catalog.pg_stats s ON s.schemaname = n.nspname
653+ AND s.tablename = i.attrelname
654+ AND s.attname = i.attname
655+ GROUP BY 1,2,3,4,5,6,7,8,9,10,11
656+ ) AS rows_data_stats
657+ ) AS rows_hdr_pdg_stats
658+ ) AS relation_stats
659+ ORDER BY nspname, tblname, idxname
660+ ) a on
661+ a.idxoid = s.indexrelid
499662WHERE
500663 n.nspname NOT IN ('pg_catalog', 'information_schema')
501664 AND c.relkind = 'i'
0 commit comments