Как максимально прозрачно перенести существующую таблицу Postgres в многораздельную таблицу? - PullRequest
0 голосов
/ 03 декабря 2018

У меня есть существующая таблица в postgres-DB.Для демонстрации это выглядит так:

create table myTable(
    forDate date not null,
    key2 int not null,
    value int not null,
    primary key (forDate, key2)
);

insert into myTable (forDate, key2, value) values
    ('2000-01-01', 1, 1),
    ('2000-01-01', 2, 1),
    ('2000-01-15', 1, 3),
    ('2000-03-02', 1, 19),
    ('2000-03-30', 15, 8),
    ('2011-12-15', 1, 11);

Однако, в отличие от этих нескольких значений, myTable на самом деле ОГРОМНО и непрерывно растет.Я создаю различные отчеты из этой таблицы, но в настоящее время 98% моих отчетов работают за один месяц, а остальные запросы работают с еще более коротким периодом времени.Часто мои запросы заставляют Postgres выполнять сканирование таблицы по этой огромной таблице, и я ищу способы уменьшить проблему. Разделение таблицы , кажется, идеально подходит для моей проблемы.Я мог бы просто разделить мой стол на месяцы.Но как мне превратить мою существующую таблицу в многораздельную таблицу?В руководстве прямо говорится:

Невозможно превратить обычную таблицу в многораздельную таблицу или наоборот

Так что мне нужно разработать собственный скрипт миграции, которыйпроанализирует текущую таблицу и перенесет ееПотребности заключаются в следующем:

  • Во время разработки временной интервал, который охватывает myTable, неизвестен.
  • Каждый раздел должен охватывать один месяц с первого дня этого месяца допоследний день этого месяца.
  • Таблица будет расти бесконечно, поэтому у меня нет разумного «стоп-значения» для того, сколько таблиц нужно сгенерировать
  • Результат должен быть максимально прозрачным, что означает, чтоЯ хочу коснуться как можно меньше моего существующего кода.В лучшем случае это похоже на обычную таблицу, в которую я могу вставлять и выбирать из нее без каких-либо специальных данных.
  • Допустимо время простоя базы данных для миграции
  • .вещи, которые необходимо установить на сервере, очень предпочтительны.
  • База данных - это PostgreSQL 10, обновление до более новой версии в любом случае произойдет раньше или позже, так что это вариант, если он помогает

Как перенести таблицу в раздел?

1 Ответ

0 голосов
/ 03 декабря 2018

В Postgres 10 было представлено «Декларативное разбиение», которое может избавить вас от большой работы, такой как генерация триггеров или правил с огромными операторами if / else, перенаправляющими на правильную таблицу.Postgres может сделать это автоматически сейчас.Давайте начнем с миграции:

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

    alter table myTable rename to myTable_old;
    
    create table myTable_master(
        forDate date not null,
        key2 int not null,
        value int not null
    ) partition by range (forDate);
    

Это вряд ли должно требовать каких-либо объяснений.Старая таблица переименовывается (после переноса данных мы ее удаляем), и мы получаем основную таблицу для нашего раздела, которая в основном совпадает с нашей исходной таблицей, но без индексов)

Создайте функцию, которая может генерировать новые разделы по мере необходимости:

create function createPartitionIfNotExists(forDate date) returns void
as $body$
declare monthStart date := date_trunc('month', forDate);
    declare monthEndExclusive date := monthStart + interval '1 month';
    -- We infer the name of the table from the date that it should contain
    -- E.g. a date in June 2005 should be int the table mytable_200506:
    declare tableName text := 'mytable_' || to_char(forDate, 'YYYYmm');
begin
    -- Check if the table we need for the supplied date exists.
    -- If it does not exist...:
    if to_regclass(tableName) is null then
        -- Generate a new table that acts as a partition for mytable:
        execute format('create table %I partition of myTable_master for values from (%L) to (%L)', tableName, monthStart, monthEndExclusive);
        -- Unfortunatelly Postgres forces us to define index for each table individually:
        execute format('create unique index on %I (forDate, key2)', tableName);
    end if;
end;
$body$ language plpgsql;

Это пригодится позже.

Создайте представление, которое в основном просто делегирует нашу основную таблицу:

create or replace view myTable as select * from myTable_master;

Создайте правило, чтобы при вставке в правило мы не просто обновлялииз разделенной таблицы, но при необходимости создайте новый раздел:

create or replace rule autoCall_createPartitionIfNotExists as on insert
    to myTable
    do instead (
        select createPartitionIfNotExists(NEW.forDate);
        insert into myTable_master (forDate, key2, value) values (NEW.forDate, NEW.key2, NEW.value)
    );

Конечно, если вам также нужны update и delete, вам также нужно правило для техкоторый должен быть прямым.

Фактически перенесите старую таблицу:

-- Finally copy the data to our new partitioned table
insert into myTable (forDate, key2, value) select * from myTable_old;

-- And get rid of the old table
drop table myTable_old;

Теперь миграция таблицы завершена без необходимости знать, сколько нужно разделов, а такжевид myTable будет абсолютно прозрачным.Вы можете просто вставить и выбрать из этой таблицы, как и раньше, но вы можете получить выигрыш в производительности от секционирования.

Обратите внимание, что представление требуется только потому, что секционированная таблица не может иметь триггеры строк.Если вы можете справиться с вызовом createPartitionIfNotExists вручную, когда это необходимо из вашего кода, вам не нужно представление и все его правила.В этом случае вам необходимо добавить разделы als вручную во время миграции:

do
$$
declare rec record;
begin
    -- Loop through all months that exist so far...
    for rec in select distinct date_trunc('month', forDate)::date yearmonth from myTable_old loop
        -- ... and create a partition for them
        perform createPartitionIfNotExists(rec.yearmonth);
    end loop;
end
$$;
...