4 changed files with 4 additions and 534 deletions
@ -0,0 +1,3 @@ |
|||||||
|
[submodule "pg_index_watch"] |
||||||
|
path = pg_index_watch |
||||||
|
url = https://github.com/dataegret/pg_index_watch |
||||||
@ -1,459 +0,0 @@ |
|||||||
CREATE EXTENSION IF NOT EXISTS dblink; |
|
||||||
ALTER EXTENSION dblink UPDATE; |
|
||||||
|
|
||||||
--current version of code |
|
||||||
CREATE OR REPLACE FUNCTION index_watch.version() |
|
||||||
RETURNS TEXT AS |
|
||||||
$BODY$ |
|
||||||
BEGIN |
|
||||||
RETURN '0.9'; |
|
||||||
END; |
|
||||||
$BODY$ |
|
||||||
LANGUAGE plpgsql IMMUTABLE; |
|
||||||
|
|
||||||
--minimum table structure version required |
|
||||||
CREATE OR REPLACE FUNCTION index_watch._check_structure_version() |
|
||||||
RETURNS VOID AS |
|
||||||
$BODY$ |
|
||||||
DECLARE |
|
||||||
_tables_version INTEGER; |
|
||||||
_required_version INTEGER :=1; |
|
||||||
BEGIN |
|
||||||
SELECT version INTO STRICT _tables_version FROM index_watch.tables_version; |
|
||||||
IF (_tables_version<_required_version) THEN |
|
||||||
RAISE EXCEPTION 'current tables version % is less than minimally required % for % code version, please update tables structure', _tables_version, _required_version, index_watch.version(); |
|
||||||
END IF; |
|
||||||
RETURN; |
|
||||||
END; |
|
||||||
$BODY$ |
|
||||||
LANGUAGE plpgsql STABLE; |
|
||||||
|
|
||||||
--convert patterns from psql format to like format |
|
||||||
CREATE OR REPLACE FUNCTION index_watch._pattern_convert(_var text) |
|
||||||
RETURNS TEXT AS |
|
||||||
$BODY$ |
|
||||||
BEGIN |
|
||||||
--replace * with .* |
|
||||||
_var := replace(_var, '*', '.*'); |
|
||||||
--replace ? with . |
|
||||||
_var := replace(_var, '?', '.'); |
|
||||||
|
|
||||||
RETURN '^('||_var||')$'; |
|
||||||
END; |
|
||||||
$BODY$ |
|
||||||
LANGUAGE plpgsql STRICT IMMUTABLE; |
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION index_watch.get_setting(_datname text, _schemaname text, _relname text, _indexrelname text, _key TEXT) |
|
||||||
RETURNS TEXT AS |
|
||||||
$BODY$ |
|
||||||
DECLARE |
|
||||||
_value TEXT; |
|
||||||
BEGIN |
|
||||||
PERFORM index_watch._check_structure_version(); |
|
||||||
--RAISE NOTICE 'DEBUG: |%|%|%|%|', _datname, _schemaname, _relname, _indexrelname; |
|
||||||
SELECT _t.value INTO _value FROM ( |
|
||||||
--per index setting |
|
||||||
SELECT 1 AS priority, value FROM index_watch.config WHERE |
|
||||||
_key=config.key |
|
||||||
AND (_datname OPERATOR(pg_catalog.~) index_watch._pattern_convert(config.datname)) |
|
||||||
AND (_schemaname OPERATOR(pg_catalog.~) index_watch._pattern_convert(config.schemaname)) |
|
||||||
AND (_relname OPERATOR(pg_catalog.~) index_watch._pattern_convert(config.relname)) |
|
||||||
AND (_indexrelname OPERATOR(pg_catalog.~) index_watch._pattern_convert(config.indexrelname)) |
|
||||||
AND config.indexrelname IS NOT NULL |
|
||||||
AND TRUE |
|
||||||
UNION ALL |
|
||||||
--per table setting |
|
||||||
SELECT 2 AS priority, value FROM index_watch.config WHERE |
|
||||||
_key=config.key |
|
||||||
AND (_datname OPERATOR(pg_catalog.~) index_watch._pattern_convert(config.datname)) |
|
||||||
AND (_schemaname OPERATOR(pg_catalog.~) index_watch._pattern_convert(config.schemaname)) |
|
||||||
AND (_relname OPERATOR(pg_catalog.~) index_watch._pattern_convert(config.relname)) |
|
||||||
AND config.relname IS NOT NULL |
|
||||||
AND config.indexrelname IS NULL |
|
||||||
UNION ALL |
|
||||||
--per schema setting |
|
||||||
SELECT 3 AS priority, value FROM index_watch.config WHERE |
|
||||||
_key=config.key |
|
||||||
AND (_datname OPERATOR(pg_catalog.~) index_watch._pattern_convert(config.datname)) |
|
||||||
AND (_schemaname OPERATOR(pg_catalog.~) index_watch._pattern_convert(config.schemaname)) |
|
||||||
AND config.schemaname IS NOT NULL |
|
||||||
AND config.relname IS NULL |
|
||||||
UNION ALL |
|
||||||
--per database setting |
|
||||||
SELECT 4 AS priority, value FROM index_watch.config WHERE |
|
||||||
_key=config.key |
|
||||||
AND (_datname OPERATOR(pg_catalog.~) index_watch._pattern_convert(config.datname)) |
|
||||||
AND config.datname IS NOT NULL |
|
||||||
AND config.schemaname IS NULL |
|
||||||
UNION ALL |
|
||||||
--global setting |
|
||||||
SELECT 5 AS priority, value FROM index_watch.config WHERE |
|
||||||
_key=config.key |
|
||||||
AND config.datname IS NULL |
|
||||||
) AS _t |
|
||||||
WHERE value IS NOT NULL |
|
||||||
ORDER BY priority |
|
||||||
LIMIT 1; |
|
||||||
RETURN _value; |
|
||||||
END; |
|
||||||
$BODY$ |
|
||||||
LANGUAGE plpgsql STABLE; |
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION index_watch.set_or_replace_setting(_datname text, _schemaname text, _relname text, _indexrelname text, _key TEXT, _value text, _comment text) |
|
||||||
RETURNS VOID AS |
|
||||||
$BODY$ |
|
||||||
BEGIN |
|
||||||
PERFORM index_watch._check_structure_version(); |
|
||||||
IF _datname IS NULL THEN |
|
||||||
INSERT INTO index_watch.config (datname, schemaname, relname, indexrelname, key, value, comment) |
|
||||||
VALUES (_datname, _schemaname, _relname, _indexrelname, _key, _value, _comment) |
|
||||||
ON CONFLICT (key) WHERE datname IS NULL DO UPDATE SET value=EXCLUDED.value, comment=EXCLUDED.comment; |
|
||||||
ELSIF _schemaname IS NULL THEN |
|
||||||
INSERT INTO index_watch.config (datname, schemaname, relname, indexrelname, key, value, comment) |
|
||||||
VALUES (_datname, _schemaname, _relname, _indexrelname, _key, _value, _comment) |
|
||||||
ON CONFLICT (key, datname) WHERE schemaname IS NULL DO UPDATE SET value=EXCLUDED.value, comment=EXCLUDED.comment; |
|
||||||
ELSIF _relname IS NULL THEN |
|
||||||
INSERT INTO index_watch.config (datname, schemaname, relname, indexrelname, key, value, comment) |
|
||||||
VALUES (_datname, _schemaname, _relname, _indexrelname, _key, _value, _comment) |
|
||||||
ON CONFLICT (key, datname, schemaname) WHERE relname IS NULL DO UPDATE SET value=EXCLUDED.value, comment=EXCLUDED.comment; |
|
||||||
ELSIF _indexrelname IS NULL THEN |
|
||||||
INSERT INTO index_watch.config (datname, schemaname, relname, indexrelname, key, value, comment) |
|
||||||
VALUES (_datname, _schemaname, _relname, _indexrelname, _key, _value, _comment) |
|
||||||
ON CONFLICT (key, datname, schemaname, relname) WHERE indexrelname IS NULL DO UPDATE SET value=EXCLUDED.value, comment=EXCLUDED.comment; |
|
||||||
ELSE |
|
||||||
INSERT INTO index_watch.config (datname, schemaname, relname, indexrelname, key, value, comment) |
|
||||||
VALUES (_datname, _schemaname, _relname, _indexrelname, _key, _value, _comment) |
|
||||||
ON CONFLICT (key, datname, schemaname, relname, indexrelname) DO UPDATE SET value=EXCLUDED.value, comment=EXCLUDED.comment; |
|
||||||
END IF; |
|
||||||
RETURN; |
|
||||||
END; |
|
||||||
$BODY$ |
|
||||||
LANGUAGE plpgsql; |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION index_watch._remote_get_indexes_info(_datname name, _schemaname name, _relname name, _indexrelname name) |
|
||||||
RETURNS TABLE(datname name, schemaname name, relname name, indexrelname name, indexsize BIGINT, estimated_tuples BIGINT) |
|
||||||
AS |
|
||||||
$BODY$ |
|
||||||
BEGIN |
|
||||||
RETURN QUERY SELECT |
|
||||||
_datname, _res.schemaname, _res.relname, _res.indexrelname, _res.indexsize, |
|
||||||
CASE WHEN relpages=0 THEN greatest(1, indexreltuples) ELSE (relsize::real/(relpages::real*current_setting('block_size')::real)*indexreltuples::real)::BIGINT END AS estimated_tuples |
|
||||||
FROM |
|
||||||
dblink('port='||current_setting('port')||' dbname='||pg_catalog.quote_ident(_datname), |
|
||||||
E' |
|
||||||
SELECT |
|
||||||
pg_stat_user_indexes.schemaname, |
|
||||||
pg_stat_user_indexes.relname, |
|
||||||
pg_stat_user_indexes.indexrelname, |
|
||||||
c1.relpages::BIGINT, |
|
||||||
c2.reltuples::BIGINT AS indexreltuples, |
|
||||||
pg_catalog.pg_relation_size(pg_stat_user_indexes.relid)::BIGINT AS relsize, |
|
||||||
pg_catalog.pg_relation_size(pg_stat_user_indexes.indexrelid)::BIGINT AS indexsize |
|
||||||
FROM pg_catalog.pg_stat_user_indexes |
|
||||||
JOIN pg_catalog.pg_class AS c1 on c1.oid=pg_stat_user_indexes.relid |
|
||||||
JOIN pg_catalog.pg_class AS c2 on c2.oid=pg_stat_user_indexes.indexrelid |
|
||||||
WHERE NOT EXISTS (SELECT FROM pg_constraint WHERE pg_constraint.conindid=pg_stat_user_indexes.indexrelid and pg_constraint.contype=\'x\') |
|
||||||
') |
|
||||||
AS _res(schemaname name, relname name, indexrelname name, relpages BIGINT, indexreltuples BIGINT, relsize BIGINT, indexsize BIGINT) |
|
||||||
WHERE |
|
||||||
(_schemaname IS NULL OR _res.schemaname=_schemaname) |
|
||||||
AND |
|
||||||
(_relname IS NULL OR _res.relname=_relname) |
|
||||||
AND |
|
||||||
(_indexrelname IS NULL OR _res.indexrelname=_indexrelname) |
|
||||||
; |
|
||||||
END; |
|
||||||
$BODY$ |
|
||||||
LANGUAGE plpgsql; |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION index_watch._record_indexes_info(_datname name, _schemaname name, _relname name, _indexrelname name) |
|
||||||
RETURNS VOID |
|
||||||
AS |
|
||||||
$BODY$ |
|
||||||
BEGIN |
|
||||||
INSERT INTO index_watch.index_history |
|
||||||
(datname, schemaname, relname, indexrelname, indexsize, estimated_tuples) |
|
||||||
SELECT datname, schemaname, relname, indexrelname, indexsize, estimated_tuples |
|
||||||
FROM index_watch._remote_get_indexes_info(_datname, _schemaname, _relname, _indexrelname) |
|
||||||
WHERE |
|
||||||
( |
|
||||||
indexsize >= pg_size_bytes(index_watch.get_setting(datname, schemaname, relname, indexrelname, 'index_size_threshold')) |
|
||||||
AND |
|
||||||
index_watch.get_setting(datname, schemaname, relname, indexrelname, 'skip')::boolean IS DISTINCT FROM TRUE |
|
||||||
--AND |
|
||||||
--index_watch.get_setting (for future configurability) |
|
||||||
) |
|
||||||
; |
|
||||||
END; |
|
||||||
$BODY$ |
|
||||||
LANGUAGE plpgsql; |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION index_watch._cleanup_old_records() RETURNS VOID AS |
|
||||||
$BODY$ |
|
||||||
BEGIN |
|
||||||
--TODO replace with fast distinct implementation |
|
||||||
WITH |
|
||||||
rels AS MATERIALIZED (SELECT DISTINCT datname, schemaname, relname, indexrelname FROM index_watch.index_history), |
|
||||||
age_limit AS MATERIALIZED (SELECT *, now()-index_watch.get_setting(datname,schemaname,relname,indexrelname, 'index_history_retention_period')::interval AS max_age FROM rels) |
|
||||||
DELETE FROM index_watch.index_history |
|
||||||
USING age_limit |
|
||||||
WHERE |
|
||||||
index_history.datname=age_limit.datname |
|
||||||
AND index_history.schemaname=age_limit.schemaname |
|
||||||
AND index_history.relname=age_limit.relname |
|
||||||
AND index_history.indexrelname=age_limit.indexrelname |
|
||||||
AND index_history.entry_timestamp<age_limit.max_age; |
|
||||||
|
|
||||||
--TODO replace with fast distinct implementation |
|
||||||
WITH |
|
||||||
rels AS MATERIALIZED (SELECT DISTINCT datname, schemaname, relname, indexrelname FROM index_watch.reindex_history), |
|
||||||
age_limit AS MATERIALIZED (SELECT *, now()-index_watch.get_setting(datname,schemaname,relname,indexrelname, 'reindex_history_retention_period')::interval AS max_age FROM rels) |
|
||||||
DELETE FROM index_watch.reindex_history |
|
||||||
USING age_limit |
|
||||||
WHERE |
|
||||||
reindex_history.datname=age_limit.datname |
|
||||||
AND reindex_history.schemaname=age_limit.schemaname |
|
||||||
AND reindex_history.relname=age_limit.relname |
|
||||||
AND reindex_history.indexrelname=age_limit.indexrelname |
|
||||||
AND reindex_history.entry_timestamp<age_limit.max_age; |
|
||||||
RETURN; |
|
||||||
END; |
|
||||||
$BODY$ |
|
||||||
LANGUAGE plpgsql; |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION index_watch.get_index_bloat_estimates(_datname name) |
|
||||||
RETURNS TABLE(datname name, schemaname name, relname name, indexrelname name, indexsize bigint, estimated_bloat real) |
|
||||||
AS |
|
||||||
$BODY$ |
|
||||||
BEGIN |
|
||||||
PERFORM index_watch._check_structure_version(); |
|
||||||
-- compare current index size per tuple with the best result since reindex value (including just after reindex data from reindex_history) |
|
||||||
RETURN QUERY |
|
||||||
WITH |
|
||||||
_last_reindex_values AS ( |
|
||||||
SELECT |
|
||||||
DISTINCT ON (schemaname, relname, indexrelname) |
|
||||||
reindex_history.schemaname, reindex_history.relname, reindex_history.indexrelname, entry_timestamp, estimated_tuples, indexsize_after AS indexsize |
|
||||||
FROM index_watch.reindex_history |
|
||||||
WHERE |
|
||||||
reindex_history.datname = _datname |
|
||||||
ORDER BY schemaname, relname, indexrelname, entry_timestamp DESC |
|
||||||
), |
|
||||||
_all_history_since_reindex AS ( |
|
||||||
--last reindexed value |
|
||||||
SELECT _last_reindex_values.schemaname, _last_reindex_values.relname, _last_reindex_values.indexrelname, _last_reindex_values.entry_timestamp, _last_reindex_values.estimated_tuples, _last_reindex_values.indexsize |
|
||||||
FROM _last_reindex_values |
|
||||||
UNION ALL |
|
||||||
--all values since reindex or from start |
|
||||||
SELECT index_history.schemaname, index_history.relname, index_history.indexrelname, index_history.entry_timestamp, index_history.estimated_tuples, index_history.indexsize |
|
||||||
FROM index_watch.index_history |
|
||||||
LEFT JOIN _last_reindex_values USING (schemaname, relname, indexrelname) |
|
||||||
WHERE |
|
||||||
index_history.datname = _datname |
|
||||||
AND index_history.entry_timestamp>=coalesce(_last_reindex_values.entry_timestamp, '-INFINITY'::timestamp) |
|
||||||
), |
|
||||||
_best_values AS ( |
|
||||||
--only valid best if reindex entry exists |
|
||||||
SELECT |
|
||||||
DISTINCT ON (schemaname, relname, indexrelname) |
|
||||||
_all_history_since_reindex.* |
|
||||||
FROM _all_history_since_reindex |
|
||||||
JOIN _last_reindex_values USING (schemaname, relname, indexrelname) |
|
||||||
WHERE |
|
||||||
_all_history_since_reindex.indexsize > pg_size_bytes(index_watch.get_setting(_datname, _all_history_since_reindex.schemaname, _all_history_since_reindex.relname, _all_history_since_reindex.indexrelname, 'minimum_reliable_index_size')) |
|
||||||
ORDER BY schemaname, relname, indexrelname, _all_history_since_reindex.estimated_tuples::real/_all_history_since_reindex.indexsize::real DESC |
|
||||||
), |
|
||||||
_current_state AS ( |
|
||||||
SELECT |
|
||||||
DISTINCT ON (schemaname, relname, indexrelname) |
|
||||||
_all_history_since_reindex.* |
|
||||||
FROM _all_history_since_reindex |
|
||||||
ORDER BY schemaname, relname, indexrelname, entry_timestamp DESC |
|
||||||
), |
|
||||||
_result AS ( |
|
||||||
SELECT |
|
||||||
_current_state.*, |
|
||||||
--((_current_state.indexsize::real/_current_state.estimated_tuples::real)/(_best_values.indexsize::real/_best_values.estimated_tuples::real)) AS estimated_bloat |
|
||||||
case WHEN (_best_values.indexsize::real*_current_state.estimated_tuples::real=0) THEN 1000 ELSE ((_current_state.indexsize::real*_best_values.estimated_tuples::real)/(_best_values.indexsize::real*_current_state.estimated_tuples::real)) END AS estimated_bloat |
|
||||||
FROM _current_state |
|
||||||
LEFT JOIN _best_values USING (schemaname, relname, indexrelname) |
|
||||||
) |
|
||||||
SELECT _datname, _result.schemaname, _result.relname, _result.indexrelname, _result.indexsize, _result.estimated_bloat FROM _result; |
|
||||||
END; |
|
||||||
$BODY$ |
|
||||||
LANGUAGE plpgsql STRICT; |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION index_watch._reindex_index(_datname name, _schemaname name, _relname name, _indexrelname name) |
|
||||||
RETURNS VOID |
|
||||||
AS |
|
||||||
$BODY$ |
|
||||||
DECLARE |
|
||||||
_indexsize_before BIGINT; |
|
||||||
_indexsize_after BIGINT; |
|
||||||
_timestamp TIMESTAMP; |
|
||||||
_reindex_duration INTERVAL; |
|
||||||
_analyze_duration INTERVAL; |
|
||||||
_estimated_tuples BIGINT; |
|
||||||
BEGIN |
|
||||||
|
|
||||||
--RAISE NOTICE 'working with %.%.% %', _datname, _schemaname, _relname, _indexrelname; |
|
||||||
|
|
||||||
--get initial index size |
|
||||||
SELECT indexsize INTO _indexsize_before |
|
||||||
FROM index_watch._remote_get_indexes_info(_datname, _schemaname, _relname, _indexrelname); |
|
||||||
--index doesn't exist anymore |
|
||||||
IF NOT FOUND THEN |
|
||||||
RETURN; |
|
||||||
END IF; |
|
||||||
|
|
||||||
--time to dance |
|
||||||
_timestamp := pg_catalog.clock_timestamp (); |
|
||||||
PERFORM dblink('port='||current_setting('port')||' dbname='||pg_catalog.quote_ident(_datname), 'REINDEX INDEX CONCURRENTLY '||pg_catalog.quote_ident(_schemaname)||'.'||pg_catalog.quote_ident(_indexrelname)); |
|
||||||
_reindex_duration := pg_catalog.clock_timestamp ()-_timestamp; |
|
||||||
|
|
||||||
--analyze |
|
||||||
_timestamp := clock_timestamp (); |
|
||||||
PERFORM dblink('port='||current_setting('port')||' dbname='||pg_catalog.quote_ident(_datname), 'ANALYZE '||pg_catalog.quote_ident(_schemaname)||'.'||pg_catalog.quote_ident(_relname)); |
|
||||||
_analyze_duration := pg_catalog.clock_timestamp ()-_timestamp; |
|
||||||
|
|
||||||
--get final index size |
|
||||||
SELECT indexsize, estimated_tuples INTO STRICT _indexsize_after, _estimated_tuples |
|
||||||
FROM index_watch._remote_get_indexes_info(_datname, _schemaname, _relname, _indexrelname); |
|
||||||
|
|
||||||
--log reindex action |
|
||||||
INSERT INTO index_watch.reindex_history |
|
||||||
(datname, schemaname, relname, indexrelname, indexsize_before, indexsize_after, estimated_tuples, reindex_duration, analyze_duration) |
|
||||||
VALUES |
|
||||||
(_datname, _schemaname, _relname, _indexrelname, _indexsize_before, _indexsize_after, _estimated_tuples, _reindex_duration, _analyze_duration); |
|
||||||
|
|
||||||
RETURN; |
|
||||||
END; |
|
||||||
$BODY$ |
|
||||||
LANGUAGE plpgsql STRICT; |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE PROCEDURE index_watch.do_reindex(_datname name, _schemaname name, _relname name, _indexrelname name, _force BOOLEAN DEFAULT FALSE) |
|
||||||
AS |
|
||||||
$BODY$ |
|
||||||
DECLARE |
|
||||||
_index RECORD; |
|
||||||
BEGIN |
|
||||||
PERFORM index_watch._check_structure_version(); |
|
||||||
FOR _index IN |
|
||||||
SELECT datname, schemaname, relname, indexrelname, indexsize, estimated_bloat |
|
||||||
FROM index_watch.get_index_bloat_estimates(_datname) |
|
||||||
WHERE |
|
||||||
(_schemaname IS NULL OR schemaname=_schemaname) |
|
||||||
AND |
|
||||||
(_relname IS NULL OR relname=_relname) |
|
||||||
AND |
|
||||||
(_indexrelname IS NULL OR indexrelname=_indexrelname) |
|
||||||
AND |
|
||||||
(_force OR |
|
||||||
( |
|
||||||
( |
|
||||||
estimated_bloat IS NULL OR |
|
||||||
estimated_bloat >= index_watch.get_setting(datname, schemaname, relname, indexrelname, 'index_rebuild_scale_factor')::float |
|
||||||
) |
|
||||||
AND |
|
||||||
indexsize >= pg_size_bytes(index_watch.get_setting(datname, schemaname, relname, indexrelname, 'index_size_threshold')) |
|
||||||
AND |
|
||||||
index_watch.get_setting(datname, schemaname, relname, indexrelname, 'skip')::boolean IS DISTINCT FROM TRUE |
|
||||||
--AND |
|
||||||
--index_watch.get_setting (for future configurability) |
|
||||||
) |
|
||||||
) |
|
||||||
LOOP |
|
||||||
PERFORM index_watch._reindex_index(_index.datname, _index.schemaname, _index.relname, _index.indexrelname); |
|
||||||
COMMIT; |
|
||||||
END LOOP; |
|
||||||
RETURN; |
|
||||||
END; |
|
||||||
$BODY$ |
|
||||||
LANGUAGE plpgsql; |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION index_watch._check_lock() |
|
||||||
RETURNS bigint AS |
|
||||||
$BODY$ |
|
||||||
DECLARE |
|
||||||
_id bigint; |
|
||||||
_is_not_running boolean; |
|
||||||
BEGIN |
|
||||||
SELECT oid FROM pg_namespace WHERE nspname='index_watch' INTO _id; |
|
||||||
SELECT pg_try_advisory_lock(_id) INTO _is_not_running; |
|
||||||
IF NOT _is_not_running THEN |
|
||||||
RAISE 'The previous launch of the index_watch.periodic is still running.'; |
|
||||||
END IF; |
|
||||||
RETURN _id; |
|
||||||
END; |
|
||||||
$BODY$ |
|
||||||
LANGUAGE plpgsql; |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE PROCEDURE index_watch.periodic(real_run BOOLEAN DEFAULT FALSE) AS |
|
||||||
$BODY$ |
|
||||||
DECLARE |
|
||||||
_datname NAME; |
|
||||||
_id bigint; |
|
||||||
BEGIN |
|
||||||
SELECT index_watch._check_lock() INTO _id; |
|
||||||
|
|
||||||
PERFORM index_watch._check_structure_version(); |
|
||||||
PERFORM index_watch._cleanup_old_records(); |
|
||||||
COMMIT; |
|
||||||
|
|
||||||
FOR _datname IN |
|
||||||
SELECT datname FROM pg_database |
|
||||||
WHERE |
|
||||||
NOT datistemplate |
|
||||||
AND datallowconn |
|
||||||
AND datname<>current_database() |
|
||||||
AND index_watch.get_setting(datname, NULL, NULL, NULL, 'skip')::boolean IS DISTINCT FROM TRUE |
|
||||||
ORDER BY datname |
|
||||||
LOOP |
|
||||||
PERFORM index_watch._record_indexes_info(_datname, NULL, NULL, NULL); |
|
||||||
COMMIT; |
|
||||||
IF (real_run) THEN |
|
||||||
CALL index_watch.do_reindex(_datname, NULL, NULL, NULL, FALSE); |
|
||||||
COMMIT; |
|
||||||
END IF; |
|
||||||
END LOOP; |
|
||||||
|
|
||||||
PERFORM pg_advisory_unlock(_id); |
|
||||||
END; |
|
||||||
$BODY$ |
|
||||||
LANGUAGE plpgsql; |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,75 +0,0 @@ |
|||||||
CREATE SCHEMA IF NOT EXISTS index_watch; |
|
||||||
|
|
||||||
--history of performed REINDEX action |
|
||||||
CREATE TABLE index_watch.reindex_history |
|
||||||
( |
|
||||||
id bigserial primary key, |
|
||||||
entry_timestamp timestamptz not null default now(), |
|
||||||
datname name not null, |
|
||||||
schemaname name not null, |
|
||||||
relname name not null, |
|
||||||
indexrelname name not null, |
|
||||||
server_version_num integer not null default current_setting('server_version_num')::integer, |
|
||||||
indexsize_before BIGINT not null, |
|
||||||
indexsize_after BIGINT not null, |
|
||||||
estimated_tuples bigint not null, |
|
||||||
reindex_duration interval not null, |
|
||||||
analyze_duration interval not null |
|
||||||
); |
|
||||||
create index reindex_history_index on index_watch.reindex_history(datname, schemaname, relname, indexrelname, entry_timestamp); |
|
||||||
|
|
||||||
|
|
||||||
--history of index sizes (not really neccessary to keep all this data but very useful for future analyzis of bloat trends |
|
||||||
CREATE TABLE index_watch.index_history |
|
||||||
( |
|
||||||
id bigserial primary key, |
|
||||||
entry_timestamp timestamptz not null default now(), |
|
||||||
datname name not null, |
|
||||||
schemaname name not null, |
|
||||||
relname name not null, |
|
||||||
indexrelname name not null, |
|
||||||
server_version_num integer not null default current_setting('server_version_num')::integer, |
|
||||||
indexsize BIGINT not null, |
|
||||||
estimated_tuples BIGINT not null |
|
||||||
); |
|
||||||
create index index_history_index on index_watch.index_history(datname, schemaname, relname, indexrelname, entry_timestamp); |
|
||||||
|
|
||||||
--settings table |
|
||||||
CREATE TABLE index_watch.config |
|
||||||
( |
|
||||||
id bigserial primary key, |
|
||||||
datname name, |
|
||||||
schemaname name, |
|
||||||
relname name, |
|
||||||
indexrelname name, |
|
||||||
key text not null, |
|
||||||
value text, |
|
||||||
comment text |
|
||||||
); |
|
||||||
CREATE UNIQUE INDEX config_u1 on index_watch.config(key) WHERE datname IS NULL; |
|
||||||
CREATE UNIQUE INDEX config_u2 on index_watch.config(key, datname) WHERE schemaname IS NULL; |
|
||||||
CREATE UNIQUE INDEX config_u3 on index_watch.config(key, datname, schemaname) WHERE relname IS NULL; |
|
||||||
CREATE UNIQUE INDEX config_u4 on index_watch.config(key, datname, schemaname, relname) WHERE indexrelname IS NULL; |
|
||||||
CREATE UNIQUE INDEX config_u5 on index_watch.config(key, datname, schemaname, relname, indexrelname); |
|
||||||
ALTER TABLE index_watch.config ADD CONSTRAINT inherit_check1 CHECK (indexrelname IS NULL OR indexrelname IS NOT NULL AND relname IS NOT NULL); |
|
||||||
ALTER TABLE index_watch.config ADD CONSTRAINT inherit_check2 CHECK (relname IS NULL OR relname IS NOT NULL AND schemaname IS NOT NULL); |
|
||||||
ALTER TABLE index_watch.config ADD CONSTRAINT inherit_check3 CHECK (schemaname IS NULL OR schemaname IS NOT NULL AND datname IS NOT NULL); |
|
||||||
|
|
||||||
--DEFAULT GLOBAL settings |
|
||||||
INSERT INTO index_watch.config (key, value, comment) VALUES |
|
||||||
('index_size_threshold', '10MB', 'ignore indexes under 10MB size unless forced entries found in history'), |
|
||||||
('index_rebuild_scale_factor', '2', 'rebuild indexes by default estimated bloat over 2x'), |
|
||||||
('minimum_reliable_index_size', '32kB', 'small indexes not reliable to use as gauge'), |
|
||||||
('reindex_history_retention_period','10 years', 'reindex history default retention period'), |
|
||||||
('index_history_retention_period', '1 year', 'index history default retention period') |
|
||||||
; |
|
||||||
|
|
||||||
|
|
||||||
--current version of table structure |
|
||||||
CREATE TABLE index_watch.tables_version |
|
||||||
( |
|
||||||
version smallint NOT NULL |
|
||||||
); |
|
||||||
CREATE UNIQUE INDEX tables_version_single_row ON index_watch.tables_version((version IS NOT NULL)); |
|
||||||
INSERT INTO index_watch.tables_version VALUES(1); |
|
||||||
|
|
||||||
Loading…
Reference in new issue