Как разделить существующую таблицу в postgres?

19

Я хотел бы разбить таблицу с 1M + строк по диапазону дат. Как это обычно делается, не требуя большого времени простоя или риска потери данных? Вот стратегии, которые я рассматриваю, но открыты для предложений:

  1. Существующая таблица является главной, а потомки наследуются от нее. Со временем данные перемещаются из основной в дочернюю, но будет период, когда некоторые данные будут в основной таблице, а некоторые - в дочерних.

  2. Создайте новый мастер и дочерние таблицы. Создайте копию данных в существующей таблице в дочерних таблицах (чтобы данные находились в двух местах). Как только у дочерних таблиц появятся самые последние данные, измените все вставки, чтобы они указывали на новую основную таблицу, и удалите существующую таблицу.

Эван Эпплби
источник
1
Вот мои идеи: если у таблиц есть столбец datetime -> создать новый мастер + новый дочерний элемент -> вставить новые данные в NEW + OLD (например: datetime = 2015-07-06 00:00:00) -> скопировать из OLD в базу NEW в столбце времени (где: datetime <2015-07-06 00:00:00) -> переименовать таблицу -> изменить вставку на NEW else -> создать «триггер раздела» для вставки / обновления на главном сервере (вставка / обновление новых данных - > перейти к потомкам, чтобы новые данные были вставлены в потомки) -> обновить мастер, триггер переместит данные к потомкам.
Луан Хейн
@Innnh, поэтому вы предлагаете второй вариант, но затем, когда данные скопированы, удалите старую таблицу и переименуйте новую таблицу, чтобы она имела то же имя, что и старая таблица. Это правильно?
Эван Эпплби,
переименуйте новую таблицу в старую таблицу, но вы должны сохранять старую таблицу, пока новые таблицы разделов потока не будут полностью в порядке.
Луан Хуинх
2
Всего несколько миллионов строк, я не думаю, что разделение действительно необходимо. Почему вы думаете, что это нужно? Какую проблему ты пытаешься решить?
a_horse_with_no_name
1
@EvanAppleby DELETE FROM ONLY master_tableэто решение.
Дезсо

Ответы:

21

Поскольку # 1 требует копирования данных с мастера на дочерний объект, пока он находится в активной производственной среде, я лично пошел с # 2 (создание нового мастера). Это предотвращает сбои в работе исходной таблицы, когда она активно используется, и если есть какие-либо проблемы, я могу легко удалить новый мастер без проблем и продолжить использовать исходную таблицу. Вот шаги, чтобы сделать это:

  1. Создать новую мастер-таблицу.

    CREATE TABLE new_master (
        id          serial,
        counter     integer,
        dt_created  DATE DEFAULT CURRENT_DATE NOT NULL
    );
  2. Создайте детей, которые наследуют от мастера.

    CREATE TABLE child_2014 (
        CONSTRAINT pk_2014 PRIMARY KEY (id),
        CONSTRAINT ck_2014 CHECK ( dt_created < DATE '2015-01-01' )
    ) INHERITS (new_master);
    CREATE INDEX idx_2014 ON child_2014 (dt_created);
    
    CREATE TABLE child_2015 (
        CONSTRAINT pk_2015 PRIMARY KEY (id),
        CONSTRAINT ck_2015 CHECK ( dt_created >= DATE '2015-01-01' AND dt_created < DATE '2016-01-01' )
    ) INHERITS (new_master);
    CREATE INDEX idx_2015 ON child_2015 (dt_created);
    
    ...
  3. Скопируйте все исторические данные в новую основную таблицу

    INSERT INTO child_2014 (id,counter,dt_created)
    SELECT id,counter,dt_created
    from old_master
    where dt_created < '01/01/2015'::date;
  4. Временно приостановить новые вставки / обновления производственной базы данных

  5. Скопируйте самые последние данные в новую главную таблицу

    INSERT INTO child_2015 (id,counter,dt_created)
    SELECT id,counter,dt_created
    from old_master
    where dt_created >= '01/01/2015'::date AND dt_created < '01/01/2016'::date;
  6. Переименуйте таблицы, чтобы new_master стал производственной базой данных.

    ALTER TABLE old_master RENAME TO old_master_backup;
    ALTER TABLE new_master RENAME TO old_master;
  7. Добавьте функцию для операторов INSERT в old_master, чтобы данные передавались в правильный раздел.

    CREATE OR REPLACE FUNCTION fn_insert() RETURNS TRIGGER AS $$
    BEGIN
        IF ( NEW.dt_created >= DATE '2015-01-01' AND
             NEW.dt_created < DATE '2016-01-01' ) THEN
            INSERT INTO child_2015 VALUES (NEW.*);
        ELSIF ( NEW.dt_created < DATE '2015-01-01' ) THEN
            INSERT INTO child_2014 VALUES (NEW.*);
        ELSE
            RAISE EXCEPTION 'Date out of range';
        END IF;
        RETURN NULL;
    END;
    $$
    LANGUAGE plpgsql;
  8. Добавьте триггер, чтобы функция вызывалась на INSERTS

    CREATE TRIGGER tr_insert BEFORE INSERT ON old_master
    FOR EACH ROW EXECUTE PROCEDURE fn_insert();
  9. Установите ограничение исключения на ON

    SET constraint_exclusion = on;
  10. Повторно включите ОБНОВЛЕНИЯ и ВСТАВКИ в производственной базе данных.

  11. Настройте триггер или cron, чтобы создавались новые разделы и обновлялась функция для назначения новых данных правильному разделу. Ссылка на эту статью для примеров кода

  12. Удалить old_master_backup

Эван Эпплби
источник
1
Хорошая рецензия. Было бы интересно, если это на самом деле делает ваши запросы быстрее. 10 миллионов все еще не так много строк, которые я бы подумал о разбиении. Интересно, может быть, ваша унизительная производительность была вызвана тем, что вы vacuumне наверстали упущенное или были предотвращены из-за сеансов "простоя в транзакции".
a_horse_with_no_name
@a_horse_with_no_name, до сих пор это не сделало запросы значительно лучше :( Я использую Heroku, которая имеет настройки авто-вакуума, и это, кажется, происходит ежедневно для этой большой таблицы. Буду больше смотреть на это.
Эван Эпплби,
Разве вставки на шаге 3 и 5 не должны быть в таблицу new_master и позволить postgresql выбрать правильную дочернюю таблицу / раздел?
PakMan
@pakman функция назначения правильного ребенка не будет добавлена ​​до шага 7
Эван Эпплби,
4

Существует новый инструмент под названием pg_pathman ( https://github.com/postgrespro/pg_pathman ), который сделает это автоматически.

Так что-то вроде следующего сделало бы это.

SELECT create_range_partitions('master', 'dt_created', 
   '2015-01-01'::date, '1 day'::interval);
kakoni
источник