Eclipse, Git, SSH2, Einträge in der ~/.ssh/config, Bug bei Ciphers mit Domain-Angabe

Wenn man mit Eclipse (Oxygen2, Neon3) Git-Repositories auf Remote-Servern versorgen will, macht man das natürlich über SSH. Ich hatte in einem früheren Blogbeitrag schon mal auf einen Artikel

https://stribika.github.io/2015/01/04/secure-secure-shell.html

hingewiesen, in dem verschiedene Härtungsmaßnahmen aufgezeigt werden. Natürlich verwendet man dabei auch eine Public Key Authentication, wobei der öffentliche RSA-Schlüssel in hinreichender Länge (> 2048 Bit) auf dem Server implementiert wird. Er ist dort wie üblich beim entsprechenden externen User in die Datei "~/.ssh/authorized_keys" einzukopieren.

Unter Eclipse ist dann in jedem Fall noch Folgendes erforderlich: Unter dem Menüpunkt

Window >> Preferences >> General >> Network Connections >> SSH2

trägt man im Fensterchen zum "General"-Tab das Verzeichnis ein, in dem man seine privaten Keys aufbewahrt - und listet zudem die von Eclipse zu verwendenden Keys für die verschiedenen Git- bzw. SSH-Server auf. Das sieht dann etwa so aus:

Ohne diese Einträge funktioniert eine key-basierte SSH-Authentifizierung unter Eclipse nicht. Das gilt nicht nur für Git sondern auch für andere Arten von SSH-Verbindungen etwa unter den "Remote Systems"-Modulen, für SFTP- und für SSH-Shell-Zugriffe. Die korrespondierende Git-Push-Konfiguration für SSH kann dann etwa wie folgt aussehen:

"55511" sei dabei der gewählte SSH-Port, unter dem der Server erreichbar sein soll. Der Leser muss an dieser Stelle natürlich die für ihn geletenden Einträge vorzunehmen.

Auf einem typischen OpenSSH-Server unter Linux lassen sich die Vorgaben des oben genannten Artikels zu Einträgen in der SSH-Konfigurationsdatei "/etc/ssh/sshd_config" problemlos umsetzen. Etwas anders sieht es aber auf dem OpenSSH-Client-System aus. Dort würde man gerne zumindest folgende empfohlenen Härtungseinträge in der Datei "~/.ssh/config" unterbringen:

Korrekte Einträge in der OpenSSH "~/.ssh/config"-Datei, die aber unter Eclipse nicht funktionieren:

  
Host myserv.myhosteddomain.net
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes256-cbc

Nutzt man die Zeile für "Ciphers" in der angegebenen Form, so funktioniert dies zwar für eine native SSH-Verbindung von der Kommandozeile eines Terminalfensters aus; Eclipse reagiert jedoch traurigerweise mit einer Exception "..... java.lang.NullPointerException".

Ursache dafür ist eine falsche Behandlung von Domain-Angaben in den Cipher-Verfahren durch die zuständigen Eclipse-Module. Interessanterweise funktionieren aber die Domain-Angaben hinter den KEX-Verfahren!

Folgende Variante funktioniert mit den SSH-Modulen von Eclipse:

Funktionierende SSH-config-Datei für Eclipse:

  
Host myserv.myhosteddomain.net
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256
# The following does not work in Eclipse 
# Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr,aes192-cbc
# This works, however: 
Ciphers aes256-cbc,aes256-ctr 

Es ist irgendwie traurig, dass Eclipse sich in puncto SSH und Cipher-Angaben fehlerhaft verhält; denn man steht nun vor einer (geringfügigen) Einschränkung der Sicherheit. 256Bit basierte AES-Verfahren sollten im Moment allerdings noch ausreichen.

Ich hoffe, das hilft dem einen oder anderen, der auf die oben genannte Exception stößt. Zu hoffen bleibt auch, dass die Eclipse-Entwickler solche Bugs baldmöglichst beseitigen.

MariaDB, Master/Slave – Master/Slave-Replikation – Synchronisation der Binary Logs mit dem Storage ? – II

Im letzten Beitrag hatte ich eine row-basierte Master/Slave-Replikation zwischen zwei MariaDB-Servern konfiguriert und mit kontinuierlichen INSERTs getestet. Ein Ergebnis war, dass es bei einer Dauerlast auf dem Master und einer hochfrequenten Synchronisation der Log-Files auf dem Slave-System mit Festplatten durchaus zu einem systematischen Anwachsen der "Catch Up"-Zeit zwischen Master- und Slave-System kommen kann. Als Ursache erschien plausibel, dass der Slave mehr mit Updates der Logfiles und deren Synchronisation [SYNC] in Richtung Storage-System beschäftigt ist als mit der Durchführung der SQL-Änderungen in der Datenbank selbst.

Ein Heilmittel bestand darin, die Sychronisation der Log-Files im Bedarfsfall nicht, wie üblicherweise empfohlen, nach jeder Änderung sondern erst nach mehreren Änderungen vornehmen zu lassen. Das RDBMS kann dann wieder Commits in hinreichender Anzahl ausführen und der Änderungsfrequenz auf dem Master-RDBMS nahtlos folgen.

Im vorliegenden Beitrag überprüfen wir diesen Befund nun mittels eines etwas komplexeren Szenarios - nämlich einer parallel stattfindenden "Master/Slave - Master/Slave"-Replikation zwischen zwei Servern für zwei unterschiedliche Datenbanken (Schemata).

Konfiguration des ersten Servers

Die Skizze aus dem letzten Beitrag zeigt die Konfiguration schematisch:

