Як розділити існуючу таблицю на постгресах?


19

Я б хотів розділити таблицю з 1М + рядками за діапазоном дат. Як це зазвичай робиться, не вимагаючи великих простоїв або ризикуючи втратити дані? Ось стратегії, які я розглядаю, але відкриті для пропозицій:

  1. Існуюча таблиця є господарем і діти успадковують від неї. З часом перенесіть дані від головного до дочірнього, але настане період часу, коли частина даних знаходиться в головній таблиці, а частина - у дітей.

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


1
Ось мої ідеї: якщо в таблицях є стовпець дата-часу -> створити нового головного + нового дочірнього пристрою -> вставити нові дані в НОВИЙ + СТАРИЙ (наприклад: datetime = 2015-07-06 00:00:00) -> скопіювати зі СТАРОЇ в НОВУ базу у стовпці часу (де: datetime <2015-07-06 00:00:00) -> перейменувати таблицю -> змінити вставку на НОВЕ інше -> створити "тригер розділу" для вставки / оновлення на майстер (вставити / оновити нові дані - > переходити до дітей, тому нові дані будуть вставлятись до дітей) -> майстер оновлення, тригер переміщатиме дані до дітей.
Luan Huynh

@Innnh, тому ви пропонуєте другий варіант, але після того, як дані будуть скопійовані, видаліть стару таблицю та перейменуйте нову таблицю, щоб вона мала те саме ім'я, що і стара таблиця. Це так?
Еван Епплбі

перейменуйте нову таблицю на стару таблицю, але вам слід зберегти стару таблицю, поки нові таблиці розділів потоку не стануть повністю нормальними.
Luan Huynh

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. Додайте тригер, щоб функція викликалася на ВСТУП

    CREATE TRIGGER tr_insert BEFORE INSERT ON old_master
    FOR EACH ROW EXECUTE PROCEDURE fn_insert();
  9. Встановіть виключення обмежень на ВКЛ

    SET constraint_exclusion = on;
  10. Повторно ввімкніть ОНОВЛЕННЯ та ВСТАВКИ у виробничій базі даних

  11. Встановіть тригер або cron так, щоб створювалися нові розділи, а функція оновлювалася для призначення нових даних для виправлення розділу. Перегляньте цю статтю для прикладу коду

  12. Видаліть резервну копію old_master_back


1
Приємний запис. Було б цікаво, якщо це насправді робить ваші запити швидшими. 10 мільйонів все ще не так багато рядків, які я б думав про розділення. Цікаво, чи може ваша принижуюча ефективність була спричинена тим, що ви vacuumне наздоганяєте або не перешкоджаєте через сеанси "простою в транзакціях".
a_horse_with_no_name

@a_horse_with_no_name, поки що це не зробило запитів значно кращими :( я використовую Heroku, який має налаштування автоматичного вакуумування, і, здається, щодня трапляється для цієї великої таблиці. Я буду більше дивитися в цю гру.
Еван Епплбі,

Чи не повинні вставки на кроках 3 та 5 вносити в таблицю new_master, а дозволити postgresql вибрати правильну дочірню таблицю / розділ?
пакман

@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);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.