Описание проблемы
В некоторых случаях в DWH приходится периодическим заданием очищать таблицу и заполнять ее новыми актуальными данными. Наприм��р, раз в сутки. Если в таблице десятки или сотни тысяч строк, то это не проблема. А вот если миллиард - то уже точно проблема. Потому что каким бы способом ее не заполнять, но в течении достаточно длительного времени данные из этой таблицы не будут доступны пользователям. А если система должна быть доступна 24/7, то такие процессы начинают заметно ухудшать SLA.
Варианты решения
Один из вариантов решения - это свалить данную проблему на клиента. Создаем две таблицы одинаковой структуры. Периодическим заданием заполняем эти таблицы по очереди. Сегодня одну, завтра другую. В третьей таблице указываем, какая из этих таблиц актуальна на данный момент. Клиент же определяет нужную таблицу предварительным запросом к этой третьей таблице. Минус решения - динамический SQL на каждом клиенте и более сложная логика.
Второй вариант - через переименование таблиц. И именно он выглядит предпочтительней.
Описание решения
Нашу таблицу очищает и заполняет какая-то конкретная хранимая процедура. Назовем ее условно SomeScheme.ThisTablePeriodicFill_sp. Тогда у нас возникает логический ресурс to_regproc('SomeScheme.ThisTablePeriodicFill_sp')::oid::integer, который мы и будем использовать для логических блокировок. Перед доступом к нашей таблице каждый клиент должен выполнить:
PERFORM pg_advisory_lock_shared( to_regproc('SomeScheme.ThisTablePeriodicFill_sp')::oid::integer, 0);
А по окончании работы с нашей таблицей клиент должен освободить ресурс:
PERFORM pg_advisory_unlock_shared( to_regproc('SomeScheme.ThisTablePeriodicFill_sp')::oid::integer, 0);
Теперь процедура SomeScheme.ThisTablePeriodicFill_sp может создать новую таблицу SomeScheme.ThisTable_Shadow совпадающую по структуре с таблицей SomeScheme.ThisTable, но без индексов. После чего, никому не мешая, заполнить эту таблицу и, после ее заполнения, создать для нее необходимые индексы.
Однако просто переименовать эту таблицу мало. Ведь индексы в PostgreSQL являются relation и их имена глобальны. Для решения этой проблемы имена индексов делаем всегда начинающиеся с имени таблицы (например, ThisTable_IndexName_Idx) и создаем такую хранимую процедуру:
CREATE OR REPLACE PROCEDURE SomeSchema.normalize_index_names_sp ( schema_name varchar, table_name varchar, from_index_prefix varchar ) AS $proc$ <<proc>> DECLARE sql_str varchar; BEGIN SELECT string_agg('ALTER INDEX ' ||schema_name||'.'||indexname||' RENAME TO '||LOWER(table_name) ||RIGHT(indexname,LENGTH(indexname)-LENGTH(from_index_prefix)),'; ') INTO sql_str FROM pg_catalog.pg_indexes WHERE schemaname=LOWER(schema_name) AND tablename=LOWER(table_name) AND indexname LIKE LOWER(from_index_prefix)||'%'; EXECUTE sql_str; END; $proc$ LANGUAGE plpgsql;
Используя данную процедуру мы получаем возможность быстро переименовать таблицы и их индексы:
PERFORM pg_advisory_lock( to_regproc('SomeScheme.ThisTablePeriodicFill_sp')::oid::integer, 0); ALTER TABLE IF EXISTS SomeScheme.ThisTable RENAME TO ThisTable_Bak; CALL SomeScheme.normalize_index_names_sp ( 'SomeScheme','ThisTable_Bak','ThisTable'); ALTER TABLE IF EXISTS SomeScheme.ThisTable_Shadow RENAME TO ThisTable; CALL SomeScheme.normalize_index_names_sp ( 'SomeScheme','ThisTable','ThisTable_Shadow'); DROP TABLE IF EXISTS SomeScheme.ThisTable_Bak; PERFORM pg_advisory_unlock( to_regproc('SomeScheme.ThisTablePeriodicFill_sp')::oid::integer, 0);
В данном коде опущен блок EXCEPTION, в котором при возникновении ошибок производится журналирование и попытка восстановить имя ThisTable.
Процесс переименования таблиц и их индексов в PostgreSQL выполняется очень быстро. Таким образом мы улучшаем SLA, сократив недоступность таблицы до времени ожидания в очереди, в худшем случае, на время логической блокировки (advisory_lock_shared) клиента, что намного меньше времени заполнения такой таблицы.