Wir haben zwei RDBMS-Serversysteme "histux" und "hertux". "hertux" fungiert als Master bzgl. der Datenbank "Kirk", "histux" dagegen als Master bzgl. der Datenbank "Spock". Repliziert wird zum jeweils anderen Server.

Die für die Replikation relevanten Einträge in der Konfigurationsdatei "/etc/my.cnf" auf dem Server "hertux" sehen dann etwa wie folgt aus:

Server "hertux":

[mysqld]
# Enforce GTID consistency
#gtid-mode=ON
#enforce_gtid_consistency=1

# Binary Log 
log_bin=/var/lib/mysql/mysql-bin
binlog_format=row 
binlog_do_db='Kirk'

# Slave with respect to "Spock"
relay_log=/var/lib/mysql/relay-bin
skip_slave_start
log_slave_updates=1

slave-parallel-threads=10
replicate_do_db='Spock' 

# SYNC statements
sync_binlog=1
sync_master_info=2
sync_relay_log=2
sync_relay_log_info=2

# Server ID
server-id	= 2

# These are commonly set, remove the # and set as required.
# port = 3306
...
...

 
Die meisten Einträge kennen wir bereits aus dem letzten Artikel. Die Statements, die sich auf die Slave-Rolle beziehen, wurden dort für das System "histux" erläutert. Sie sind nun in gleicher Weise auf dem System "hertux" für die Datenbank "Spock" zur Anwendung zu bringen.

Neu ist dagegen das Statement

binlog_do_db='Kirk'

Dieses Statement schränkt die Aufzeichnung der zu replizierenden Master-Änderungen(SQL-Statements oder Row-Änderungen) im Binary Log File auf die Datenbank "Kirk" ein! Das ist im vorliegenden Fall harmlos. Anders sähe die Situation dagegen im Fall einer statement-basierten Replikation aus: Die Einschränkung bezieht sich nämlich immer nur auf die aktive Bank! Das hat Auswirkungen bei bank-übergreifenden SQL-Statements! Im Fall statement-basierter Replikaton sollte man deshalb die Warnungen unter "https://dev.mysql.com/doc/refman/5.7/en/replication-options-binary-log.html" beachten:

Zitat bzgl. der Variable "binlog-do-db=db_name":
.... Statement-based logging: Only those statements are written to the binary log where the default database (that is, the one selected by USE) is db_name. To specify more than one database, use this option multiple times, once for each database; however, doing so does not cause cross-database statements such as UPDATE some_db.some_table SET foo='bar' to be logged while a different database (or no database) is selected. ..." (Hervorhebung durch mich).

Für die im aktuellen Szenario verwendete "row-basierte" Replikation ist die Situation jedoch einfacher.

Zitat:
"... Row-based logging: Logging is restricted to database db_name. Only changes to tables belonging to db_name are logged; the default database has no effect on this. Suppose that the server is started with --binlog-do-db=sales and row-based logging is in effect, and then the following statements are executed:

USE prices;
UPDATE sales.february SET amount=amount+100;

The changes to the february table in the sales database are logged in accordance with the UPDATE statement; this occurs whether or not the USE statement was issued. However, when using the row-based logging format and --binlog-do-db=sales, changes made by the following UPDATE are not logged:

USE prices;
UPDATE prices.march SET amount=amount-25;

Even if the USE prices statement were changed to USE sales, the UPDATE statement's effects would still not be written to the binary log. ...."

Das entspricht aus meiner Sicht doch recht gut der Erwartungshaltung eines Anwenders.

Bzgl. seiner zweiten Rolle als Slave-Systems schränkt das Statement

replicate_do_db='Spock'

die Replikation auf die Bank "Spock" ein; "replicate_do_db" ist das slave-bezogene Gegenstück zum Statement "binlog_do_db" für die Master-Rolle.

Im Falle einer Replikation mehrerer Datenbanken ist zudem folgender Punkt zu beachten:

"To specify multiple databases you must use multiple instances of this option. Because database names can contain commas, the list will be treated as the name of a single database if you supply a comma-separated list."

Konfiguration des zweiten Servers

Für den Server "histux" nehmen wir eine gespiegelte Konfiguration vor:

Server "histux"

[mysqld]
# Enforce GTID consistency
#gtid_mode=ON
#enforce_gtid_consistency=1

# Binary Log File 
log_bin=/var/lib/mysql/mysql-bin
binlog_format=row 
binlog_do_db='Spock'

# Slave with respect to database "Kirk"
relay_log=/var/lib/mysql/relay-bin
skip_slave_start
log_slave_updates=1
slave-parallel-threads=10
replicate_do_db='Kirk' 

# SYNC statements  
sync_binlog=1
sync_master_info=2
sync_relay_log=2
sync_relay_log_info=2

#Server ID 
server-id	= 3

# These are commonly set, remove the # and set as required.
port = 3386
...
...

 
Wir haben hier zusätzlich den MySQL-Port geändert! Natürlich müssen festgelegte Ports für die Replikation in evtl. zwischengeschalteten Firewalls geöffnet werden.

Test 1: 10000 INSERTs auf beiden Servern / SYNC-Werte ≤ 2 / Replikationsstart zeitlich versetzt

Auf beiden Servern sehen wir - wie im letzten Artikel beschrieben - einen manuellen Start der Replikation vor (skip_slave_start !). Den priviligierten Datenbank-User für die Durchführung von Replikationen hatten wir bereits im letzten Artikel auf beiden RDBMS angelegt.

