@@ -14,8 +14,186 @@ metrics:
1414 gauges :
1515 - ' *'
1616
17+ index_definitions :
18+ description : " Index definitions for unused and redundant indexes only"
19+ sqls :
20+ 11 : |-
21+ with fk_indexes as (
22+ select
23+ n.nspname as schema_name,
24+ ci.relname as index_name,
25+ cr.relname as table_name,
26+ (confrelid::regclass)::text as fk_table_ref,
27+ array_to_string(indclass, ', ') as opclasses
28+ from pg_index i
29+ join pg_class ci on ci.oid = i.indexrelid and ci.relkind = 'i'
30+ join pg_class cr on cr.oid = i.indrelid and cr.relkind = 'r'
31+ join pg_namespace n on n.oid = ci.relnamespace
32+ join pg_constraint cn on cn.conrelid = cr.oid
33+ left join pg_stat_all_indexes as si on si.indexrelid = i.indexrelid
34+ where
35+ contype = 'f'
36+ and not i.indisunique
37+ and conkey is not null
38+ and ci.relpages > 5
39+ and si.idx_scan < 10
40+ ),
41+ -- Unused indexes
42+ table_scans as (
43+ select relid,
44+ tables.idx_scan + tables.seq_scan as all_scans,
45+ (tables.n_tup_ins + tables.n_tup_upd + tables.n_tup_del) as writes,
46+ pg_relation_size(relid) as table_size
47+ from pg_stat_all_tables as tables
48+ join pg_class c on c.oid = relid
49+ where c.relpages > 5
50+ ),
51+ indexes as (
52+ select
53+ i.indrelid,
54+ i.indexrelid,
55+ n.nspname as schema_name,
56+ cr.relname as table_name,
57+ ci.relname as index_name,
58+ si.idx_scan,
59+ pg_relation_size(i.indexrelid) as index_bytes,
60+ ci.relpages,
61+ (a.amname = 'btree') as idx_is_btree,
62+ array_to_string(i.indclass, ', ') as opclasses
63+ from pg_index i
64+ join pg_class ci on ci.oid = i.indexrelid and ci.relkind = 'i'
65+ join pg_class cr on cr.oid = i.indrelid and cr.relkind = 'r'
66+ join pg_namespace n on n.oid = ci.relnamespace
67+ join pg_am a on ci.relam = a.oid
68+ left join pg_stat_all_indexes as si on si.indexrelid = i.indexrelid
69+ where
70+ not i.indisunique
71+ and i.indisvalid
72+ and ci.relpages > 5
73+ ),
74+ unused_index_ids as (
75+ select
76+ i.indexrelid as index_id,
77+ i.schema_name,
78+ i.table_name,
79+ i.index_name
80+ from indexes i
81+ join table_scans ts on ts.relid = i.indrelid
82+ where
83+ i.idx_scan = 0
84+ and i.idx_is_btree
85+ order by i.index_bytes desc
86+ limit 10000
87+ ),
88+ -- Redundant indexes
89+ index_data as (
90+ select
91+ *,
92+ indkey::text as columns,
93+ array_to_string(indclass, ', ') as opclasses
94+ from pg_index i
95+ join pg_class ci on ci.oid = i.indexrelid and ci.relkind = 'i'
96+ where
97+ indisvalid
98+ and ci.relpages > 5
99+ ),
100+ redundant_index_pairs as (
101+ select
102+ i2.indexrelid as redundant_index_id,
103+ i1.indexrelid as reason_index_id,
104+ tnsp.nspname as schema_name,
105+ trel.relname as table_name,
106+ irel.relname as index_name,
107+ pg_relation_size(i2.indexrelid) as index_size_bytes
108+ from (
109+ select indrelid, indexrelid, opclasses, indclass, indexprs, indpred, indisprimary, indisunique, columns
110+ from index_data
111+ order by indexrelid
112+ ) as i1
113+ join index_data as i2 on
114+ i1.indrelid = i2.indrelid
115+ and i1.indexrelid <> i2.indexrelid
116+ inner join pg_opclass op1 on i1.indclass[0] = op1.oid
117+ inner join pg_opclass op2 on i2.indclass[0] = op2.oid
118+ inner join pg_am am1 on op1.opcmethod = am1.oid
119+ inner join pg_am am2 on op2.opcmethod = am2.oid
120+ join pg_stat_all_indexes as s on s.indexrelid = i2.indexrelid
121+ join pg_class as trel on trel.oid = i2.indrelid
122+ join pg_namespace as tnsp on trel.relnamespace = tnsp.oid
123+ join pg_class as irel on irel.oid = i2.indexrelid
124+ where
125+ not i2.indisprimary
126+ and not i2.indisunique
127+ and am1.amname = am2.amname
128+ and i1.columns like (i2.columns || '%')
129+ and i1.opclasses like (i2.opclasses || '%')
130+ and pg_get_expr(i1.indexprs, i1.indrelid) is not distinct from pg_get_expr(i2.indexprs, i2.indrelid)
131+ and pg_get_expr(i1.indpred, i1.indrelid) is not distinct from pg_get_expr(i2.indpred, i2.indrelid)
132+ ),
133+ redundant_indexes_tmp_num as (
134+ select row_number() over () num, rip.*
135+ from redundant_index_pairs rip
136+ ),
137+ redundant_indexes_tmp_links as (
138+ select
139+ ri1.*,
140+ ri2.num as r_num
141+ from redundant_indexes_tmp_num ri1
142+ left join redundant_indexes_tmp_num ri2 on
143+ ri2.reason_index_id = ri1.redundant_index_id
144+ and ri1.reason_index_id = ri2.redundant_index_id
145+ ),
146+ redundant_index_ids as (
147+ select distinct
148+ index_id,
149+ schema_name,
150+ table_name,
151+ index_name
152+ from (
153+ select
154+ redundant_index_id as index_id,
155+ schema_name,
156+ table_name,
157+ index_name,
158+ index_size_bytes
159+ from redundant_indexes_tmp_links
160+ where num < r_num or r_num is null
161+ union all
162+ select
163+ reason_index_id as index_id,
164+ schema_name,
165+ table_name,
166+ index_name,
167+ index_size_bytes
168+ from redundant_indexes_tmp_links
169+ where
170+ num < r_num
171+ or r_num is null
172+ ) as combined
173+ order by index_size_bytes desc
174+ limit 10000
175+ ),
176+ -- Combine unused and redundant index IDs
177+ all_target_indexes as (
178+ select distinct index_id, schema_name, table_name, index_name
179+ from unused_index_ids
180+ union
181+ select distinct index_id, schema_name, table_name, index_name
182+ from redundant_index_ids
183+ )
184+ select /* pgwatch_generated */
185+ ati.index_name as indexrelname,
186+ ati.schema_name as schemaname,
187+ ati.table_name as relname,
188+ pg_get_indexdef(ati.index_id) as index_definition
189+ from all_target_indexes ati
190+ order by schemaname, relname, indexrelname;
191+ gauges :
192+ - ' *'
193+
17194presets :
18195 full :
19196 description : " Full metrics for PostgreSQL storage"
20197 metrics :
21- pgss_queryid_queries : 300
198+ pgss_queryid_queries : 300
199+ index_definitions : 3600
0 commit comments