You are here

MaxScale Konfigurations-Synchronisation

Inhaltsverzeichnis


Übersicht

Ein Feature, welches ich beim Stöbern kürzlich entdecke habe, ist die MaxScale Konfigurations-Synchronisation Funktionalität.

Es geht hier nicht primär um einen MariaDB Replikations-Cluster oder einen MariaDB Galera Cluster sondern um einen Cluster bestehend aus zwei oder mehreren MaxScale Knoten. Bzw. etwas genauer gesagt, den Austausch der Konfiguration unter diesen MaxScale Knoten.

Pon Suresh Pandian hat bereits 2022 einen Blog-Artikel über dieses Feature geschrieben, der noch etwas ausführlicher ist, als dieser Beitrag hier.

Vorbereitungen

Es wurde eine Incus Container-Umgebung vorbereitet, bestehend aus 3 Datenbank-Containern (deb12-n1 (10.139.158.33), deb12-n2 (10.139.158.178), deb12-n3(10.139.158.39)) und 2 MaxScale Containern (deb12-mxs1 (10.139.158.66), deb12-mxs2(10.139.158.174)). Die Datenbankversion ist eine MariaDB 10.11.6 aus dem Debian-Repository und MaxScale wurde in der Version 22.08.5 von der MariaDB plc Website heruntergeladen.

Die Datenbank-Konfiguration sieht für alle 3 Knoten analog aus:

#
# /etc/mysql/mariadb.conf.d/99-fromdual.cnf
#

[server]

server_id               = 1
log_bin                 = deb12-n1-binlog
binlog_format           = row
bind_address            = *
proxy_protocol_networks = ::1, 10.139.158.0/24, localhost
gtid_strict_mode        = on
log_slave_updates       = on
skip_name_resolve       = on

Die MaxScale Knoten wurden wie im Artikel Sharding mit MariaDB MaxScale beschrieben gebaut.

Der maxscale_admin User hat genau die selben Rechte wie dort beschrieben, der maxscale_monitor User hat folgende Rechte erhalten:

RELOAD, SUPER, REPLICATION SLAVE, READ_ONLY ADMIN

Siehe auch hier: Required Grants.

Die MaxScale Start-Konfiguration sieht wie folgt aus:

#
# /etc/maxscale.cnf
#

[maxscale]
threads                      = auto
admin_gui                    = false

[deb12-n1]
type                         = server
address                      = 10.139.158.33
port                         = 3306
proxy_protocol               = true

[deb12-n2]
type                         = server
address                      = 10.139.158.178
port                         = 3306
proxy_protocol               = true

[Replication-Monitor]
type                         = monitor
module                       = mariadbmon
servers                      = deb12-n1,deb12-n2
user                         = maxscale_monitor
password                     = secret
monitor_interval             = 500ms
auto_failover                = true
auto_rejoin                  = true
enforce_read_only_slaves     = true
replication_user             = replication
replication_password         = secret
cooperative_monitoring_locks = majority_of_running

[WriteListener]
type                         = listener
service                      = WriteService
port                         = 3306

[WriteService]
type                         = service
router                       = readwritesplit
servers                      = deb12-n1,deb12-n2
user                         = maxscale_admin
password                     = secret
transaction_replay           = true
transaction_replay_timeout   = 30s

Wichtig: Die Konfiguration sollte auf allen MaxScale Knoten gleich aussehen!

Und dann noch ein paar Checks um sicher zu sein, dass alles stimmt:

shell> maxctrl list listeners
┌───────────────┬──────┬──────┬─────────┬──────────────┐
│ Name          │ Port │ Host │ State   │ Service      │
├───────────────┼──────┼──────┼─────────┼──────────────┤
│ WriteListener │ 3306 │ ::   │ Running │ WriteService │
└───────────────┴──────┴──────┴─────────┴──────────────┘

shell> maxctrl list services
┌──────────────┬────────────────┬─────────────┬───────────────────┬────────────────────┐
│ Service      │ Router         │ Connections │ Total Connections │ Targets            │
├──────────────┼────────────────┼─────────────┼───────────────────┼────────────────────┤
│ WriteService │ readwritesplit │ 0           │ 0                 │ deb12-n1, deb12-n2 │
└──────────────┴────────────────┴─────────────┴───────────────────┴────────────────────┘