Wie im letzten Artikel erzeugen wir auf beiden Servern für eine begrenzte Zeit von ca. 80 Sek. eine Dauerlast durch permanente Durchführung von 10000 Einzel-INSERTs in Tabellen der Bank "Kirk" auf "hertux" und nun such in Tabellen der Bank "Spock" auf "histux". Nach dem Start des Testprograms starten wir auf beiden Servern etwas zeitversetzt die jeweilige Slave-Replikation. Vor dem Replikationsstart auf "hertux" müssen wir allerdings noch (einmalig) die Daten für den Master-Server "histux" (DB Spock) am MySQL-Prompt setzen; dies geschieht in Anlehnung an das Vorgehen im letzten Artikel durch folgende Statements:

MariaDB [Spock]> CHANGE MASTER TO MASTER_HOST='histux.mynet.de',  MASTER_USER='repl', MASTER_PASSWORD='MyReplPasswd', MASTER_PORT=3386;
MariaDB [Spock]> CHANGE MASTER TO master_use_gtid=slave_pos;
MariaDB [Spock]> START SLAVE;

Für SYNC-Werte

sync_binlog=1
sync_master_info=2
sync_relay_log=2
sync_relay_log_info=2

auf beiden Servern erhielt ich für meine HW-Konfiguration ein systematisch anwachsendes Nachlaufen eines der beiden RDBMS:

30 < Catch Up-Zeit < 60 Sekunden

Welcher Server hinterherhinkte und welchen genauen Wert die Catch Up-Zeit annahm, hing u.a. davon ab

  • auf welchem Server die INSERTs zuerst gestartet wurden,
  • und wie groß der zeitliche Versatz des Starts der Slave-Replikationen gewählt wurde.

Festzuhalten bleibt aber auch diesmal, dass ein systematisches Anwachsen der Catch Up-Zeit durchaus möglich ist, wenn die SYNC-Parameter zu klein gewählt werden. Die absoluten Werte für die Catch Up-Zeit wirken klein; angesichts der gesamten INSERT-Zeit von ca. 80 Sekunden pro Server sind sie aber signifikant groß.

Test 2: 10000 INSERTs auf beiden Servern / SYNC-Werte ≥ 2 / Replikationsstart zeitlich versetzt

Wählt man hingegen SYNC-Werte von

# SYNC-Werte
sync_binlog=1
sync_master_info=3
sync_relay_log=3
sync_relay_log_info=3

und führt wieder zeitlich etwas versetzte INSERTS auf beiden Servern aus, so ergibt sich keine nenneswerte Catch Up-Zeit:

Server "histux":

MariaDB [(none)]> SHOW SLAVE STATUS\G
....              
                  Master_Host: hertux.mynet.de
                  Master_User: repl
                  Master_Port: 3306
....
              Replicate_Do_DB: Kirk
....
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 1660492
              Relay_Log_Space: 943
....
        Seconds_Behind_Master: 0
....
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 2
                   Using_Gtid: Slave_Pos
                  Gtid_IO_Pos: 0-2-537280
1 row in set (0.00 sec)

 
Server "hertux":

MariaDB [(none)]> SHOW SLAVE STATUS\G

                  Master_Host: histux.mynet.de
                  Master_User: repl
                  Master_Port: 3386
...
              Replicate_Do_DB: Spock
...                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 342
              Relay_Log_Space: 1671507
...
        Seconds_Behind_Master: 0
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 3
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
                   Using_Gtid: Slave_Pos
                  Gtid_IO_Pos: 0-3-535624
1 row in set (0.00 sec)

 
Beide Server arbeiten also synchron; auf beiden RDBMS gilt:

Seconds_Behind_Master: 0

Bewertung und Fazit

Steht der bzw. stehen die Master-Server in einer Replikation vom Typ

  • "Master-Slave">
  • oder "Master/Slave - Master/Slave"

unter dauerhafter Last, sollte man ein Auge auf die SYNC-Parametrierung für die Log-Files werfen. Ggf. sollten die slave-bezogenen SYNC-Variablen auf Werte > 2 gesetzt werden, um ein systematisches Anwachsen von Catch Up-Zeiten zwischen den RDBMS-Systemen zu vermeiden.

sync_binlog=1
sync_master_info=3
sync_relay_log=3
sync_relay_log_info=3

Der zu wählende Wert ist natürlich abhängig von der HW-Konfiguration.

Wir groß ist der potentielle Schaden im Fall eines Crashes, in dem Änderungen verloren gehen, die nicht rechtzeitig auf das Plattensystem geschrieben wurden? Aus meiner Sicht nicht groß. Relevant nach einem Systemcrash ist ja der zu wählende richtige Aufsetzpunkt für die jeweilige lave-Replikation. (Bzgl. der Änderungen in einer Master-Datenbank müssen sowieso Mechanismen der jeweiligen Applikation greifen.) Werden die SYNC-Parameter klein gewählt, sind nur einige wenige letzte Transaktionen vor dem Crash zu prüfen, um den richtigen Aufsetzpunkt (bzw. die zugehörige GTID) zu bestimmen. In einer Abwägung

zwischen einem nicht ganz nahtlosen Wiederaufsetzen der Replikation nach einem Crash
undeiner systematisch anwachsenden Catch UP-Zeit während der Replikation

erscheint mir der begrenzte Aufwand für die Identifikation der richtigen GTID das kleinere Übel zu sein.

Links

https://mariadb.com/kb/en/library/gtid/
https://www.lexiconn.com/blog/2014/04/how-to-set-up-selective-master-slave-replication-in-mysql/
https://docs.jelastic.com/mariadb-master-slave-replication

MariaDB, Master/Slave – Master/Slave-Replikation – Synchronisation der Binary Logs mit dem Storage ? – I

Datenbank-Replikationen sind nützlich und können in vielen Anwendungen einen Gleichstand von Daten über System- und Netzgrenzen hinweg ermöglichen. Es ist relativ einfach, eine Master-Slave-Replikation mit MariaDB oder MySQL zu implementieren. Ich verwende solche Replikationen inzwischen häufig, um Ergebnisdaten komplexer Berechnungen zwischen RDBMS-Serversystemen zu übertragen.

Je nachdem, wie groß

  • die Übertragungsrate des Netzwerkes zwischen den RDBMS-Servern ist,
  • wie groß die Transfergeschwindigkeit zum Storage-Sub-System auf jedem der Server ist
  • und wie die Parametrierung der Replikation erfolgt

kann man dabei Überraschungen mit den "CatchUp"-Zeiten erleben. Unter der CatchUp-Zeit verstehe ich hier die Zeit, die das Slave-System hinter dem Status seines Master-Systems hinterherhinkt.

Relevant werden diese Zeiten natürlich vor allem dann, wenn auf dem Master über einen längeren Zeitraum hinweg die Änderungsrate durch INSERTs und UPDATEs kontinuierlich hoch bleibt. Sprich: Wenn das RDBMS des Masters richtig unter Last steht und neben den eigentlichen SQL-Statements permanent Log-Einträge zum Slave-System geschaufelt werden müssen.

In einem Lastszenario mit Perioden dauerhafter Last muss man natürlich auch an die Fähigkeit zur Restaurierung des gesamten RDBMS-Verbundes denken. Dass man regelmäßige Backups braucht, ist sowieso klar. Es stellt sich aber auch die Frage, wie man die Replikation nach Crashes eines der beteiligten Systeme wieder aufsetzen kann.

