Comment partitionner une table existante en postgres?

19

Je voudrais partitionner une table avec 1M + lignes par plage de dates. Comment cela se fait-il généralement sans nécessiter beaucoup de temps d'arrêt ou risquer de perdre des données? Voici les stratégies que j'envisage, mais ouvertes aux suggestions:

  1. La table existante est le maître et les enfants en héritent. Au fil du temps, déplacez les données du maître vers l'enfant, mais il y aura une période de temps où certaines des données seront dans la table principale et d'autres dans les enfants.

  2. Créez une nouvelle table principale et des tables enfants. Créez une copie des données dans la table existante dans les tables enfants (pour que les données résident à deux endroits). Une fois que les tables enfants ont les données les plus récentes, modifiez toutes les insertions à l'avenir pour pointer vers la nouvelle table principale et supprimez la table existante.

Evan Appleby
la source
1
Voici mes idées: si les tables ont une colonne datetime -> créer un nouveau maître + un nouvel enfant -> insérer de nouvelles données dans NEW + OLD (ex: datetime = 2015-07-06 00:00:00) -> copier de OLD vers NEW base sur la colonne de temps (où: datetime <2015-07-06 00:00:00) -> renommer la table -> remplacer l'insertion par NEW else -> créer un "déclencheur de partition" pour l'insertion / la mise à jour sur le maître (insérer / mettre à jour de nouvelles données - > déplacer vers les enfants, donc de nouvelles données seront insérées dans les enfants) -> mettre à jour le maître, le déclencheur déplacera les données vers les enfants.
Luan Huynh
@Innnh, vous proposez donc la deuxième option, mais une fois les données copiées, supprimez l'ancienne table et renommez la nouvelle table pour qu'elle ait le même nom que l'ancienne table. Est-ce correct?
Evan Appleby
renommer la nouvelle table en ancienne table, mais vous devez conserver l'ancienne table jusqu'à ce que les nouvelles tables de partition de flux soient complètement correctes.
Luan Huynh
2
Pour seulement quelques millions de lignes, je ne pense pas que le partitionnement soit réellement nécessaire. Pourquoi pensez-vous en avoir besoin? Quel problème essayez-vous de résoudre?
a_horse_with_no_name
1
@EvanAppleby DELETE FROM ONLY master_tableest la solution.
dezso

Réponses:

21

Étant donné que le n ° 1 nécessite la copie des données du maître vers l'enfant alors qu'il se trouve dans un environnement de production actif, j'ai personnellement opté pour le n ° 2 (création d'un nouveau maître). Cela empêche les perturbations de la table d'origine pendant qu'elle est activement utilisée et s'il y a des problèmes, je peux facilement supprimer le nouveau maître sans problème et continuer à utiliser la table d'origine. Voici les étapes pour le faire:

  1. Créez une nouvelle table principale.

    CREATE TABLE new_master (
        id          serial,
        counter     integer,
        dt_created  DATE DEFAULT CURRENT_DATE NOT NULL
    );
  2. Créez des enfants qui héritent du maître.

    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. Copiez toutes les données historiques dans une nouvelle table 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. Suspendre temporairement les nouvelles insertions / mises à jour de la base de données de production

  5. Copiez les données les plus récentes dans la nouvelle table 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. Renommez les tables pour que new_master devienne la base de données de production.

    ALTER TABLE old_master RENAME TO old_master_backup;
    ALTER TABLE new_master RENAME TO old_master;
  7. Ajoutez une fonction pour les instructions INSERT à old_master afin que les données soient transmises à la partition correcte.

    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. Ajouter un déclencheur pour que la fonction soit appelée sur INSERTS

    CREATE TRIGGER tr_insert BEFORE INSERT ON old_master
    FOR EACH ROW EXECUTE PROCEDURE fn_insert();
  9. Définissez l'exclusion des contraintes sur ON

    SET constraint_exclusion = on;
  10. Réactivez les MISES À JOUR et INSERTS sur la base de données de production

  11. Configurez le déclencheur ou le cron afin que de nouvelles partitions soient créées et que la fonction soit mise à jour pour affecter de nouvelles données à la partition correcte. Référencez cet article pour des exemples de code

  12. Supprimer old_master_backup

Evan Appleby
la source
1
Beau résumé. Il serait intéressant que cela accélère réellement vos requêtes. 10 millions, ce n'est toujours pas autant de lignes que je pense au partitionnement. Je me demande si vos performances dégradantes ont peut-être été provoquées par le fait de vacuumne pas rattraper le retard ou d'être empêchées en raison de sessions "inactives en transaction".
a_horse_with_no_name
@a_horse_with_no_name, jusqu'à présent , il n'a pas fait les requêtes beaucoup mieux :( J'utilise Heroku qui a des paramètres d' auto-vide et il semble se produire tous les jours pour cette grande table Regardera plus dans ce tho..
Evan Appleby
Les insertions des étapes 3 et 5 ne devraient-elles pas concerner la table new_master et laisser postgresql choisir la bonne table / partition enfant?
pakman
@pakman la fonction d'attribution du bon enfant n'est pas ajoutée avant l'étape 7
Evan Appleby
4

Il existe un nouvel outil appelé pg_pathman ( https://github.com/postgrespro/pg_pathman ) qui le ferait automatiquement pour vous.

Donc, quelque chose comme le suivant le ferait.

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