shell> maxctrl list servers
┌──────────┬────────────────┬──────┬─────────────┬─────────────────┬────────┬─────────────────────┐
│ Server   │ Address        │ Port │ Connections │ State           │ GTID   │ Monitor             │
├──────────┼────────────────┼──────┼─────────────┼─────────────────┼────────┼─────────────────────┤
│ deb12-n1 │ 10.139.158.33  │ 3306 │ 0           │ Master, Running │ 0-1-19 │ Replication-Monitor │
├──────────┼────────────────┼──────┼─────────────┼─────────────────┼────────┼─────────────────────┤
│ deb12-n2 │ 10.139.158.178 │ 3306 │ 0           │ Slave, Running  │ 0-1-19 │ Replication-Monitor │
└──────────┴────────────────┴──────┴─────────────┴─────────────────┴────────┴─────────────────────┘

SQL> SELECT @@hostname, test.* FROM test.test;
+------------+----+-----------+---------------------+
| @@hostname | id | data      | ts                  |
+------------+----+-----------+---------------------+
| deb12-n2   |  1 | Some data | 2024-03-26 09:40:21 |
+------------+----+-----------+---------------------+

SQL> SELECT @@hostname, test.* FROM test.test FOR UPDATE;
+------------+----+-----------+---------------------+
| @@hostname | id | data      | ts                  |
+------------+----+-----------+---------------------+
| deb12-n1   |  1 | Some data | 2024-03-26 09:40:21 |
+------------+----+-----------+---------------------+

Und noch ein weiterer Test ob MaxScale den Failover auch wirklich richtig ausführt:

shell> systemctl stop mariadb
2024-03-26 16:27:05   error  : Monitor was unable to connect to server deb12-n2[10.139.158.178:3306] : 'Can't connect to server on '10.139.158.178' (115)'
2024-03-26 16:27:05   notice : Server changed state: deb12-n2[10.139.158.178:3306]: master_down. [Master, Running] -> [Down]
2024-03-26 16:27:05   warning: [mariadbmon] Primary has failed. If primary does not return in 4 monitor tick(s), failover begins.
2024-03-26 16:27:07   notice : [mariadbmon] Selecting a server to promote and replace 'deb12-n2'. Candidates are: 'deb12-n1'.
2024-03-26 16:27:07   notice : [mariadbmon] Selected 'deb12-n1'.
2024-03-26 16:27:07   notice : [mariadbmon] Performing automatic failover to replace failed primary 'deb12-n2'.
2024-03-26 16:27:07   notice : [mariadbmon] Failover 'deb12-n2' -> 'deb12-n1' performed.
2024-03-26 16:27:07   notice : Server changed state: deb12-n1[10.139.158.33:3306]: new_master. [Slave, Running] -> [Master, Running]

shell> systemctl start mariadb
2024-03-26 16:28:03   notice : Server changed state: deb12-n2[10.139.158.178:3306]: server_up. [Down] -> [Running]
2024-03-26 16:28:03   notice : [mariadbmon] Directing standalone server 'deb12-n2' to replicate from 'deb12-n1'.
2024-03-26 16:28:03   notice : [mariadbmon] Replica connection from deb12-n2 to [10.139.158.33]:3306 created and started.
2024-03-26 16:28:03   notice : [mariadbmon] 1 server(s) redirected or rejoined the cluster.
2024-03-26 16:28:03   notice : Server changed state: deb12-n2[10.139.158.178:3306]: new_slave. [Running] -> [Slave, Running]

Welcher MaxScale Knoten gerade für das Monitoring und Failover zuständig ist (cooperatve_monitoring) kann wir folgt eruiert werden:

shell> maxctrl show monitor Replication-Monitor | grep -e 'Diagnostics' -e '"primary"' -e 'lock_held' | uniq
│ Monitor Diagnostics │ {                                                                                                                                                                                                                      │
│                     │     "primary": true,                                                                                                                                                                                                   │
│                     │             "lock_held": true,                                                                                                                                                                                         │

Es sollte sichergestellt sein, das bis hier hin alles sauber funktioniert. Ansonsten haben die weiteren Schritte keinen wirklichen Sinn.

MaxScale Konfigurations-Synchronisation aktivieren

Für die Konfigurations-Synchronisation braucht es einen eigenen Datenbank-User mit folgenden Rechten:

SQL> CREATE USER 'maxscale_confsync'@'%' IDENTIFIED BY 'secret';
SQL> GRANT SELECT, INSERT, UPDATE, CREATE ON `mysql`.`maxscale_config` TO maxscale_confsync@'%';

Anschliessend muss MaxScale entsprechend konfiguriert werden (auf beiden MaxScale Knoten), damit die Konfigurations-Synchronisation aktiviert wird. Diese Konfiguration erfolgt im globalen MaxScale Abschnitt:

#
# /etc/maxscale.cnf
#

[maxscale]
config_sync_cluster  = Replication-Monitor
config_sync_user     = maxscale_confsync
config_sync_password = secret

Dann erfolgt der Neustart der MaxScale Knoten:

shell> systemctl restart maxscale

Die MaxScale Konfigurations-Synchronisation kann auch dynamisch aktiviert und deaktiviert werden:

shell> maxctrl show maxscale | grep config_sync
│              │     "config_sync_cluster": null,                                        │
│              │     "config_sync_db": "mysql",                                          │
│              │     "config_sync_interval": "5000ms",                                   │
│              │     "config_sync_password": null,                                       │
│              │     "config_sync_timeout": "10000ms",                                   │
│              │     "config_sync_user": null,                                           │

Hier ist wichtig, die richtige Reihenfolge der 3 Befehle einzuhalten, da es sonst einen Fehler gibt:

shell> MAXCTRL_WARNINGS=0 maxctrl alter maxscale config_sync_user='maxscale_confsync'
shell> MAXCTRL_WARNINGS=0 maxctrl alter maxscale config_sync_password='secret'
shell> MAXCTRL_WARNINGS=0 maxctrl alter maxscale config_sync_cluster='Replication-Monitor'

MaxScale Parameter ändern

Als ersten Test haben wir die MaxScale Monitor Variable monitor_interval ins Auge gefasst, die in diesem Fall auf beiden MaxScale-Knoten sogar unterschiedlich ist:

shell>  maxctrl show monitor Replication-Monitor | grep monitor_interval
│                     │     "monitor_interval": "750ms",

shell> maxctrl show monitor Replication-Monitor | grep monitor_interval
│                     │     "monitor_interval": "1000ms",

Mit dem alter monitor Befehl kann die Variable jetzt auf einem MaxScale Knoten gesetzt werden:

shell> MAXCTRL_WARNINGS=0 maxctrl alter monitor Replication-Monitor monitor_interval=500ms
OK

was einerseits im MaxScale Error Log ersichtlich ist:

2024-03-26 14:09:16   notice : (ConfigManager); Updating to configuration version 1

anderseits sollte der Wert innerhalb von 5 Sekunden (config_sync_interval) auf den zweiten MaxScale Knoten propagiert werden, was mit dem obigen Befehl überprüft werden kann.

Neuer Slave hinzufügen und MaxScale bekannt machen

Ein neuer Slave (deb12-n3) wird zuerst erstellt und dem MariaDB Replikations-Cluster von Hand hinzugefügt. Anschliessen wird der Slave einem MaxScale Knoten bekannt gemacht:

shell> maxctrl create server deb12-n3 10.139.158.39

shell> MAXCTRL_WARNINGS=0 maxctrl link monitor Replication-Monitor deb12-n3
OK

shell> MAXCTRL_WARNINGS=0 maxctrl link service WriteService deb12-n3
OK

shell> maxctrl list servers
┌──────────┬────────────────┬──────┬─────────────┬─────────────────┬────────────┬─────────────────────┐
│ Server   │ Address        │ Port │ Connections │ State           │ GTID       │ Monitor             │
├──────────┼────────────────┼──────┼─────────────┼─────────────────┼────────────┼─────────────────────┤
│ deb12-n1 │ 10.139.158.33  │ 3306 │ 3           │ Slave, Running  │ 0-2-479618 │ Replication-Monitor │
├──────────┼────────────────┼──────┼─────────────┼─────────────────┼────────────┼─────────────────────┤
│ deb12-n2 │ 10.139.158.178 │ 3306 │ 3           │ Master, Running │ 0-2-479618 │ Replication-Monitor │
├──────────┼────────────────┼──────┼─────────────┼─────────────────┼────────────┼─────────────────────┤
│ deb12-n3 │ 10.139.158.39  │ 3306 │ 1           │ Slave, Running  │ 0-2-479618 │ Replication-Monitor │
└──────────┴────────────────┴──────┴─────────────┴─────────────────┴────────────┴─────────────────────┘

Alter Slave entfernen und MaxScale bekannt machen

Bevor ein Slave gelöscht werden kann, sollte er bei einem MaxScale Knoten aus dem Replikations-Cluster entfernt werden:

shell> maxctrl destroy server deb12-n1 --force
OK

shell> maxctrl list servers
┌──────────┬────────────────┬──────┬─────────────┬─────────────────┬────────────┬─────────────────────┐
│ Server   │ Address        │ Port │ Connections │ State           │ GTID       │ Monitor             │
├──────────┼────────────────┼──────┼─────────────┼─────────────────┼────────────┼─────────────────────┤
│ deb12-n2 │ 10.139.158.178 │ 3306 │ 3           │ Master, Running │ 0-2-493034 │ Replication-Monitor │
├──────────┼────────────────┼──────┼─────────────┼─────────────────┼────────────┼─────────────────────┤
│ deb12-n3 │ 10.139.158.39  │ 3306 │ 1           │ Slave, Running  │ 0-2-493032 │ Replication-Monitor │
└──────────┴────────────────┴──────┴─────────────┴─────────────────┴────────────┴─────────────────────┘

Anschliessend kann der Slave abgebaut werden.

Wie wird die Konfiguration synchronisiert?

Die Konfiguration der beiden MaxScale Knoten wird über die Datenbank synchronisiert, was ich persönlich als unglückliche Design-Entscheidung ansehe, da eine Konfigurationsänderung potentiell hackelt, wenn der Master kaputt geht oder Netzwerkprobleme zwischen den Datenbanknoten auftreten...

Die Konfiguration wird in der Tabelle mysql.maxscale_config abgespeichert, welche wie folgt aussieht:

CREATE TABLE `maxscale_config` (
  `cluster` varchar(256) NOT NULL,
  `version` bigint(20) NOT NULL,
  `config` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`config`)),
  `origin` varchar(254) NOT NULL,
  `nodes` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`nodes`)),
  PRIMARY KEY (`cluster`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

Diese Tabelle hat in etwa folgenden Inhalt:

SQL> SELECT cluster, version, CONCAT(SUBSTR(config, 1, 32), ' ... ', SUBSTR(config, -32)) AS config , origin, nodes FROM mysql.maxscale_config;
+---------------------+---------+-----------------------------------------------------------------------+------------+------------------------------------------+
| cluster             | version | config                                                                | origin     | nodes                                    |
+---------------------+---------+-----------------------------------------------------------------------+------------+------------------------------------------+
| Replication-Monitor |       2 | {"config":[{"id":"deb12-n1","typ ... ter_name":"Replication-Monitor"} | deb12-mxs1 | {"deb12-mxs1": "OK", "deb12-mxs2": "OK"} |
+---------------------+---------+-----------------------------------------------------------------------+------------+------------------------------------------+

Eine lokale Kopie liegt zur Sicherheit auf jedem Knoten vor:

shell> cut -b-32 /var/lib/maxscale/maxscale-config.json
{"config":[{"id":"deb12-n2","typ

Was passiert im Konfliktfall?

Siehe hierzu auch: Error Handling in Configuration Synchronization

Wenn zeitgleich (innerhalb von config_sync_interval?) auf zwei unterschiedlichen MaxScale Knoten die Konfiguration geändert wird erhalten wir folgende Fehlermeldung:

Error: Server at http://127.0.0.1:8989 responded with 400 Bad Request to `PATCH monitors/Replication-Monitor`
{
    "errors": [
        {
            "detail": "Cannot start configuration change: Configuration conflict detected: version stored in the cluster (3) is not the same as the local version (2), MaxScale is out of sync."
        }
    ]
}

Folgender Befehl hilft allenfalls bei grösseren Störungen das Problem zu erkennen:

shell> maxctrl show maxscale | grep -A9 'Config Sync'
│ Config Sync  │ {                                                                       │
│              │     "checksum": "0052fe6f775168bf00778abbe37775f6f642adc7",             │
│              │     "nodes": {                                                          │
│              │         "deb12-mxs1": "OK",                                             │
│              │         "deb12-mxs2": "OK"                                              │
│              │     },                                                                  │
│              │     "origin": "deb12-mxs2",                                             │
│              │     "status": "OK",                                                     │
│              │     "version": 3                                                        │
│              │ }                                                                       │

Tests

Alle Tests wurden auch unter Last ausgeführt. Folgende Tests liefen parallel:

  • insert_test.php
  • insert_test.sh
  • mixed_test.php
  • while [ true ] ; do mariadb -s --user=app --host=10.139.158.174 --port=3306 --password=secret --execute='SELECT @@hostname, COUNT(*) FROM test.test GROUP BY @@hostname' ; sleep 0.5 ; done
  • while [ true ] ; do mariadb -s --user=app --host=10.139.158.174 --port=3306 --password=secret --execute='SELECT @@hostname, COUNT(*) FROM test.test GROUP BY @@hostname FOR UPDATE' ; sleep 0.5 ; done

Alle Tests sind bei allen Manipulationen einwandfrei und ohne Probleme durchgelaufen.

MaxScale Konfigurations-Synchronisation wieder deaktivieren

Auf beiden MaxScale Knoten ist folgender Befehl auszuführen und damit ist die Konfigurations-Synchronisation wieder beendet:

shell> MAXCTRL_WARNINGS=0 maxctrl alter maxscale config_sync_cluster=''

Literatur/Quellen