Folgt man dem Standardwerk "High Performance MySQL" von Schwartz, Zaitsev, Tkachenko (O'Reilly Verlag, 3rd Ed.), so erhält man den Eindruck, dass man vor Crashes gehörigen Respekt haben sollte. Die Autoren empfehlen deshalb in mehreren Abschnitten (sehr von Vorsicht geprägte) Einstellungen zur Synchronisation von "Binary Log Files" und "Information Log Files" mit den Storage-Systemen - sowohl auf dem Master- wie auch auf dem Slave-System.

Es ist grundsätzlich interessant, sich das mal anzusehen. Ferner gilt: Replizierende Systeme haben nicht immer ein identisches Leistungsvermögen. Wie wirken sich SYNC-Einstellungen dann auf CatchUp-Zeiten aus? Bei einem Kunden ergab sich vor einiger Zeit zudem die Frage, wie es eigentlich mit einer "Master/Slave-Master/Slave"-Replikation aussieht. Gemeint waren hier gegenseitige Replikationen mit vertauschten Master/Slave-Rollen für zwei unterschiedliche Datenbanken; beide RDBMS-Systeme spielen dabei sowohl Master als auch Slave, nur eben für verschiedene Datenbanken (Schemata).

Zeit, mal einen Blick auf das grundsätzliche Replikationsverhalten für 2 Server in unserem LAN zu werfen. Im vorliegenden Artikel bespreche ich, wie man eine einfache Master/Slave-Replikation aufsetzt. Der Text ist also auch für Leute gedacht, die MySQL/MariaDB zwar nutzen, sich dem Thema Replikation aber noch nie genähert haben. Die angegebenen Kommandos sind einfach nachzustellen. Ein zweiter Artikel greift dann die komplexere "Master/Slave-Master/Slave"-Konstellation auf.

Eigentliches Ziel beider Artikel ist es aber, herauszuarbeiten, ob und wie man ggf. an SYNC-Einstellungen drehen kann, um die CatchUp-Zeit möglichst gering zu halten.

Testsituation Master/Slave - Master/Slave

Die nachfolgende Skizze zeigt den Testaufbau schematisch:

Wir haben zwei RDBMS-Serversysteme "histux" und "hertux". Wir betrachten nachfolgend zunächst nur den oberen Teil der Skizze - also ein reines Master/Slave-Szenario bzgl. der Datenbank "Kirk". "hertux" fungiert dabei als Master, "histux" als Slave.

Im zweiten Artikel dieser Serie ergänzen wir dann das Master/Slave-Szenario mit umgekehrtem Vorzeichen für eine weitere Datenbank "Spock" (unterer Teil der Skizze). "histux" wird dann für die Datenbank "Spock" die Rolle des Masters übernehmen, "hertux" die des Slaves. Es entsteht so das dargestellte "Master/Slave - Master/Slave" Szenario. Beide Server spielen darin gleichzeitig sowohl Master, als auch Slave für jeweils eine der Datenbanken [DBen].

Ich verwende in meinem Setup übrigens Systeme mit einer MariaDB - und nicht mit einem originalen MySQL-RDBMS. In meinem Fall ist das "histux"-System etwas schwächer ausgelegt als das "hertux"-System; das betrifft den Prozessor, aber auch das Storage-System (einfache HD vs. einfache SSD; für Tests mit schnellen SSD-Raid-Verbänden hatte ich noch keine Zeit ...).

Ich betrachte nachfolgend nur InnoDB-Tabellen, deren Änderungen (Inserts) mit einer sog. row-basierten Replikation (s.u.) auf die jeweiligen Slave-Server übertragen werden sollen. Um der Replikation eine Chance zu geben, werden die Änderungen (Inserts) durch echte Einzelstatements für jeden Record erzeugt. Inserts, Updates für mehrere Datensätze werden also nicht in einem SQL-Statement gebündelt.

Master/Slave: Konfiguration des Master-Servers

Die DB "Kirk" existiere zunächst als einzige DB in den RDBMS der Server "histux" und "hertux". Die notwendigen Konfigurationsstatements für die RDBMS-Replikation sind dann recht einfach; wir tragen folgende Zeilen in die Datei "/etc/my.cnf" des Master-Systems "hertux" ein:

# Binary Log file / Log Method 
log_bin=/var/lib/mysql/mysql-bin
binlog_format=row
# binlog_format=mixed

# Syn des Logfiles auf die Platte 
sync_binlog=1

#ID des Servers 
server-id=2

Wir gehen kurz die drei Einstellungsgruppen durch:

Binary-Log-File und Log- bzw. Replikations-Verfahren
Transaktionen auf einem System werden in sog. "Binary Log"-Dateien protokolliert. Die ersten zwei Statements legen fest, wo das Binary-Log-File gelagert wird und welches Format des Loggings verwendet wird:

Eine Replikation kann (SQL-) statement-bezogen oder row-bezogen durchgeführt werden.
Im ersteren Fall werden SQL-Statements vom Master auf den Slave übertragen und dort nachgefahren. Im zweiten Fall werden Daten-Änderungen betroffener Tabellen-Rows übertragen. Je nach Art von (komplexen) SQL-Statements kann das Nachfahren solcher Statements auf dem Slave deutlich aufwändiger sein, als die Änderungen der betroffenen Datensätze selbst nachzuziehen. Andererseits gilt evtl. genau das Umgekehrte für Massenänderungen über viele Rows hinweg. Man sollte die Art des Loggings also an der Charakteristik seiner SQL-Anwendungen ausrichten. Das gilt im Grunde aber auch für den Einsatz von MyISAM vs. InnoDB-Tabllen. Im Kontext von InnoDB-Tabellen und Anwendungen mit Einzeländerungen verwende ich selbst oft eine row-basierte Replikation. Stehen dagegen Massenänderungen vieler numerischer Daten im Fokus einer Anwendung weiche ich oft auf MyISAM-Tabellen und statement-basierte Replikation aus. Pauschalisieren lässt sich das aber nicht.

Synchronisation mit dem Storage-Subsystem
Das Statement "sync_binlog=1" legt fest, dass das Binary Log File mit dem Storage-Systems des Servers (z.B. einer lokalen Harddisk) nach jedem Commit synchronisiert werden soll. Das sichert gegenüber Datenverlust während Systemcrashes ab; aber eine solche Synchronisation ist eine durchaus aufwändige Operation, die die Performance bei vielen Änderungen pro Sekunde beeinträchtigen kann. In kleineren Firmen sind hier batteriegepufferte Caches von Raid-Controllern ggf. ein Ausweg.

Server-ID
Sind in einem Netzwerkverbund mehrere Server vorhanden, so sollte jedes RDBMS mit einer netzwerkweit eindeutigen Server-ID versehen werden. Die Festlegung einer eindeutigen ID der MySQL/MariaDB-RDBMS ist notwendig, um sog. "Globale Transaktions IDs" [GTIDs] zu unterstützen. In die Transaktions-IDs geht u.a. die eindeutige ID eines Servers mit ein.
Ich weise daraufhin, dass Format/Implementierung von GTIDs einer MariaDB (> Version 10.0.3) anders aussehen als bei einer herkömmlichen MySQL-Datenbank. Siehe hierzu:
https://mariadb.com/kb/en/library/gtid/#setting-up-a-new-slave-server-with-global-transaction-id.

Mit der bisherigen Einstellung werden Änderungen aller Datenbanken [DBen] des MariaDB RDBMS "hertux" im "Binary Log File" des Masters aufgezeichnet. Wir werden das im nächsten Artikel durch einen einfachen Filter für DBen abändern.

Alle anderen Einstellungen (u.a. bzgl. der IO-capacity und Anzahl IO Threads für InnoDB, ..) bleiben in meinem Test auf Standardwerten.

Master/Slave: Konfiguration des Slave-Servers

Die "/etc/my.cnf" des Slave-Systems "histux" sieht etwas erwartungsgemäß anders aus:

# Binary Log file / Log Method 
log_bin=/var/lib/mysql/mysql-bin
# binlog_format=mixed
binlog_format=row

# Relay Log File, automat. Starten der Replikation?   
relay_log=/var/lib/mysql/relay-bin
skip_slave_start

# Log der Updates
log_slave_updates=1

# Threads 
slave-parallel-threads=4

#ID des Servers 
server-id=3
# read-only=1

Hinweise zu den Einstellungen:

Relay-Log-File
Auf dem Slave ist die Festlegung des Relay-Log-Files erforderlich. Hierhin werden die durchzuführenden Transaktionen des Masters übertragen; in unserem einfachen Fall, in dem keine Änderungen auf dem Slave durchgeführt werden, ergibt sich letztlich eine Kopie des Binary-Logs des Masters.

Kein automatischer Start der Replikation
Wir schalten über "skip_slave_start" ein automatisches Starten der Replikation nach dem Starten des RDBMS ab. Das ist für Testsituationen OK; auf Produktiv-Systemen mag der Bedarf anders sein. Die Replikation lässt sich manuell immer zu einem geeigneten Zeitpunkt starten. Z.B., wenn man sicher ist, dass der Master auch aktionsbereit ist; s.u.. Über weitere Parameter (s. die MariaDB-Dokumentation) kann man steuern, wie oft und in welchem zeitlichen Abstand der Slave einen Verbindungsversuch zum Master unternehmen soll.)

