* Avoir une clé primaire sur chaque table à répliquer (peut être contourné dans certaines conditions, mais fortement déconseillé par les dév. de Slony)
* Ne pas utiliser la commande SQL "TRUNCATE" (avant PG 8.4)
### Introduction et principe
Slony (_éléphants_ en russe, en rapport avec le logo de PostgreSQL entre autre) permet de faire de la réplication en mode master-slave. La réplication fonctionne à l'aide de triggers ajoutés au niveau des tables à répliquer. La réplication peut donc se paramétrer individuellement pour chaque table et est asynchrone (lors d'une modification sur le master, le changement est effectué dans un second temps sur le slave). Slony ne rejoue pas la requête entière sur le slave, il se contente de copier les nouvelles valeurs (intéressant dans le cas de requêtes imbriquées).
# compte propriétaire de la base de test qui sera répliquée
createuser -D -R -A dbrepl
# compte SUPERUSER
createuser -s slony -P
# base répliquée de test
createdb -O dbrepl dbrepl
# installation du langage PLPGSQL dans la base de test, nécessaire pour Slony -> nécessaire ?
#createlang plpgsql dbrepl
# Création d'une table de test (avec une primary key !)
echo "create table table1 (id serial primary key, t int);" | psql dbrepl
~~~
### Configuration de Slony
Slonik (_petit éléphant_ en russe) est un langage de script permettant de contrôler Slony (les éléphants donc). Il peut soit être édité manuellement, soit être généré à l'aide de scripts Perl fournit avec le paquet Debian.
#### Première méthode : édition manuelle des scripts Slonik
# Keeping the following three lines for backwards compatibility in
# case this gets incorporated into a 1.0.6 release.
#
# TODO: The scripts should check for an environment variable
# containing the location of a configuration file. That would
# simplify this configuration file and allow Slony-I tools to still work
# in situations where it doesn't exist.
#
if ($ENV{"SLONYNODES"}) {
require $ENV{"SLONYNODES"};
} else {
# The name of the replication cluster. This will be used to
# create a schema named _$CLUSTER_NAME in the database which will
# contain Slony-related data.
$CLUSTER_NAME = 'slony_namespace';
# The directory where Slony should record log messages. This
# directory will need to be writable by the user that invokes
# Slony.
$LOGDIR = '/var/log/slony1';
# SYNC check interval (slon -s option)
# $SYNC_CHECK_INTERVAL = 1000;
# Which node is the default master for all sets?
$MASTERNODE = 1;
# Which debugging level to use? [0-4]
$DEBUGLEVEL = 2;
# Include add_node lines for each node in the cluster. Be sure to
# use host names that will resolve properly on all nodes
# (i.e. only use 'localhost' if all nodes are on the same host).
# Also, note that the user must be a superuser account.
add_node(node => 1,
host => 'test1',
dbname => 'dbrepl',
port => 5432,
user => 'slony',
password => 'slonypass');
add_node(node => 2,
host => 'test2',
dbname => 'dbrepl',
port => 5432,
user => 'slony',
password => 'slonypass');
}
# The $SLONY_SETS variable contains information about all of the sets
# in your cluster.
$SLONY_SETS = {
# A unique name for the set
"set1" => {
# The set_id, also unique
"set_id" => 1,
"table_id" => 1,
"sequence_id" => 1,
# This array contains a list of tables that already have
# primary keys.
"pkeyedtables" => [
'table1',
],
},
};
# Keeping the following three lines for backwards compatibility in
# case this gets incorporated into a 1.0.6 release.
#
# TODO: The scripts should check for an environment variable
# containing the location of a configuration file. That would
# simplify this configuration file and allow Slony tools to still work
# in situations where it doesn't exist.
#
if ($ENV{"SLONYSET"}) {
require $ENV{"SLONYSET"};
}
# Please do not add or change anything below this point.
1;
~~~
Explications: ce fichier de conf permet de décrire la réplication (le cluster et ses machines (nodes), les ensembles de tables (sets) à répliquer, etc…). Les paramètres importants sont:
* _$MASTERNODE_: la machine qui sera le master par défaut lors du démarrage de Slony (on peut ensuite changer facilement le «sens» de réplication);
* _add_node()_: cette fonction permet d'ajouter une base de données dans le cluster, en indiquant ses paramètres de connexion classique;
* _$SLONY_SETS_: ce tableau contient une liste de sets décrivant les tables à répliquer. Pour chaque set, on peut redéfinir quel node est le master avec la clé _origin_. Autrement, c'est $MASTERNODE qui est utilisé. La clé _pkeyedtables_ contient la liste des tables à répliquer.
* On peut maintenant faire appel aux scripts Perl pour mettre en place la réplication. Les scripts Perl ne font que générer le code slonik, il est nécessaire de les piper dans ~~~slonik}} (l'interpréteur) pour qu'il l'exécute. Sur une des deux machines :
Dans le cas où le master est down, et donc que le `slonik_move_set` ne marche pas (il a besoin de se connecter au futur slave pour le passer en slave):
#### Modifier de la structure d'une table répliquée
Slony ne répliquant que les données, en cas de modification de la structure d'une table sur un node, il faut la répercuter manuellement sur tous les autres. Un script Perl existe pour le faire sur tout les nodes à la fois:
_table_id_ et _sequence_id_ indiquent à partir de quel ID Slony peut commencer à numéroter les nouvelles tables/sequences à ajouter. Ils doivents être uniques dans tout le cluster. Pour les déterminer:
* Désactiver un set. Cela va supprimer les trigger positionnés sur toutes les tables du set, sur tous les nodes. Le schéma _replication_ est laissé intact:
* Désactiver un node. Cela va supprimer les triggers de tous les sets du node, et supprimer le schéma _replication_. La commande échoue si des nodes sont encore abonnés au node à supprimer:
insert into "public"."nom_de_la_table" (...) values (...);
" ERROR: duplicate key value violates unique constraint "..."
- qualification was: where log_origin = 1 and ( (
log_tableid in (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295)
and (log_txid < '12782788' and "pg_catalog".txid_visible_in_snapshot(log_txid, '12782168:12782788:12782168,12782782'))
and (log_txid >= '12782762' or log_txid IN (select * from "pg_catalog".txid_snapshot_xip('12782168:12782762:12782168')))
) )
~~~
~~Si on s'est assuré après vérification qu'il n'y a pas besoin de rejouer cette requête, on peut la supprimer manuellement des logs de réplication :~~
* ~~arrêter les démons slon~~
* ~~rechercher l'ID de la table concernée par l'erreur de réplication : `select tab_id from _replication.sl_table where tab_relname='nom_de_la_table';`~~
* ~~sur le master, rechercher la requête dans les logs : `select * from _replication.sl_log_{1,2} where log_tableid=id_table` + rajouter les conditions sur log_txid indiquées par les 2 dernières lignes des logs ci-dessus peut aider~~
* ~~récupérer la valeur du champ log_actionseq, puis `delete from _replication.sl_log_{1,2} where log_actionseq=id_query;`~~
* ~~relancer les démons slon~~
~~Une manière plus radicale de faire, notamment si il y a plusieurs erreurs sur une même table, peut être de désabonner le slave du master, vider la table sur le slave, et réabonner le slave.~~
On peut faire simplement un `TRUNCATE table` sur le slave à chaud, sans arrêter les démons slon. Cela va débloquer rapidement la réplication, ~~et les éventuelles lignes manquantes seront automatiquement recopiées depuis le master.~~ -> pas sûr, à tester !
1. Opération d'écriture sur le nœud maître -> les données modifiées vont être insérées dans la table _sl_log_1_, grace au trigger __replication_logtrigger_ positionné sur la table ayant subie les modifications.
2. Sur le nœud esclave, le démon _slon_ lit à intervalles réguliers la table de log active, ici _sl_log_1_. Il va alors récupérer les nouvelles entrées pour les copier dans sa propre table de log. À noter qu'avant de se connecter à la base PostgreSQL sur le nœud maître, il «pingue» le démon slon sur le master, et si il ne répond pas, ne fait rien. Par conséquent, si un des deux démon _slon_ ne tourne pas, la réplication ne se fait pas.
3. Les nouvelles modifications sont alors rejouer sur le slave.
Par défaut (option _cleanup_deletelogs=false_), les entrées dans les tables de logs ne sont pas supprimées, étant donné qu'un _DELETE_ à chaque fois peut être assez coûteux en terme de ressources pour la base de données. Un basculement de tables est alors opéré (par défaut toutes les 10 minutes, modifiable par l'option _cleanup_interval_), de _sl_log_1_ vers _sl_log_2_ ou inversement, ce qui permet de repartir sur une table vide. Un _TRUNCATE_ est alors exécuté sur l'ancienne table de log pour la nettoyer. Tous les 3 cycles de basculement, un _VACUUM_ est exécuté sur la table de log (définit par la variable _vac_frequency_).
Pour connaître la table de log active :
### Monitoring
Des informations sur la réplication en temps réel sont disponibles dans les tables _sl_*_ du schéma __CLUSTERNAME_ (par défaut __slony_namespace_). Le détail de ces tables est disponible ici : <http://slony.info/documentation/monitoring.html>
Un script fournit avec Slony, _check_slony_cluster.sh_peut être utilisé comme check Nagios pour vérifier que la réplication est ok. Pour qu'il puisse être utilisé en tant que postgres sans demander de mot de passe, il est nécessaire de l'adapter un peu :