Seconds_Behind_Master no para de crecer

12 Febrero 2013 at 20:46 by Adrián Pérez

timeYa hemos hablado de replicación Master-Slave (o replicación) con anterioridad, pero, ¿cómo saber en qué estado están los slaves? ¿Está todo funcionando?

Hay un comando sumamente útil (y necesario) que nos muestra una serie de información, que nos puede venir muy bien para saber cómo están nuestros slaves. El comando lo ejecutaríamos en el cliente MySQL de cada servidor slave:

mysql> show slave status\G;

Entre la información que devuelve, destacan dos variables que han de estar a "Yes" para asegurar que la replicación está funcionando:

Slave_IO_Running: Yes
Slave_SQL_Running: Yes

El parámetro "Seconds_Behind_Master" también nos ayudará a ver cómo de sincronizado va ese slave, respecto al master, en segundos, pero no es un valor muy exacto.

Problema: Seconds_Behind_Master no para de crecer

Si aun así no nos fiamos, una buena prueba es hacer una modificación en el master y ver si se replica a los slaves, pero si aun así notamos que algo no va como debería, podemos empezar a ir un poco más allá.

Por ejemplo, podríamos observar casos raros, como el que hoy presento, donde todo parezca estar funcionando correctamente, pero sin embargo tengamos al slave cada vez a más segundos tras el master. En mi caso, "Seconds_Behind_Master" se incrementaba (aprox) un segundo cada segundo. Mal asunto, y más para una migración donde:

  • La CPU del nuevo slave era 4 veces mejor que la del antiguo.
  • La RAM del nuevo slave era 4 veces mejor que la del antiguo.
  • La i/o del nuevo slave era un 20% mejor que la del antiguo (pero aun así probamos con RAID 0, prácticamente doblando las escrituras).
  • El Bandwidth entre el nuevo Master y el nuevo slave era como 20 veces más rápido que el antiguo escenario.
  • Aun así, el seconds behind master funcionaba bien en el antiguo escenario, pero no en el nuevo.

Comprobaciones

Para buscar la causa del problema, se han realizado todo un seguido de cambios y comprobaciones, hasta que finalmente se ha dado con la causa del problema (descrita al final de este post).

Procesos "binlog dump" en el master

Para ir un poco más allá, en el master., ejecutaremos un "show processlist" para buscar comandos del estilo "Binlog Dump". Este thread sirve para mantener abierta una comunicación con cada slave, para ir enviando los datos del "binary log", tal y como ya vimos en este otro post.

mysql> show processlist;
| 3577871 | repluser             | 80.81.82.83:53259 | NULL    | Binlog Dump | 8246 | Writing to net   | NULL
| 3633136 | repluser             | 80.81.82.84:40057 | NULL    | Binlog Dump | 6631 | Writing to net   | NULL

Así pues, debería aparecer un resultado, por cada uno de los slaves que tenemos. Si no, entonces estaremos en problemas.

"Show slave hosts" en el master

Con esta instrucción, se pueden confirmar los slaves que tiene un master, así confirmamos que efectivamente el slave que está dando problemas esté contemplado en el master.

mysql> SHOW SLAVE HOSTS;
+-----------+------+------+-----------+
| Server_id | Host | Port | Master_id |
+-----------+------+------+-----------+
|         2 |      | 3306 |         1 |
|         3 |      | 3306 |         1 |
+-----------+------+------+-----------+

Logs binarios en los slaves

Con la instrucción "show slave status\G" obtendremos, entre otros, 6 resultados que nos ayudarán a entender mejor si el slave es capaz de leer del master y ejecutar las instrucciones que recibe. Esta información está sacada directamente de la documentación oficial de MySQL. Deberemos fijarnos en:

  • (Master_Log_file, Read_Master_Log_Pos): índica la última posición que ha leído ese slave, en el binary log que mantiene el master.
  • (Relay_Master_Log_File, Exec_Master_Log_Pos): índica la última posición que ha ejecutado el slave, en el binary log que mantiene el master.
  • (Relay_Log_File, Relay_Log_Pos): en el relay log que mantiene el slave, indica la última posición que dicho slave ha ejecutado.

