sql >> Base de Datos >  >> RDS >> Mysql

Actualización de esquema en línea en MySQL Galera Cluster utilizando el método RSU

Esta publicación es una continuación de nuestra publicación anterior sobre Actualización de esquema en línea en Galera utilizando el método TOI. Ahora le mostraremos cómo realizar una actualización de esquema utilizando el método de actualización de esquema continuo (RSU).

RSU y TOI

Como discutimos, cuando se usa TOI, ocurre un cambio al mismo tiempo en todos los nodos. Esto puede convertirse en una seria limitación ya que tal forma de ejecutar cambios de esquema implica que no se pueden ejecutar otras consultas. Para sentencias ALTER largas, es posible que el clúster no esté disponible incluso durante horas. Obviamente, esto no es algo que puedas aceptar en producción. El método RSU aborda esta debilidad:los cambios ocurren en un nodo a la vez, mientras que otros nodos no se ven afectados y pueden atender el tráfico. Una vez que ALTER se complete en un nodo, se volverá a unir al clúster y podrá continuar con la ejecución de un cambio de esquema en el siguiente nodo.

Tal comportamiento viene con su propio conjunto de limitaciones. La principal es que el cambio de esquema programado tiene que ser compatible. ¿Qué significa? Pensemos en ello por un momento. En primer lugar, debemos tener en cuenta que el clúster está en funcionamiento todo el tiempo:el nodo alterado debe poder aceptar todo el tráfico que llega a los nodos restantes. En resumen, un DML ejecutado en el esquema anterior tiene que funcionar también en el nuevo esquema (y viceversa si usa algún tipo de distribución de conexión tipo round-robin en su Galera Cluster). Nos centraremos en la compatibilidad con MySQL, pero también debe recordar que su aplicación debe funcionar con nodos modificados y no modificados; asegúrese de que su modificación no rompa la lógica de la aplicación. Una buena práctica es pasar explícitamente los nombres de las columnas a las consultas; no confíe en "SELECCIONAR *" porque nunca sabe cuántas columnas obtendrá a cambio.

Formato de registro binario basado en filas y Galera

Ok, entonces DML tiene que funcionar en esquemas antiguos y nuevos. ¿Cómo se transfieren los DML entre los nodos de Galera? ¿Afecta qué cambios son compatibles y cuáles no? Sí, de hecho - lo hace. Galera no usa la replicación regular de MySQL, pero aún depende de ella para transferir eventos entre los nodos. Para ser precisos, Galera utiliza el formato ROW para eventos. Un evento en formato de fila (después de la decodificación) puede verse así:

### INSERT INTO `schema`.`table`
### SET
###   @1=1
###   @2=1
###   @3='88764053989'
###   @4='14700597838'

O:

### UPDATE `schema`.`table`
### WHERE
###   @1=1
###   @2=1
###   @3='88764053989'
###   @4='14700597838'
### SET
###   @1=2
###   @2=2
###   @3='88764053989'
###   @4='81084251066'

Como puede ver, hay un patrón visible:una fila se identifica por su contenido. No hay nombres de columna, solo su orden. Esto solo debería encender algunas luces de advertencia:"¿qué pasaría si elimino una de las columnas?" Bueno, si es la última columna, esto es aceptable. Si eliminara una columna en el medio, se alteraría el orden de las columnas y, como resultado, se interrumpiría la replicación. Sucederá algo similar si agrega alguna columna en el medio, en lugar de al final. Sin embargo, hay más restricciones. Cambiar la definición de la columna funcionará siempre que sea del mismo tipo de datos:puede modificar la columna INT para que se convierta en BIGINT pero no puede cambiar la columna INT en VARCHAR; esto interrumpirá la replicación. Puede encontrar una descripción detallada de qué cambio es compatible y qué no lo es en la documentación de MySQL. Independientemente de lo que pueda ver en la documentación, para mantenerse seguro, es mejor ejecutar algunas pruebas en un clúster de desarrollo/prueba independiente. Asegúrese de que funcione no solo de acuerdo con la documentación, sino que también funcione bien en su configuración particular.

