From e41967f871c59c5c4683f77782564e4619a163f6 Mon Sep 17 00:00:00 2001 From: jsmith Date: Mon, 19 Sep 2022 16:37:43 +0000 Subject: [PATCH] Add 'PostgreSQL/uuid-index-maintenance.sql' --- PostgreSQL/uuid-index-maintenance.sql | 205 ++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 PostgreSQL/uuid-index-maintenance.sql diff --git a/PostgreSQL/uuid-index-maintenance.sql b/PostgreSQL/uuid-index-maintenance.sql new file mode 100644 index 0000000..a19b88f --- /dev/null +++ b/PostgreSQL/uuid-index-maintenance.sql @@ -0,0 +1,205 @@ +/* + * Author: DBRE - Joe Smith + * Date: Sep 2022 + * Purpose: sets up tables, functions and extensions for index maintenance when using uuids. +*/ + +-- required to schedule job +create extension pg_cron; + +-- required to calculate bloat +create extension pgstattuple; + +-- table defines indexes to maintain, target bloat percentage which triggers maintenance, and an acceptable window to +-- do maintenance if the bloat is met during hours we dont want to run the maintenance +CREATE TABLE index_maintenance_configuration ( + id int primary key, + index_name varchar(512), + maintenance_threshold_percent float, + man_window_start timestamp, + man_window_end timestamp +); + +-- table tracks number of times maintenance is done. +-- when maintenance was done +CREATE TABLE index_maintenance ( + index_name varchar(512), + index_iteration bigint, + maintenance_done timestamp, + scheduled boolean + PRIMARY KEY(index_name, index_iteration, scheduled) +); + +-- no maintenance if you don't have a config +ALTER TABLE index_maintenance + ADD FOREIGN KEY (index_name) + REFERENCES index_maintenance_configuration(index_name); + +-- returns bloat as float for an index. +create or replace function return_idx_bloat (idx varchar(512)) + language plpgsql + as + $$ + RETURNS float AS $$ + declare + current_bloat float; + + begin + SELECT 100-(pgstatindex(idx)).avg_leaf_density into current_bloat; + return current_bloat; + end; +$$; + +-- calls the re-index function, concurrently. +-- raises a notice in logs which can be parsed from cloud watch +-- guarantees date is between dates, casts then to timestamps. +create or replace function call_reindex(ind varchar(512), man_window_start timestamp, + man_window_end timestamp) + language plpgsql + as + $$ + RETURNS boolean AS $$ + declare + today timestamp; + new_index bigint; + begin + today = now(); + + if not man_window_start then; + -- see call_reindex function + RAISE NOTICE 'call_reindex(%, no_man_window_specified)', idx; + REINDEX CONCURRENTLY idx; + return true; + end if; + + -- if the time of the run is within the right window, we re-index. + -- we return the same iteration to close out that record. + if today::timestamp between man_window_start::timestamp and man_window_end::timestamp; + RAISE NOTICE 'call_reindex(%, %)', idx, today; + REINDEX CONCURRENTLY idx; + return true; + else; + return false; + end if; + end; +$$; + + +create or replace function do_idx_maintenance(idx varchar(512)) + language plpgsql + as + $$ + RETURNS RECORD AS $$ + declare + current_iteration bigint; + new_iteration bigint; + is_scheduled boolean; + man_window_start timestamp; + man_window_end timestamp; + maintenance_threshold_percent float; + current_bloat float; + maintenance_ran boolean; + + begin + + -- look for configuration for current ind + maintenance_threshold_percent, man_window_start, man_window_end = select maintenance_threshold_percent, + man_window_start. man_window_end + from index_maintenance_configuration + where index_name = idx; + + -- check for a current iteration to increment, if none, then we start one at 0. + SELECT max(index_iteration), scheduled INTO current_iteration, is_scheduled FROM index_maintenance WHERE index_name = idx; + + -- set iteration of current record if one does not exist. + -- other wise, we use the current_iteration from the table. + if not current_iteration then; + current_iteration = 0; + end if; + + -- no maintenance window is allowed and will reindex each time. + if not man_window_start then; + -- see call_reindex function + maintenance_ran = call_reindex(idx, man_window_start, man_window_end); + + end if; + + -- if the row we get back is scheduled, we try to run it. + if is_scheduled; + + maintenance_ran = call_reindex(idx, man_window_start, man_window_end); + + + else; + -- here we have no row that is scheduled. + -- we have a maintenance window to check. + -- we check the current bloat, and try to re-index. + current_bloat = return_idx_bloat(idx); + + if current_bloat > maintenance_threshold_percent then; + maintenance_ran = call_reindex(idx, man_window_start, man_window_end); + end if; + + end if; + + -- using simple logic, we will either increment the maintenance row, or create a new one. + return maintenance_ran, current_iteration; + + end; +$$; + +create or replace function uuid_idx_maintenance() + language plpgsql + as + $$ + declare + idx_array varchar[]; + + begin + -- return all indexes we are to maintain, if none, we do nothing. + select array_agg( distinct idx_name ) + into idx_array + from index_maintenance_configuration; + + -- if there are no indexes, its a no op. + if array_length(idx_array) > 0 then; + + -- iterate over each name in array + foreach idx in array idx_array loop + + -- define variable for current iteration + maintenance_done boolean; + current_iteration bigint; + + rec record; + + -- call function to check idx, return record with multiple results. + -- parse out information into variables. + + rec = do_idx_maintenance(idx); + maintenance_done = rec[0] + current_iteration = rec[1] + + -- maintenance_done coming back true means the maintenance was done. + -- maintenance_done coming back false means that there was no maintenance done. + if maintenance_done then; + UPDATE set maintenance_done = now(), scheduled = false + WHERE index_name = idx (idx, current_iteration, now()) + AND index_iteration = current_iteration; + else; + -- this record will violate a primary key if a row already exists. + -- otherwise, it schedules a row for the next run to review. + INSERT into + index_maintenance(index_name, index_iteration, maintenance_done, scheduled ) + VALUES (idx, current_iteration,null, true) on conflict do nothing; + end if; + + continue; + + end loop; + else; + return + end if; + end; +$$; +