Estos parámetros no dejarán de incrementarse constantemente, si todo va bien (y si el master recibe modificaciones constantemente, claro).

Otros testeos básicos

Cuando se ha comprobado que todo parecía funcionar correctamente, se ha pasado a descartar otro tipo de problemas:

  • Se ha comprobado que en los servers, iptables estaba correctamente configurado para aceptar el tráfico entre los servidores.
  • Se ha comprobado que se tenía selinux deshabilitato.
  • Se ha testeado la velocidad de transferencia entre los servidores, que era más que suficiente.
  • Se ha comprobado que, en nuestro entorno, los discos RAID estaban correctamente sincronizados, con "cat /proc/mdstat".
  • Con "top", "isotat" e "iotop" se ha visto como el Slave iba muy bien de recursos (excepto con el %iowait que estaba sobre el 7% constante). En cualquier caso, en el anterior Slave (el que funcionaba) el %iowait era incluso mayor.
  • No se ha visto nada raro en los logs del Slave (ni en los de sistema ni en los de MySQL).
  • Se ha comprobado que los servidores resolvian correctamente el nombre del otro servidor, pero aun así se ha verificado en el /etc/hosts. También se ha incluído el parámetro "skip-name-resolve" en el my.cnf del Slave, para evitar la resolución DNS y usar la IP para ganar algo de velocidad, que ha resultado ser negligible.
  • Se ha hecho un mysqlbinlog del relay-log del Slave para revisar las operaciones que incluye el relay-log, en busca de tiempos de ejecución demasiado grandes, sin encontrar nada significativo.
  • En el mysqlbinlog del relay-log, se ha aprovechado para ver el número de queries que se ejecutan por segundo (según el relay-log) con un símple grep del estilo "grep '^SET TIMESTAMP=<TIMESTAMP_AQUI>' relay-bin.000013.txt | wc -l".

Testeos nivel heavy metal

Finalmente se ha decidido optar por testear con cambios más agresivos:

  • Se ha cambiado el binlog_format pasando de MIXED a STATEMENT en todos los servers y reiniciando la replicación. El formato STATEMENT es el que viene por defecto y suele ir bastante bien.
  • Se han parado todos los slaves excepto 1, dejando así 1 único slave, para ver si el hecho de tener a varios slaves replicando influía en la velocidad de replicación.
  • Se ha quidato el RAID 1 software en un slave y se ha instalado de nuevo sin RAID. El RAID 1 software añade una penalización en el rendimiento del disco.
  • Se ha puesto RAID 0 software en un slave para aumentar la velocida de las escrituras a prácticamente el doble.
  • Se ha pensado en usar MySQL 5.6, que es  multi-threading, en los slaves, para mejorar la velocidad de la replicación. Sin embargo, únicamente usa 1 thread por base de datos. En mi caso únicamente tenía una base de datos, así que no supondría diferencia, pero aun así lo he probado y he podido constatar como a pesar de abrir 10 threads en el Slave, únicamente funcionaba 1.
  • Me he asegurado de sincronizar todos los servers con NTP.
  • Me he asegurado de contar con un "ulimit" suficientemente alto (20.000)
  • Se ha incrementado el parámetro "max_connections" del master a 2.000, y también de los slaves.
  • Se ha desactivado el bin_log en los slaves (dejando activado el relay_log) para evitar más escrituras de las necesarias.

Ninguna de estas soluciones ha funcionado. En realidad, ninguna ha supuesto ninguna diferencia, a pesar de ser buenas mejoras, sobre el papel.

SOLUCIÓN FINAL: noatime,nobarrier

En mi caso, el problema se ha solucionado modificando los atributos de la partición donde estaba el dbpath. Concretamente, el dbpath se encontraba dentro de una partición definida de la siguiente manera en el /etc/fstab:

