/* * 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; $$;