PostgreSQLLa base de données la plus sophistiquée au monde.
Documentation PostgreSQL 10.23 » Langage SQL » Définition des données » Partitionnement de tables

5.10. Partitionnement de tables

PostgreSQL offre un support basique du partitionnement de table. Cette section explique pourquoi et comment implanter le partitionnement lors de la conception de la base de données.

5.10.1. Aperçu

Le partitionnement fait rĂ©fĂ©rence Ă  la division d'une table logique volumineuse en plusieurs parties physiques plus petites. Le partitionnement comporte de nombreux avantages :

  • les performances des requĂȘtes peuvent ĂȘtre significativement amĂ©liorĂ©es dans certaines situations, particuliĂšrement lorsque la plupart des lignes fortement accĂ©dĂ©es d'une table se trouvent sur une seule partition ou sur un petit nombre de partitions. Le partitionnement se substitue aux colonnes principales des index, rĂ©duisant ainsi la taille des index et facilitant la tenue en mĂ©moire des parties les plus utilisĂ©es de l'index ;

  • lorsque les requĂȘtes ou les mises Ă  jour accĂšdent Ă  un important pourcentage d'une seule partition, les performances peuvent ĂȘtre grandement amĂ©liorĂ©es par l'utilisation avantageuse de parcours sĂ©quentiels sur cette partition plutĂŽt que d'utiliser un index et des lectures alĂ©atoires rĂ©parties sur toute la table ;

  • les chargements et suppressions importants de donnĂ©es peuvent ĂȘtre obtenus par l'ajout ou la suppression de partitions, sous rĂ©serve que ce besoin ait Ă©tĂ© pris en compte lors de la conception du partitionnement. ExĂ©cuter ALTER TABLE DETACH PARTITION ou supprimer une partition individuelle en utilisant DROP TABLE est bien plus rapide qu'une opĂ©ration de masse. Cela supprime Ă©galement la surcharge due au VACUUM causĂ© par un DELETE massif ;

  • les donnĂ©es peu utilisĂ©es peuvent ĂȘtre dĂ©placĂ©es sur un mĂ©dia de stockage moins cher et plus lent.

Les bĂ©nĂ©fices ne sont rĂ©ellement intĂ©ressants que si cela permet d'Ă©viter une table autrement plus volumineuse. Le point d'Ă©quilibre exact Ă  partir duquel une table tire des bĂ©nĂ©fices du partitionnement dĂ©pend de l'application. Toutefois, le partitionnement doit ĂȘtre envisagĂ© si la taille de la table peut ĂȘtre amenĂ©e Ă  dĂ©passer la taille de la mĂ©moire physique du serveur.

PostgreSQL offre un support natif pour les formes suivantes de partitionnement :

Partitionnement par intervalles

La table est partitionnĂ©e en « intervalles Â» (ou Ă©chelles) dĂ©finis par une colonne clĂ© ou par un ensemble de colonnes, sans recouvrement entre les intervalles de valeurs affectĂ©es aux diffĂ©rentes partitions. Il est possible, par exemple, de partitionner par Ă©chelles de date ou par intervalles d'identifiants pour des objets mĂ©tier particuliers.

Partitionnement par liste

La table est partitionnée en listant explicitement les valeurs clés qui apparaissent dans chaque partition.

Si votre application nĂ©cessite d'utiliser d'autres formes de partitionnement qui ne sont pas listĂ©es au-dessus, des mĂ©thodes alternatives comme l'hĂ©ritage et des vues UNION ALL peuvent ĂȘtre utilisĂ©es Ă  la place. De telles mĂ©thodes offrent de la flexibilitĂ©, mais n'ont pas certains des bĂ©nĂ©fices de performance du partitionnement dĂ©claratif natif.

5.10.2. Partitionnement dĂ©claratif

PostgreSQL donne un moyen de spécifier comment diviser une table en morceaux appelés partitions. La table qui est divisée est appelée table partitionnée. La spécification consiste en une méthode de partitionnement et une liste de colonnes ou expressions à utiliser comme la clé de partitionnement.

Toutes les lignes insĂ©rĂ©es dans la table partitionnĂ©e seront redirigĂ©es vers une des partitions en se basant sur la valeur de la clĂ© de partitionnement. Chaque partition a un sous-ensemble des donnĂ©es dĂ©fini par ses limites de partition. Pour l'instant, les mĂ©thodes de partitionnement supportĂ©es sont le partitionnement par intervalles et par liste, oĂč on assigne Ă  chaque partition respectivement un intervalle de clĂ©s et une liste de clĂ©s.

Les partitions peuvent elles-mĂȘme ĂȘtre dĂ©finies comme des tables partitionnĂ©es, en utilisant ce qu'on appelle du sous-partitionnement. Les partitions peuvent avoir leurs propres index, contraintes et valeurs par dĂ©faut, diffĂ©rents de ceux des autres partitions. Les index doivent ĂȘtre créés sĂ©parĂ©ment pour chaque partition. Voir CREATE TABLE pour plus de dĂ©tails sur la crĂ©ation des tables partitionnĂ©es et des partitions.

Il n'est pas possible de transformer une table standard en table partitionnée et inversement. Par contre, il est possible d'ajouter une table standard ou une table partitionnée contenant des données comme une partition d'une table partitionnée, ou de supprimer une partition d'une table partitionnée, la transformant en table standard; voir ALTER TABLE pour en apprendre plus sur les sous-commandes ATTACH PARTITION et DETACH PARTITION.

Dans les faits, les partitions individuelles sont liĂ©es Ă  la table partitionnĂ©e grĂące Ă  l'hĂ©ritage  ; il n'est pas possible d'utiliser une partie des fonctionnalitĂ©s de l'hĂ©ritage vues dans la section prĂ©cĂ©dente avec les tables partitionnĂ©es et les partitions. Par exemple, une partition ne peut pas avoir d'autre parent qu'une table partitionnĂ©e dont elle est une partition, et une table standard ne peut pas hĂ©riter d'une table partitionnĂ©e faisant d'elle son parent. Cela veut dire que les tables partitionnĂ©es et les partitions ne participent pas Ă  l'hĂ©ritage avec les tables standard. Puisqu'une hiĂ©rarchie de partition est constituĂ©e d'une table partitionnĂ©e et de ses partitions, il s'agit toujours d'une hiĂ©rarchie d'hĂ©ritage, tableoid et toutes les rĂšgles normales de l'hĂ©ritage s'appliquent comme dĂ©crit dans Section 5.9 avec quelques exceptions, les plus notables Ă©tant :

  • Les contraintes CHECK et NOT NULL d'une table partitionnĂ©e sont toujours hĂ©ritĂ©es par toutes les partitions. La crĂ©ation de contraintes CHECK qui sont marquĂ©es comme NO INHERIT n'est pas autorisĂ©e sur des tables partitionnĂ©es.

  • Utiliser ONLY pour ajouter ou supprimer une contrainte sur la table partitionnĂ©e uniquement est supportĂ© s'il n'y a pas de partition. Une fois qu'il existe des partitions, utiliser ONLY remontera une erreur puisqu'ajouter ou supprimer des contraintes sur uniquement la table partitionnĂ©e, quand des partitions existent, n'est pas supportĂ©. À la place, des contraintes peuvent ĂȘtre ajoutĂ©es ou supprimĂ©es, quand elles ne sont pas prĂ©sentes sur la table parente, directement sur les partitions. Comme une table partitionnĂ©e ne contient jamais de donnĂ©es directement, essayer d'utiliser TRUNCATE ONLY sur une table partitionnĂ©e retournera toujours une erreur.

  • Les partitions ne peuvent pas avoir de colonnes qui ne sont pas prĂ©sentes dans le parent. Il n'est pas non plus possible de spĂ©cifier des colonnes quand une partition est créée avec CREATE TABLE, pas plus qu'il n'est possible d'ajouter des colonnes aux partitions une fois celles-ci créées en utilisant ALTER TABLE. Des tables peuvent ĂȘtre ajoutĂ©es comme des partitions avec ALTER TABLE ... ATTACH PARTITION seulement si leurs colonnes correspondent exactement Ă  celles du parent, en incluant toutes les colonnes oid.

  • Vous ne pouvez pas supprimer la contrainte NOT NULL d'une colonne d'une partition si la contrainte est prĂ©sente dans la table parente.

Les partitions peuvent Ă©galement ĂȘtre des tables Ă©trangĂšres (voir CREATE FOREIGN TABLE), bien que ce cas ait des limitations qui n'existent pas avec des tables normales. Par exemple, les donnĂ©es insĂ©rĂ©es dans la table partitionnĂ©e ne sont pas redirigĂ©es vers la partition qui est une table Ă©trangĂšre.

5.10.2.1. Exemple

Imaginons que nous soyons en train de construire une base de données pour une grande société de crÚme glacée. La société mesure les pics de températures chaque jour, ainsi que les ventes de crÚme glacée dans chaque région. Conceptuellement, nous voulons une table comme ceci :

CREATE TABLE mesure (
    id_ville        int not null,
    date_trace      date not null,
    temperature     int,
    ventes          int
);

La plupart des requĂȘtes n'accĂšdent qu'aux donnĂ©es de la derniĂšre semaine, du dernier mois ou du dernier trimestre, car cette table est essentiellement utilisĂ©e pour prĂ©parer des rapports en ligne pour la direction. Pour rĂ©duire le nombre de donnĂ©es anciennes Ă  stocker, seules les trois derniĂšres annĂ©es sont conservĂ©es. Au dĂ©but de chaque mois, les donnĂ©es du mois le plus ancien sont supprimĂ©es. Dans cette situation, le partitionnement permet de rĂ©pondre aux diffĂ©rents besoins identifiĂ©s sur la table des mesures.

Pour utiliser le partitionnement déclaratif dans ce cas d'utilisation, il faut suivre les étapes suivantes :

  1. Créer une table measurement comme une table partitionnée en spécifiant la clause PARTITION BY, ce qui inclut la méthode de partitionnement ( RANGE dans ce cas) ainsi que la liste de la ou des colonnes à utiliser comme clé de partitionnement.

    +CREATE TABLE measurement (
        city_id         int not null,
        logdate         date not null,
        peaktemp        int,
        unitsales       int
    ) PARTITION BY RANGE (logdate);
         

    Vous pourriez dĂ©cider d'utiliser plusieurs colonnes dans la clĂ© de partitionnement pour le partitionnement par intervalles, si vous le souhaitez. Bien sĂ»r, cela aura souvent pour consĂ©quence un plus grand nombre de partitions, chacune Ă©tant individuellement plus petite. D'un autre cĂŽtĂ©, utiliser moins de partitions pourrait entraĂźner un critĂšre de partitionnement plus grossier et moins de partitions. Une requĂȘte accĂ©dant Ă  la table partitionnĂ©e aura Ă  parcourir moins de partitions si les conditions impliquent une partie ou la totalitĂ© de ces colonnes. Par exemple, imaginez une table partitionnĂ©e par intervalles utilisant les colonnes lastname et firstname (dans cet ordre) comme clĂ© de partitionnement.

  2. CrĂ©ez les partitions. La dĂ©finition de chaque partition doit spĂ©cifier les limites qui correspondent Ă  la mĂ©thode de partitionnement ainsi qu'Ă  la clĂ© de partitionnement du parent. Veuillez noter que spĂ©cifier des limites telles que les valeurs de la nouvelle partition pourront se chevaucher avec celles d'une ou plusieurs autres partitions retournera une erreur. InsĂ©rer des donnĂ©es dans la table parent qui ne correspondent pas Ă  une des partitions existantes remontera une erreur  ; la partition appropriĂ©e doit ĂȘtre ajoutĂ©e manuellement.

    Les partitions ainsi créées sont de tous les points de vue des tables PostgreSQL normales (ou, potentiellement, des tables étrangÚres). Il est possible de spécifier un tablespace et des paramÚtres de stockage pour chacune des partitions séparément.

    Il n'est pas nĂ©cessaire de crĂ©er les contraintes de table dĂ©crivant les conditions limites de la partition pour les partitions. À la place, des contraintes de partitions sont gĂ©nĂ©rĂ©es implicitement Ă  partir de la spĂ©cification de limite de partition chaque fois qu'il est nĂ©cessaire de s'y rĂ©fĂ©rer.

    CREATE TABLE measurement_y2006m02 PARTITION OF measurement
        FOR VALUES FROM ('2006-02-01') TO ('2006-03-01');
    
    CREATE TABLE measurement_y2006m03 PARTITION OF measurement
        FOR VALUES FROM ('2006-03-01') TO ('2006-04-01');
    
     ...
    CREATE TABLE measurement_y2007m11 PARTITION OF measurement
        FOR VALUES FROM ('2007-11-01') TO ('2007-12-01');
    
    CREATE TABLE measurement_y2007m12 PARTITION OF measurement
        FOR VALUES FROM ('2007-12-01') TO ('2008-01-01')
        TABLESPACE fasttablespace;
    
    CREATE TABLE measurement_y2008m01 PARTITION OF measurement
        FOR VALUES FROM ('2008-01-01') TO ('2008-02-01')
        WITH (parallel_workers = 4)
        TABLESPACE fasttablespace;
         

    Pour implémenter le sous-partitionnement, spécifiez la clause PARTITION BY dans les commandes utilisées pour créer des partitions individuelles, par exemple :

    CREATE TABLE measurement_y2006m02 PARTITION OF measurement
        FOR VALUES FROM ('2006-02-01') TO ('2006-03-01')
        PARTITION BY RANGE (peaktemp);
         

    AprĂšs avoir créé les partitions de measurement_y2006m02, toute donnĂ©e insĂ©rĂ©e dans measurement qui correspond Ă  measurement_y2006m02 (ou donnĂ©e qui est directement insĂ©rĂ©e dans measurement_y2006m02, sous rĂ©serve qu'elle satisfasse sa contrainte de partition) sera redirigĂ©e vers l'une de ses partitions en se basant sur la colonne peaktemp. La clĂ© de partition spĂ©cifiĂ©e pourrait se chevaucher avec la clĂ© de partition du parent, il faut donc faire spĂ©cialement attention lorsque les limites d'une sous-partition sont spĂ©cifiĂ©es afin que l'ensemble de donnĂ©es qu'elle accepte constitue un sous-ensemble de ce que les propres limites de la partition acceptent  ; le systĂšme n'essayera pas de vĂ©rifier si c'est vraiment le cas.

  3. Créez un index sur la ou les colonnes de la clé, ainsi que tout autre index que vous pourriez vouloir pour chaque partition. (L'index sur la clé n'est pas strictement nécessaire, mais c'est utile dans la plupart des scénarios. Si vous voulez que les valeurs de la clé soient uniques, alors vous devriez toujours créer un index unique ou une contrainte de clé primaire pour chaque partition.)

    CREATE INDEX ON measurement_y2006m02 (logdate);
    CREATE INDEX ON measurement_y2006m03 (logdate);
    ...
    CREATE INDEX ON measurement_y2007m11 (logdate);
    CREATE INDEX ON measurement_y2007m12 (logdate);
    CREATE INDEX ON measurement_y2008m01 (logdate);

    Assurez-vous que le paramĂštre de configuration constraint_exclusion ne soit pas dĂ©sactivĂ© dans postgresql.conf. S'il l'est, les requĂȘtes ne seront pas optimisĂ©es comme voulu.

Dans l'exemple ci-dessus, nous créerions une nouvelle partition chaque mois, il serait donc avisé d'écrire un script qui génÚre le DDL nécessaire automatiquement.

5.10.2.2. Maintenance des partitions

Normalement, l'ensemble des partitions établies lors de la définition initiale de la table n'a pas vocation à demeurer statique. Il est normal de vouloir supprimer d'anciennes partitions de données et périodiquement ajouter de nouvelles partitions pour les nouvelles données. Un des avantages les plus importants du partitionnement est précisément qu'il permet d'exécuter cette tùche de maintenance normalement pénible instantanément en manipulant la structure de la partition, plutÎt que de physiquement bouger de grands ensembles de données.

Le moyen le plus simple pour supprimer d'anciennes données est de supprimer la partition qui n'est plus nécessaire :

DROP TABLE measurement_y2006m02;
    

Cela peut supprimer des millions d'enregistrements trÚs rapidement, car il n'est pas nécessaire de supprimer chaque enregistrement séparément. Veuillez noter toutefois que la commande ci-dessus nécessite de prendre un verrou de type ACCESS EXCLUSIVE sur la table parente.

Une autre possibilité, qui est généralement préférable, est de ne pas supprimer la partition de la table partitionnée, mais de la conserver en tant que table :

ALTER TABLE measurement DETACH PARTITION measurement_y2006m02;
    

Cela permet d'effectuer ensuite d'autres opĂ©rations sur les donnĂ©es avant de la supprimer. Par exemple, il s'agit souvent du moment idĂ©al pour sauvegarder les donnĂ©es en utilisant COPY, pg_dump, ou des outils similaires. Cela pourrait Ă©galement ĂȘtre le bon moment pour agrĂ©ger les donnĂ©es dans un format moins volumineux, effectuer d'autres manipulations de donnĂ©es ou exĂ©cuter des rapports.

De la mĂȘme maniĂšre, nous pouvons ajouter une nouvelle partition pour gĂ©rer les nouvelles donnĂ©es. Nous pouvons crĂ©er une partition vide dans la table partitionnĂ©e exactement comme la premiĂšre partition a Ă©tĂ© créée prĂ©cĂ©demment :

CREATE TABLE measurement_y2008m02 PARTITION OF measurement
    FOR VALUES FROM ('2008-02-01') TO ('2008-03-01')
    TABLESPACE fasttablespace;
    

De maniÚre alternative, il est parfois plus utile de créer la nouvelle table en dehors de la structure de la partition, et d'en faire une partition plus tard. Cela permet de charger des données, les vérifier et effectuer des transformations avant que les données apparaissent dans la table partitionnée :

CREATE TABLE measurement_y2008m02
  (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS)
  TABLESPACE fasttablespace;

ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
   CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' );

\copy measurement_y2008m02 from 'measurement_y2008m02'
-- possibly some other data preparation work

ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02
    FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' );
    

Avant d'exĂ©cuter une commande ATTACH PARTITION, il est recommandĂ© de crĂ©er une contrainte CHECK sur la table qui doit ĂȘtre attachĂ©e dĂ©crivant la contrainte de partition dĂ©sirĂ©e. De cette maniĂšre, le systĂšme n'aura pas besoin d'effectuer un parcours de la table pour valider la contrainte de partition implicite. Sans la contrainte CHECK, la table sera parcourue pour valider la contrainte de partition tout en ayant un verrou de niveau ACCESS EXCLUSIVE sur la table parente. Vous pouvez alors supprimer la contrainte CHECK redondante aprĂšs que ATTACH PARTITION soit fini.

5.10.2.3. Limitations

Les limitations suivantes s'appliquent aux tables partitionnĂ©es :

  • Il n'y a pas de commande disponible pour crĂ©er les index correspondant sur toutes les partitions automatiquement. Les index doivent ĂȘtre ajoutĂ©s sur chaque partition avec des commandes sĂ©parĂ©es. Cela signifie Ă©galement qu'il n'y a pas de moyen de crĂ©er une clĂ© primaire, un index unique ou une contrainte d'exclusion couvrant toutes les partitions  ; il est seulement possible de contraindre chaque partition de niveau feuille individuellement.

  • Puisque les clĂ©s primaires ne sont pas supportĂ©es sur les tables partitionnĂ©es, les clĂ©s Ă©trangĂšres rĂ©fĂ©rençant des tables partitionnĂ©es ne sont pas supportĂ©es, pas plus qu'une clĂ© Ă©trangĂšre depuis une table partitionnĂ©e rĂ©fĂ©rençant une autre table.

  • Utiliser la clause ON CONFLICT avec des tables partitionnĂ©es remontera une erreur, car les contraintes unique ou d'exclusion ne peuvent ĂȘtre créées que sur les partitions individuelles. Il n'y a pas de support pour imposer l'unicitĂ© (ou une contrainte d'exclusion) sur l'intĂ©gralitĂ© d'une hiĂ©rarchie de partitionnement.

  • Un UPDATE qui a pour consĂ©quence de dĂ©placer une ligne d'une partition Ă  une autre Ă©chouera, car la nouvelle ligne ne satisfera pas la contrainte de partition implicite de la partition d'origine.

  • Les triggers de lignes, si nĂ©cessaires, doivent ĂȘtre dĂ©finis sur les partitions individuelles et non sur la table partitionnĂ©e.

  • Mixer des tables temporaires et permanentes dans le mĂȘme arbre de partition n'est pas autorisĂ©. De ce fait, si la table partitionnĂ©e est permanente, toutes ces partitions le sont. De la mĂȘme façon si la table partitionnĂ©e est temporaire. Lors de l'utilisation de tables temporaires, tous les membres de l'arbre de partition doit provenir de la mĂȘme session.

5.10.3. ImplĂ©mentation utilisant l'hĂ©ritage

Bien que le partitionnement dĂ©claratif natif soit adaptĂ© pour la plupart des cas d'usage courant, il y a certains cas oĂč une approche plus flexible pourrait ĂȘtre utile. Le partitionnement peut ĂȘtre implĂ©mentĂ© en utilisant l'hĂ©ritage de table, ce qui permet plusieurs autres fonctionnalitĂ©s qui ne sont pas supportĂ©es par le partitionnement dĂ©claratif, comme :

  • Le partitionnement impose le fait que toutes les partitions doivent avoir exactement le mĂȘme ensemble de colonnes que le parent, mais l'hĂ©ritage de table permet aux enfants d'avoir des colonnes supplĂ©mentaires qui ne sont pas prĂ©sentes dans la table parent.

  • L'hĂ©ritage de table permet de multiples hĂ©ritages.

  • Le partitionnement dĂ©claratif ne supporte que le partitionnement par liste et par intervalles, alors que l'hĂ©ritage de table autorise la division des donnĂ©es de la maniĂšre choisie par l'utilisateur. (Notez toutefois que si les contraintes d'exclusions ne sont pas capables de filtrer la liste des partitions de maniĂšre efficace, la performance des requĂȘtes sera trĂšs mauvaise.)

  • Certaines opĂ©rations nĂ©cessitent un verrou plus fort en utilisant le partitionnement dĂ©claratif qu'en utilisant l'hĂ©ritage de table. Par exemple, ajouter ou supprimer une partition d'une table partitionnĂ©e nĂ©cessite de prendre un verrou de type ACCESS EXCLUSIVE sur la table parente, alors qu'un verrou de type SHARE UPDATE EXCLUSIVE est suffisant dans le cas de l'hĂ©ritage classique.

5.10.3.1. Exemple

Nous utilisons la mĂȘme table measurement, non partitionnĂ©e, que nous avons dĂ©jĂ  utilisĂ©e au-dessus. Pour l'implĂ©menter comme une table partitionnĂ©e en utilisant l'hĂ©ritage, utilisez les Ă©tapes suivantes :

  1. CrĂ©ez la table « master Â», Ă  partir de laquelle toutes les partitions seront hĂ©ritĂ©es. Cette table ne contiendra aucune donnĂ©e. Ne dĂ©finissez aucune contrainte de vĂ©rification sur cette table, Ă  moins que vous n'ayez l'intention de l'appliquer de maniĂšre identique sur toutes les partitions. Il n'y a aucun intĂ©rĂȘt Ă  dĂ©finir d'index ou de contrainte unique sur elle non plus. Pour notre exemple, la table master correspond Ă  la table measurement dĂ©finie Ă  l'origine.

  2. CrĂ©ez plusieurs tables « enfant Â» qui chacune hĂ©rite de la table master. Normalement, ces tables n'auront aucune colonne supplĂ©mentaire par rapport Ă  celles hĂ©ritĂ©es de la table master. Tout comme avec le partitionnement dĂ©claratif, ces partitions ont tous les aspects des tables (ou tables Ă©trangĂšres) PostgreSQL normales.

    CREATE TABLE measurement_y2006m02 () INHERITS (measurement);
    CREATE TABLE measurement_y2006m03 () INHERITS (measurement);
    ...
    CREATE TABLE measurement_y2007m11 () INHERITS (measurement);
    CREATE TABLE measurement_y2007m12 () INHERITS (measurement);
    CREATE TABLE measurement_y2008m01 () INHERITS (measurement);
           

  3. Ajoutez les contraintes de tables qui ne se chevauchent pas sur les tables de partition pour définir les valeurs de clé autorisées dans chaque partition.

    Les exemples typiques seraient :

    CHECK ( x = 1 )
    CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
    CHECK ( outletID >= 100 AND outletID < 200 )
           

    Assurez-vous que les contraintes garantissent qu'il n'y a pas de chevauchement entre les valeurs de clés permises dans différentes partitions. Une erreur fréquente est de mettre en place des contraintes d'intervalle comme ceci :

    CHECK ( outletID BETWEEN 100 AND 200 )
    CHECK ( outletID BETWEEN 200 AND 300 )
           

    Cet exemple est faux puisqu'on ne peut pas savoir à quelle partition appartient la valeur de clé 200.

    Il serait préférable de créer à la place les partitions comme cela :

    CREATE TABLE measurement_y2006m02 (
        CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
    ) INHERITS (measurement);
    
    CREATE TABLE measurement_y2006m03 (
        CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )
    ) INHERITS (measurement);
    
    ...
    CREATE TABLE measurement_y2007m11 (
        CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' )
    ) INHERITS (measurement);
    
    CREATE TABLE measurement_y2007m12 (
        CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' )
    ) INHERITS (measurement);
    
    CREATE TABLE measurement_y2008m01 (
        CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' )
    ) INHERITS (measurement);
           

  4. Pour chaque partition, créez un index sur la ou les colonnes de la clé, ainsi que tout autre index que vous voudriez.

    CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
    CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
    CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
    CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
    CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
           

  5. Nous voulons que notre application soit capable de dire INSERT INTO measurement ... et d'avoir les donnĂ©es redirigĂ©es dans la table de partition appropriĂ©e. Nous pouvons rĂ©aliser cela en attachant une fonction de dĂ©clencheur convenable sur la table master. Si les donnĂ©es doivent ĂȘtre ajoutĂ©es sur la derniĂšre table de partition uniquement, nous pouvons utiliser une fonction de dĂ©clencheur trĂšs simple :

    CREATE OR REPLACE FUNCTION measurement_insert_trigger()
    RETURNS TRIGGER AS $$
    BEGIN
        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
        RETURN NULL;
    END;
    $$
    LANGUAGE plpgsql;
           

    AprÚs avoir créé la fonction, nous créons un déclencheur qui appelle la fonction de déclencheur :

    CREATE TRIGGER insert_mesure_trigger
        BEFORE INSERT ON mesure
        FOR EACH ROW EXECUTE PROCEDURE mesure_insert_trigger();
           

    La fonction dĂ©clencheur doit ĂȘtre redĂ©finie chaque mois pour qu'elle pointe toujours sur la partition active. La dĂ©finition du dĂ©clencheur n'a pas besoin d'ĂȘtre redĂ©finie.

    Il est Ă©galement possible de laisser le serveur localiser la partition dans laquelle doit ĂȘtre insĂ©rĂ©e la ligne proposĂ©e en entrĂ©e. Une fonction dĂ©clencheur plus complexe peut ĂȘtre utilisĂ©e pour cela  :

    CREATE OR REPLACE FUNCTION mesure_insert_trigger()
    RETURNS TRIGGER AS $$
    BEGIN
        IF ( NEW.date_trace >= DATE '2006-02-01' AND
             NEW.date_trace < DATE '2006-03-01' ) THEN
            INSERT INTO mesure_a2006m02 VALUES (NEW.*);
        ELSIF ( NEW.date_trace >= DATE '2006-03-01' AND
                NEW.date_trace < DATE '2006-04-01' ) THEN
            INSERT INTO mesure_a2006m03 VALUES (NEW.*);
        ...
        ELSIF ( NEW.date_trace >= DATE '2008-01-01' AND
                NEW.date_trace < DATE '2008-02-01' ) THEN
            INSERT INTO mesure_a2008m01 VALUES (NEW.*);
        ELSE
            RAISE EXCEPTION 'Date en dehors de l''intervalle. Corrigez la fonction mesure_insert_trigger() !';
        END IF;
        RETURN NULL;
    END;
    $$
    LANGUAGE plpgsql;
           

    La définition du déclencheur ne change pas. Chaque test IF doit correspondre exactement à la contrainte CHECK de cette partition.

    Bien que cette fonction soit plus complexe que celle du mois seul, il n'est pas nĂ©cessaire de l'actualiser aussi frĂ©quemment, les branches pouvant ĂȘtre ajoutĂ©es avant d'ĂȘtre utiles.

    Note

    En pratique, il pourrait ĂȘtre prĂ©fĂ©rable de vĂ©rifier prioritairement la derniĂšre partition créée si la plupart des insertions lui sont destinĂ©es. Pour des raisons de simplicitĂ©, les tests du dĂ©clencheur sont prĂ©sentĂ©s dans le mĂȘme ordre que les autres parties de l'exemple.

    Une approche différente est de rediriger les insertions dans la table de partition appropriée à l'aide de rÚgles, plutÎt qu'un déclencheur, sur la table master. Par exemple :

    CREATE RULE measurement_insert_y2006m02 AS
    ON INSERT TO measurement WHERE
        ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
    DO INSTEAD
        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
    ...
    CREATE RULE measurement_insert_y2008m01 AS
    ON INSERT TO measurement WHERE
        ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' )
    DO INSTEAD
        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
           

    Une rĂšgle Ă  un surcoĂ»t bien plus important qu'un trigger, mais le surcoĂ»t n'est payĂ© qu'une fois par requĂȘte plutĂŽt qu'une fois par ligne, cette mĂ©thode peut ĂȘtre avantageuse pour les situations d'insertions en masse. Toutefois, dans la plupart des cas, la mĂ©thode du dĂ©clencheur offrira de meilleures performances.

    Soyez conscient que COPY ignore les rÚgles. Si vous voulez utiliser COPY pour insérer des données, vous devrez les copier dans la bonne table de partition plutÎt que dans la table master. COPY déclenche les triggers, vous pouvez donc l'utilisez normalement si vous utilisez l'approche par déclencheur.

    Un autre inconvénient à l'approche par rÚgle est qu'il n'y a pas de moyen simple de forcer une erreur si l'ensemble de rÚgles ne couvre pas la date d'insertion; les données iront silencieusement dans la table master à la place.

  6. Assurez-vous que le paramĂštre de configuration constraint_exclusion ne soit pas dĂ©sactivĂ© dans postgresql.conf. S'il l'est, les requĂȘtes ne seront pas optimisĂ©es comme voulu.

Comme nous pouvons le voir, un schéma de partitionnement complexe peut nécessiter une quantité de DDL non négligeable. Dans l'exemple du dessus, nous créerions une nouvelle partition chaque mois, il serait donc sage d'écrire un script qui génÚre le DDL requis automatiquement.

5.10.3.2. Maintenance de partition

Pour supprimer les anciennes données rapidement, il suffit de supprimer la partition qui n'est plus nécessaire :

DROP TABLE mesure_a2006m02;
    

Pour supprimer la partition de la table partitionnée, mais pour garder l'accÚs à la table en tant que telle :

ALTER TABLE mesure_a2006m02 NO INHERIT mesure;
    

Pour ajouter une nouvelle partition pour gérer les nouvelles données, créez une partition vide tout comme les partitions originales ont été créées au-dessus :

CREATE TABLE mesure_a2008m02 (
    CHECK ( date_trace >= DATE '2008-02-01' AND date_trace < DATE '2008-03-01' )
    ) INHERITS (mesure);
    

De maniÚre alternative, vous pourriez vouloir créer la nouvelle table en dehors de la structure de partition, et en faire une partition aprÚs avoir chargé, vérifié et transformé les données :

CREATE TABLE mesure_a2008m02
  (LIKE mesure INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
ALTER TABLE mesure_a2008m02 ADD CONSTRAINT y2008m02
   CHECK ( date_trace >= DATE '2008-02-01' AND date_trace < DATE '2008-03-01' );
\copy mesure_a2008m02 from 'mesure_a2008m02'
-- quelques travaux de préparation des données
ALTER TABLE mesure_a2008m02 INHERIT mesure;
    

5.10.3.3. Restrictions

Les restrictions suivantes s'appliquent aux tables partitionnĂ©es utilisant l'hĂ©ritage :

  • Il n'existe pas de moyen automatique de vĂ©rifier que toutes les contraintes de vĂ©rification (CHECK) sont mutuellement exclusives. Il est plus sĂ»r de crĂ©er un code qui fabrique les partitions et crĂ©e et/ou modifie les objets associĂ©s plutĂŽt que de les crĂ©er manuellement ;

  • les schĂ©mas montrĂ©s ici supposent que les colonnes clĂ©s du partitionnement d'une ligne ne changent jamais ou, tout du moins, ne changent pas suffisamment pour nĂ©cessiter un dĂ©placement vers une autre partition. Une commande UPDATE qui tente de le faire Ă©choue Ă  cause des contraintes CHECK. Pour gĂ©rer ce type de cas, des dĂ©clencheurs peuvent ĂȘtre convenablement positionnĂ©s pour la mise Ă  jour sur les tables de partition, mais cela rend la gestion de la structure beaucoup plus complexe.

  • Si VACUUM ou ANALYZE sont lancĂ©s manuellement, il est obligatoire de les utiliser sur chaque partition. Une commande comme :

    ANALYZE measurement;
           

    ne traite que la table maĂźtre.

  • Les commandes INSERT avec des clauses ON CONFLICT ont probablement peu de chances de fonctionner comme attendu, dans la mesure oĂč l'action du ON CONFLICT est uniquement effectuĂ©e dans le cas de violations qui sont uniques Ă  la table cible, pas Ă  ses tables enfants.

  • Les dĂ©clencheurs ou les rĂšgles seront nĂ©cessaires pour rediriger les lignes vers la partition voulue, Ă  moins que l'application ne soit explicitement au courant du schĂ©ma de partitionnement. Les dĂ©clencheurs peuvent ĂȘtre plus compliquĂ©s Ă  Ă©crire, et seront bien plus lents que la redirection de ligne effectuĂ©e en interne par le partitionnement dĂ©claratif.

5.10.4. Partitionnement et exclusion de contrainte

L'exclusion de contrainte est une technique d'optimisation des requĂȘtes pour amĂ©liorer les performances sur les tables partitionnĂ©es telles que dĂ©crites plus haut (les tables partitionnĂ©es qui utilisent le partitionnement dĂ©claratif ainsi que celles qui sont implĂ©mentĂ©es en utilisant l'hĂ©ritage). Par exemple :

SET constraint_exclusion = on;
SELECT count(*) FROM mesure WHERE date_trace >= DATE '2008-01-01';

Sans exclusion de contrainte, la requĂȘte ci-dessus parcourt chacune des partitions de la table mesure. Avec l'exclusion de contrainte activĂ©e, le planificateur examine les contraintes de chaque partition et tente de prouver que la partition n'a pas besoin d'ĂȘtre parcourue parce qu'elle ne peut pas contenir de lignes correspondant Ă  la clause WHERE de la requĂȘte. Quand le planificateur peut le prouver, il exclut la partition du plan de requĂȘte.

La commande EXPLAIN permet d'afficher la diffĂ©rence entre un plan avec constraint_exclusion activĂ© (on) et un plan avec ce paramĂštre dĂ©sactivĂ© (off). Un plan typique non optimisĂ© pour ce type de table est :

SET constraint_exclusion = off;
EXPLAIN SELECT count(*) FROM mesure WHERE date_trace >= DATE '2008-01-01';

                                          QUERY PLAN
-------------------------------------------------------------------------------------
 Aggregate  (cost=158.66..158.68 rows=1 width=0)
   ->  Append  (cost=0.00..151.88 rows=2715 width=0)
         ->  Seq Scan on mesure  (cost=0.00..30.38 rows=543 width=0)
               Filter: (date_trace >= '2008-01-01'::date)
         ->  Seq Scan on mesure_a2006m02 mesure  (cost=0.00..30.38 rows=543 width=0)
               Filter: (date_trace >= '2008-01-01'::date)
         ->  Seq Scan on mesure_ay2006m03 mesure  (cost=0.00..30.38 rows=543 width=0)
               Filter: (date_trace >= '2008-01-01'::date)
...
         ->  Seq Scan on mesure_a2007m12 mesure  (cost=0.00..30.38 rows=543 width=0)
               Filter: (date_trace >= '2008-01-01'::date)
         ->  Seq Scan on mesure_a2008m01 mesure  (cost=0.00..30.38 rows=543 width=0)
               Filter: (date_trace >= '2008-01-01'::date)
   

Quelques partitions, voire toutes, peuvent utiliser des parcours d'index Ă  la place des parcours sĂ©quentiels de la table complĂšte, mais le fait est qu'il n'est pas besoin de parcourir les anciennes partitions pour rĂ©pondre Ă  cette requĂȘte. Lorsque l'exclusion de contrainte est activĂ©e, un plan significativement moins coĂ»teux est obtenu, qui dĂ©livre la mĂȘme rĂ©ponse :

SET constraint_exclusion = on;
EXPLAIN SELECT count(*) FROM mesure WHERE date_trace >= DATE '2008-01-01';
                                          QUERY PLAN
-------------------------------------------------------------------------------------
 Aggregate  (cost=63.47..63.48 rows=1 width=0)
   ->  Append  (cost=0.00..60.75 rows=1086 width=0)
         ->  Seq Scan on mesure  (cost=0.00..30.38 rows=543 width=0)
               Filter: (date_trace >= '2008-01-01'::date)
         ->  Seq Scan on mesure_a2008m01 mesure  (cost=0.00..30.38 rows=543 width=0)
               Filter: (date_trace >= '2008-01-01'::date)

L'exclusion de contrainte n'est pilotĂ©e que par les contraintes CHECK, pas par la prĂ©sence d'index. Il n'est donc pas nĂ©cessaire de dĂ©finir des index sur les colonnes clĂ©s. Le fait qu'un index doive ĂȘtre créé pour une partition donnĂ©e dĂ©pend de ce que les requĂȘtes qui parcourent la partition parcourent en gĂ©nĂ©ral une grande partie de la partition ou seulement une petite partie. Un index est utile dans le dernier cas, pas dans le premier.

La valeur par dĂ©faut (et donc recommandĂ©e) de constraint_exclusion n'est ni on ni off, mais un Ă©tat intermĂ©diaire appelĂ© partition, qui fait que la technique est appliquĂ©e seulement aux requĂȘtes qui semblent fonctionner avec des tables partitionnĂ©es. La valeur on fait que le planificateur examine les contraintes CHECK dans chaque requĂȘte, y compris les requĂȘtes simples qui ont peu de chance d'en profiter.

Les restrictions suivantes s'appliquent Ă  l'exclusion de contraintes, qui sont utilisĂ©es Ă  la fois par l'hĂ©ritage et par les tables partitionnĂ©es :

  • L'exclusion de contrainte ne fonctionne que si la clause WHERE de la requĂȘte contient des constantes (ou des paramĂštres externes). Par exemple, une comparaison entre une fonction non immutable telle que CURRENT_TIMESTAMP ne peut pas ĂȘtre optimisĂ©e, car le planificateur ne peut pas savoir dans quelle partition la valeur de la fonction ira lors de l'exĂ©cution.

  • Les contraintes de partitionnement doivent rester simples. Dans le cas contraire, le planificateur peut rencontrer des difficultĂ©s Ă  dĂ©terminer les partitions qu'il n'est pas nĂ©cessaire de parcourir. Des conditions simples d'Ă©galitĂ© pour le partitionnement de liste ou des tests d'intervalle simples lors de partitionnement par intervalles sont recommandĂ©es, comme cela est illustrĂ© dans les exemples prĂ©cĂ©dents. Une bonne rĂšgle consiste Ă  s'assurer que les comparaisons entre colonnes de partitionnement et constantes utilisĂ©es par les contraintes de partitionnement se fassent uniquement Ă  l'aide d'opĂ©rateurs utilisables par les index B-tree, ce qui s'applique mĂȘme aux tables partitionnĂ©es, car seules les colonnes indexables avec un index B-tree sont autorisĂ©es dans la clĂ© de partitionnement. (Ce n'est pas un problĂšme quand on utilise le partitionnement dĂ©claratif, puisque les contraintes gĂ©nĂ©rĂ©es automatiquement sont suffisamment simples pour ĂȘtre comprises par l'optimiseur).

  • Toutes les contraintes de toutes les partitions de la table maĂźtre sont examinĂ©es lors de l'exclusion de contraintes. De ce fait, un grand nombre de partitions augmente considĂ©rablement le temps de planification de la requĂȘte. Un partitionnement qui utilise ces techniques fonctionne assez bien jusqu'environ une centaine de partitions ; il est impensable de vouloir atteindre des milliers de partitions.

5.10.5. Bonnes pratiques pour le partitionnement dĂ©claratif

Le choix de la mĂ©thode de partitionnement d'une table doit ĂȘtre fait avec beaucoup d'attention car les performances de l'optimisation des requĂȘtes et leur exĂ©cution peuvent ĂȘtre fortement affectĂ©es nĂ©gativement par un mauvais design.

Une des dĂ©cisions les plus critiques au niveau du design est le choix de la clĂ© (ou des clĂ©s) de partitionnement. Souvent, le meilleur choix revient Ă  partitionner par la (ou les) colonne(s) qui apparaissent le plus frĂ©quemment dans les clauses WHERE des requĂȘtes en cours d'exĂ©cution sur la table partitionnĂ©e. Les Ă©lĂ©ments de la clause WHERE qui correspondent ou sont compatibles avec la clĂ© de partitionnement peuvent ĂȘtre utilisĂ©s pour ignorer les partitions inutiles. La suppression des donnĂ©es inutiles est aussi un facteur Ă  considĂ©rer lors de la conception de votre stratĂ©gie de partitionnement. Une partition entiĂšre peut ĂȘtre dĂ©tachĂ©e rapidement, donc il peut ĂȘtre bĂ©nĂ©fique de concevoir la stratĂ©gie de partitionnement d'une telle façon que tout les donnĂ©es Ă  supprimer d'un coup soient concentrĂ©es sur une seule partition.

Choisir le nombre cible de partitions pour la table est aussi une dĂ©cision critique Ă  prendre. Ne pas avoir suffisamment de partitions pourrait avoir pour consĂ©quence des index trop gros, et un emplacement des donnĂ©es pauvre qui rĂ©sulterait en un ratio bas de lecture en cache. NĂ©anmoins, diviser la table en trop de partitions pourrait aussi causer des problĂšmes. Trop de partitions pourrait signifier une optimisation plus longue des requĂȘtes et une consommation mĂ©moire plus importante durant l'optimisation et l'exĂ©cution. Lors de la conception du partitionnement de votre table, il est aussi important de prendre compte les changements pouvant survenir dans le futur. Par exemple, si vous choisissez d'avoir une partition par client et que vous avez un petit nombre de gros clients, il est important de rĂ©flĂ©chir aux implications si, dans quelques annĂ©es, vous vous trouvez avec un grand nombre de petits clients. Dans ce cas, il serait mieux de choisir de partitionner par RANGE et de choisir un nombre raisonnable de partitions, chacune contenant un nombre fixe de clients, plutĂŽt que d'essayer de partitionner par LIST en espĂ©rant que le nombre de clients ne dĂ©passe pas ce qui est possible au niveau du partitionnement des donnĂ©es.

Le sous-partitionnement peut aussi ĂȘtre utile pour diviser encore plus les partitions pour lesquelles on s'attend Ă  ce qu'elles deviennent bien plus grosses que les autres partitions. Un sous-partitionnement excessif peut facilement amener Ă  un grand nombre de partitions et peut causer les problĂšmes mentionnĂ©s dans le paragraphe prĂ©cĂ©dent.

Il est aussi important de considérer la surcharge du partitionnement lors de l'optimisation et de l'exécution. L'optimiseur est généralement capble de gérer les hiérarchies de partitions qui montent à quelques centaines de partitions. Les durées d'optimisation deviennent plus longues et la consommation de mémoire devient plus importante au fur et à mesure de l'ajout de partitions. Ceci est tout particuliÚrement vrai pour les commandes UPDATE et DELETE. Une autre raison de se soucier d'un grand nombre de partitions est que la consommation mémoire du serveur pourrait grossir de façon significative sur une période de temps, et tout spécialement si beaucoup de sessions touchent un grand nombre de partitions. Ceci est dû au chargement des métadonnées nécessaires pour chaque partition en mémoire locale.

Avec une charge de type entrepĂŽt de donnĂ©es, il peut ĂȘtre sensĂ© d'utiliser un plus grand nombre de partitions que pour une charge de type OLTP. En gĂ©nĂ©ral, dans les entrepĂŽts de donnĂ©es, le temps d'optimisation d'une requĂȘte est peu importante parce que la majoritĂ© du temps de traitement est passĂ©e sur l'exĂ©cution de la requĂȘte. Avec l'une de ces deux types de charges, il est important de prendre les bonnes dĂ©cisions dĂšs le dĂ©but, car le re-partitionnement de grosses quantitĂ©s de donnĂ©es peut ĂȘtre trĂšs lent. Les simulations de la charge attendue sont souvent bĂ©nĂ©fiques pour optimiser la stratĂ©gie de partitionnement. Ne jamais supposer qu'un plus grand nombre de partitions est toujours mieux qu'un petit nombre de partitions, et vice-versa.