wiki/HowtoMySQL/Replication.md

310 lines
13 KiB
Markdown
Raw Normal View History

2016-11-02 12:12:48 +01:00
---
2017-01-07 16:25:45 +01:00
title: Howto réplication MySQL
2016-11-02 12:18:19 +01:00
...
2016-11-02 12:12:48 +01:00
2017-01-07 16:25:45 +01:00
Pour le guide d'installation et d'usage courant, voir [HowtoMySQL](/HowtoMySQL).
2016-11-02 12:12:48 +01:00
2017-01-07 16:41:16 +01:00
## Préparation d'une réplication MASTER/SLAVE
2016-11-02 12:12:48 +01:00
2017-01-07 16:18:16 +01:00
Il faut :
2016-11-02 12:12:48 +01:00
2017-01-07 16:18:16 +01:00
- soit disposer de deux serveurs MySQL inactifs avec un _datadir_ identique,
- soit réaliser un `mysqldump --master-data` sur le serveur où se trouve les données à conserver :
2016-11-02 12:12:48 +01:00
~~~
# mysqldump --master-data --all-databases > mysql.dump
~~~
2017-01-07 16:53:55 +01:00
`--master-data` ajoute un `CHANGE MASTER TO` dans le dump contenant les informations nécessaires à la réplication (nom de fichier et position).
/!\\ **Cette option implique `--lock-all-tables` qui bloque toutes les tables pendant le dump !**
2016-11-02 12:12:48 +01:00
Il faut également :
2016-11-15 10:49:41 +01:00
* autoriser les connexions MySQL distantes ;
2017-01-07 16:18:16 +01:00
* activer les logs binaires sur chaque serveur : _log_bin mixed_ ;
2017-01-07 16:25:45 +01:00
* positionner un _server-id_ différent sur chaque serveur (ne pas utiliser 0) ;
* créer un utilisateur dédié pour la réplication : `GRANT REPLICATION SLAVE ON *.* TO repl@'%' IDENTIFIED BY 'PASSWORD';`.
2016-11-02 12:12:48 +01:00
2017-01-07 16:41:16 +01:00
## Activation d'une réplication MASTER/SLAVE
2016-11-02 12:12:48 +01:00
2017-01-07 16:53:55 +01:00
Il faut récupérer les informations *MASTER_LOG_FILE* et *MASTER_LOG_POS* :
2017-01-07 16:18:16 +01:00
- soit sur l'un des deux serveurs inactifs avec `SHOW MASTER STATUS` (dans le cas de deux serveurs avec _datadir_ identique),
- soit récupérer l'information dans le `mysqldump --master-data` (avec la commande `head` par exemple).
Sur le serveur SLAVE, exécuter :
2016-11-02 12:12:48 +01:00
~~~
2016-12-16 02:03:44 +01:00
mysql> CHANGE MASTER TO
2016-11-02 12:12:48 +01:00
MASTER_HOST='$MASTER_IP',
MASTER_USER='repl',
MASTER_PASSWORD='XXX',
MASTER_LOG_FILE='mysql-bin.NNNNNN',
MASTER_LOG_POS=NNN;
~~~
2017-01-07 16:53:55 +01:00
/!\\ **Bien que non obligatoire, on recommande de toujours indiquer les directives *MASTER_LOG_FILE* et *MASTER_LOG_POS* pour éviter des problèmes**
2016-11-02 12:12:48 +01:00
2016-12-16 02:03:44 +01:00
Pour exclure une base de la réplication :
2016-11-02 12:12:48 +01:00
2016-12-16 02:03:44 +01:00
~~~{.ini}
[mysqld]
2016-11-02 12:12:48 +01:00
binlog-ignore-db = mysql
~~~
Puis démarrer la réplication sur le serveur B avec la commande : `START SLAVE`.
Enfin, exécuter `SHOW SLAVE STATUS` pour vérifier le bon fonctionnement.
2017-01-07 16:41:16 +01:00
## Désactivation
2016-11-02 12:12:48 +01:00
Pour supprimer toute trace de réplication (sauf si des infos sont en dur dans la configuration) :
~~~
mysql> RESET SLAVE;
Query OK, 0 rows affected (0.00 sec)
~~~
Pour éviter que la réplication démarre automatiquement au démarrage, on ajoutera dans la configuration :
2016-12-16 02:03:44 +01:00
~~~{.ini}
[mysqld]
2016-11-02 12:12:48 +01:00
skip-slave-start
~~~
2017-01-07 16:41:16 +01:00
## Trucs et astuces pour la réplication MySQL
2016-11-02 12:12:48 +01:00
**Astuce 1** : Une astuce parfois très utile est la possibilité d'exécuter des requêtes qui ne seront pas prises en compte
par le binlog (et donc non répliquée !). Cela nécessite le droit SUPER :
~~~
mysql> SET sql_log_bin = 0;
~~~
**Astuce 2** : Pour divers raisons (notamment la réplication de données déjà répliquées !), on devra activer l'option
2016-12-16 02:03:44 +01:00
suivante :
2016-11-02 12:12:48 +01:00
2016-12-16 02:03:44 +01:00
~~~{.ini}
[mysqld]
2016-11-02 12:12:48 +01:00
log-slave-updates
~~~
**Astuce 3** : Sauter une requête déjà présente dans les binlog sur le slave (à tester) :
2017-01-02 14:46:47 +01:00
<https://stackoverflow.com/questions/17701524/mysql-replication-skip-statement-is-it-possible>
2016-11-02 12:12:48 +01:00
2017-01-07 16:41:16 +01:00
## Réplication MASTER/MASTER
2016-11-02 12:12:48 +01:00
2017-01-07 16:25:45 +01:00
Pour une réplication MASTER/MASTER, il faut simplement activer deux réplications MASTER/SLAVE entre les deux serveurs concernés.
2016-11-02 12:12:48 +01:00
2017-01-07 16:25:45 +01:00
On conseille également de :
2016-11-02 12:12:48 +01:00
2017-01-07 16:25:45 +01:00
- positionner la directive `auto-increment-increment = 10` sur chaque serveur
- positionner la directive `auto-increment-offset` avec une valeur numérique différente sur chaque serveur
2016-11-02 12:12:48 +01:00
2017-01-07 16:25:45 +01:00
Exemple : `auto-increment-offset 2` sur l'un des deux serveurs
2016-11-02 12:12:48 +01:00
2017-01-07 16:25:45 +01:00
2017-01-07 16:41:16 +01:00
## Résolution des erreurs lors de la réplication
2016-11-02 12:12:48 +01:00
On vérifie les erreurs avec les commandes `SHOW SLAVE STATUS` et `SHOW MASTER STATUS`.
2017-01-07 16:18:16 +01:00
En cas d'erreur, il faut « simplement » résoudre l'erreur, puis relancer la réplication avec la commande `START SLAVE`. Voici quelques erreurs possibles :
2016-11-02 12:12:48 +01:00
**Zapper l'erreur en cours**
2016-11-02 12:12:48 +01:00
2017-01-07 16:18:16 +01:00
On peut faire manuellement :
~~~
2016-12-16 02:03:44 +01:00
mysql> SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1; START SLAVE;
~~~
**Fichier de clé incorrect**
~~~
Incorrect key file for table './base/table.MYI'; try to repair it
~~~
2016-12-16 02:03:44 +01:00
Il faut réparer la table concernée.
**Doublon sur clé unique**
~~~
Duplicate entry 'NNNNNN' for key N
~~~
2017-01-07 16:19:33 +01:00
Une solution *peut* être de supprimer la ligne concernée (ou de zapper l'erreur).
2017-01-02 14:46:47 +01:00
**Beaucoup d'erreurs à ignorer**
2017-01-07 16:53:55 +01:00
Si pour une raison ou un autre, on a plein de **DUPLICATE ENTRY** mais que l'est *sûr* de vouloir les ignorer, on peut faire cela en redémarrant MySQL avec le paramètre : `slave-skip-errors = 1062` ; on peut faire également cela avec d'autres types d'erreurs. Malheureusement, il faut forcément redémarrer MySQL car cette commande ne se fait pas à chaud : <http://bugs.mysql.com/bug.php?id=35611>
2017-01-02 14:46:47 +01:00
2017-01-07 16:53:55 +01:00
On peut également avoir d'autres erreurs, par exemple _Could not execute Delete_rows event on table foo.bar; Can't find record in 'bar', Error_code: 1032; handler error HA_ERR_KEY_NOT_FOUND; the event's master log [...]_ et on mettre cette fois `slave-skip-errors = 1032`
2017-01-02 14:46:47 +01:00
Si plusieurs types d'erreur à ignorer : `slave-skip-errors = 1032,1062`
2017-01-07 16:53:55 +01:00
L'inconvénient est qu'il faut redémarrer MySQL. Pour éviter cela on peut automatiser le zap de l'erreur (exemple avec l'erreur _1062_) en cours :
2017-01-07 16:18:16 +01:00
~~~
# while true; do while mysql -e "show slave status" | grep '1062.Error.*REPORT'; \
do mysql -e "SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1; START SLAVE;"; done ; sleep 1; done
~~~
2017-01-07 16:18:16 +01:00
On peut également utilisé un script Shell plus évolué qui prendre les motifs à ignorer dans un fichier *error.txt* (expressions rationnelles étendues, compatibles _grep -E_) et qui proposera de zapper manuellement (ou pas) si l'erreur ne correspondant aux motifs :
~~~{.sh}
#!/bin/sh
# File containing error messages to skip (one per line).
error_messages="errors.txt"
# Sleep interval between 2 check.
sleep_interval="1"
# Exit when Seconds_Behind_Master reached 0.
exit_when_uptodate="false"
# Options to pass to mysql.
#mysql_opt="-P 3307"
# File to log skipped queries to (leave empty for no logs).
log_file=""
mysql_skip_error() {
error="$1"
printf "Skiping: $error\n"
mysql $mysql_opt -e 'SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1; START SLAVE;'
[ -n "$log_file" ] && echo "$error" >>"$log_file"
}
while true; do
slave_status="$(mysql $mysql_opt -e 'SHOW SLAVE STATUS\G')"
seconds_behind_master=$(echo "$slave_status" |grep 'Seconds_Behind_Master: ' |awk -F ' ' '{print $2}')
last_SQL_error="$(echo "$slave_status" |grep 'Last_SQL_Error: ' |sed 's/^.\+Last_SQL_Error: //')"
if $exit_when_uptodate && [ "$seconds_behind_master" = "0" ]; then
printf 'Replication is up to date!\n'
exit 0
elif [ -z "$last_SQL_error" ]; then
sleep $sleep_interval
elif echo "$last_SQL_error" |grep -q -f $error_messages; then
mysql_skip_error "$last_SQL_error"
else
printf "Current SQL error doesn't match the pattern:\n"
printf "$last_SQL_error\n"
printf "Skip it? [y/N]: "
read reply
if [ "$reply" = "y" ] || [ "$reply" = "Y" ]; then
mysql_skip_error $last_SQL_error
fi
fi
done
~~~
2017-01-07 16:18:16 +01:00
**Récupération de position impossible**
~~~
[ERROR] Error reading packet from server: Client requested master to start replication from impossible position (server_errno=1236)
~~~
Cela signifie que la position indiquée sur le binlog du master est impossible à récupérer. On peut le vérifier avec une commande du type `mysqlbinlog mysqld-bin.00123 --start-position=251` sur le master.
2017-01-07 16:53:55 +01:00
Si l'on constate que le binlog est corrompu avec des erreurs du type _ERROR: Error in Log_event::read_log_event(): 'read error' # Warning: this binlog is either in use or was not closed properly._ ou _ERROR: Error in Log_event::read_log_event(): 'Event too small', data_len: 0, event_type: 0_ l'idée est d'identifier les requêtes non jouées sur le slave dans le binlog corrompu (voir le *Relay_Master_Log_File* via `SHOW SLAVE STATUS`) et de les rejouer (cf [HowtoMySQL#Replay]()) puis de passer au binlog suivant via une commande du type `CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000124' , MASTER_LOG_POS=106; START SLAVE;` (la position à indiquer est souvent `106`, cf `mysqlbinlog mysql-bin.000124`).
Si l'on juge cela non nécessaire (données non critiques), on pourra bien sûr passer directement au binlog suivant en ignorant les requêtes du binlog corrompu. Bien sûr, suite à ces manipulations risquées, on vérifiera ensuite la cohérence de la base de données répliquée (`COUNT(*)` ou outils plus avancés).
**Could not parse relay log event entry**
~~~
2017-01-07 16:53:55 +01:00
Could not parse relay log event entry. The possible reasons are: the master's binary log is corrupted (you can check this by running 'mysqlbinlog' on the binary log),
the slave's relay log is corrupted (you can check this by running 'mysqlbinlog' on the relay log), a network problem, or a bug in the master's or slave's MySQL code.
If you want to check the master's binary log or slave's relay log, you will be able to know their names by issuing 'SHOW SLAVE STATUS' on this slave.
~~~
2017-01-07 16:53:55 +01:00
Souvent un binlog corrompu, voir le *Relay_Master_Log_File* `SHOW SLAVE STATUS`.
**Note**: Jusqu'à MySQL <= 5.1 au moins, changer la position dans un `Relay_log` avec un `CHANGE MASTER TO` ne marche pas. Voir [#ChangementdelapositiondansunRelay_log].
**Erreur fatale à la lecture du binlog**
2016-11-02 12:12:48 +01:00
Erreur : `Got fatal error 1236 from master when reading data from binary log: 'log event entry exceeded max_allowed_packet; Increase max_allowed_packet on master'`
2016-11-02 12:12:48 +01:00
On obtient apparemment cela dans différents cas.
2016-11-02 12:12:48 +01:00
* L'un d'eux serait si max_allowed_packet est inférieur à read_buffer_size ; voir <http://www.mysqlperformanceblog.com/2012/06/06/read_buffer_size-can-break-your-replication/> ;
* dans d'autre cas, il faudra forcer la réplication à se poursuivre via `STOP SLAVE; CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.00XXXX' , MASTER_LOG_POS=XXXX; START SLAVE;`
* dans un autre cas, la position indiquée n'existe pas dans le binlog
* enfin voir <http://dev.mysql.com/doc/refman/5.1/en/replication-features-max-allowed-packet.html>
2016-11-02 12:12:48 +01:00
**Réinitialiser la réplication**
2016-11-02 12:12:48 +01:00
2017-01-07 16:53:55 +01:00
Dans certains cas **exceptionnels**, une solution radicale est de réinitialiser la réplication avec un `STOP SLAVE; RESET SLAVE; START SLAVE;` Attention, cela doit être fait dans de très rares cas maîtrisés (attention notamment aux conflits _DUPLICATE ENTRY_ que cela risque de provoquer).
2016-11-02 12:12:48 +01:00
**Status OK, mais pas de réplication**
2016-11-02 12:12:48 +01:00
Si un `SHOW SLAVE STATUS` ne retourne pas d'erreur mais que la réplication ne se fait pas, les logs du slave peuvent contenir une erreur du type :
2016-11-02 12:12:48 +01:00
~~~
[Note] Slave I/O thread: Failed reading log event, reconnecting to retry, log 'mysql-bin.003357' at position 389449
[Note] Slave: received end packet from server, apparent master shutdown:
~~~
Il se peut que le master se réplique sur 2 slaves ayant un server-id identique !
2017-01-07 16:53:55 +01:00
### Changement de la position dans un Relay_log
2016-11-02 12:12:48 +01:00
2017-01-07 16:53:55 +01:00
À faire uniquement si en tentant de changer la position d'un _Relay_log_ sur un slave, vous obtenez cette erreur :
2016-11-02 12:12:48 +01:00
~~~
Error initializing relay log position: Could not find target log during
relay log initialization
~~~
Il faut alors stopper le processus slave de réplication :
~~~
mysql> STOP SLAVE;
~~~
Puis éditer (en gardant une sauvegarde) le fichier `${datadir}/relay-log.info`. La première ligne correspond au `Relay_Log_File`, la seconde au `Relay_Log_Pos`.
Redémarrer MySQL.
2017-01-07 16:41:16 +01:00
## Contrôle de l'intégrité de la réplication
2016-11-02 12:12:48 +01:00
2017-01-07 16:41:16 +01:00
#### pt-table-checksum
2016-11-02 12:12:48 +01:00
2017-01-07 16:53:55 +01:00
<https://www.percona.com/doc/percona-toolkit/2.1/pt-table-checksum.html>
2016-11-02 12:12:48 +01:00
C'est un outil de [Percona](https://www.percona.com/downloads/percona-toolkit/) intégré dans son toolkit. (Package Debian [percona-toolkit](https://packages.debian.org/search?keywords=percona-toolkit) disponible à partir de Wheezy).
L'outil vérifie l'intégrité de la réplication en effectuant des requêtes de checksum (crc32 par défaut) sur le master, puis les requêtes sont joués sur les slaves permettant de trouver des différences.
2016-11-15 10:49:41 +01:00
La méthode la plus simple pour l'utiliser est d'autoriser le master à se connecter au slave (authentification MySQL). Ainsi, il s'occupe lui-même de faire le nécessaire pour identifier les erreurs. Il suffira donc de lancer la commande sans argument pour qu'il identifie les incohérences. On pourra réaliser un cron avec l'argument `-q` qui ne fait remonter que les erreurs.
2016-11-02 12:12:48 +01:00
~~~
2016-12-16 02:03:44 +01:00
MAILTO=jdoe@example.com
2016-11-02 12:12:48 +01:00
42 9 * * 7 pt-table-checksum -q
~~~
2017-01-07 16:41:16 +01:00
### pt-table-sync
2016-11-02 12:12:48 +01:00
2016-12-16 02:03:44 +01:00
Si *pt-table-checksum* vous a remonté des incohérences, vous pouvez avec cet outil les corriger. Cela va identifier les différences et les corriger avec un `REPLACE` sur le master (qui sera donc répliqué sur le slave), garantissant la cohérence des données.
2016-11-02 12:12:48 +01:00
Exemple :
~~~
# pt-table-sync --print --replicate percona.checksums localhost
# pt-table-sync --execute --replicate percona.checksums localhost
~~~
En cas de `Can't make changes on the master because no unique index exists`. On peut synchroniser directement les différences sur le slave depuis le master.
~~~
# pt-table-sync --execute --no-check-slave localhost slave
~~~