You are here

Attribute Promotion und Demotion im MariaDB Galera Cluster

In der MariaDB Master/Slave Replikation gibt es ein Feature welches sich Attribute Promotion/Demotion nennt.

Das kann man in etwa übersetzten mit Spalten Erweiterung/Einschränkung.

Einfach gesagt geht es darum, wie sich der Slave verhält oder verhalten soll, wenn Master und Slave unterschiedliche Spalten-Definitionen oder gar eine unterschiedliche Anzahl von Spalten oder eine Unterschiedliche Reihenfolge der Spalten aufweisen.

Use case des Kunden

Diese Woche haben wir mit einem Kunden den Fall diskutiert, wie er ein Rolling-Schema-Upgrade (RSU) in einem Galera Cluster ausführen könnte.

Bei früheren Schema-Änderungen hat er immer Probleme gekriegt was bis zum Totalausfall des Clusters für mehrere Stunden geführt hat.

Der Kunde meint, dass niemals Spalten gelöscht und neue Spalten immer nur am Ende einer Tabelle hinzugefügt werden.

Und das NICHT sichergestellt werden kann, dass wärend des Rolling-Schema-Upgrades keine schreibenden Verbindungen mehr existieren.

Verwendet wird das PHP ORM-Framework Doctrine.

Was sagt die MariaDB Dokumentation dazu?

Das Studium der MariaDB Dokumentation führte zu keinem schlüssigen Resultat ob ein Rolling-Schema-Upgrade im laufenden Galera Cluster Betrieb MIT Änderungen (DML Statements) auf dem zu ändernden Schema (UND den zu ändernden Tabellen) unterstützt wird oder nicht und somit funktionieren sollte oder nicht.

Quelle: Rolling Schema Upgrade (RSU)

Beim Replizieren mit unterschiedlichen Tabellenstrukturen steht dazu nur Allgemeines zur Replikation aber nichts Spezifisches zu Galera (ob es geht oder nicht):

"Tables on the replica and the primary do not need to have the same definition in order for replication to take place. There can be differing numbers of columns, or differing data definitions and, in certain cases, replication can still proceed."

Quelle: Replication When the Primary and Replica Have Different Table Definitions

Für das Attribute Promotion/Demotion Feature gibt es einen speziellen MariaDB Server Konfigurationsparameter der das verhalten steuert: slave_type_conversions.

Wenn man hier zwischen den Zeilen liest, könnte das auch für Galera Cluster funktionieren:

"Determines the type conversion mode on the replica when using row-based replication, including replications in MariaDB Galera cluster."

Quelle: slave_type_conversions

Testplanung

Bei solchen Fragestellungen mache ich immer eine grobe Risikoabschätzung: Häufig verwendete und breit eingesetzte Features: Risiko für Probleme ist eher gering. Selten verwendete oder neue Features: Risiko für Probleme ist hoch! Diese Einschätzung beruht leider weniger auf handfesten Zahlen sondern mehr auf Erfahrung...

Mir ist keine einziger MariaDB Nutzer bekannt, der Rolling-Schema-Upgrade im laufenden Betrieb durchführt. Geschweige denn Schreiblast auf dem aktuellen Schema und den aktuellen Tabellen hat (Attribute Promotion/Demotion).
Also: Einsatz von Rolling-Schema-Upgrade x Häufigkeit der Verwendung von Attribute Promotion/Demotion ist sehr selten und somit das Risiko sehr hoch!

Da die Situation nicht klar ist und die Dokumentation nichts eindeutiges her gibt wurde getestet.

Um mögliche bereits gefixte MariaDB Bugs zu vermeiden wurde die neuste MariaDB 11.8.5 LTS Version verwendet.

An speziellen Datenbank Konfigurationsparameter wurde verwendet:

slave_type_conversions = 'ALL_NON_LOSSY,ALL_LOSSY'

Unsere Test-Tabelle sieht wie üblich wie folgt aus:

