¿Cómo particionar la tabla existente en postgres?

19

Me gustaría particionar una tabla con 1M + filas por rango de fechas. ¿Cómo se hace esto comúnmente sin requerir mucho tiempo de inactividad o arriesgarse a perder datos? Estas son las estrategias que estoy considerando, pero abiertas a sugerencias:

  1. La tabla existente es la maestra y los hijos heredan de ella. Con el tiempo, mueva los datos de maestro a hijo, pero habrá un período de tiempo en el que algunos de los datos estarán en la tabla maestra y otros en los hijos.

  2. Cree una nueva tabla maestra y secundaria. Cree una copia de los datos en la tabla existente en las tablas secundarias (para que los datos residan en dos lugares). Una vez que las tablas secundarias tengan datos más recientes, cambie todas las inserciones en adelante para apuntar a una nueva tabla maestra y elimine la tabla existente.

Evan Appleby
fuente
1
Aquí mis ideas: si las tablas tienen una columna de fecha y hora -> crear nuevo maestro + nuevo hijo -> insertar nuevos datos en NUEVO + ANTIGUO (por ejemplo: fecha y hora = 2015-07-06 00:00:00) -> copiar de ANTIGUA a NUEVA base en la columna de tiempo (donde: fecha y hora <2015-07-06 00:00:00) -> renombrar tabla -> cambiar insertar a NUEVO más -> crear "activador de partición" para insertar / actualizar en maestro (insertar / actualizar nuevos datos - > mover a niños, por lo que se insertarán nuevos datos en niños) -> actualizar maestro, el activador moverá datos a niños.
Luan Huynh
@Innnh, por lo que sugiere la segunda opción, pero una vez que se copien los datos, elimine la tabla anterior y cambie el nombre de la nueva tabla para que tenga el mismo nombre que la tabla anterior. ¿Está bien?
Evan Appleby
cambie el nombre de la nueva tabla a la antigua, pero debe mantenerla hasta que las nuevas tablas de partición de flujo estén completamente bien.
Luan Huynh
2
Por solo unos pocos millones de filas, no creo que la partición sea realmente necesaria. ¿Por qué crees que lo necesitas? ¿Que problema estas tratando de resolver?
a_horse_with_no_name
1
@EvanAppleby DELETE FROM ONLY master_tablees la solución.
dezso

Respuestas:

21

Como el n. ° 1 requiere copiar datos del maestro al hijo mientras está en un entorno de producción activo, personalmente fui con el n. ° 2 (crear un nuevo maestro). Esto evita interrupciones en la tabla original mientras está en uso activo y si hay algún problema, puedo eliminar fácilmente el nuevo maestro sin problemas y continuar usando la tabla original. Estos son los pasos para hacerlo:

  1. Crear nueva tabla maestra.

    CREATE TABLE new_master (
        id          serial,
        counter     integer,
        dt_created  DATE DEFAULT CURRENT_DATE NOT NULL
    );
  2. Crea hijos que hereden del 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. Copie todos los datos históricos a la nueva tabla maestra

    INSERT INTO child_2014 (id,counter,dt_created)
    SELECT id,counter,dt_created
    from old_master
    where dt_created < '01/01/2015'::date;
  4. Pause temporalmente nuevas inserciones / actualizaciones en la base de datos de producción

  5. Copie los datos más recientes a la nueva tabla maestra

    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. Cambie el nombre de las tablas para que new_master se convierta en la base de datos de producción.

    ALTER TABLE old_master RENAME TO old_master_backup;
    ALTER TABLE new_master RENAME TO old_master;
  7. Agregue la función para las instrucciones INSERT a old_master para que los datos pasen a la partición correcta.

    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. Agregue el disparador para que esa función se invoque en INSERTOS

    CREATE TRIGGER tr_insert BEFORE INSERT ON old_master
    FOR EACH ROW EXECUTE PROCEDURE fn_insert();
  9. Establezca la exclusión de restricción en ON

    SET constraint_exclusion = on;
  10. Vuelva a habilitar ACTUALIZACIONES e INSERTOS en la base de datos de producción

  11. Configure el disparador o cron para que se creen nuevas particiones y la función se actualice para asignar nuevos datos a la partición correcta. Consulte este artículo para ver ejemplos de código

  12. Eliminar old_master_backup

Evan Appleby
fuente
1
Buena redacción. Sería interesante si eso realmente hace que sus consultas sean más rápidas. 10 millones todavía no son tantas filas que pensaría en particionar. Me pregunto si su rendimiento degradante fue causado por vacuumno ponerse al día o evitarse debido a sesiones "inactivas en la transacción".
a_horse_with_no_name
@a_horse_with_no_name, hasta ahora no ha mejorado significativamente las consultas :( Utilizo Heroku, que tiene la configuración de vacío automático y parece suceder diariamente para esta gran mesa. Examinaré más a fondo eso.
Evan Appleby
¿No deberían las inserciones en los pasos 3 y 5 estar en la tabla new_master y dejar que postgresql elija la tabla / partición secundaria correcta?
pakman
@pakman, la función para asignar el hijo correcto no se agrega hasta el paso 7
Evan Appleby
4

Hay una nueva herramienta llamada pg_pathman ( https://github.com/postgrespro/pg_pathman ) que haría esto automáticamente por usted.

Entonces algo como lo siguiente lo haría.

SELECT create_range_partitions('master', 'dt_created', 
   '2015-01-01'::date, '1 day'::interval);
kakoni
fuente