PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Comment migrer une table Postgres existante vers une table partitionnée de la manière la plus transparente possible ?

Dans Postgres 10, le "partitionnement déclaratif" a été introduit, ce qui peut vous soulager d'une bonne partie du travail, comme la génération de déclencheurs ou de règles avec d'énormes instructions if/else redirigeant vers la bonne table. Postgres peut le faire automatiquement maintenant. Commençons par la migration :

  1. Renommez l'ancienne table et créez une nouvelle table partitionnée

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

Cela ne devrait guère nécessiter d'explication. L'ancienne table est renommée (après la migration des données, nous la supprimerons) et nous obtenons une table maître pour notre partition qui est fondamentalement la même que notre table d'origine, mais sans index)

  1. Créez une fonction qui peut générer de nouvelles partitions au fur et à mesure que nous en avons besoin :

    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;
    

Cela vous sera utile plus tard.

  1. Créez une vue qui ne fait que déléguer à notre table principale :

    create or replace view myTable as select * from myTable_master;
    
  2. Créez une règle afin que lorsque nous l'insérons dans la règle, nous ne mettions pas simplement à jour la table partitionnée, mais créons également une nouvelle partition si nécessaire :

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

Bien sûr, si vous avez également besoin d'une update et delete , vous avez également besoin d'une règle pour ceux qui doivent être simples.

  1. Migrez réellement l'ancienne table :

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

Maintenant la migration de la table est terminée sans qu'il soit nécessaire de savoir combien de partitions sont nécessaires et aussi la vue myTable sera absolument transparent. Vous pouvez simplement insérer et sélectionner à partir de cette table comme auparavant, mais vous pourriez bénéficier des performances du partitionnement.

Notez que la vue est uniquement nécessaire, car une table partitionnée ne peut pas avoir de déclencheurs de ligne. Si vous pouvez vous entendre en appelant createPartitionIfNotExists manuellement chaque fois que nécessaire à partir de votre code, vous n'avez pas besoin de la vue et de toutes ses règles. Dans ce cas, vous devez également ajouter les partitions manuellement lors de la migration :

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