Тази публикация е продължение на предишната ни публикация за онлайн надстройка на схема в Galera с помощта на метода TOI. Сега ще ви покажем как да извършите надстройка на схемата с помощта на метода за непрекъснато надграждане на схема (RSU).
RSU и TOI
Както обсъдихме, когато се използва TOI, промяната се случва по едно и също време на всички възли. Това може да се превърне в сериозно ограничение, тъй като такъв начин на изпълнение на промени в схемата предполага, че не могат да бъдат изпълнени други заявки. За дълги оператори ALTER, клъстерът може да не е наличен с часове дори. Очевидно това не е нещо, което можете да приемете в производството. Методът RSU адресира тази слабост - промените се случват на един възел в даден момент, докато други възли не са засегнати и могат да обслужват трафик. След като ALTER завърши на един възел, той ще се присъедини отново към клъстера и можете да продължите с изпълнението на промяна на схемата на следващия възел.
Такова поведение идва със собствен набор от ограничения. Основното е, че планираната промяна на схемата трябва да е съвместима. Какво означава? Нека да помислим за това за малко. Преди всичко трябва да имаме предвид, че клъстерът работи и работи през цялото време - промененият възел трябва да може да приеме целия трафик, който удря останалите възли. Накратко, DML, изпълнен върху старата схема, трябва да работи и върху новата схема (и обратно, ако използвате някаква кръгова разпределение на връзките във вашия Galera Cluster). Ще се съсредоточим върху съвместимостта с MySQL, но също така трябва да запомните, че приложението ви трябва да работи както с променени, така и с непроменени възли - уверете се, че вашата промяна няма да наруши логиката на приложението. Една добра практика е изрично да предавате имена на колони към заявките – не разчитайте на „SELECT *“, защото никога не знаете колко колони ще получите в замяна.
Galera и базиран на ред двоичен регистрационен формат
Добре, така че DML трябва да работи върху стари и нови схеми. Как се прехвърлят DML между възлите на Galera? Влияе ли това кои промени са съвместими и кои не? Да, наистина - така е. Galera не използва обикновена MySQL репликация, но все още разчита на нея за прехвърляне на събития между възлите. За да бъдем точни, Galera използва ROW формат за събития. Събитие във формат на ред (след декодиране) може да изглежда така:
### INSERT INTO `schema`.`table`
### SET
### @1=1
### @2=1
### @3='88764053989'
### @4='14700597838'
Или:
### UPDATE `schema`.`table`
### WHERE
### @1=1
### @2=1
### @3='88764053989'
### @4='14700597838'
### SET
### @1=2
### @2=2
### @3='88764053989'
### @4='81084251066'
Както можете да видите, има видим модел:редът се идентифицира по съдържанието му. Няма имена на колони, само техния ред. Само това трябва да включи някои предупредителни светлини:„какво ще се случи, ако премахна една от колоните?“ Е, ако това е последната колона, това е приемливо. Ако премахнете колона в средата, това ще обърка реда на колоните и в резултат на това репликацията ще се счупи. Подобно нещо ще се случи, ако добавите колона в средата, вместо в края. Има обаче повече ограничения. Промяната на дефиницията на колона ще работи, стига да е от същия тип данни - можете да промените колоната INT, за да стане BIGINT, но не можете да промените колоната INT в VARCHAR - това ще наруши репликацията. Можете да намерите подробно описание на това коя промяна е съвместима и коя не е в документацията на MySQL. Без значение какво можете да видите в документацията, за да останете в безопасност, по-добре е да изпълните някои тестове на отделен клъстер за разработка/постановка. Уверете се, че ще работи не само според документацията, но и че работи добре във вашата конкретна настройка.
Като цяло, както можете ясно да видите, изпълнението на RSU по безопасен начин е много по-сложно от простото изпълнение на няколко команди. И все пак, тъй като командите са важни, нека да разгледаме примера за това как можете да изпълните RSU и какво може да се обърка в процеса.
Пример на RSU
Първоначална настройка
Нека си представим един доста прост пример за приложение. Ще използваме инструмент за сравнение, Sysbench, за генериране на съдържание и трафик, но потокът ще бъде един и същ за почти всяко приложение - Wordpress, Joomla, Drupal, каквото и да е. Ще използваме HAProxy, разположен заедно с нашето приложение, за да разделим четенията и записите между възлите на Galera по кръговрат. Можете да проверите по-долу как HAProxy вижда клъстера Galera.
Цялата топология изглежда по-долу:
Трафикът се генерира с помощта на следната команда:
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
Схемата изглежда по-долу:
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)
Първо, нека видим как можем да добавим индекс към тази таблица. Добавянето на индекс е съвместима промяна, която може лесно да се направи с помощта на 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)
Както можете да видите в раздела Node, хостът, на който изпълнихме промяната, автоматично премина в състояние на донор/десинхронизирано, което гарантира, че този хост няма да засегне останалата част от клъстера, ако бъде забавен от ALTER.
Нека проверим как изглежда нашата схема сега:
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)
Както можете да видите, индексът е добавен. Моля, имайте предвид обаче, че това се случи само на този конкретен възел. За да извършите пълна промяна на схемата, трябва да следвате този процес на останалите възли на клъстера Galera. За да завършим с първия възел, можем да превключим wsrep_OSU_method обратно към TOI:
SET SESSION wsrep_OSU_method=TOI;
Query OK, 0 rows affected (0.00 sec)
Няма да показваме останалата част от процеса, защото е същият - активирайте RSU на ниво сесия, стартирайте ALTER, активирайте TOI. По-интересното е какво ще се случи, ако промяната е несъвместима. Нека отново да разгледаме набързо схемата:
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)
Да приемем, че искаме да променим типа на колоната „k“ от INT на VARCHAR(30) на един възел.
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
Сега, нека да разгледаме схемата:
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)
Всичко е както очакваме - колоната „k“ е променена на VARCHAR. Сега можем да проверим дали тази промяна е приемлива или не за клъстера Galera. За да го тестваме, ще използваме един от останалите, непроменени възли, за да изпълним следната заявка:
mysql> INSERT INTO sbtest1.sbtest1 (k, c, pad) VALUES (123, 'test', 'test');
Query OK, 1 row affected (0.19 sec)
Да видим какво се случи. Определено не изглежда добре - нашият възел е надолу. Регистрациите ще ви дадат повече подробности:
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…
Както се вижда, Галера се оплака от факта, че колоната не може да се преобразува от INT в VARCHAR(30). Той се опита да изпълни отново набора за запис четири пъти, но не успя, не е изненадващо. Като такъв, Galera установи, че консистенцията на възела е компрометирана и възелът е изхвърлен от клъстера. Оставащото съдържание на регистрационните файлове показва този процес:
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.
Разбира се, ClusterControl ще се опита да възстанови такъв възел – възстановяването включва стартиране на SST, така че несъвместимите промени в схемата ще бъдат премахнати, но ние ще се върнем на квадратното – промяната на нашата схема ще бъде обърната.
Както можете да видите, докато стартирането на RSU е много прост процес, отдолу може да бъде доста сложен. Изисква някои тестове и подготовка, за да сте сигурни, че няма да загубите възел само защото промяната на схемата не е съвместима.