Dit bericht is een voortzetting van ons vorige bericht over Online Schema Upgrade in Galera met behulp van de TOI-methode. We laten u nu zien hoe u een schema-upgrade uitvoert met behulp van de Rolling Schema Upgrade (RSU)-methode.
RSU en TOI
Zoals we hebben besproken, gebeurt er bij het gebruik van TOI tegelijkertijd een wijziging op alle knooppunten. Dit kan een serieuze beperking worden, aangezien een dergelijke manier om schemawijzigingen uit te voeren, inhoudt dat er geen andere query's kunnen worden uitgevoerd. Voor lange ALTER-instructies is het cluster mogelijk zelfs uren niet beschikbaar. Dit is natuurlijk niet iets wat je in productie kunt accepteren. De RSU-methode lost deze zwakte op - wijzigingen vinden plaats op één knooppunt tegelijk, terwijl andere knooppunten niet worden beïnvloed en verkeer kunnen bedienen. Zodra ALTER op één knooppunt is voltooid, wordt het opnieuw lid van het cluster en kunt u doorgaan met het uitvoeren van een schemawijziging op het volgende knooppunt.
Dergelijk gedrag heeft zijn eigen beperkingen. De belangrijkste is dat geplande schemawijziging compatibel moet zijn. Wat betekent het? Laten we er even over nadenken. Allereerst moeten we in gedachten houden dat het cluster de hele tijd actief is - het gewijzigde knooppunt moet al het verkeer kunnen accepteren dat de resterende knooppunten bereikt. Kortom, een DML die op het oude schema wordt uitgevoerd, moet ook werken op het nieuwe schema (en vice versa als je een soort van round-robin-achtige verbindingsdistributie gebruikt in je Galera-cluster). We zullen ons concentreren op de MySQL-compatibiliteit, maar u moet ook onthouden dat uw toepassing moet werken met zowel gewijzigde als niet-gewijzigde knooppunten - zorg ervoor dat uw wijziging de toepassingslogica niet verbreekt. Een goede gewoonte is om kolomnamen expliciet door te geven aan zoekopdrachten - vertrouw niet op "SELECT *" omdat u nooit weet hoeveel kolommen u ervoor terugkrijgt.
Galera en op rijen gebaseerd binair logformaat
Ok, dus DML moet werken op oude en nieuwe schema's. Hoe worden DML's overgedragen tussen Galera-knooppunten? Heeft het invloed op welke wijzigingen compatibel zijn en welke niet? Ja, inderdaad - dat doet het. Galera gebruikt geen reguliere MySQL-replicatie, maar vertrouwt er nog steeds op om gebeurtenissen tussen de knooppunten over te dragen. Om precies te zijn, Galera gebruikt het ROW-formaat voor evenementen. Een gebeurtenis in rij-indeling (na decodering) kan er als volgt uitzien:
### INSERT INTO `schema`.`table`
### SET
### @1=1
### @2=1
### @3='88764053989'
### @4='14700597838'
Of:
### UPDATE `schema`.`table`
### WHERE
### @1=1
### @2=1
### @3='88764053989'
### @4='14700597838'
### SET
### @1=2
### @2=2
### @3='88764053989'
### @4='81084251066'
Zoals je kunt zien, is er een zichtbaar patroon:een rij wordt geïdentificeerd door zijn inhoud. Er zijn geen kolomnamen, alleen hun volgorde. Dit alleen al zou enkele waarschuwingslampjes moeten doen branden:"wat zou er gebeuren als ik een van de kolommen verwijder?" Welnu, als het de laatste kolom is, is dit acceptabel. Als u een kolom in het midden zou verwijderen, zal dit de kolomvolgorde in de war brengen en als gevolg daarvan zal de replicatie worden verbroken. Iets soortgelijks zal gebeuren als je een kolom in het midden toevoegt, in plaats van aan het einde. Er zijn echter meer beperkingen. Het wijzigen van de kolomdefinitie werkt zolang het hetzelfde gegevenstype is - u kunt de INT-kolom wijzigen in BIGINT, maar u kunt de INT-kolom niet wijzigen in VARCHAR - dit verbreekt de replicatie. U kunt een gedetailleerde beschrijving vinden van welke wijziging compatibel is en wat niet in de MySQL-documentatie. Wat u ook in de documentatie kunt zien, om aan de veilige kant te blijven, is het beter om enkele tests uit te voeren op een afzonderlijk ontwikkelings-/staging-cluster. Zorg ervoor dat het niet alleen werkt volgens de documentatie, maar dat het ook goed werkt in jouw specifieke opstelling.
Al met al, zoals je duidelijk kunt zien, is het uitvoeren van RSU op een veilige manier veel complexer dan het uitvoeren van een paar commando's. Maar aangezien commando's belangrijk zijn, laten we eens kijken naar het voorbeeld van hoe u de RSU kunt uitvoeren en wat er mis kan gaan in het proces.
RSU-voorbeeld
Eerste installatie
Laten we ons een vrij eenvoudig voorbeeld van een toepassing voorstellen. We zullen een bechmark-tool, Sysbench, gebruiken om inhoud en verkeer te genereren, maar de stroom zal voor bijna elke toepassing hetzelfde zijn - Wordpress, Joomla, Drupal, noem maar op. We zullen HAProxy gebruiken samen met onze applicatie om lees- en schrijfbewerkingen te splitsen tussen Galera-knooppunten op een round-robin-manier. Hieronder kun je zien hoe HAProxy het Galera-cluster ziet.
De hele topologie ziet er als volgt uit:
Verkeer wordt gegenereerd met het volgende commando:
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
Schema ziet er als volgt uit:
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)
Laten we eerst eens kijken hoe we een index aan deze tabel kunnen toevoegen. Het toevoegen van een index is een compatibele wijziging die eenvoudig kan worden gedaan met 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)
Zoals je kunt zien op het tabblad Node, is de host waarop we de wijziging hebben uitgevoerd automatisch overgeschakeld naar de status Donor/Desynced, wat ervoor zorgt dat deze host geen invloed heeft op de rest van het cluster als deze wordt vertraagd door de ALTER.
Laten we eens kijken hoe ons schema er nu uitziet:
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)
Zoals u kunt zien, is de index toegevoegd. Houd er echter rekening mee dat dit alleen op dat specifieke knooppunt is gebeurd. Om een volledige schemawijziging door te voeren, moet u dit proces volgen op de resterende knooppunten van de Galera-cluster. Om te eindigen met het eerste knooppunt, kunnen we wsrep_OSU_method terugschakelen naar TOI:
SET SESSION wsrep_OSU_method=TOI;
Query OK, 0 rows affected (0.00 sec)
We gaan de rest van het proces niet laten zien, omdat het hetzelfde is:schakel RSU in op sessieniveau, voer ALTER uit, schakel TOI in. Wat interessanter is, is wat er zou gebeuren als de verandering onverenigbaar zou zijn. Laten we nog eens snel naar het schema kijken:
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)
Laten we zeggen dat we het type kolom 'k' willen wijzigen van INT in VARCHAR (30) op één knooppunt.
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
Laten we nu eens naar het schema kijken:
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)
Alles is zoals we verwachten - kolom 'k' is gewijzigd in VARCHAR. Nu kunnen we controleren of deze wijziging acceptabel is of niet voor de Galera Cluster. Om het te testen, zullen we een van de resterende, ongewijzigde nodes gebruiken om de volgende query uit te voeren:
mysql> INSERT INTO sbtest1.sbtest1 (k, c, pad) VALUES (123, 'test', 'test');
Query OK, 1 row affected (0.19 sec)
Laten we eens kijken wat er gebeurd is. Het ziet er absoluut niet goed uit - ons knooppunt is uitgevallen. Logboeken geven u meer details:
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…
Zoals te zien is, klaagde Galera over het feit dat de kolom niet kan worden geconverteerd van INT naar VARCHAR(30). Het probeerde de schrijfset vier keer opnieuw uit te voeren, maar dat mislukte, niet verwonderlijk. Als zodanig heeft Galera vastgesteld dat de consistentie van het knooppunt is aangetast en wordt het knooppunt uit het cluster geschopt. De resterende inhoud van de logs toont dit proces:
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.
Natuurlijk zal ClusterControl proberen om zo'n knooppunt te herstellen - herstel omvat het uitvoeren van SST, dus incompatibele schemawijzigingen worden verwijderd, maar we zijn weer terug bij af - onze schemawijziging wordt teruggedraaid.
Zoals je kunt zien, is het uitvoeren van RSU een heel eenvoudig proces, maar daaronder kan het nogal complex zijn. Het vereist enkele tests en voorbereidingen om ervoor te zorgen dat u geen node verliest alleen omdat de schemawijziging niet compatibel was.