/dev/md4 /data ext4 defaults 0 0

Como se puede observar, se trata de una partición en RAID (Raid 1, concretamente), ext4 con los parámetros default. Sin embargo, cambiando los parámetros tal y como sigue y reiniciando la máquina se ha solucionado el problema:

/dev/md4 /data ext4 defaults,noatime,barrier=0 0 0

¿Por qué? ¿Qué son estos dos parámetros?

noatime: Por defecto, a cada lectura de un fichero, el sistema modifica el access time (o fecha de último acceso) de dicho fichero. Así pues, para cada lectura, se realiza una escritura para modificar el access time. Añadiendo el parámetro "noatime" evitamos que ésto ocurra, y por extraño que pueda parecer, ésto incrementa el rendimiento de forma más que notable. [Fuente]

nobarrier / barrier=0: Al parecer, las barriers (o barreras), se implementaron para ext4 a partir del kernel 2.6.30. Sirven para mejorar la integridad de los datos, añadiendo barreras ahí donde toque. Este parámetro mu cuesta más activarlo, puesto que ganamos rendimiento en decremento de la integridad de los datos, pero por ejemplo, las máquinas virtuales de RackSpace vienen con este parámetro activado. Seguramente sea una buena idea activar promero el "noatime" y si no tenemos suficiente, sopesar el activar "nobarrier". [Fuente]

Tras activar estos parámetros, el Seconds_Behind_Master ha continuado subiendo durante 5-10 minutos, tras lo cual ha empezado a decrecer hasta que finalmente ha vuelto a estar a 0. Y hablo de un Seconds_Behind_Master que ha llegado a ir más de 16000 segundos por detrás del Master. Así que todo un logro.

Actualización Diciembre 2013

Un amigo me ha hecho llegar una serie de recomendaciones que me ha parecido interesante compartir, pues ayudarán a mejorar el rendimiento en cuanto a la replicación de los slaves, únicamente modificando algunos parámetros en la configuración (my.cnf).

log-slave-updates

Normalmente, no necesitaremos tener habilitado el registro de sentencias que el slave replica del master. Ésto es necesario cuando un segundo slave replica de este slave, de la siguiente manera: MASTER > SLAVE1 > SLAVE2, pero si no es el caso y todos los slaves replican del master, no es necesario tener habilitado este parámetro, ahorrándonos así escrituras en el disco.

log-slave-updates = 0

sync_binlog

Siguiendo la misma filosofía, podemos desactivar todo el binlog en los slaves, puesto que no es necesario mantener la precaución ante "crashes" en los slaves. En este punto, se ha aprovechado y se ha desactivado todo lo referente al binlog:

#log_bin = /data/mysql/mysql-bin
#expire_logs_days = 14
sync_binlog = 0
#binlog_format = statement

innodb_io_capacity

Por defecto, el límite de operaciones de i/o para las tareas de background de InnoDB (como flush de páginas del buffer pool o mezclar datos del insert buffer) está marcado a 200 iops. Este valor dependerá del tipo de discos en los que estén los datos del MySQL, según sean de 7200rpm, 10Krpm, 15Krpm, SSD, etc.

innodb_io_capacity = 300

innodb_read/write_io_threads

Por último, también me han recomendado configurar dos parámetros que (copio literalmente) "a la hora de replicar no, pero a la hora de leer/escribir si usa threads ayudaría". El valor a ajustar es 4 veces el número de cores.

innodb_read_io_threads = 32
innodb_write_io_threads = 32

Decir que si llegamos a este punto, seguramente deberíamos empezar a mirar de cambiar nuestros discos de los slaves a SSD, en RAID, revisar la velocidad de la conexión entre master y slaves, revisar el hardware de los slaves, y seguramente, empezar a plantearnos si en realidad, MySQL es la solución que necesitamos.

Fuente:

http://dev.mysql.com/doc/refman/5.1/en/replication-administration-status.htm

Flickr!Foto por Fey Ilyas