Таблицы в базе данных разбиты по неделям на основе метки времени данных, т.е. tablename_y2019w20.Но когда были введены разделы, postgres начал занимать слишком много времени процессора.
Статистика использования ЦП сбора при запуске
SELECT substring(query, 1, 50) AS short_query, round(total_time::numeric, 2) AS total_time, calls, rows, round(total_time::numeric / calls, 2) AS avg_time, round((100 * total_time / sum(total_time::numeric) OVER ())::numeric, 2) AS percentage_cpu FROM pg_stat_statements ORDER BY total_time DESC LIMIT 20;
показала, что узким местом является утверждение SELECT NOT EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name=tablename)
из триггерафункция (см. ниже), которая выполняется при каждой вставке.
Например, EXPLAIN ANALYZE
массовой вставки 18 элементов, которая происходит каждую секунду, выглядит следующим образом:
Planning time: 0.787 ms
Trigger before_insert_data_trigger: time=253.374 calls=18
Execution time: 254.161 ms
Но так как нам нужно создавать таблицы разделов только раз в неделю, все операторы в большинстве случаев бесполезны.Но я не могу создать разделы за пределами postgres заранее.
Можно ли запланировать создание разделов в postgres, например, только каждое воскресенье?
Вот функция и соответствующий триггер:
CREATE OR REPLACE FUNCTION data_insert_child_date()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
DECLARE
match data."timestamp"%TYPE;
checks TEXT;
tablename_parent text := "data";
tablename text;
BEGIN
IF NEW."timestamp" IS NULL THEN
tablename := tablename_parent||'_null';
checks := '"timestamp" IS NULL';
ELSE
match := DATE_TRUNC('week', NEW."timestamp");
tablename := tablename_parent||'_' || TO_CHAR(NEW."timestamp", '"y"IYYY"w"IW');
checks := '"timestamp" >= ''' || match || ''' AND "timestamp" < ''' || (match + INTERVAL '1 week') || '''';
END IF;
IF NOT EXISTS(
SELECT 1 FROM information_schema.tables WHERE table_name=tablename)
THEN
BEGIN
EXECUTE 'CREATE TABLE part.' || tablename || ' (
CHECK (' || checks || '),
LIKE "data" INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES
) INHERITS (part."'||tablename_parent||'");
';
EXCEPTION WHEN duplicate_table THEN
-- pass
END;
END IF;
EXECUTE 'INSERT INTO part.' || tablename || ' VALUES (($1).*);' USING NEW;
RETURN NEW;
END;
$function$;
Итриггер
CREATE TRIGGER before_insert_data_trigger BEFORE INSERT
ON data
FOR EACH ROW
EXECUTE PROCEDURE data_insert_child_date();