Con todo, como puede ver claramente, ejecutar RSU de manera segura es mucho más complejo que simplemente ejecutar un par de comandos. Aún así, dado que los comandos son importantes, echemos un vistazo al ejemplo de cómo puede realizar el RSU y qué puede salir mal en el proceso.

Ejemplo de RSU

Configuración inicial

Imaginemos un ejemplo bastante simple de una aplicación. Usaremos una herramienta de referencia, Sysbench, para generar contenido y tráfico, pero el flujo será el mismo para casi todas las aplicaciones:Wordpress, Joomla, Drupal, lo que sea. Usaremos HAProxy junto con nuestra aplicación para dividir las lecturas y escrituras entre los nodos de Galera de forma rotativa. Puede comprobar a continuación cómo ve HAProxy el clúster de Galera.

Toda la topología se ve a continuación:

El tráfico se genera usando el siguiente comando:

while true ; do sysbench /root/sysbench/src/lua/oltp_read_write.lua --threads=4 --max-requests=0 --time=3600 --mysql-host=10.0.0.100 --mysql-user=sbtest --mysql-password=sbtest --mysql-port=3307 --tables=32 --report-interval=1 --skip-trx=on --table-size=100000 --db-ps-mode=disable run ; done

El esquema se ve a continuación:

mysql> SHOW CREATE TABLE sbtest1.sbtest1\G
*************************** 1. row ***************************
       Table: sbtest1
Create Table: CREATE TABLE `sbtest1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `k` int(11) NOT NULL DEFAULT '0',
  `c` char(120) NOT NULL DEFAULT '',
  `pad` char(60) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=29986632 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

Primero, veamos cómo podemos agregar un índice a esta tabla. Agregar un índice es un cambio compatible que se puede hacer fácilmente usando RSU.

mysql> SET SESSION wsrep_OSU_method=RSU;
Query OK, 0 rows affected (0.00 sec)
mysql> ALTER TABLE sbtest1.sbtest1 ADD INDEX idx_new (k, c); 
Query OK, 0 rows affected (5 min 19.59 sec)

Como puede ver en la pestaña Nodo, el host en el que ejecutamos el cambio cambió automáticamente al estado Donante/Desincronizado, lo que garantiza que este host no afectará al resto del clúster si ALTER lo ralentiza.

Veamos cómo se ve nuestro esquema ahora:

mysql> SHOW CREATE TABLE sbtest1.sbtest1\G
*************************** 1. row ***************************
       Table: sbtest1
Create Table: CREATE TABLE `sbtest1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `k` int(11) NOT NULL DEFAULT '0',
  `c` char(120) NOT NULL DEFAULT '',
  `pad` char(60) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `k_1` (`k`),
  KEY `idx_new` (`k`,`c`)
) ENGINE=InnoDB AUTO_INCREMENT=29986632 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

Como puede ver, el índice ha sido agregado. Sin embargo, tenga en cuenta que esto sucedió solo en ese nodo en particular. Para lograr un cambio de esquema completo, debe seguir este proceso en los nodos restantes de Galera Cluster. Para terminar con el primer nodo, podemos volver a cambiar wsrep_OSU_method a TOI:

SET SESSION wsrep_OSU_method=TOI;
Query OK, 0 rows affected (0.00 sec)

No vamos a mostrar el resto del proceso, porque es lo mismo:habilite RSU en el nivel de sesión, ejecute ALTER, habilite TOI. Lo que es más interesante es lo que sucedería si el cambio fuera incompatible. Echemos de nuevo un vistazo rápido al esquema:

mysql> SHOW CREATE TABLE sbtest1.sbtest1\G
*************************** 1. row ***************************
       Table: sbtest1
Create Table: CREATE TABLE `sbtest1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `k` int(11) NOT NULL DEFAULT '0',
  `c` char(120) NOT NULL DEFAULT '',
  `pad` char(60) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `k_1` (`k`),
  KEY `idx_new` (`k`,`c`)
) ENGINE=InnoDB AUTO_INCREMENT=29986632 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

Digamos que queremos cambiar el tipo de columna 'k' de INT a VARCHAR(30) en un nodo.

mysql> SET SESSION wsrep_OSU_method=RSU;
Query OK, 0 rows affected (0.00 sec)
mysql> ALTER TABLE sbtest1.sbtest1 MODIFY COLUMN k VARCHAR(30) NOT NULL DEFAULT '';
Query OK, 10004785 rows affected (1 hour 14 min 51.89 sec)
Records: 10004785  Duplicates: 0  Warnings: 0

Ahora, echemos un vistazo al esquema:

mysql> SHOW CREATE TABLE sbtest1.sbtest1\G
*************************** 1. row ***************************
       Table: sbtest1
Create Table: CREATE TABLE `sbtest1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `k` varchar(30) NOT NULL DEFAULT '',
  `c` char(120) NOT NULL DEFAULT '',
  `pad` char(60) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `k_1` (`k`),
  KEY `idx_new` (`k`,`c`)
) ENGINE=InnoDB AUTO_INCREMENT=29986632 DEFAULT CHARSET=latin1
1 row in set (0.02 sec)

Todo es como esperábamos:la columna 'k' se ha cambiado a VARCHAR. Ahora podemos comprobar si este cambio es aceptable o no para el Clúster de Galera. Para probarlo, usaremos uno de los nodos restantes sin modificar para ejecutar la siguiente consulta:

mysql> INSERT INTO sbtest1.sbtest1 (k, c, pad) VALUES (123, 'test', 'test');
Query OK, 1 row affected (0.19 sec)
Vamos a ver que pasó.

Definitivamente no se ve bien:nuestro nodo está inactivo. Los registros le darán más detalles:

2017-04-07T10:51:14.873524Z 5 [ERROR] Slave SQL: Column 1 of table 'sbtest1.sbtest1' cannot be converted from type 'int' to type 'varchar(30)', Error_code: 1677
2017-04-07T10:51:14.873560Z 5 [Warning] WSREP: RBR event 3 Write_rows apply warning: 3, 982675
2017-04-07T10:51:14.879120Z 5 [Warning] WSREP: Failed to apply app buffer: seqno: 982675, status: 1
         at galera/src/trx_handle.cpp:apply():351
Retrying 2th time
2017-04-07T10:51:14.879272Z 5 [ERROR] Slave SQL: Column 1 of table 'sbtest1.sbtest1' cannot be converted from type 'int' to type 'varchar(30)', Error_code: 1677
2017-04-07T10:51:14.879287Z 5 [Warning] WSREP: RBR event 3 Write_rows apply warning: 3, 982675
2017-04-07T10:51:14.879399Z 5 [Warning] WSREP: Failed to apply app buffer: seqno: 982675, status: 1
         at galera/src/trx_handle.cpp:apply():351
Retrying 3th time
2017-04-07T10:51:14.879618Z 5 [ERROR] Slave SQL: Column 1 of table 'sbtest1.sbtest1' cannot be converted from type 'int' to type 'varchar(30)', Error_code: 1677
2017-04-07T10:51:14.879633Z 5 [Warning] WSREP: RBR event 3 Write_rows apply warning: 3, 982675
2017-04-07T10:51:14.879730Z 5 [Warning] WSREP: Failed to apply app buffer: seqno: 982675, status: 1
         at galera/src/trx_handle.cpp:apply():351