CREATE TABLE `test` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `data` varchar(128) DEFAULT NULL,
  `ts` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`)
);

Und Testdaten wurden wie folgt generiert:

INSERT INTO test VALUES (NULL, 'Some data to fill table up', NOW());
... -- 9 x

Befehle zur Überwachung der Tests:

SQL> SHOW GLOBAL VARIABLES LIKE 'slave_type_conversions';
SQL> SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment';
SQL> SHOW CREATE TABLE test\G
SQL> CHECKSUM TABLE test;
SQL> SELECT * FROM test;

Testen

Test 1: Attribute Promotion mit DML remote (auf Node C)

Dies ist der einfachste Fall: Eine Spalte wird hinzugefügt und ein Default-Wert festgelegt. Änderungen auf der Tabelle erfolgen auf einem anderen Knoten:

nodeA> SET SESSION wsrep_OSU_method = 'RSU';
nodeA> ALTER TABLE test ADD COLUMN c1 VARCHAR(64) NOT NULL DEFAULT 'foo';
nodeA> SET SESSION wsrep_OSU_method = 'TOI';

MariaDB Error Log Datei:

[Note] WSREP: Member 0.0 (Node A) desyncs itself from group
[Note] WSREP: Shifting SYNCED -> DONOR/DESYNCED (TO: 36)
[Note] WSREP: pause
[Note] WSREP: Provider paused at 22db9ea1-cb7b-11f0-b26e-6fbce72757f9:36 (43)
[Note] WSREP: Provider paused at: 36
[Note] WSREP: resume
[Note] WSREP: resuming provider at 43
[Note] WSREP: Provider resumed.
[Note] WSREP: Member 0.0 (Node A) resyncs itself to group.
[Note] WSREP: Shifting DONOR/DESYNCED -> JOINED (TO: 36)
[Note] WSREP: Processing event queue:... -nan% (0/0 events) complete.
[Note] WSREP: Member 0.0 (Node A) synced with group.
[Note] WSREP: Processing event queue:... 100.0% (1/1 events) complete.
[Note] WSREP: Shifting JOINED -> SYNCED (TO: 36)
[Note] WSREP: Server Node A synced with group

Dann die weiteren Befehle:

nodeC> INSERT INTO test VALUES (NULL, 'Some data to fill table up', NOW());
nodeC> UPDATE test SET data = 'Some data changed' WHERE id = 10;
nodeC> DELETE FROM test WHERE id = 19;

Anschliessend wurde der ALTER TABLE Befehl auf Knoten 2 und 3 ausgeführt.

Alle 3 Operationen haben einwandfrei funktioniert. Der Cluster ist noch voll funktionsfähig. Anschliessend:

SQL> ALTER TABLE test DROP COLUMN c1;

um das System für weitere Tests wieder in seinen Anfangszustand zurück zu überführen.

Test 2: Attribute Promotion mit DML remote (auf Node B)

Wenn man das gleiche Experiment auf Knoten B macht, fliegt einem der Cluster um die Ohren!

nodeB> INSERT INTO test VALUES (NULL, 'Some data to fill table up', NOW());

nodeC> SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment';
+---------------------------+--------------+
| Variable_name             | Value        |
+---------------------------+--------------+
| wsrep_local_state_comment | Inconsistent |
+---------------------------+--------------+

MariaDB Error Log Datei:

[Note] WSREP: Member 1(Node A) initiates vote on 22db9ea1-cb7b-11f0-b26e-6fbce72757f9:45,dbc9a6ea898b6a29:  Got error 171 "The event was corrupt, leading to illegal data being read" from storage engine InnoDB, Error_code: 1030;
[Note] WSREP: Votes over 22db9ea1-cb7b-11f0-b26e-6fbce72757f9:45:
   dbc9a6ea898b6a29:   1/3
Waiting for more votes.
[Note] WSREP: Member 2(Node C) initiates vote on 22db9ea1-cb7b-11f0-b26e-6fbce72757f9:45,dbc9a6ea898b6a29:  Got error 171 "The event was corrupt, leading to illegal data being read" from storage engine InnoDB, Error_code: 1030;
[Note] WSREP: Votes over 22db9ea1-cb7b-11f0-b26e-6fbce72757f9:45:
   dbc9a6ea898b6a29:   2/3
Winner: dbc9a6ea898b6a29
[Note] WSREP: Got vote request for seqno 22db9ea1-cb7b-11f0-b26e-6fbce72757f9:45
[Note] WSREP: Recovering vote result from history: 22db9ea1-cb7b-11f0-b26e-6fbce72757f9:45,dbc9a6ea898b6a29
[ERROR] WSREP: Vote 0 (success) on 22db9ea1-cb7b-11f0-b26e-6fbce72757f9:45 is inconsistent with group. Leaving cluster.
[Note] WSREP: Closing send monitor...
[Note] WSREP: Closed send monitor.
[Note] WSREP: gcomm: terminating thread
[Note] WSREP: gcomm: joining thread
[Note] WSREP: gcomm: closing backend
[Note] WSREP: view(view_id(NON_PRIM,1456a640-aca0,15) memb {
        1456a640-aca0,0
} joined {
} left {
} partitioned {
        97230fdb-b973,0
        a2e10f2c-8929,0
})
[Note] WSREP: PC protocol downgrade 1 -> 0
[Note] WSREP: view((empty))
[Note] WSREP: gcomm: closed
[Note] WSREP: New COMPONENT: primary = no, bootstrap = no, my_idx = 0, memb_num = 1
[Note] WSREP: Flow-control interval: [16, 16]
[Note] WSREP: Received NON-PRIMARY.
[Note] WSREP: Shifting SYNCED -> OPEN (TO: 45)
[Note] WSREP: New SELF-LEAVE.
[Note] WSREP: Flow-control interval: [0, 0]
[Note] WSREP: Received SELF-LEAVE. Closing connection.
[Note] WSREP: Shifting OPEN -> CLOSED (TO: 45)
[Note] WSREP: RECV thread exiting 0: Success
[Note] WSREP: recv_thread() joined.
[Note] WSREP: Closing send queue.
[Note] WSREP: Closing receive queue.
[Note] WSREP: ================================================
View:
  id: 22db9ea1-cb7b-11f0-b26e-6fbce72757f9:45
  status: non-primary
  protocol_version: 4
  capabilities: MULTI-MASTER, CERTIFICATION, PARALLEL_APPLYING, REPLAY, ISOLATION, PAUSE, CAUSAL_READ, INCREMENTAL_WS, UNORDERED, PREORDERED, STREAMING, NBO
  final: no
  own_index: 0
  members(1):
        0: 1456a640-cc36-11f0-aca0-e388a5f80ba9, Node B
=================================================
[Note] WSREP: Non-primary view
[Note] WSREP: Server status change synced -> connected
[Note] WSREP: wsrep_notify_cmd is not defined, skipping notification.
[Note] WSREP: ================================================
View:
  id: 22db9ea1-cb7b-11f0-b26e-6fbce72757f9:45
  status: non-primary
  protocol_version: 4
  capabilities: MULTI-MASTER, CERTIFICATION, PARALLEL_APPLYING, REPLAY, ISOLATION, PAUSE, CAUSAL_READ, INCREMENTAL_WS, UNORDERED, PREORDERED, STREAMING, NBO
  final: yes
  own_index: -1
  members(0):
=================================================
[Note] WSREP: Non-primary view
[Note] WSREP: Server status change connected -> disconnected
[Note] WSREP: wsrep_notify_cmd is not defined, skipping notification.
[Note] WSREP: Applier thread exiting ret: 6 thd: 2
[Note] WSREP: Applier thread exiting ret: 6 thd: 9
[Warning] Aborted connection 2 to db: 'unconnected' user: 'unauthenticated' host: '' (This connection closed normally without authentication)
[Note] WSREP: Applier thread exiting ret: 6 thd: 5
[Warning] Aborted connection 5 to db: 'unconnected' user: 'unauthenticated' host: '' (This connection closed normally without authentication)
[Warning] Aborted connection 9 to db: 'unconnected' user: 'unauthenticated' host: '' (This connection closed normally without authentication)
[Note] WSREP: Service thread queue flushed.
[Note] WSREP: ####### Assign initial position for certification: 00000000-0000-0000-0000-000000000000:-1, protocol version: 6
[Note] WSREP: Applier thread exiting ret: 0 thd: 6
[Warning] Aborted connection 6 to db: 'unconnected' user: 'unauthenticated' host: '' (This connection closed normally without authentication)

Keine Ahnung, warum Knoten B und C sich unterschiedlich verhalten. Aber damit wir der ganze Rolling-Schema-Upgrade (RSU) Prozess völlig willkürlich und unplanbar.

Das Ganze wurde in verschiedenen Varianten getestet und der Knoten wird mal inkonsistent und mal nicht.

Der inkonsistente Knoten B wird über einen erzwungenen SST wieder in den Cluster synchronisiert.

Test 3: Attribute Promotion mit DML remote (auf Node C)

Dieser Fall ist etwas heikler, da kein Default-Wert vorgegeben wird.

nodeA> SET SESSION wsrep_OSU_method = 'RSU';
nodeA> ALTER TABLE test ADD COLUMN c1 VARCHAR(64) NOT NULL;   -- DEFAULT ''
nodeA> SET SESSION wsrep_OSU_method = 'TOI';

nodeC> INSERT INTO test VALUES (NULL, 'Some data to fill table up', NOW());
nodeC> UPDATE test SET data = 'Some data changed' WHERE id = 13;
nodeC> DELETE FROM test WHERE id = 22;

Anschliessend wurde der ALTER TABLE Befehl auf Knoten 2 und 3 ausgeführt.

Alle 3 Operationen haben einwandfrei funktioniert. Der Cluster ist noch voll funktionsfähig.

Test 4: Attribute Promotion mit DML remote (auf Node B)

Gleicher Test aber der Befehl ALTER TABLE ADD COLUMN wird auf Knoten A und der DML Befehl auf Knoten B ausgeführt.

Knoten A und C werden "Inconsistent" und Knoten B ist noch "Synced".

Test 5: Attribute Promotion mit DML lokal (auf Node A)

Analoger Test aber der DML Befehl wird lokal, auf dem selben Knoten wie der DDL Befehl, ausgeführt.

nodeA> SET SESSION wsrep_OSU_method = 'RSU';
nodeA> ALTER TABLE test ADD COLUMN c1 VARCHAR(64) NOT NULL;   -- DEFAULT ''
nodeA> SET SESSION wsrep_OSU_method = 'TOI';

nodeA> INSERT INTO test VALUES (NULL, 'Some data to fill table up', NOW());
ERROR 1136 (21S01): Column count doesn't match value count at row 1

nodeA> INSERT INTO test (id, data, ts) VALUES (NULL, 'Some data to fill table up', NOW());
ERROR 1364 (HY000): Field 'c1' doesn't have a default value

nodeA> INSERT INTO test (id, data, ts, c1) VALUES (NULL, 'Some data to fill table up', NOW(), '');

nodeA> SELECT * FROM test;
ERROR 1047 (08S01): WSREP has not yet prepared node for application use

nodeA> SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment';
+---------------------------+--------------+
| Variable_name             | Value        |
+---------------------------+--------------+
| wsrep_local_state_comment | Inconsistent |
+---------------------------+--------------+

Cluster Knoten wurde inkonsistent. Interessanter weise hat bei weiteren Test das Ganze plötzlich geklappt! Also vollständig unvorhersagbar...

Test 6: DDL auf 2 Knoten

Neue Fragestellung: Was passiert nachdem der DDL Befehl auf 2 Knoten ausgeführt wurde und dann DML Befehle auftreten?

nodeA> SET SESSION wsrep_OSU_method = 'RSU';
nodeA> ALTER TABLE test ADD COLUMN c1 VARCHAR(64) NOT NULL DEFAULT 'foo';
nodeA> SET SESSION wsrep_OSU_method = 'TOI';

nodeB> SET SESSION wsrep_OSU_method = 'RSU';
nodeB> ALTER TABLE test ADD COLUMN c1 VARCHAR(64) NOT NULL DEFAULT 'foo';
nodeB> SET SESSION wsrep_OSU_method = 'TOI';

nodeC> INSERT INTO test (id, data, ts) VALUES (NULL, 'Some data to fill table up', NOW());

klappt, aber:

nodeB> INSERT INTO test (id, data, ts) VALUES (NULL, 'Some data to fill table up', NOW());

root@localhost [test]>  SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment';
+---------------------------+--------------+
| Variable_name             | Value        |
+---------------------------+--------------+
| wsrep_local_state_comment | Inconsistent |
+---------------------------+--------------+

Test 7: UPDATE und DELETE Befehle vom selben Knoten

nodeA> SET SESSION wsrep_OSU_method = 'RSU';
nodeA> ALTER TABLE test ADD COLUMN c1 VARCHAR(64) NOT NULL DEFAULT 'foo';
nodeA> SET SESSION wsrep_OSU_method = 'TOI';

nodeA> UPDATE test SET data = 'Some data changed' WHERE id = 16;

nodeA> SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment';
+---------------------------+--------------+
| Variable_name             | Value        |
+---------------------------+--------------+
| wsrep_local_state_comment | Inconsistent |
+---------------------------+--------------+

nodeA> DELETE FROM test WHERE id = 25;
nodeA> SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment';
+---------------------------+--------+
| Variable_name             | Value  |
+---------------------------+--------+
| wsrep_local_state_comment | Synced |
+---------------------------+--------+

...
[Warning] WSREP: Ignoring error 'Can't find record in 'test'' on Delete_rows_v1 event. Error_code: 1032
[Warning] Slave SQL: Could not execute Delete_rows_v1 event on table test.test; Can't find record in 'test', Error_code: 1032; Can't find record in 'test', Error_code: 1032; Can't find record in 'test', Error_code: 1032; Can't find record in 'test', Error_code: 1032; Can't find record in 'test', Error_code: 1032; Can't find record in 'test', Error_code: 1032; Can't find record in 'test', Error_code: 1032; Can't find record in 'test', Error_code: 1032; Can't find record in 'test', Error_code: 1032; Can't find record in 'test', Error_code: 1032; Can't find record in 'test', Error_code: 1032; Can't find record in 'test', Error_code: 1032; Can't find record in 'test', Error_code: 1032; Can't find record in 'test', Error_code: 1032; Can't find record in 'test', Error_code: 1032; Can't find record in 'test', Error_code: 1032; Can't find record in 'test', Error_code: 1032; Can't find record in 'test', Error_code: 1032; Can't find record in 'test', Error_code: 1032; Can't find record in 'test', Error_code: 1032; Can't find re
[ERROR] Slave SQL: Could not read field 'id' of table 'test.test', Internal MariaDB error code: 1610
[ERROR] mariadbd: Can't find record in 'test'
...

Prozessliste von Knoten C:

nodeC> SHOW PROCESSLIST;
+----+-------------+-----------+------+---------+------+-------------------------+----------------------------------------------------------------------------------------+----------+
| Id | User        | Host      | db   | Command | Time | State                   | Info                                                                                   | Progress |
+----+-------------+-----------+------+---------+------+-------------------------+----------------------------------------------------------------------------------------+----------+
|  2 | system user |           | NULL | Sleep   | 1670 | After apply log event   | NULL                                                                                   |    0.000 |
|  1 | system user |           | NULL | Sleep   | 4609 | wsrep aborter idle      | NULL                                                                                   |    0.000 |
|  7 | system user |           | NULL | Sleep   | 4608 |                         | NULL                                                                                   |    0.000 |
|  8 | system user |           | test | Sleep   | 1441 | Executing               | DELETE FROM test WHERE id = 25?5jR                                                     |    0.000 |
| 10 | system user |           | NULL | Sleep   | 2002 | wsrep applied write set | INSERT INTO test (id, data, ts) VALUES (NULL, 'Some data to fill table up', NOW()) 9O? |    0.000 |
| 28 | root        | localhost | NULL | Query   |    0 | starting                | show processlist                                                                       |    0.000 |
+----+-------------+-----------+------+---------+------+-------------------------+----------------------------------------------------------------------------------------+----------+

Knoten C ist aber immer noch Synced:

nodeC> SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment';
+---------------------------+--------+
| Variable_name             | Value  |
+---------------------------+--------+
| wsrep_local_state_comment | Synced |
+---------------------------+--------+

Herunterfahren des Knotens für zu folgender Fehlermeldung:

nodeC> SQL> shutdown;
ERROR 1047 (08S01): WSREP has not yet prepared node for application use

Anschliessend war der ganze Cluster nicht mehr nutzbar. Und musste neu gestartet werden (Bootstrap).

Zusammenfassung

Wir haben zu dieser Thematik einen Bug bei MariaDB eröffnet: Attribute Promotion/Demotion in Galera Cluster

Weitere Tests wurden vorerst nicht durchgeführt, da dieses Feature in der getesteten Version zu instabil ist und DROP COLUMN kein Use Case unseres Kunden ist.

Fazit: MariaDB Galera Cluster fängt diese Situation in der untersuchten Version nicht sauber ab, der Cluster verhindert diesen Fall auch nicht. Cluster Knoten werden als inkonsistent markiert. Unsere aktuelle Empfehlung: Rolling-Schema-Upgrade (RSU) mit konkurrierenden DML-Befehlen (INSERT, UPDATE, DELETE) auf den zu ändernden Tabellen mit der untersuchten Version NICHT tun!

Weitere Quellen:


Taxonomy upgrade extras: