Статьи

Несоответствие изменения схемы с Galera Cluster для MySQL

Первоначально написано Stephane Combaudon

Недавно я работал над случаем, когда схема одного узла кластера Galera десинхронизирована с другими узлами. И это было, хотя метод Total Order Isolation действовал для выполнения изменений схемы. Посмотрим, что случилось.

Фон

Для тех из вас, кто не знаком с тем, как Galera может выполнять изменения схемы, вот краткое резюме:

  • В зависимости от значения wsrep_OSU_methodнастройки доступны два метода . Оба имеют свои преимущества и недостатки, это не главная тема этого поста.
  • С TOI (Total Order Isolation) оператор DDL выполняется в одной и той же точке потока репликации на всех узлах, что дает строгие гарантии того, что схема всегда идентична на всех узлах.
  • С RSU (обновление скользящей схемы) оператор DDL не реплицируется на другие узлы. До тех пор, пока оператор DDL не будет выполнен на всех узлах, схема не везде согласована (поэтому следует соблюдать осторожность, чтобы не нарушить репликацию).

Вы можете посмотреть официальный документ здесь .

Если вы внимательно прочитаете раздел о TOI, вы увидите, что «[…] транзакции TOI никогда не пройдут сертификацию и гарантированно будут выполнены». Но также, что «Система реплицирует запрос TOI перед выполнением, и нет способа узнать, успешно он или нет. Таким образом, проверка ошибок в запросах TOI отключена ».

Смешение? На самом деле, нет. Это просто означает, что с TOI оператор DDL всегда будет проходить сертификацию. Но если по какой-то причине оператор DDL завершится неудачей на одном из узлов, он не будет откатываться на других узлах. Это открывает дверь для несоответствий схемы между узлами.

Контрольный пример

Давайте создадим таблицу в кластере Percona XtraDB Cluster 5.6 с 3 узлами и  вставим несколько строк:

pxc1> create table t (id int not null auto_increment primary key, c varchar(10));
pxc1> insert into t (c) values ('aaaa'),('bbbb');

Затем на узле 3 давайте представим изменение схемы для t, которое может привести к сбою других изменений схемы:

pxc3> set global wsrep_OSU_method=RSU;
pxc3> alter table t add d int;
pxc3> set global wsrep_OSU_method=TOI;

Поскольку изменение схемы было выполнено на узле 3 с RSU, оно не реплицируется на другие узлы.

Теперь давайте попробуем другое изменение схемы на узле 1:

pxc1> alter table t add d varchar(10);
Query OK, 0 rows affected (0,14 sec)
Records: 0  Duplicates: 0  Warnings: 0

Видимо, все прошло хорошо, и действительно, на узлах 1 и 2 у нас правильная схема:

pxc2>show create table tG
*************************** 1. row ***************************
       Table: t
Create Table: CREATE TABLE t (
  id int(11) NOT NULL AUTO_INCREMENT,
  c varchar(10) DEFAULT NULL,
  d varchar(10) DEFAULT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=latin1

Но на узле 3 оператор не удался, поэтому схема не была изменена:

pxc3> show create table tG
*************************** 1. row ***************************
       Table: t
Create Table: CREATE TABLE t (
  id int(11) NOT NULL AUTO_INCREMENT,
  c varchar(10) DEFAULT NULL,
  d int(11) DEFAULT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=latin1

Ошибка видна в журнале ошибок узла 3:

2014-07-18 10:37:14 9649 [ERROR] Slave SQL: Error 'Duplicate column name 'd'' on query. Default database: 'repl_test'. Query: 'alter table t add d varchar(10)', Error_code: 1060
2014-07-18 10:37:14 9649 [Warning] WSREP: RBR event 1 Query apply warning: 1, 200
2014-07-18 10:37:14 9649 [Warning] WSREP: Ignoring error for TO isolated action: source: 577ffd51-0e52-11e4-a30e-4bde3a7ad3f2 version: 3 local: 0 state: APPLYING flags: 65 conn_id: 3 trx_id: -1 seqnos (l: 17, g: 200, s: 199, d: 199, ts: 7722177758966)

Но, конечно, это легко пропустить. И тогда простой INSERT может вызвать отключение на узле 3:

pxc2> insert into t (c,d) values ('cccc','dddd');
Query OK, 1 row affected (0,00 sec)

сработает на узле 3:

2014-07-18 10:42:27 9649 [ERROR] Slave SQL: Column 2 of table 'repl_test.t' cannot be converted from type 'varchar(10)' to type 'int(11)', Error_code: 1677
2014-07-18 10:42:27 9649 [Warning] WSREP: RBR event 3 Write_rows apply warning: 3, 201
2014-07-18 10:42:27 9649 [Warning] WSREP: Failed to apply app buffer: seqno: 201, status: 1
	 at galera/src/trx_handle.cpp:apply():340
[...]
2014-07-18 10:42:27 9649 [Note] WSREP: Received NON-PRIMARY.
2014-07-18 10:42:27 9649 [Note] WSREP: Shifting SYNCED -> OPEN (TO: 201)
2014-07-18 10:42:27 9649 [Note] WSREP: Received self-leave message.
2014-07-18 10:42:27 9649 [Note] WSREP: Flow-control interval: [0, 0]
2014-07-18 10:42:27 9649 [Note] WSREP: Received SELF-LEAVE. Closing connection.
2014-07-18 10:42:27 9649 [Note] WSREP: Shifting OPEN -> CLOSED (TO: 201)
2014-07-18 10:42:27 9649 [Note] WSREP: RECV thread exiting 0: Success
2014-07-18 10:42:27 9649 [Note] WSREP: recv_thread() joined.
2014-07-18 10:42:27 9649 [Note] WSREP: Closing replication queue.
2014-07-18 10:42:27 9649 [Note] WSREP: Closing slave action queue.
2014-07-18 10:42:27 9649 [Note] WSREP: bin/mysqld: Terminated.

Вывод

Как и на обычном MySQL, с Galera изменения схемы непростые. Некоторые тонкости могут создать много проблем, если вы не знаете о них. Поэтому, прежде чем запускать оператор DDL, убедитесь, что вы полностью понимаете, как работают методы TOI и RSU.