Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
400 lines (347 sloc) 15.9 KB
<?xml version="1.0" encoding="UTF-8"?>
<!-- Dernière modification
le $Date$
par $Author$
révision $Revision$ -->
<sect1 id="ddlchanges">
<title>Changements du schéma de la base (DDL)</title>
<indexterm>
<primary>Changements DDL</primary>
<secondary>changements du schéma de la base</secondary>
</indexterm>
<para>
Lorsque des changements sont faits sur la base, <emphasis>tels que</emphasis>
l'ajout de champs à une table, il est nécessaire de le faire prudemment,
pour éviter que certains n&oelig;uds ne soient bloqués lors de la création de
certaines tables.
</para>
<para>
Si vous opérez les changements à travers &slony1; via <xref
linkend="stmtddlscript"/> (slonik) / &funddlscript; (procédure stockée),
cela vous garantie que les changements prendront effet au même niveau dans
les flux de transactions sur tous les n&oelig;uds. Cela n'a pas la même
importance si vous pouvez arrêter votre système pour appliquer vos
modifications de schéma mais si vous voulez faire vos mises à jour alors
que vos transactions continuent leur vie, cela devient nécessaire.
</para>
<para>
Il est essentiel d'utiliser <command>EXECUTE SCRIPT</command> si vous
modifiez des tables pour changer leurs structures. Si vous ne le faites
pas, vous pouvez rencontrer des problèmes identiques à ceux <link
linkend="neededexecddl">décrits ici</link> où les triggers des tables
modifiées ne tiennent pas compte des changements de schéma réalisés. Cela
peut conduire à une corruption des données sur le n&oelig;ud abonné.
</para>
<para>
Il est utile de faire quelques <quote>recommandations</quote> sur <xref
linkend="stmtddlscript"/>&nbsp;:
</para>
<itemizedlist>
<listitem>
<para>
Le script <emphasis>ne doit pas</emphasis> contenir d'instructions
<command>BEGIN</command> ou <command>END</command> car il s'exécute
déjà dans une transaction. Avec la version 8 de &postgres;,
l'introduction des transactions imbriquées change cela quelque peu,
mais vous devez rester attentif au fait que les actions du script sont
exécutées comme une transaction simple dans laquelle on ne maîtrise pas
le <command>BEGIN</command> et le <command>END</command>.
</para>
</listitem>
<listitem>
<para>
S'il y a une erreur <emphasis>quelconque</emphasis> dans le script ou un
problème quelconque sur la manière dont il s'exécute sur un n&oelig;ud
particulier, cela conduira le démon <xref linkend="slon"/> de ce
n&oelig;ud à paniquer et à tomber. Vous pouvez consulter plusieurs types
de messages attendus lors d'un déroulement normal ou anormal à <xref
linkend="ddllogs"/>. Si vous redémarrez le démon &lslon;, il tentera, de
manière très probable, de <emphasis>rejouer</emphasis> le script DDL, ce
qui conduira certainement au même échec qu'à sa première tentative. J'ai
eu un tel scénario, m'obligeant à supprimer du n&oelig;ud
<quote>maître</quote> l'évènement de la table <envar>sl_event</envar>
pour arrêter de le voir tomber en échec.
</para>
<para>
La conséquence est qu'il est <emphasis>vital</emphasis> que les
modifications ne soient pas faites de manière hasardeuse sur un n&oelig;ud
ou un autre. Les schémas doivent toujours rester synchronisés.
</para>
</listitem>
<listitem>
<para>
Pour &lslon; aussi, à ce niveau, <quote>panic</quote> est probablement la
réponse <emphasis>correcte</emphasis> car il permet au DBA de sortir la
base du n&oelig;ud défecteux, de corriger manuellement le problème en
supprimant l'évènement incorrect et de redémarrer &lslon;. Vous pouvez
être certain que les mises à jour faites <emphasis>après</emphasis> le
changement DDL sur le n&oelig;ud maître sont enregistrées dans une file
d'attente, avant d'être traitées par l'abonné. Vous ne courrez ainsi pas
le risque qu'il y ait des mises à jour demandées alors qu'elles dépendaient
des modifications DDL.
</para>
</listitem>
<listitem>
<para>
Lorsque vous exécutez <xref linkend="stmtddlscript"/>, cela conduit
&lslonik; à poser, <emphasis>pour chaque table faisant partie du jeu de
réplication</emphasis>, un verrou exclusif sur la table.
</para>
<para>
Cela commence par la pose du verrou, poursuit avec l'altération de la
table pour supprimer les triggers &slony1;, et se termine avec la
restauration de tous les triggers qui ont été cachés.
<screen>
BEGIN;
LOCK TABLE table_name;
SELECT _oxrsorg.altertablerestore(tab_id);
--tab_id is _slony_schema.sl_table.tab_id
</screen>
</para>
<para>
Après l'exécution du script, chaque table est <quote>restaurée</quote> dans
le but d'ajouter en fin soit le trigger qui collecte les mises à jour sur le
n&oelig;ud origine soit celui qui rejette les mises à jour sur l'abonné.
<screen>
SELECT _oxrsorg.altertableforreplication(tab_id);
--tab_id is _slony_schema.sl_table.tab_id
COMMIT;
</screen>
</para>
<para>
Notez que c'est ce qui permet à &slony1; de prendre connaissance de
l'altération de tables&nbsp;: <emphasis>avant</emphasis> ce
<command>SYNC</command>, &slony1; réplique des lignes suivant le
<emphasis>vieux</emphasis> schéma&nbsp;; <emphasis>après</emphasis>
le script <command>DDL_SCRIPT</command>, les lignes sont répliquées
suivant le <emphasis>nouveau</emphasis> schéma.
</para>
<para>
Sur un système réalisant de nombreuses mises à jour, il peut être difficile
<quote>d'obtenir</quote> tous les verrous nécessaires. Les verrous peuvent
conduire à des situations de blocages. Vous pouvez régler le problème de
deux manières différentes&nbsp;:
</para>
</listitem>
<listitem>
<para>
Vous devez envisager de <link linkend="definesets">définir des ensembles
de réplication</link> représentant un plus petit nombre de tables de
manière à ce que moins de verrous soient posés et permettent au script
DDL d'être joué.
</para>
<para>
Si un script DDL affecte une seule table, il ne devrait pas être
nécessaire de verrouiller <emphasis>toutes</emphasis> les tables de
l'application.
</para>
<note>
<para>
Véritablement, à partir de la version 1.1.5 ou plus, ce n'est
<emphasis>plus vrai</emphasis>. Le danger que quelqu'un opère des
changements DDL interférant avec l'ensemble de réplication est
suffisamment évident pour que &lslon; verrouille
<emphasis>toutes</emphasis> les tables répliquées, qu'elles soient
ou pas dans l'ensemble de réplication.
</para>
</note>
</listitem>
<listitem>
<para>
Vous pouvez avoir besoin de faire un arrêt de votre système pour vous
assurer que vos applications ne demandent pas des verrous qui
rentreraient en conflit avec ceux dont vous auriez besoin pour faire les
changements de schéma de base.
</para>
</listitem>
<listitem>
<para>
Dans les versions 1.0 à 1.1.5 de &slony1;, le script s'exécute comme une
simple requête, ce qui peut poser problème si vous réalisez des
modifications complexes. À partir de la version 1.2, le script est
découpé en traitements SQL individuels, et chaque requête est soumise
séparemment, ce qui est préférable.
</para>
<para>
Le problème avec une requête traitant une <quote>requête imbriquée</quote>
est que l'analyseur SQL réalise son plan d'exécution pour le jeu entier de
requête <emphasis>avant même</emphasis> l'exécution de la requête.
</para>
<para>
Cela ne pose pas de problème particulier si les requêtes sont
indépendantes les unes des autres, telles que deux requêtes d'ajout de
deux colonnes à une table.
</para>
<para>
<command>ALTER TABLE t1 ADD COLUMN c1 integer;
ALTER TABLE t1 ADD COLUMN c2 integer;</command></para>
<para>
Par contre, les problèmes se rencontrent si vous devez référencer une
requête précédente. Considérez la requête DDL suivante...
</para>
<para>
<command>ALTER TABLE t1 ADD COLUMN c1 integer;
CREATE SEQUENCE s1;
UPDATE t1 SET c1=nextval('s1');
ALTER TABLE t1 ALTER COLUMN c1 SET NOT NULL;
ALTER TABLE t1 ADD PRIMARY KEY(c1);</command>
</para>
<para>
Jusqu'à la version 1.2 de &slony1;, cette requête aurait
<emphasis>échouée</emphasis> lors de l'atteinte de la condition
<command>UPDATE</command>, indiquant l'inexistence de la colonne
<envar>c1</envar>. Cela est dû au fait que <emphasis>toutes</emphasis>
ces requêtes sont découpées et analysées avant même l'exécution de
l'une d'entre elles. Ainsi, l'<command>UPDATE</command> est évalué sur
une table avant que la colonne n'ait été ajoutée. Oops.
</para>
<para>
Si vous avez des versions antérieures, vous pouvez contourner cela en
invoquant une requête séparée <xref linkend="stmtddlscript"/> dans un
script distinct, en générant un nouveau script pour chaque requête qui
référence un objet créé dans une requête précédente.
</para>
<para>
Dans la version 1.2 de &slony1;, il y a un mécanisme qui sépare le script
DDL en requêtes individuelles. Chaque requête est soumise par une requête
<function>PQexec()</function> séparée, ce qui ne pose plus de problème.
</para>
</listitem>
</itemizedlist>
<para>
Malheureusement, cela conduit à une vulnérabilité du script DDL, suivant la
manière dont les changements DDL sont écrits. Si vos applications n'ont pas
de schémas SQL suffisamment stables, l'utilisation de &slony1; pour le
mécanisme de réplication peut ne pas être adaptée. Voir la section sur les
<link linkend="locking">questions de verrous</link> pour plus de discussion
sur le sujet.
</para>
<para>
Voici un article sur l'administration des changements de schémas avec
&slony1;&nbsp;: <ulink url="http://www.varlena.com/varlena/GeneralBits/88.php">
Varlena General Bits</ulink>.
</para>
<sect2>
<title>Changements <emphasis>hors</emphasis> utilisation de
<command>EXECUTE SCRIPT</command></title>
<para>
Alors qu'il est <emphasis>vital</emphasis> d'utiliser <command>EXECUTE
SCRIPT</command> pour propager des modifications DDL sur des tables
répliquées, il y a plusieurs autres modifications que vous pouvez vouloir
réaliser d'une autre façon&nbsp;:
</para>
<itemizedlist>
<listitem>
<para>
Il y a plusieurs types d'objets n'ayant pas de trigger que &slony1;
<emphasis>ne</emphasis> peut pas répliquer, tels que les procédures
stockées. Et il est assez probable que cela vous posera problème de
propager ces mises à jour en association avec un ensemble de réplication
où <command>EXECUTE SCRIPT</command> va verrouiller tout un jeu de
tables qui n'ont pas réellement besoin de l'être.
</para>
<para>
Si vous propagez une procédure stockée qui n'est pas utilisée tout le
temps (de telle sorte que vous acceptiez une petite désynchronisation
entre les n&oelig;uds), alors vous pouvez simplement la soumettre à
chaque n&oelig;ud par l'intermédiare d'une commande
<application>psql</application>, ne faisant pas d'utilisation
particulière de &slony1;.
</para>
<para>
Si vous <emphasis>avez</emphasis> besoin d'une parfaite synchronisation
sur l'ensemble des n&oelig;uds, vous devez utiliser <command>EXECUTE
SCRIPT</command> pour verrouiller vos travaux.
</para>
</listitem>
<listitem>
<para>
Vous pouvez avoir besoin d'index supplémentaires sur quelques
n&oelig;uds répliqués pour accroître les performances.
</para>
<para>
Par exemple, une table consistant en des transactions peut seulement
avoir besoin des index relatifs à l'intégrité référentielle sur le
n&oelig;ud <quote>origine</quote>. Maximiser les performances dicte
l'impératif de n'avoir que les index nécessaires. Mais rien ne vous
empêche d'ajouter des index supplémentaires pour améliorer les
performances des rapports qui s'exécutent via les n&oelig;uds répliqués.
</para>
<para>
Il serait imprudent d'ajouter des indicateurs qui
<emphasis>contraindraient</emphasis> les évènements sur les n&oelig;uds
répliqués. En cas de problème, cela conduirait la réplication à s'arrêter
car le ou les n&oelig;uds abonnés ne pourraient appliquer les
changements provenant du n&oelig;ud origine violant ainsi la contrainte.
</para>
<para>
Cela ne pose pas de difficultés d'ajouter quelques mesures
d'améliorations de performances. Il ne faut pratiquement
<emphasis>jamais</emphasis> utiliser <command>EXECUTE SCRIPT</command>
dans ce cadre&nbsp;; cela conduit certains jeux de réplication à
verrouiller/déverrouiller des tables et potentiellement échouer à
l'application de l'événement à cause de verrous en cours sur les objets,
puis devoir ré-essayer un certain nombre de fois avant de pouvoir
effectuer le changement. À la place, si vous appliquez l'index
<quote>directement</quote> au travers de <application>psql</application>,
vous pouvez déterminer le moment auquel le verrou s'exerce sur la table.
Ajouter un index à une table nécessite un verrou exlusif pour la durée
de création de cet index&nbsp;: cela va implicitement stopper la
réplication pendant la durée de construction de l'index, mais sans causer
de problemes particuliers. Si vous ajoutez un index sur une table et que
sa construction dure 20 minutes, la réplication sera stoppée pendant 20
minutes, mais repartira juste après la fin de la création.
</para>
</listitem>
<listitem>
<para>
&slony1; stocke le nom de l'<quote>index primaire</quote> dans <xref
linkend="table.sl-table"/>, et utilise ce nom pour contrôler les colonnes
considérées comme les <quote>colonnes clés</quote> lorsque le declencheur
de table est présent. Il pourrait être envisageable de supprimer cet
index et de le remplacer par une autre clé primaire potentielle, mais
un changement de nom de cette clé primaire casserait certainement
certaines choses.
</para>
</listitem>
</itemizedlist>
</sect2>
<sect2>
<title>Tester les changements DDL</title>
<indexterm><primary>tester les changements DDL</primary></indexterm>
<para>
Une méthode de test des changements DDL a été validée comme <quote>bonne
pratique</quote>.
</para>
<para>
Vous <emphasis>devez</emphasis> tester les scripts DDL de façon non
destructrice.
</para>
<para>
Si les n&oelig;uds sont, pour une raison ou une autre, totalement
désynchonisés, la replication va très certainement échouer, et cela a lieu
la plupart du temps au plus mauvais moment&nbsp;: celui où vous vouliez que
cela <emphasis>fonctionne</emphasis>.
</para>
<para>
Vous pouvez vérifier si vos scripts DDL fonctionnent bien ou pas en les
exécutant manuellement, en ajoutant <command>BEGIN;</command> au début et
<command>ROLLBACK;</command> à la fin. De cette façon, les changements
effectuent un retour arrière.
</para>
<para>
Si le script s'effectue correctement sur tous les n&oelig;uds, cela semble
dire qu'il devrait s'exécuter également correctement via &lslonik;. Si des
problèmes apparaissent sur certains n&oelig;uds, cela devrait vous permettre
de remettre ces machines à niveau, de façon à ce que le script
<emphasis>s'exécute</emphasis> sans erreur.
<warning>
<para>
Attention, si le script contient un <command>COMMIT;</command> n'importe
où avant le <command>ROLLBACK;</command>, cela va effectuer des
changements auxquels vous ne vous attendiez pas.
</para>
</warning>
</para>
</sect2>
</sect1>