sql >> Base de Datos >  >> RDS >> PostgreSQL

¿Cómo migrar una tabla Postgres existente a una tabla particionada de la manera más transparente posible?

En Postgres 10 se introdujo el "particionamiento declarativo", que puede liberarlo de una gran cantidad de trabajo, como generar disparadores o reglas con declaraciones if/else enormes que redirigen a la tabla correcta. Postgres puede hacer esto automáticamente ahora. Comencemos con la migración:

  1. Cambie el nombre de la tabla anterior y cree una nueva tabla particionada

    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);
    

Esto no debería requerir ninguna explicación. Se cambia el nombre de la tabla anterior (después de la migración de datos, la eliminaremos) y obtenemos una tabla maestra para nuestra partición que es básicamente la misma que nuestra tabla original, pero sin índices)

  1. Cree una función que pueda generar nuevas particiones a medida que las necesitemos:

    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;
    

Esto será útil más adelante.

  1. Cree una vista que básicamente solo delegue a nuestra tabla maestra:

    create or replace view myTable as select * from myTable_master;
    
  2. Cree una regla para que cuando insertemos en la regla, no solo actualicemos la tabla particionada, sino que también creemos una nueva partición si es necesario:

    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)
        );
    

Por supuesto, si también necesita update y delete , también necesita una regla para aquellos que deberían ser sencillos.

  1. En realidad, migre la tabla anterior:

    -- 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;
    

Ahora la migración de la tabla está completa sin necesidad de saber cuántas particiones se necesitan y también la vista myTable será absolutamente transparente. Puede simplemente insertar y seleccionar de esa tabla como antes, pero puede obtener el beneficio de rendimiento de la partición.

Tenga en cuenta que la vista solo es necesaria, porque una tabla particionada no puede tener activadores de fila. Si puede llamar a createPartitionIfNotExists manualmente siempre que sea necesario desde su código, no necesita la vista y todas sus reglas. En este caso, debe agregar las particiones als manualmente durante la migración:

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
$$;