Eintrag der Slave-Aktionen im eigenen Binary-Log?
"log_slave_updates=1" sorgt dafür, dass die Replikationstransaktionen im eigenen Binary Log des Slaves vermerkt werden. (Das kann wichtig sein, wenn der Slave selbst wieder Master gegenüber dritten Systemen spielen soll).

Mehrere Threads zum Nachfahren der übermittelten Änderungen
Wir stellen zudem mehrere Threads zur Verfügung, um die einlaufenden row-basierten Änderungen ggf. parallel in die Bank zu übertragen Dies betrifft ausschließlich die SQL-Ebene; s. https://mariadb.com/kb/en/library/parallel-replication/. Welche Anzahl von Threads sinnvoll ist, hängt von der Änderungsrate auf dem Master UND der Leistungsfähigkeit des Slave-Systems ab. Man muss testen, bis zu welcher Thread-Anzahl sich noch eine Performanceverbesserung ergibt.

Auf dem Slave "histux" könnte die Nutzung global auch auf "read-only" gesetzt werden, wenn die Datenbankinhalte wirklich nur replizierte Inhalte beinhalten sollen. In unserem Fall ist dies wenig sinnvoll, da "histux" ja später Master für die zusätzliche DB "Spock" werden soll. Das entsprechende Statement wurde daher auskommentiert.

Was brauchen wir noch für eine funktionsgerechte Master-Slave-Replikation?

Auf dem Master müssen wir einem Datenbank-User (hier: "repl") Replikationsrechte einräumen; das zugehörige SQL-Statement, das wir nach Einloggen in das Master-RDBMS am MySQL-Prompt absetzen, ist:

GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO repl@'192.168.0.%' IDENTIFIED BY 'MyReplPasswd';

"repl" ist ein in priviligierter Account, der alle transaktionalen Änderungen auf dem RDBMS einsehen kann. Auch, wenn dieser Account an Datenbankeinträgen nicht direkt etwas ändern kann, können sich aus seinen Rechten dennoch sekundäre Sicherheitsprobleme ergeben. Also: Vorsicht! Der gleiche User sollte natürlich auch auf dem Slave angelegt werden.

Füllen des Masters

Nun führen wir auf dem Master-Server und seiner/n Tabelle/n einen Schwung von SQL-Statements aus. Ich benutze dazu immer PHP-Test-Files, die hunderttausende von Inserts vornehmen.

Wir prüfen dann auf dem Master Server den Status und damit gleichzeitig den genauen Namen des Binary-Log-Files ab:


MariaDB [Kirk]> SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000007 | 16601394 |              |                  |
+------------------+----------+--------------+------------------+
1 row in set (0.00 sec)

 
Man sieht hier, dass auf dem Master schon Einiges passiert ist.

Starten der Replikation auf dem Slave

Wir starten nun unsere erste Replikation. Wir melden uns dazu als Datenbank-User "root" am Slave RDBMS von "histux" an ("mysql -u root -p"). Am MySQL-Prompt geben wir ein:

MariaDB [Kirk]> CHANGE MASTER TO MASTER_HOST='hertux.mynet.de',  MASTER_USER='repl', MASTER_PASSWORD='MyReplPasswd', MASTER_LOG_FILE='mysql-bin.000007;
MariaDB [Kirk]> CHANGE MASTER TO master_use_gtid=slave_pos;
MariaDB [Kirk]> START SLAVE;

 
Das erste Statement definiert den Master und seine Eigenschaften (dauerhaft) für das Slave-System! das zweite Statement setzt den Ausgangspunkt der Replikation auf die richtige Position im Binary File. Das ist im vorliegenden Fall natürlich die 0-Position. Das angegebene Statement

CHANGE MASTER TO master_use_gtid=slave_pos;

lässt sich im regulären Betrieb nach einem evtl. erfolgten Restart des Slaves oder einem Stop und Neustart der Replikation aber immer wieder einsetzen: Die Replikation soll ab der Position im Log-File ansetzen, zu der der Slave bislang gekommen war.

Die Master-Log-Files ändern im Lauf des Betriebs ihre Bezeichnung. Wir müssen uns im weiteren Normalbetrieb allerdings nicht mehr um die Angabe des richtigen Master-Log-Files kümmern. Der Slave erkennt dies in der Regel über GTIDs automatisch. Im Gegensatz zu früheren MariaDB-Varianten, in denen GTIDs nicht automatisch benutzt wurden, muss man sich nun im Normalbetrieb also keine Gedanken mehr zu Byte-bezogenen Positionen im binären Master-Log machen. Das Statement "CHANGE MASTER TO master_use_gtid=slave_pos;" sorgt dafür, dass der richtige Aufsetzpunkt für die Replikation im Verhältnis zum Status des Slaves gefunden wird.

Man kann den Ablauf der Replikation auf dem Slave nun mit

MariaDB [Kirk]> SHOW SLAVE STATUS\G

verfolgen. Am Schluss des Nachfahrens aller bisherigen Einträge in unsere Test-DB "Kirk" erhält man dann etwas in der Art :

MariaDB [(none)]> SHOW SLAVE STATUS\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: hertux.mynet.de
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000007
          Read_Master_Log_Pos: 16601394
               Relay_Log_File: relay-bin.000002
                Relay_Log_Pos: 16601681
        Relay_Master_Log_File: mysql-bin.000007
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 16601394
              Relay_Log_Space: 16601972
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 2
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
                   Using_Gtid: Slave_Pos
                  Gtid_IO_Pos: 0-2-311024
1 row in set (0.00 sec)

 
Wer mag kann den korrekten Tabellenstatus ja mit PhpMyAdmin kontrollieren. Nebenbei: Es lohnt sich, bei Gelegenheit die Features von PhpMyAdmin für Replikationseinstellungen und die Einsichtnahme in zugehörige Parameter des RDBMS zu studieren.

Interessant ist der Unterschied zwischen der GTID-Position und der Byte-Position:

Gtid_IO_Pos: 0-2-311024
Exec_Master_Log_Pos: 16601394

Verschiedene Infos; gleiche Positionierung in den Binary-Logs!

Die aktuellen Positionen lassen sich auf Master und Slave übrigens jederzeit wie folgt abfragen:

Master

MariaDB [massa]> SELECT @@GLOBAL.gtid_binlog_pos;
+--------------------------+
| @@GLOBAL.gtid_binlog_pos |
+--------------------------+
| 0-2-331028               |
+--------------------------+
1 row in set (0.00 sec)
  
<strong>Slave</strong> 
MariaDB [massa]> SELECT @@GLOBAL.gtid_slave_pos;
+-------------------------+
| @@GLOBAL.gtid_slave_pos |
+-------------------------+
| 0-2-331028              |
+-------------------------+
1 row in set (0.00 sec)

Master/Slave: Test mit 10000 neuen Inserts am Master - ohne SYNC-Einstellungen am Slave

Für meinen Test zur CatchUP-Zeit verwende ich ein Skript, das fortlaufende Inserts in einer InnoDB-Tabelle vornimmt - und zwar Record/Row für Record/Row - inkl. paralleler Indexgenerierung. Ohne Prepared Statements. Es werden dabei mehrere Stringfelder eines neuen Datensatzes gefüllt.

Ich begrenze die Anzahl auf 10000 Inserts. Die Zeitdauer für meine Inserts beträgt 80 Sek..

Die Rate ist also nicht besonders hoch; das liegt u.a. an einer parallel stattfindenden Index-Generierung und fehlender Optimierung. Die Insert-Rate lässt sich viel höher schrauben; allein eine Bündelung von Inserts in einem Statement und/oder die Nutzung von "prepared statements" würde Faktoren an Geschwindigkeit bringen. Aber es geht mir hier nicht um Optimierung; es kommt mir nur auf relative Anteile der CatchUp-Zeiten an.

Welche CatchUp-Zeit zieht eine solche Insert-Folge während der 80 Sekunden Laufzeit nach sich?

Wir greifen hierzu - während das Testprogramm mit den Inserts läuft - den Slave-Status am Slave ab:

 
MariaDB [massa]> SHOW SLAVE STATUS\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: histux.mynet.de
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000008
          Read_Master_Log_Pos: 1878830
               Relay_Log_File: relay-bin.000002
                Relay_Log_Pos: 1879117
        Relay_Master_Log_File: mysql-bin.000008
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 1878830
              Relay_Log_Space: 1879408
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 2
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
                   Using_Gtid: Slave_Pos
                  Gtid_IO_Pos: 0-2-332342
1 row in set (0.00 sec)

 
Relevant ist die Zeile:

Seconds_Behind_Master: 0

Wunderbar! Offenbar kann der Slave unter den herrschenden Bedingungen [LAN, keine SYNC-Prozesse] locker mithalten - obwohl er schwächer ausgelegt ist als der Master!

Auf den Master hat der Datenaustausch für die Replikation übrigens kaum Auswirkungen: Die benötigte Gesamtzeit steigt dort von etwa 80 Sekunden auf 84 Sekunden an.

Master/Slave: Test mit 10000 neuen Inserts am Master - mit SYNCs auf die HD am Slave

Wir verändern die Einstellung des Slave-RDBMS bzgl. der Synchronisation von dortigen Log-Files zum Storage-System. Es gibt mehrere Dateien, die im Replikationsvorgang auf dem Slave-System fortgeschrieben werden (Relay-Log=> nachzufahrende Transaktionen / master.info-File: Infos über die Position des Masters / Relay_log.info-File => eigene Position im Relay-Log.) Wir folgen zunächst 1:1 den Empfehlungen des Buchs "High Performance MySQL":

#sync_binlog=1
sync_master_info=1
sync_relay_log=1
sync_relay_log_info=1

Damit starten wir das MariaDB-RDBMS auf dem Slave-Server "histux" neu und loggen uns wieder mittels "mysql -u root -p" in das RDBMS ein. Am MySQL-Prompt verfolgen wir dann einen neuen Testlauf und fragen sukzessive den Slave-Status ab. Es zeigte für meine Systeme leider, dass die CatchUp-Zeit relativ zum Master-Status nun permanent und systematisch zunahm!:

 
MariaDB [(none)]> SHOW SLAVE STATUS\G
*************************** 1. row ***************************
               Slave_IO_State: Queueing master event to the relay log
                  Master_Host: histux.mynet.de
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000008
          Read_Master_Log_Pos: 6766074
               Relay_Log_File: relay-bin.000002
                Relay_Log_Pos: 5466
        Relay_Master_Log_File: mysql-bin.000008
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 5318184
              Relay_Log_Space: 1453647
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: 131
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 2
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
                   Using_Gtid: Slave_Pos
                  Gtid_IO_Pos: 0-2-361783
1 row in set (0.12 sec)

 

Am Schluss erhielt ich als maximale Differenz:

Seconds_Behind_Master: 158 (Sekunden)

