Come partizionare la tabella esistente in Postgres?


19

Vorrei partizionare una tabella con 1 milione di righe per intervallo di date. Come si fa comunemente senza richiedere molto tempo morto o rischiare di perdere dati? Ecco le strategie che sto prendendo in considerazione, ma aperte ai suggerimenti:

  1. La tabella esistente è il master e i figli ereditano da essa. Nel tempo sposta i dati da master a child, ma ci sarà un periodo di tempo in cui alcuni dei dati si trovano nella tabella master e altri nei child.

  2. Crea una nuova tabella principale e secondaria. Crea una copia dei dati nella tabella esistente nelle tabelle secondarie (in modo che i dati risiedano in due posizioni). Una volta che le tabelle figlio contengono i dati più recenti, modificare tutti gli inserti andando avanti per puntare alla nuova tabella principale ed eliminare la tabella esistente.


1
Ecco le mie idee: se le tabelle hanno una colonna datetime -> crea nuovo master + nuovo figlio -> inserisci nuovi dati in NEW + OLD (es: datetime = 2015-07-06 00:00:00) -> copia da OLD alla NUOVA base nella colonna del tempo (dove: datetime <2015-07-06 00:00:00) -> rinomina tabella -> cambia inserimento in NUOVO altrimenti -> crea "trigger di partizione" per inserire / aggiornare sul master (inserisci / aggiorna nuovi dati - > sposta su child, quindi verranno inseriti nuovi dati su child) -> aggiorna master, il trigger sposta i dati su child.
Luan Huynh,

@Innnh, quindi stai suggerendo la seconda opzione, ma una volta copiati i dati, elimina la vecchia tabella e rinomina la nuova tabella per avere lo stesso nome della vecchia tabella. È giusto?
Evan Appleby

rinominare la nuova tabella nella vecchia tabella, ma è necessario mantenere la vecchia tabella fino a quando le nuove tabelle delle partizioni di flusso non sono completamente corrette.
Luan Huynh,

2
Per pochi milioni di righe non penso che il partizionamento sia effettivamente necessario. Perché pensi di averne bisogno? Che problema stai cercando di risolvere?
a_horse_with_no_name

1
@EvanAppleby DELETE FROM ONLY master_tableè la soluzione.
dezso

Risposte:


21

Poiché il n. 1 richiede la copia dei dati dal master al figlio mentre si trova in un ambiente di produzione attivo, sono andato personalmente con il n. 2 (creazione di un nuovo master). Ciò impedisce interruzioni della tabella originale mentre è attivamente in uso e, in caso di problemi, posso facilmente eliminare il nuovo master senza problemi e continuare a utilizzare la tabella originale. Ecco i passaggi per farlo:

  1. Crea una nuova tabella principale.

    CREATE TABLE new_master (
        id          serial,
        counter     integer,
        dt_created  DATE DEFAULT CURRENT_DATE NOT NULL
    );
  2. Crea figli che ereditano dal maestro.

    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. Copia tutti i dati storici nella nuova tabella principale

    INSERT INTO child_2014 (id,counter,dt_created)
    SELECT id,counter,dt_created
    from old_master
    where dt_created < '01/01/2015'::date;
  4. Sospendi temporaneamente nuovi inserti / aggiornamenti nel database di produzione

  5. Copia i dati più recenti nella nuova tabella principale

    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. Rinominare le tabelle in modo che new_master diventi il ​​database di produzione.

    ALTER TABLE old_master RENAME TO old_master_backup;
    ALTER TABLE new_master RENAME TO old_master;
  7. Aggiungi la funzione per le istruzioni INSERT a old_master in modo che i dati vengano passati alla partizione corretta.

    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. Aggiungi trigger in modo che la funzione venga chiamata su INSERTS

    CREATE TRIGGER tr_insert BEFORE INSERT ON old_master
    FOR EACH ROW EXECUTE PROCEDURE fn_insert();
  9. Impostare l'esclusione dei vincoli su ON

    SET constraint_exclusion = on;
  10. Riattivare AGGIORNAMENTI e INSERTI nel database di produzione

  11. Imposta trigger o cron in modo da creare nuove partizioni e aggiornare la funzione per assegnare nuovi dati alla partizione corretta. Fai riferimento a questo articolo per esempi di codice

  12. Elimina old_master_backup


1
Bel writeup. Sarebbe interessante se ciò rendesse le tue query più veloci. 10 milioni non sono ancora così tante righe che vorrei pensare al partizionamento. Mi chiedo se le tue prestazioni degradanti siano state forse causate dal vacuummancato recupero o dalla prevenzione a causa di sessioni "inattive nelle transazioni".
a_horse_with_no_name

@a_horse_with_no_name, finora non ha migliorato significativamente le query :( Uso Heroku che ha impostazioni di auto-vuoto e sembra accadere ogni giorno per questo grande tavolo. Guarderò di più in questo tho.
Evan Appleby

Gli inserti nei passaggi 3 e 5 non dovrebbero essere la tabella new_master e lasciare che postgresql scelga la tabella / partizione figlio corretta?
Pakman,

@pakman la funzione di assegnare il bambino giusto non viene aggiunta fino al passaggio 7
Evan Appleby

4

Esiste un nuovo strumento chiamato pg_pathman ( https://github.com/postgrespro/pg_pathman ) che lo farebbe automaticamente per te.

Quindi qualcosa come il seguente lo farebbe.

SELECT create_range_partitions('master', 'dt_created', 
   '2015-01-01'::date, '1 day'::interval);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.