|
| 1 | +-- btree index stats query |
| 2 | +-- estimates bloat for btree indexes |
1 | 3 | WITH btree_index_atts AS ( |
2 | | - SELECT nspname, relname, reltuples, relpages, indrelid, relam, |
| 4 | + SELECT nspname, |
| 5 | + indexclass.relname as index_name, |
| 6 | + indexclass.reltuples, |
| 7 | + indexclass.relpages, |
| 8 | + indrelid, indexrelid, |
| 9 | + indexclass.relam, |
| 10 | + tableclass.relname as tablename, |
3 | 11 | regexp_split_to_table(indkey::text, ' ')::smallint AS attnum, |
4 | 12 | indexrelid as index_oid |
5 | 13 | FROM pg_index |
6 | | - JOIN pg_class ON pg_class.oid=pg_index.indexrelid |
7 | | - JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace |
8 | | - JOIN pg_am ON pg_class.relam = pg_am.oid |
9 | | - WHERE pg_am.amname = 'btree' |
10 | | - AND nspname NOT IN ('pg_catalog','information_schema') |
| 14 | + JOIN pg_class AS indexclass ON pg_index.indexrelid = indexclass.oid |
| 15 | + JOIN pg_class AS tableclass ON pg_index.indrelid = tableclass.oid |
| 16 | + JOIN pg_namespace ON pg_namespace.oid = indexclass.relnamespace |
| 17 | + JOIN pg_am ON indexclass.relam = pg_am.oid |
| 18 | + WHERE pg_am.amname = 'btree' and indexclass.relpages > 0 |
| 19 | + --AND nspname NOT IN ('pg_catalog','information_schema') |
11 | 20 | ), |
12 | 21 | index_item_sizes AS ( |
13 | 22 | SELECT |
14 | | - i.nspname, i.relname, i.reltuples, i.relpages, i.relam, |
15 | | - s.starelid, a.attrelid AS table_oid, index_oid, |
| 23 | + ind_atts.nspname, ind_atts.index_name, |
| 24 | + ind_atts.reltuples, ind_atts.relpages, ind_atts.relam, |
| 25 | + indrelid AS table_oid, index_oid, |
16 | 26 | current_setting('block_size')::numeric AS bs, |
17 | 27 | 8 AS maxalign, |
18 | 28 | 24 AS pagehdr, |
19 | | - /* per tuple header: add index_attribute_bm if some cols are null-able */ |
20 | | - CASE WHEN max(coalesce(s.stanullfrac,0)) = 0 |
| 29 | + CASE WHEN max(coalesce(pg_stats.null_frac,0)) = 0 |
21 | 30 | THEN 2 |
22 | 31 | ELSE 6 |
23 | 32 | END AS index_tuple_hdr, |
24 | | - /* data len: we remove null values save space using it fractionnal part from stats */ |
25 | | - sum( (1-coalesce(s.stanullfrac, 0)) * coalesce(s.stawidth, 2048) ) AS nulldatawidth |
26 | | - FROM pg_attribute AS a |
27 | | - JOIN pg_statistic AS s ON s.starelid=a.attrelid AND s.staattnum = a.attnum |
28 | | - JOIN btree_index_atts AS i ON i.indrelid = a.attrelid AND a.attnum = i.attnum |
29 | | - WHERE a.attnum > 0 |
| 33 | + sum( (1-coalesce(pg_stats.null_frac, 0)) * coalesce(pg_stats.avg_width, 1024) ) AS nulldatawidth |
| 34 | + FROM pg_attribute |
| 35 | + JOIN btree_index_atts AS ind_atts ON pg_attribute.attrelid = ind_atts.indexrelid AND pg_attribute.attnum = ind_atts.attnum |
| 36 | + JOIN pg_stats ON pg_stats.schemaname = ind_atts.nspname |
| 37 | + -- stats for regular index columns |
| 38 | + AND ( (pg_stats.tablename = ind_atts.tablename AND pg_stats.attname = pg_catalog.pg_get_indexdef(pg_attribute.attrelid, pg_attribute.attnum, TRUE)) |
| 39 | + -- stats for functional indexes |
| 40 | + OR (pg_stats.tablename = ind_atts.index_name AND pg_stats.attname = pg_attribute.attname)) |
| 41 | + WHERE pg_attribute.attnum > 0 |
30 | 42 | GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9 |
31 | 43 | ), |
32 | | -index_aligned AS ( |
33 | | - SELECT maxalign, bs, nspname, relname AS index_name, reltuples, |
| 44 | +index_aligned_est AS ( |
| 45 | + SELECT maxalign, bs, nspname, index_name, reltuples, |
34 | 46 | relpages, relam, table_oid, index_oid, |
35 | | - ( 2 + |
36 | | - maxalign - CASE /* Add padding to the index tuple header to align on MAXALIGN */ |
37 | | - WHEN index_tuple_hdr%maxalign = 0 THEN maxalign |
38 | | - ELSE index_tuple_hdr%maxalign |
39 | | - END |
40 | | - + nulldatawidth + maxalign - CASE /* Add padding to the data to align on MAXALIGN */ |
41 | | - WHEN nulldatawidth::integer%maxalign = 0 THEN maxalign |
42 | | - ELSE nulldatawidth::integer%maxalign |
43 | | - END |
44 | | - )::numeric AS nulldatahdrwidth, pagehdr |
45 | | - FROM index_item_sizes AS s1 |
46 | | -), |
47 | | -otta_calc AS ( |
48 | | - SELECT bs, nspname, table_oid, index_oid, index_name, relpages, coalesce( |
49 | | - ceil((reltuples*(4+nulldatahdrwidth))/(bs-pagehdr::float)) + |
50 | | - CASE WHEN am.amname IN ('hash','btree') THEN 1 ELSE 0 END , 0 -- btree and hash have a metadata reserved block |
51 | | - ) AS otta |
52 | | - FROM index_aligned AS s2 |
53 | | - LEFT JOIN pg_am am ON s2.relam = am.oid |
| 47 | + coalesce ( |
| 48 | + ceil ( |
| 49 | + reltuples * ( 6 |
| 50 | + + maxalign |
| 51 | + - CASE |
| 52 | + WHEN index_tuple_hdr%maxalign = 0 THEN maxalign |
| 53 | + ELSE index_tuple_hdr%maxalign |
| 54 | + END |
| 55 | + + nulldatawidth |
| 56 | + + maxalign |
| 57 | + - CASE /* Add padding to the data to align on MAXALIGN */ |
| 58 | + WHEN nulldatawidth::integer%maxalign = 0 THEN maxalign |
| 59 | + ELSE nulldatawidth::integer%maxalign |
| 60 | + END |
| 61 | + )::numeric |
| 62 | + / ( bs - pagehdr::NUMERIC ) |
| 63 | + +1 ) |
| 64 | + , 0 ) |
| 65 | + as expected |
| 66 | + FROM index_item_sizes |
54 | 67 | ), |
55 | 68 | raw_bloat AS ( |
56 | | - SELECT current_database() as dbname, nspname, c.relname AS table_name, index_name, |
57 | | - bs*(sub.relpages)::bigint AS totalbytes, otta as expected, |
| 69 | + SELECT current_database() as dbname, nspname, pg_class.relname AS table_name, index_name, |
| 70 | + bs*(index_aligned_est.relpages)::bigint AS totalbytes, expected, |
58 | 71 | CASE |
59 | | - WHEN sub.relpages <= otta THEN 0 |
60 | | - ELSE bs*(sub.relpages-otta)::bigint END |
61 | | - AS wastedbytes, |
| 72 | + WHEN index_aligned_est.relpages <= expected |
| 73 | + THEN 0 |
| 74 | + ELSE bs*(index_aligned_est.relpages-expected)::bigint |
| 75 | + END AS wastedbytes, |
62 | 76 | CASE |
63 | | - WHEN sub.relpages <= otta |
64 | | - THEN 0 ELSE bs*(sub.relpages-otta)::bigint * 100 / (bs*(sub.relpages)::bigint) END |
65 | | - AS realbloat, |
66 | | - pg_relation_size(sub.table_oid) as table_bytes, |
| 77 | + WHEN index_aligned_est.relpages <= expected |
| 78 | + THEN 0 |
| 79 | + ELSE bs*(index_aligned_est.relpages-expected)::bigint * 100 / (bs*(index_aligned_est.relpages)::bigint) |
| 80 | + END AS realbloat, |
| 81 | + pg_relation_size(index_aligned_est.table_oid) as table_bytes, |
67 | 82 | stat.idx_scan as index_scans |
68 | | - FROM otta_calc AS sub |
69 | | - JOIN pg_class AS c ON c.oid=sub.table_oid |
70 | | - JOIN pg_stat_user_indexes AS stat ON sub.index_oid = stat.indexrelid |
| 83 | + FROM index_aligned_est |
| 84 | + JOIN pg_class ON pg_class.oid=index_aligned_est.table_oid |
| 85 | + JOIN pg_stat_user_indexes AS stat ON index_aligned_est.index_oid = stat.indexrelid |
71 | 86 | ), |
72 | 87 | format_bloat AS ( |
73 | 88 | SELECT dbname as database_name, nspname as schema_name, table_name, index_name, |
|
0 commit comments