El problema más grave de la replicación en MySQL es su funcionamiento asíncrono. Cuando se añade o modifica algún dato en el master, este commitea los datos en local sin esperar a que los slaves lo hagan. Esto normalmente no supone un gran problema, ya que la replicación, si no hay ningún problema con índices o con la red, es casi instantanea. Pero aún así se pueden dar algunos problemas:
-
El master commitea los datos sin esperar. Durante un tiempo, aunque pequeño, master y slave tendrán datos diferentes. Contra mas alto sea el valor seconds behind master, mayor será el problema.
-
El master no comprueba que los esclavos hayan recibido los binlogs con los cambios.
-
El master no comprueba que los esclavos hayan hecho efectivos los cambios en sus bases de datos.
Este es un problema solucionado en MySQL Cluster, donde la replicación es totalmente síncrona. Los nodos no commitean los cambios hasta que estos se hayan escrito correctamente en los node groups que correspondan. Si esto no es así, se hace un rollback. Pero en la replicación normal no tenemos tanta suerte.
Una de las novedades de MySQL 5.5 viene a medio solventar el problema. Con esta nueva versión de desarrollo disponemos de replicación semi-síncrona. Algo es algo :)
Su funcionamiento es simple. Ahora el master antes de hacer commit espera a que al menos uno de los slaves reciba los logs binarios. Pero aún así hay que tener en cuenta lo siguiente:
-
El master solamente comprueba que un slave haya recibido los logs, pero no que si lo ha podido escribir correctamente o no. Esto es, no importa el estado del SQL Thread.
-
Podemos tener 1000 slaves, pero con que solo uno reciba los logs ya se da por bueno.
-
Si pasado un tiempo ninguno de los esclavos recibe los logs, el master cambia a modo asíncrono commiteando los cambios.
Vamos a hacer unas pruebas. Necesitaremos dos cosas, MySQL 5.5 y sandbox :) Creamos un entorno de replicación con un master y dos slaves:
punisher@shyris:~$ make_replication_sandbox --how_many_slaves=2 /home/punisher/MySQL/mysql-5.5.1-m2-linux-x86_64-glibc23.tar.gz installing and starting master installing slave 1 installing slave 2 starting slave 1 .. sandbox server started starting slave 2 . sandbox server started initializing slave 1 initializing slave 2
Una vez hecho, debemos cargar el plugin que nos permite hacer uso de la replicación semi-síncrona en todos los hosts:
master [localhost] {msandbox} ((none)) > INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so'; Query OK, 0 rows affected (0.00 sec) slave1 [localhost] {msandbox} ((none)) > INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so'; Query OK, 0 rows affected (0.00 sec) slave2 [localhost] {msandbox} ((none)) > INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so'; Query OK, 0 rows affected (0.00 sec)
A continuación, debemos habilitar su uso en los ficheros de configuración:
Master: rpl_semi_sync_master_enabled=1; Slaves: rpl_semi_sync_slave_enabled=1;
Reiniciamos mysql y ya lo tenemos :) Creamos una base de datos llamada prueba y comprobamos si al menos un slave ha recibido el binlog:
master [localhost] {msandbox} ((none)) > create database pruebas; Query OK, 1 row affected (0.00 sec) master [localhost] {msandbox} ((none)) > SHOW STATUS LIKE 'Rpl_semi_sync%tx'; +-----------------------------+-------+ | Variable_name | Value | +-----------------------------+-------+ | Rpl_semi_sync_master_no_tx | 0 | | Rpl_semi_sync_master_yes_tx | 1 | +-----------------------------+-------+ 2 rows in set (0.00 sec)
Rpl_semi_sync_master_yes_tx es el número de queries correctamente replicadas. Rpl_semi_sync_master_no_tx es el número de queries que no se han replicado.
Hay que tener en cuenta, que estamos hablando de IO no de SQL. Para comprobarlo, paramos el SQL thread en los dos nodos:
slave1 [localhost] {msandbox} ((none)) > STOP SLAVE SQL_THREAD; Query OK, 0 rows affected (0.00 sec) slave2 [localhost] {msandbox} ((none)) > STOP SLAVE SQL_THREAD; Query OK, 0 rows affected (0.00 sec)
Y a continuación insertamos un dato en el master:
master [localhost] {msandbox} (pruebas) > create table t(i int(10)); Query OK, 0 rows affected (0.00 sec) master [localhost] {msandbox} (pruebas) > SHOW STATUS LIKE 'Rpl_semi_sync%tx'; +-----------------------------+-------+ | Variable_name | Value | +-----------------------------+-------+ | Rpl_semi_sync_master_no_tx | 0 | | Rpl_semi_sync_master_yes_tx | 2 | +-----------------------------+-------+ 2 rows in set (0.00 sec)
Los slaves han recibido los datos, eso es suficiente para el master y se da por bueno. Ahora vamos a parar el IO Thread:
slave1 [localhost] {msandbox} ((none)) > STOP SLAVE IO_THREAD; Query OK, 0 rows affected (0.00 sec) slave2 [localhost] {msandbox} ((none)) > STOP SLAVE IO_THREAD; Query OK, 0 rows affected (0.00 sec)
Y volvemos a meter datos en el master:
master [localhost] {msandbox} (pruebas) > create table z(i int(10)); Query OK, 0 rows affected (10.00 sec) master [localhost] {msandbox} (pruebas) > SHOW STATUS LIKE 'Rpl_semi_sync%tx'; +-----------------------------+-------+ | Variable_name | Value | +-----------------------------+-------+ | Rpl_semi_sync_master_no_tx | 1 | | Rpl_semi_sync_master_yes_tx | 2 | +-----------------------------+-------+ 2 rows in set (0.00 sec)
La query ha tardado 10 segundos en commitearse. Si durante esos 10 segundos ninguno de los slaves ha recibido los binlogs, el master hace commit y se cuenta como una query no sincronizada.