Réplication temps réel avec PostgreSQL 9.0.3 et Slony 2.0.6
Par zokar le samedi, avril 30 2011, 21:21 - Sysadmin - Lien permanent
Dans ce billet, un tutoriel pour installer un PostgreSQL 9 avec l'utilitaire de réplication Slony afin de répliquer a chaud les données.
Un schéma de réplication horizontale et verticale sera d'ailleurs expliqué, avec, dans un futur proche, un exemple de bascule de machines maîtres !
Dans ce tutoriel :
- Installation de Postgres9
- Création d'une base de données que l'on répliquera
- Création des utilisateurs
- Installation de Slony
- Création et souscription de la réplication
- Exemple de reprise après crash
Schéma de réplication
Nous envisageons donc une réplication horizontale + verticale (aussi appelée "Cascaded Master") permet de synchroniser 2 masters ensemble, eux-même répliqués sur deux esclaves. Voici un exemple :
Database Name: madb01 1: master-db01.cp2i.com (master) | -> 3: master-db02.cp2i.com (cascaded master) | | | -> 4: slave-db02.cp2i.com (cascaded slave) | -> 2: slave-db01.cp2i.com (slave)
Nous pourrons donc envisager d'avoir le master-db01 dans le DatacenterA, accompagné du slave-db01, puis, dans un DatacenterB, le master-db02 et le slave-db02. Ainsi, si un soucis intervient sur le DatacenterA (maintenance programmée, surcharge prévue, soucis machine, etc.), il sera facile de basculer d'un Datacenter vers l'autre.
D'autre part, et contrairement aux autres outils de réplication, Slony permet une réplication active Master-Master ne bloquant pas l'accès en lecture au second master. Il est cependant impossible d'envisager de l'écriture simultanée sur les deux maîtres en même temps, mais il est tout à fait possible d'envisager une lecture simultanée sur les deux sites.
Ainsi, vous pourrez "load-balancer" la charge de lecture sur les deux sites. Les délais de réplication étant très rapides (entre 2 et 8 secondes entre le maître et l'esclave cascadé), rares seront les applications qui nécessiteront une disponibilité instantanée qui ne puisse être satisfaite par ce système.
Données pré-installation
Nos machines sont intallées avec le strict minimum. Voici leurs DNS/IP
- Machine 1 : master-db01.cp2i.com /192.168.0.2
- Machine 2 : slave-db01.cp2i.com / 192.168.0.3
- Machine 3 : master-db02.cp2i.com / 192.168.0.4
- Machine 4 : slave-db02.cp2i.com / 192.168.0.5
Installation de PostgreSQL 9 (tous les serveurs)
Tout commence comme toujours par la même commande :
root@> aptitude update root@> aptitude install postgresql-9.0-slony1
Se connecter à PostgreSQL pour créer les administrateurs, tablespaces et autres "objets" nécessaires au tutoriel
root@> su - postgres postgres@> psql psql #= CREATE USER psql_admin SUPERUSER PASSWORD 'psql4dm1n'; psql #= CREATE USER slon_admin SUPERUSER PASSWORD 'sl0n4dm1n'; psql #= CREATE TABLESPACE psql_data LOCATION '/DATA/postgres'; psql #= CREATE TABLESPACE psql_slon OWNER slon_admin LOCATION '/DATA/slony'; psql #= CREATE DATABASE madb01 WITH TEMPLATE template0 ENCODING 'UTF8' TABLESPACE psql_data; psql #= GRANT ALL PRIVILEGES ON DATABASE madb01 to psql_admin; psql #= CREATE LANGUAGE plpgsql;
A ce moment-là, vous avez :
- 2 espaces de stockage pour vos données PG et Slony (tablespace)
- 1 base de données
- 2 utilisateurs (un pour la DB, un pour slony)
Continuons avec Slony
Installation de Slony (tous les serveurs)
Inévitablement, on lancera :
root@> aptitude update root@> aptitude install slony1-bin
Configuration
Nous allons maintenant nous attarder sur la partie configuration des deux logiciels installés
Posgtres
La première modification sera à faire dans le fichier /etc/postgresql/9.0/main/pg_hba.conf. Rajoutez en fin de fichier les lignes :
host madb01 psql_admin 192.168.0.0/28 md5 host madb01 slon_admin 192.168.0.0/28 md5
Ensuite, pour le fichier /etc/postgresql/9.0/main/postgresql.conf, décommentez la ligne:
listen_addresses = '*'
Puis ajustez les paramètres suivants en fonction de votre serveur (pour ma part, 32GB de RAM) :
max_connections = 400 shared_buffers = 3000MB maintenance_work_mem = 1000MB
Slony
Les fichiers Slony se décomposent en plusieurs fichiers :
- dans /etc/slony1/
- dans /etc/slony1/XXXX/
Nous verrons en détail tous ces fichiers
Conseils
Afin d'être totalement indépendant de la configuration initiale de slony dans dans un soucis de scalabilité des services, je vous conseille de générer un jeu complet de fichier de configuration pour chaque ligne de réplication (jeu de tables ou bases) afin de pouvoir relancer indépendamment chaque ligne sans impacter une autre.
Ainsi nous aurons 3 fichiers par ligne de réplication : madb01 dans notre exemple
Dans /etc/slony1
Sur tous les serveurs, créez le fichier /etc/slony1/slon_tools_replication01_madb01.conf avec comme contenu :
if ($ENV{"SLONYNODES"}) { require $ENV{"SLONYNODES"}; } else { $CLUSTER_NAME = 'slony_replication01_madb01'; # 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 = 4000; # Which node is the default master for all sets? $MASTERNODE = 1001; # 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 => 1001, host => 'master-db01.cp2i.com', dbname => 'madb01', port => 5432, user => 'slon_admin', password => 'sl0n4dm1n' ); add_node( node => 1002, host => 'slave-db01.cp2i.com', dbname => 'madb01', port => 5432, user => 'slon_admin', password => 'sl0n4dm1n' ); add_node(node => 1003, host => 'master-db02.cp2i.com', dbname => 'madb01', port => 5432, user => 'slon_admin', password => 'sl0n4dm1n' ); add_node(node => 1004, host => 'slave-db02.cp2i.com', dbname => 'madb01', port => 5432, user => 'slon_admin', password => 'sl0n4dm1n' ); # If the node should only receive event notifications from a # single node (e.g. if it can't access the other nodes), you can # specify a single parent. The downside to this approach is that # if the parent goes down, your node becomes stranded. } # The $SLONY_SETS variable contains information about all of the sets # in your cluster. $SLONY_SETS = { "set1" => { "set_id" => 1, "pkeyedtables" => ["matable_0","matable_1","matable_2","matable_3"], "table_id" => 1, }, "set2" => { "set_id" => 2, "pkeyedtables" => ["matable_4","matable_5","matable_6","matable_7"], "table_id" => 5, }, "set3" => { "set_id" => 3, "pkeyedtables" => ["matable_8","matable_9","matable_10","matable_11"], "table_id" => 9, } }; # 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;
Dans ce fichier, nous voyons donc que nous avons 4 noeuds de réplication, qui synchronisent la base de données madb01 et qu'elle contient 12 tables nommées matable_XX, et que la réplication se lance toutes les 4 secondes (SYNC_CHECK_INTERVAL).
Nous avons choisi de créer 3 sets de 4 tables. Mais rien de nous empêche de créer 2 sets de 6 tables, ou bien encore 12 sets de 1 table.
Veillez cependant à essayer d'équilibrer la taille des sets (i.e : ne pas avoir les 3 plus grosses tables dans le même set).
Ensuite, il faut, sur chaque machine, créer un sous-dossier dans /etc/slony1/ contenant l'id du noeud avec un fichier de conf à l'intérieur.
Dans /etc/slony1/XXXX/
Sur la machine 1 (master-db01.cp2i.com) créez le répertoire 1001 dans /etc/slony1/
Editez ensuite le fichier /etc/slony1/1001/slon_replication01_madb01.conf avec le contenu suivant :
# Sets how many cleanup cycles to run before a vacuum is done. # Range: [0,100], default: 3 #vac_frequency=3 # Debug log level (higher value ==> more output). Range: [0,4], default 4 log_level=1 # Check for updates at least this often in milliseconds. # Range: [10-60000], default 100 # sync_interval=100 sync_interval=4000 # Maximum amount of time in milliseconds before issuing a SYNC event, # This prevents a possible race condition in which the action sequence # is bumped by the trigger while inserting the log row, which makes # this bump is immediately visible to the sync thread, but # the resulting log rows are not visible yet. If the sync is picked # up by the subscriber, processed and finished before the transaction # commits, this transaction's changes will not be replicated until the # next SYNC. But if all application activity suddenly stops, # there will be no more sequence bumps, so the high frequent -s check # won't detect that. Thus, the need for sync_interval_timeout. # Range: [0-120000], default 1000 #sync_interval_timeout=1000 sync_interval_timeout=5000 # Maximum number of SYNC events to group together when/if a subscriber # falls behind. SYNCs are batched only if there are that many available # and if they are contiguous. Every other event type in between leads to # a smaller batch. And if there is only one SYNC available, even -g60 # will apply just that one. As soon as a subscriber catches up, it will # apply every single SYNC by itself. # Range: [0,100], default: 6 #sync_group_maxsize=6 sync_group_maxsize=80 # Size above which an sl_log_? row's log_cmddata is considered large. # Up to 500 rows of this size are allowed in memory at once. Rows larger # than that count into the sync_max_largemem space allocated and free'd # on demand. # Range: [1024,32768], default: 8192 #sync_max_rowsize=8192 # Maximum amount of memory allowed for large rows. Note that the algorithm # will stop fetching rows AFTER this amount is exceeded, not BEFORE. This # is done to ensure that a single row exceeding this limit alone does not # stall replication. # Range: [1048576,1073741824], default: 5242880 #sync_max_largemem=5242880 # If this parameter is 1, messages go both to syslog and the standard # output. A value of 2 sends output only to syslog (some messages will # still go to the standard output/error). The default is 0, which means # syslog is off. # Range: [0-2], default: 0 syslog=1 # If true, include the process ID on each log line. Default is false. log_pid=true # If true, include the timestamp on each log line. Default is true. #log_timestamp=true # A strftime()-conformant format string for use with log timestamps. # Default is '%Y-%m-%d %H:%M:%S %Z' #log_timestamp_format='%Y-%m-%d %H:%M:%S %Z' # Where to write the pid file. Default: no pid file pid_file='/var/run/slony1' # Sets the syslog "facility" to be used when syslog enabled. Valid # values are LOCAL0, LOCAL1, LOCAL2, LOCAL3, LOCAL4, LOCAL5, LOCAL6, LOCAL7. syslog_facility=LOCAL4 # Sets the program name used to identify slon messages in syslog. syslog_ident=slon # Set the cluster name that this instance of slon is running against # default is to read it off the command line cluster_name='_slony_replication01_madb01' # Set slon's connection info, default is to read it off the command line conn_info='host=master-db01.cp2i.com port=5432 dbname=madb01 user=slon_admin password=sl0n4dm1n' # maximum time planned for grouped SYNCs # If replication is behind, slon will try to increase numbers of # syncs done targetting that they should take this quantity of # time to process. in ms # Range [10000,600000], default 60000. #desired_sync_time=60000 # Execute the following SQL on each node at slon connect time # useful to set logging levels, or to tune the planner/memory # settings. You can specify multiple statements by separating # them with a ; #sql_on_connection="SET log_min_duration_statement TO '1000';" # Command to run upon committing a log archive. # This command is passed one parameter, namely the full pathname of # the archive file #command_on_logarchive="/usr/local/bin/movearchivetoarchive" # A PostgreSQL value compatible with ::interval which indicates how # far behind this node should lag its providers. # lag_interval="8 minutes" # Directory in which to stow sync archive files # archive_dir="/tmp/somewhere"
Sur les autres machines, créez le répertoire dont le nom est le n° de node (1002, 1003, 1004) en éditant le fichier ci-dessus et en modifiant la variable conn_info pour l'adapter à la machine courante.
Préparation des binaires de lancement des lignes de réplication
Nous allons créer un script de lancement de notre ligne de réplication, et vous ferez de même pour toutes les futures lignes de réplication que vous créerez
Ici, nous allons créer /etc/init.d/slony1_replication01_madb01 :
#!/bin/sh ### BEGIN INIT INFO # Provides: slony1 # Required-Start: $local_fs $remote_fs $network # Required-Stop: $local_fs $remote_fs $network # Should-Start: $time postgresql # Should-Stop: $time postgresql # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: start Slony-I daemon # Description: Slony-I is a replication system for PostgreSQL. ### END INIT INFO PATH=/sbin:/bin:/usr/sbin:/usr/bin DAEMON=/usr/bin/slon test -x $DAEMON || exit 5 if [ -r /etc/default/slony1_replication01_madb01 ]; then . /etc/default/slony1_replication01_madb01 fi . /lib/lsb/init-functions instances() { ls /etc/slony1/*/slon_replication01_madb01.conf 2>/dev/null | sed -n 's,^/etc/slony1/\(.*\)/slon_replication01_madb01.conf$,\1,p' echo $SLON_TOOLS_START_NODES } conffile() { echo "/etc/slony1/$1/slon_replication01_madb01.conf" } logfile() { dbname=`egrep -A20 "node *=> $1" /etc/slony1/slon_tools_replication01_madb01.conf|grep -m1 dbname|awk -F"'" '{print $2}'` && echo "/var/log/slony1/node$1-$dbname.log" } pidfile() { echo "/var/run/slony1/node$1.pid" } prepare_start() { mkdir -p /var/run/slony1 \ && chown postgres:postgres /var/run/slony1/ \ && chmod 2775 /var/run/slony1/ } d_start() { if [ -e $(conffile $1) ]; then (egrep -q '^\s*cluster_name' $(conffile $1) && egrep -q '^\s*conn_info' $(conffile $1)) || (log_failure_msg "cluster_name or conn_info missed in "$(conffile $1); return 1) su -c ". /lib/lsb/init-functions ; start_daemon $DAEMON -f $(conffile $1) -p $(pidfile $1) >>$(logfile $1) 2>&1 </dev/null &" - postgres else is_running $x || su -c "slon_start --nowatchdog $x >/dev/null" - postgres fi } d_stop() { killproc -p $(pidfile $1) $DAEMON } is_running() { pidofproc -p $(pidfile $1) $DAEMON >/dev/null } case $1 in start) status=0 log_daemon_msg "Starting Slony-I daemon" prepare_start for x in $(instances); do log_progress_msg $x d_start $x status=$(($status || $?)) done log_end_msg $status ;; stop) status=0 log_daemon_msg "Stopping Slony-I daemon" for x in $(instances); do log_progress_msg $x d_stop $x status=$(($status || $?)) done log_end_msg $status ;; status) status=0 for x in $(instances); do is_running $x instancestatus=$? if [ $instancestatus -eq 0 ]; then log_success_msg "Slony-I daemon $x is running." else log_failure_msg "Slony-I daemon $x is not running." fi status=$(($status || $instancestatus)) done exit $((3 * $status)) ;; restart|force-reload) status=0 log_daemon_msg "Restarting Slony-I daemon" for x in $(instances); do log_progress_msg $x d_stop $x && sleep 1 && d_start $x status=$(($status || $?)) done log_end_msg $status ;; try-restart) if $0 status >/dev/null; then $0 restart else exit 0 fi ;; reload) exit 3 ;; *) log_failure_msg "Usage: $0 {start|stop|status|restart|try-restart|reload|force-reload}" exit 2 ;; esac
Rendez-le exécutable, puis éditez le fichier /etc/init.d/slony pour le rendre comme suit :
#!/bin/bash . /lib/lsb/init-functions confirm_action() { echo "You're about to $1 all slony processes on the current server..." echo "Confirm ? [y/N]" read confirmation case $confirmation in y|Y|yes|YES|Yes|o|O|oui|Oui|OUI) return 0 ;; *) return 1 ;; esac } case $1 in start|stop|status|restart|try-restart|reload|force-reload) /etc/init.d/slony1_replication01_madb01 $1 ;; *) log_success_msg "Usage: /etc/init.d/slony {start|stop|status|restart|try-restart|reload|force-reload}" ;; esac
Ainsi, pour chaque ligne de réplication créée, vous ajouterez l'exécutable servant à démarrer votre ligne de réplication à la suite de /etc/init.d/slony1_replication01_madb01 $1.
Création / Souscription de la réplication
Sur le master du site maître (master-db01.cp2i.com)
Création du cluster :
root@> su - postgres postgres@> export PGOPTIONS='-c default_tablespace=psql_slon' postgres@> slonik_init_cluster --config /etc/slony1/slon_tools_replication01_madb01.conf |slonik
Sur toutes les machines
Lancement de la réplication Slony
root@> /etc/init.d/slony1_replication01_madb01 start
Sur le master du site maître (master-db01.cp2i.com)
Création des sets:
root@> su - postgres postgres@> export PGOPTIONS='-c default_tablespace=psql_slon' postgres@> for set in `seq 1 3`; do slonik_create_set --config /etc/slony1/slon_tools_replication01_madb01.conf $set |slonik; done
Sur le slave du site maître (slave-db01.cp2i.com)
Souscription aux sets du site maître
root@> su - postgres postgres@> export PGOPTIONS='-c default_tablespace=psql_slon' postgres@> for set in `seq 1 3`; do slonik_subscribe_set --config /etc/slony1/slon_tools_replication01_madb01.conf $set 1002 |slonik; done
Sur le master cascadé (master-db02.cp2i.com)
Souscription aux sets du site maître :
root@> su - postgres postgres@> export PGOPTIONS='-c default_tablespace=psql_slon' postgres@> for set in `seq 1 3`; do slonik_subscribe_set --config /etc/slony1/slon_tools_replication01_madb01.conf $set 1003 |slonik; done
Sur le slave cascadé (slave-db02.cp2i.com)
ATTENTION : avant de souscrire le slave cascadé, il faut s'assurer que le master cascadé ait fini de se répliquer avec le site maître.
Pour cela faites un :
root@> tail -f /var/log/slony1/node1003-madb01.log
Vérifier qu'il n'indique pas qu'il est en train de copier une table
Une fois fait, souscrire au maître cascadé :
root@> su - postgres postgres@> export PGOPTIONS='-c default_tablespace=psql_slon' postgres@> for set in `seq 1 3`; do slonik_subscribe_set --config /etc/slony1/slon_tools_replication01_madb01.conf $set 1003 | sed -e 's/provider = 1001/provider = 1003/' |slonik; done
Vous aurez noté que nous changeons le provider "à la volée" de 1001 (master du site maître) à 1003 (master du site cascadé).
Exemple de reprise après crash
Dans 90% des cas, après un arrêt de la réplication Slony, un simple redémarrage de la ligne de la réplication en question suffit :
root@> /etc/init.d/slony1_replication01_madb01 restart
Mais parfois, notamment quand la réplication s'est arrêtée trop longtemps, une reprise est impossible. Pour résoudre ce soucis, il faut casser la réplication et la créer à nouveau de zéro. Attention cependant : si la machine en défaut est le master cascadé (master-db02.cp2i.com dans notre exemple), il faudra procéder également à la reprise sur le slave cascadé.
Nous prendrons dans notre exemple le cas du slave cascadé qui est en erreur (slave-db02.cp2i.com)
Pour cela, il vous faudra commencer par stopper la ligne de réplication :
root@> /etc/init.d/slony1_replication01_madb01 stop
On informe l'ensemble de la ligne de réplication (ie les autres serveurs) que la machine slave-db02.cp2i.com ne fait plus partie de la boucle, et ce afin d'éviter de recevoir des erreurs "Could not send event to node 1004".
root@> su - postgres postgres@~> export PGOPTIONS='-c default_tablespace=psql_slon' postgres@~> slonik_drop_node --config /etc/slony1/slon_tools_replication01_madb01.conf 1004 1003|slonik
Nous demandons donc de retirer le nœud 1004 qui a pour fournisseur de données le nœud 1003.
Supprimer le schéma de réplication Slony en cascade, ce qui aura également pour conséquence de supprimer les bases de données répliquées:
root@> su - postgres postgres@> psql madb01 madb01 =# DROP SCHEMA _slony_replication01_madb01 CASCADE; madb01 =# \q
On recrée le noeud de réplication sur la machine :
postgres@> slonik_store_node --config /etc/slony1/slon_tools_replication01_madb01.conf 1004 |slonik
On redémarre la ligne de réplication sur la machine
postgres@> exit root@> /etc/init.d/slony1_replication01_madb01 start
On ressouscrit aux sets de la ligne de réplication (comme lors de la création) :
su - postgres postgres@~> export PGOPTIONS='-c default_tablespace=psql_slon' postgres@~> for set in `seq 1 3`; do slonik_subscribe_set --config /etc/slony1/slon_tools_replication01_madb01.conf $set 1003 | sed -e 's/provider = 1001/provider = 1003/' |slonik; done
Et voilà ! Dans les logs, vous vous apercevrez que la resynchronisation des données prendra plus de temps qu'à la création, surtout si vous avez injecté beaucoup de données.
Prenez donc garde à ne pas faire cela n'importe quand, surtout s'il s'agit du master cascadé, et principalement dans le cas où les deux masters soient dans des datacenters différents. La reprise s'effectue par la copie intégrale des données, et passera donc pas la liaison Internet / VPN que vous autre mis en place.
The End.. ou pas
Voilà, à l'heure où vous lisez ces lignes, votre base de données et ses 12 pauvres tables se répliquent
A vous d'améliorer, customiser et adapter la configuration !
Amusez-vous bien !