Retrying 4th time
2017-04-07T10:51:14.879911Z 5 [ERROR] Slave SQL: Column 1 of table 'sbtest1.sbtest1' cannot be converted from type 'int' to type 'varchar(30)', Error_code: 1677
2017-04-07T10:51:14.879924Z 5 [Warning] WSREP: RBR event 3 Write_rows apply warning: 3, 982675
2017-04-07T10:51:14.885255Z 5 [ERROR] WSREP: Failed to apply trx: source: 938415a6-1aab-11e7-ac29-0a69a4a1dafe version: 3 local: 0 state: APPLYING flags: 1 conn_id: 125559 trx_id: 2856843 seqnos (l: 392283, g: 9
82675, s: 982674, d: 982563, ts: 146831275805149)
2017-04-07T10:51:14.885271Z 5 [ERROR] WSREP: Failed to apply trx 982675 4 times
2017-04-07T10:51:14.885281Z 5 [ERROR] WSREP: Node consistency compromized, aborting…

Como puede verse, Galera se quejó del hecho de que la columna no se puede convertir de INT a VARCHAR(30). Intentó volver a ejecutar el conjunto de escritura cuatro veces pero fracasó, como era de esperar. Como tal, Galera determinó que la consistencia del nodo está comprometida y el nodo es expulsado del clúster. El contenido restante de los registros muestra este proceso:

2017-04-07T10:51:14.885560Z 5 [Note] WSREP: Closing send monitor...
2017-04-07T10:51:14.885630Z 5 [Note] WSREP: Closed send monitor.
2017-04-07T10:51:14.885644Z 5 [Note] WSREP: gcomm: terminating thread
2017-04-07T10:51:14.885828Z 5 [Note] WSREP: gcomm: joining thread
2017-04-07T10:51:14.885842Z 5 [Note] WSREP: gcomm: closing backend
2017-04-07T10:51:14.896654Z 5 [Note] WSREP: view(view_id(NON_PRIM,6fcd492a,37) memb {
        b13499a8,0
} joined {
} left {
} partitioned {
        6fcd492a,0
        938415a6,0
})
2017-04-07T10:51:14.896746Z 5 [Note] WSREP: view((empty))
2017-04-07T10:51:14.901477Z 5 [Note] WSREP: gcomm: closed
2017-04-07T10:51:14.901512Z 0 [Note] WSREP: New COMPONENT: primary = no, bootstrap = no, my_idx = 0, memb_num = 1
2017-04-07T10:51:14.901531Z 0 [Note] WSREP: Flow-control interval: [16, 16]
2017-04-07T10:51:14.901541Z 0 [Note] WSREP: Received NON-PRIMARY.
2017-04-07T10:51:14.901550Z 0 [Note] WSREP: Shifting SYNCED -> OPEN (TO: 982675)
2017-04-07T10:51:14.901563Z 0 [Note] WSREP: Received self-leave message.
2017-04-07T10:51:14.901573Z 0 [Note] WSREP: Flow-control interval: [0, 0]
2017-04-07T10:51:14.901581Z 0 [Note] WSREP: Received SELF-LEAVE. Closing connection.
2017-04-07T10:51:14.901589Z 0 [Note] WSREP: Shifting OPEN -> CLOSED (TO: 982675)
2017-04-07T10:51:14.901602Z 0 [Note] WSREP: RECV thread exiting 0: Success
2017-04-07T10:51:14.902701Z 5 [Note] WSREP: recv_thread() joined.
2017-04-07T10:51:14.902720Z 5 [Note] WSREP: Closing replication queue.
2017-04-07T10:51:14.902730Z 5 [Note] WSREP: Closing slave action queue.
2017-04-07T10:51:14.902742Z 5 [Note] WSREP: /usr/sbin/mysqld: Terminated.

Por supuesto, ClusterControl intentará recuperar dicho nodo; la recuperación implica ejecutar SST, por lo que se eliminarán los cambios de esquema incompatibles, pero volveremos al punto de partida:nuestro cambio de esquema se revertirá.

Como puede ver, si bien ejecutar RSU es un proceso muy simple, en el fondo puede ser bastante complejo. Requiere algunas pruebas y preparativos para asegurarse de que no perderá un nodo solo porque el cambio de esquema no era compatible.