Jak podzielić istniejącą tabelę na postgres?


19

Chciałbym podzielić tabelę z 1M + wierszy według zakresu dat. Jak to się zwykle robi, nie wymagając dużo przestojów lub ryzyka utraty danych? Oto strategie, które rozważam, ale są otwarte na sugestie:

  1. Istniejący stół jest wzorcem, a dzieci dziedziczą po nim. Z biegiem czasu przenoszą dane z głównego na potomne, ale przez pewien czas niektóre dane znajdą się w głównej tabeli, a niektóre w potomnych.

  2. Utwórz nowe tabele główne i podrzędne. Utwórz kopię danych w istniejącej tabeli w tabelach podrzędnych (więc dane będą znajdować się w dwóch miejscach). Gdy tabele potomne będą miały najnowsze dane, zmień wszystkie wstawki, aby wskazać nową tabelę główną, i usuń istniejącą tabelę.


1
Oto moje pomysły: jeśli tabele mają kolumnę daty / godziny -> stwórz nowego wzorca + nowego potomka -> wstaw nowe dane do NOWY + STARY (np .: datetime = 2015-07-06 00:00:00) -> skopiuj z OLD do Nowej bazy w kolumnie czasu (gdzie: datetime <2015-07-06 00:00:00) -> zmień nazwę tabeli -> zmień wstawianie na NOWY inny -> utwórz „wyzwalacz partycji” dla wstawiania / aktualizacji na urządzeniu głównym (wstaw / aktualizuj nowe dane - > przenieś do childs, więc nowe dane zostaną wstawione do childs) -> update master, wyzwalacz przeniesie dane do childs.
— Luan Huynh,

@Innnh, więc sugerujesz drugą opcję, ale kiedy dane zostaną skopiowane, usuń starą tabelę i zmień nazwę nowej tabeli, aby miała taką samą nazwę jak stara tabela. Czy to prawda?
— Evan Appleby,

zmień nazwę nowej tabeli na starą, ale powinieneś zachować starą, dopóki nowe tabele podziału przepływu nie będą w pełni poprawne.
— Luan Huynh,

2
W przypadku zaledwie kilku milionów wierszy nie sądzę, aby partycjonowanie było w rzeczywistości konieczne. Jak myślisz, dlaczego tego potrzebujesz? Jaki problem próbujesz rozwiązać?
— a_horse_w_no_name

1
@EvanAppleby DELETE FROM ONLY master_tablejest rozwiązaniem.
— dezso,

Odpowiedzi:


21

Ponieważ nr 1 wymaga kopiowania danych z wzorca do dziecka, gdy jest ono w aktywnym środowisku produkcyjnym, osobiście poszedłem z rozdz. 2 (tworzenie nowego wzorca). Zapobiega to zakłóceniom oryginalnej tabeli, gdy jest ona aktywnie używana, a jeśli występują jakiekolwiek problemy, mogę łatwo usunąć nowy wzorzec bez problemów i kontynuować korzystanie z oryginalnej tabeli. Oto kroki, aby to zrobić:

  1. Utwórz nowy stół główny.

    CREATE TABLE new_master (
        id          serial,
        counter     integer,
        dt_created  DATE DEFAULT CURRENT_DATE NOT NULL
    );
  2. Twórz dzieci dziedziczące od mistrza.

    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. Skopiuj wszystkie dane historyczne do nowej tabeli głównej

    INSERT INTO child_2014 (id,counter,dt_created)
    SELECT id,counter,dt_created
    from old_master
    where dt_created < '01/01/2015'::date;
  4. Tymczasowo wstrzymaj nowe wstawki / aktualizacje produkcyjnej bazy danych

  5. Skopiuj najnowsze dane do nowej tabeli głównej

    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. Zmień nazwy tabel, aby new_master stał się produkcyjną bazą danych.

    ALTER TABLE old_master RENAME TO old_master_backup;
    ALTER TABLE new_master RENAME TO old_master;
  7. Dodaj funkcję instrukcji INSERT do old_master, aby dane były przekazywane do właściwej partycji.

    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. Dodaj wyzwalacz, aby funkcja była wywoływana na WSTAWKI

    CREATE TRIGGER tr_insert BEFORE INSERT ON old_master
    FOR EACH ROW EXECUTE PROCEDURE fn_insert();
  9. Ustaw wykluczenie ograniczenia na WŁ

    SET constraint_exclusion = on;
  10. Ponownie włącz AKTUALIZACJE i WSTAWKI w produkcyjnej bazie danych

  11. Skonfiguruj wyzwalacz lub cron, aby utworzyć nowe partycje i zaktualizować funkcję w celu przypisania nowych danych do poprawnej partycji. W tym artykule znajdują się przykłady kodu

  12. Usuń old_master_backup


1
Niezły opis. Byłoby interesujące, gdyby to faktycznie przyspieszyło twoje zapytania. 10 milionów wciąż nie ma tylu rzędów, które pomyślałbym o partycjonowaniu. Zastanawiam się, czy twoja obniżająca wydajność mogła być spowodowana vacuumbrakiem nadrabiania zaległości lub zapobieganiem im z powodu sesji „bezczynności w transakcji”.
— a_horse_w_no_name

@a_horse_w_na_nazwie, jak dotąd nie poprawiło to znacznie zapytań :( Używam Heroku, który ma ustawienia automatycznego odkurzania i wydaje się, że zdarza się to codziennie dla tego dużego stołu. Zajmę się tym bardziej
— Evan Appleby

Czy wstawki w krokach 3 i 5 nie powinny zawierać tabeli new_master i pozwolić postgresql wybrać odpowiedni stół / partycję podrzędną?
— pakman,

@ pakman funkcja przypisania odpowiedniego dziecka zostaje dodana dopiero w kroku 7
— Evan Appleby

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.