Es sieht also so aus, dass eine Synchronisation von Relay-Log-Dateien auf dem Slave sehr problematische Folgen haben kann: Immerhin ist der Zeitverlust gegenüber dem Master auf fast das Doppelte der für die DB-Transaktionen benötigten Zeit angewachsen!

In anderen Tests mit über 100000 Inserts wuchs der Zeitverlust weiter extrem an - auf über 3 Stunden!

Master/Slave: SYNC in größeren Transaktions-Abständen am Slave?

Kann man das obige verheerende Ergebnis irgendwie beseitigen? Ein wenig Experimentierfreude zeigt schnell, dass eine Änderung der Parameter auf

  
log_slave_updates=0
sync_master_info=0
sync_relay_log=1
sync_relay_log_info=0

leider gar nichts bringt. Der systematisch größer werdenden Abstand zwischen Slave und Master legt den Schluss nahe, dass der Slave vor lauter SYNC-Prozessen gar nicht dazu kommt, seine SQL-Arbeit im RDBMS durchzuführen. Daten müssen über Netz vom Master geholt werden, Informationen über die Positionen des Masters und des Slaves auf dem Slave in Dateien geschrieben werden. Jede Datenbank-Transaktion am Slave führt dann zu zusätzlichen SYNC-Vorgängen, die unabhängig von den anwachsenden Master-Transaktionen durchzuführen sind. Zudem werden die Files bei einmal wachsender CatchUp-Zeit automatisch immer größer! Das riecht förmlich danach, dass unser Slave bei hoher Taktung der Änderungen auf dem Master in einen Teufelskreis gerät ...

Wenn dies Theorie stimmt, dann sollten wir den Datenbanktransaktionen mehr Zeit gegenüber Synchronisationsprozessen einräumen. Die "1" als Parameter bei den 3 Slave-SYNC-Einstellungen bedeutet ja nicht ein Einschalten im Sinne eines boolschen Flags. Vielmehr kann hier ein numerischer Parameter angegeben werden: Er legt fest, nach wieviel Änderungen das jeweilige File auf dem jeweiligen Status auf die Platten geschrieben werden soll. Wir sollten also versuchen, die SYNC-Parameterwerte zu erhöhen.

Tatsächlich ergibt sich bei einer Einstellung

  
relay_log=/var/lib/mysql/relay-bin
skip_slave_start
log_slave_updates=1
slave-parallel-threads=4
sync_master_info=4
sync_relay_log=4
sync_relay_log_info=4

das Ergebnis :

sync_relay_log=4, sync_master_info=4, sync_relay_log_info=4 => CatchUp-Zeit: 0 Sekunden

Geht man mit den sync-Parametern danach weiter runter, so erhalten wir wieder ein Anwachsen des Delays, wenn auch moderater als beim Wert "1":

  
sync_master_info=2
sync_relay_log=2
sync_relay_log_info=2

sync_relay_log=4 => CatchUp-Zeit: 54 Sekunden

Bei einem Wert von "3":

sync_relay_log=3 => CatchUp-Zeit: 3 Sekunden => 0 Sekunden

Master/Slave: SYNC und Abhängigkeit von der Anzahl der Slave-Threads?

Wir können parallel auch mal die Anzahl der Slave-Threads erhöhen. So ergibt

slave-parallel-threads=10
sync_master_info=2
sync_relay_log=2
sync_relay_log_info=2

sync_relay_log=2 / slave-parallel-threads=10 => CatchUp-Zeit: max. 3 Sekunden => 0 Sekunden

Fazit

Schon in einer normalen Master-Slave-Replikation gilt: Durch Synchronisation von Replikations-Log-Files mit dem HD- oder SSD-System auf dem Slave kann sich die CatchUp-Zeit systematisch erhöhen,

  • der Takt der Änderungen auf dem Master konstant hoch ist
  • wenn die Frequenz der SYNC-Prozesse zu hoch gewählt wird,
  • und eine row-basierte Replikation für InnoDB-Tabellen konfiguriert wurde.

Im vorliegenden Fall erwies sich die Vorgabe, nach jeder Änderung alle Log-Files direkt auf die Platte zu schreiben einfach als zu ambitiös: Das Slave-System ist mehr mit SYNC-Prozessen beschäftigt als mit der eigentlichen Durchführung der Änderungen in der Datenbank. Gerät das System erst einmal außer Takt, wächst die CatchUp-Zeit des Slaves gegenüber dem Master bei einer kontinuierlichen Last kontinuierlich an. Relevant ist das vor allem für gekoppelte Systeme, die permanente Änderungsprozesse hoher Frequenz verkraften müssen.

Für replizierende MariaDB-Systeme ist es deshalb wichtig zu testen, ob nicht mehrere Änderungen (Commits) in der Bank abgewartet werden sollten, bevor ein Schreiben der Binary-Log- und Info-Files auf das Storage-System erfolgen soll. U.U. genügt hier schon die Wahl eines nur etwas erhöhten Wertes

sync_master_info=3 (oder 4)
sync_relay_log=3 (oder 4)
sync_relay_log_info=3 (oder 4)

um schon eine signifikante Verbesserung zu erreichen. Die Arbeit, nach einem Crash des Slaves, den richtigen Aufsetzpunkt für die Replikation zu finden, hält sich dann immer noch in relativ engen Grenzen.

Im kommenden Artikel betrachten wir die Parametrierung und die gleiche Situation für eine "Master/Slave - Master/Slave"-Konfiguration.

Links

https://mariadb.com/kb/en/library/setting-up-replication/