<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>FromDual: FromDual TechFeed (de)</title><link>https://www.fromdual.com/de/aggregator/categories/4/</link><description>FromDual: FromDual TechFeed in German</description><generator>Hugo</generator><language>de-CH</language><managingEditor>oli.sennhauser@fromdual.com (Oli Sennhauser)</managingEditor><webMaster>oli.sennhauser@fromdual.com (Oli Sennhauser)</webMaster><copyright>© FromDual GmbH</copyright><atom:link href="https://www.fromdual.com/de/aggregator/categories/4/index.xml" rel="self" type="application/rss+xml"/><item><title>Percona gibt Galera Cluster für Version 9.7 frei</title><link>https://www.fromdual.com/de/blog/percona-xtradb-galera-cluster-9-7-freigegeben/</link><pubDate>Fri, 11 Sep 2026 12:08:00 +0200</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/percona-xtradb-galera-cluster-9-7-freigegeben/</guid><description>&lt;p&gt;Endlich mal wieder gute Nachrichten!&lt;/p&gt;
&lt;h2 id="was-bisher-geschah"&gt;Was bisher geschah&lt;/h2&gt;
&lt;p&gt;Nachdem MariaDB Corp. im Mai 2025 Codership OY und somit Galera Cluster für MySQL erworben [ &lt;a href="https://www.infoworld.com/article/3999634/mariadbs-acquisition-of-codership-why-enterprises-should-care.html" target="_blank" title="MariaDB’s acquisition of Codership: Why enterprises should care"&gt;1&lt;/a&gt; ], anschliessend die Einstellung des Supports für Galera Cluster für MySQL 8.4 für Ende September 2026 angekündigt hatte [ &lt;a href="https://www.theregister.com/databases/2026/07/30/mariadb-again-faces-questions-over-galeras-open-source-future/5280975" target="_blank" title="MariaDB again faces questions over Galera's open source future"&gt;2&lt;/a&gt; ] und anschliessend verkündete, Galera Cluster (für MariaDB!) nicht mehr weiter in der Community Edition unterstützten zu wollen [ &lt;a href="https://www.theregister.com/software/2026/03/09/mariadb-backs-down-on-galera-removal-after-community-outcry/5224684" target="_blank" title="MariaDB backs down on Galera removal after community outcry"&gt;3&lt;/a&gt; ], gab es in der Community einen lauten Aufschrei. MariaDB Corp. machte hierauf einen halben Rückzug (&amp;quot;&lt;em&gt;We&amp;rsquo;ve thoroughly considered your feedback and decided that &lt;strong&gt;now is not the time&lt;/strong&gt; for a major change,&lt;/em&gt;&amp;quot;). Ein klares Bekenntnis zur weiteren Unterstützung von Galera Cluster in der Community Edition klingt anders.&lt;/p&gt;
&lt;p&gt;Kurz darauf hat MariaDB Foundation angekündigt, Galera Cluster nicht forken zu wollen (&amp;quot;&lt;em&gt;Thus the Foundation &lt;strong&gt;does not see a need for forking Galera&lt;/strong&gt; and does not support or encourage such action.&lt;/em&gt;&amp;quot;) [ &lt;a href="https://mariadb.org/galera-continuity-and-responsibility-how-the-foundation-and-mariadb-plc-move-forward/" target="_blank" title="Galera, continuity, and responsibility: how the Foundation and MariaDB plc move forward"&gt;4&lt;/a&gt; ].&lt;/p&gt;
&lt;p&gt;Die Zukunft von Galera Cluster für MySQL war also bis gestern sehr ungewiss!&lt;/p&gt;
&lt;h2 id="perconas-ankündigung-von-gestern"&gt;Perconas Ankündigung von gestern&lt;/h2&gt;
&lt;p&gt;Aber zum Glück gibt es da ja noch Percona mit ihrem &lt;a href="https://www.percona.com/resource/percona-xtradb-cluster/" target="_blank"&gt;Percona XtraDB Cluster (PXC)&lt;/a&gt;, welcher ein verbesserter Branch/Fork sowohl von MySQL als auch von Galera Cluster ist.&lt;/p&gt;
&lt;p&gt;Und diese Firma Percona hat gestern (10. September 2026), endlich den Release von Percona XtraDB Cluster 9.7 angekündigt [ &lt;a href="https://docs.percona.com/new/2026/09/10/percona-xtradb-cluster-971-1-has-been-released/" target="_blank" title="Percona XtraDB Cluster 9.7.1-1 has been released"&gt;5&lt;/a&gt; ]. Das war für uns das lange erwartete und erhoffte Signal von Percona!&lt;/p&gt;
&lt;p&gt;Schön wäre noch, wenn Percona ein klareres Statement zu Galera Cluster geben könnte, damit man sich zeitlich besser darauf einstellen kann&amp;hellip;&lt;/p&gt;
&lt;h2 id="installation"&gt;Installation&lt;/h2&gt;
&lt;p&gt;Wir konnten natürlich nicht widerstehen und haben sofort den neusten Release heruntergeladen [ &lt;a href="https://www.percona.com/downloads/" target="_blank" title="Percona Download"&gt;6&lt;/a&gt; ] und einen Percona XtraDB Testcluster aufgebaut.&lt;/p&gt;
&lt;p&gt;Bedingt durch unseren Standard-Galera-Installationsmechanismus mussten wir am einen oder anderen Ort noch etwas nachhelfen, damit der Percona XtraDB Cluster auch sauber zum laufen kam.&lt;/p&gt;
&lt;p&gt;Die Details haben wir hier beschrieben: &lt;a href="https://www.fromdual.com/blog/mysql-mariadb-migration/#migration-from-mysql-galera-cluster-84-to-percona-xtradb-cluster-97"&gt;Migration from MySQL Galera Cluster 8.4 to Percona XtraDB Cluster 9.7&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Weitere Tests werden in den nächsten Tagen und Wochen folgen&amp;hellip;&lt;/p&gt;
&lt;p&gt;Jetzt wäre eigentlich ein guter Augenblick, als kommerziell orientierter Galera Cluster Nutzer, Percona zu sponsern&amp;hellip;? Für ein vitales und gesundes MySQL Open Source Ökosystem: &lt;a href="https://oursqlfoundation.org/" target="_blank"&gt;The Future of the MySQL Ecosystem&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="quellen"&gt;Quellen&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.percona.com/percona-xtradb-cluster/9.7/" target="_blank"&gt;Percona XtraDB Cluster 9.7 Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>PostgreSQL Daten retten bei korrupten Blocks</title><link>https://www.fromdual.com/de/blog/postgresql/postgresql-daten-retten-bei-korrupten-blocks/</link><pubDate>Tue, 01 Sep 2026 07:50:00 +0200</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/postgresql/postgresql-daten-retten-bei-korrupten-blocks/</guid><description>&lt;p&gt;Bei unseren Arbeiten zum Thema &amp;ldquo;PostgreSQL für Delfine und Seelöwen&amp;rdquo; lese ich gerne auch auf den PostgreSQL &lt;a href="https://www.postgresql.org/list/" target="_blank"&gt;Mailing-Listen&lt;/a&gt; mit, um zu sehen, welche Probleme so auftreten und wie sie gelöst werden.&lt;/p&gt;
&lt;p&gt;Ein Mail hat mich besonders interessiert:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&amp;rsquo;m unable to access one of the tables. Even a simple &lt;code&gt;SELECT&lt;/code&gt; statement fails with the error below.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;prod=# SELECT count(*) FROM schema.tablename;

WARNING: page verification failed, calculated checksum 26618 but expected 52580
ERROR: invalid page in block 43197 of relation base/24576/24578
CONTEXT: parallel worker
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;h2 id="interpretation-der-informationen"&gt;Interpretation der Informationen&lt;/h2&gt;
&lt;p&gt;Wie ist diese Information zu interpretieren?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&amp;ldquo;parallel worker&amp;rdquo; ➜ PostgreSQL hat das Query &lt;a href="https://www.postgresql.org/docs/current/parallel-query.html" target="_blank"&gt;parallel&lt;/a&gt; abgearbeitet. Wahrscheinlich irrelevant in diesem Fall?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&amp;ldquo;invalid page in block 43197&amp;rdquo; ➜ &amp;ldquo;page&amp;rdquo; und &amp;ldquo;block&amp;rdquo; sind im PostgreSQL Universum Synonyme, wenn man den zahlreichen Quellen im Internet glauben darf? Also ist die Fehlermeldung eigentlich widersinnig!?! Also: Page/Block Nummer 43197 ist kaputt!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&amp;ldquo;relation base/24576/24578&amp;rdquo; ➜ Welches Datenbank-Objekt (Tabelle, Index, etc.) davon betroffen ist. Mehr dazu weiter unten&amp;hellip;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&amp;ldquo;calculated checksum&amp;rdquo; ➜ PostgreSQL versieht jede Page beim Schreiben auf Platte mit einer Checksumme, welche beim Lesen überprüft wird. [ &lt;a href="https://www.postgresql.org/docs/current/checksums.html" target="_blank" title="Data Checksums"&gt;1&lt;/a&gt; ]. In diesem Fall scheint die Prüfung fehlgeschlagen zu sein.&lt;br&gt;
Das geht natürlich nur, wenn das Checksummen-Bilden aktiviert ist (default ab v18).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; postgres=&amp;gt; SHOW data_checksums;
 data_checksums 
 ----------------
 on
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="zur-relation"&gt;Zur &amp;ldquo;relation&amp;rdquo;&lt;/h2&gt;
&lt;p&gt;Tabellen werden in PostgreSQL &amp;ldquo;relations&amp;rdquo; genannt:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Relation is essentially a mathematical term for table. [ &lt;a href="https://www.postgresql.org/docs/current/tutorial-concepts.html" target="_blank" title="Concepts"&gt;2&lt;/a&gt; ]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Wenn wir uns das Ganze zuerst mal auf Platte anschauen sieht das wie folgt aus:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ cd ${PGDATA}

$ ll -d base/*
drwx------ 2 dba dba 4096 Jul 20 17:53 base/1
drwx------ 2 dba dba 12288 Aug 5 21:33 base/24576
drwx------ 2 dba dba 4096 Jul 16 09:04 base/4
drwx------ 2 dba dba 12288 Aug 5 21:33 base/49204
drwx------ 2 dba dba 12288 Aug 5 21:33 base/5
drwx------ 2 dba dba 4096 Aug 5 21:33 base/57405
drwx------ 2 dba dba 4096 Aug 5 21:33 base/57406
drwx------ 2 dba dba 4096 Aug 5 21:33 base/57408
drwx------ 2 dba dba 36864 Aug 5 21:33 base/77107
drwx------ 2 dba dba 4096 Jul 20 18:05 base/pgsql_tmp
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Hier haben wir zuerst mal alle Datenbanken gelistet. Wenn wir die Zuordnung zu den Namen erfahren wollen, müssen wir IN der Datenbank-Instanz schauen:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# SELECT oid, datname AS database FROM pg_database;
 oid | datname 
-------+-----------
 5 | postgres
 1 | template1
 4 | template0
 49204 | dba
 24576 | test
 57405 | osm_ch
 57406 | osm_chx
 57408 | osm
 77107 | enswitch
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Wir wissen also jetzt schon mal, dass der Schaden in der Datenbank &lt;code&gt;test&lt;/code&gt; entstanden ist. Also schauen wir auf dem Dateisystem mal eine Ebene tiefer:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ cd base/24576

$ ls -lrS
...
-rw------- 1 dba dba 835584 Mar 18 17:30 1255
-rw------- 1 dba dba 1294336 Jun 10 12:15 24578_fsm
-rw------- 1 dba dba 890937344 Jul 23 17:50 24578.4
-rw------- 1 dba dba 1062330368 Jun 10 12:15 24588.1
-rw------- 1 dba dba 1073741824 Jul 23 17:50 24588
-rw------- 1 dba dba 1073741824 Mar 18 18:08 24578.3
-rw------- 1 dba dba 1073741824 Mar 18 18:07 24578.2
-rw------- 1 dba dba 1073741824 Mar 18 17:36 24578.1
-rw------- 1 dba dba 1073741824 Jul 23 12:39 24578
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Hier liegen alle Datenbank-Objekte rum. Um welche Tabelle es sich handelt erfahren wir wiederum IN der Datenbank-Instanz:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# \connect test

test=# SELECT c.oid AS file, c.relname AS name, ns.nspname AS schema
 , CASE c.relkind
 WHEN &amp;#39;r&amp;#39; THEN &amp;#39;Ordinary Table&amp;#39;
 WHEN &amp;#39;i&amp;#39; THEN &amp;#39;Index&amp;#39;
 WHEN &amp;#39;S&amp;#39; THEN &amp;#39;Sequence&amp;#39;
 WHEN &amp;#39;v&amp;#39; THEN &amp;#39;View&amp;#39;
 WHEN &amp;#39;m&amp;#39; THEN &amp;#39;Materialized View&amp;#39;
 WHEN &amp;#39;c&amp;#39; THEN &amp;#39;Composite Type&amp;#39;
 WHEN &amp;#39;t&amp;#39; THEN &amp;#39;TOAST Table&amp;#39;
 WHEN &amp;#39;f&amp;#39; THEN &amp;#39;Foreign Table&amp;#39;
 WHEN &amp;#39;p&amp;#39; THEN &amp;#39;Partitioned Table&amp;#39;
 WHEN &amp;#39;I&amp;#39; THEN &amp;#39;Partitioned Index&amp;#39;
 ELSE CONCAT(&amp;#39;Unknown (&amp;#39;, c.relkind, &amp;#39;)&amp;#39;)
 END AS object_type
 , am.amname, c.relfilenode, c.reltablespace
 FROM pg_class AS c
 JOIN pg_namespace AS ns ON ns.oid = c.relnamespace
 LEFT JOIN pg_am AS am ON am.oid = c.relam
WHERE ns.nspname NOT IN (&amp;#39;pg_catalog&amp;#39;, &amp;#39;pg_toast&amp;#39;, &amp;#39;information_schema&amp;#39;)
 AND c.oid IN (24578, 24588, 1255)
;
 file | name | schema | object_type | amname | relfilenode | reltablespace 
-------+-----------+------------+----------------+--------+-------------+---------------
 24578 | test | public | Ordinary Table | heap | 24578 | 0
 24588 | test_pkey | public | Index | btree | 24588 | 0
 1255 | pg_proc | pg_catalog | Ordinary Table | heap | 0 | 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Es handelt sich also beim betroffenen Objekt um die &amp;ldquo;gewöhnliche&amp;rdquo; Tabelle (Ordinary Table) &lt;code&gt;test&lt;/code&gt; im Schema &lt;code&gt;public&lt;/code&gt; in der Datenbank &lt;code&gt;test&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="kaputt-machen"&gt;Kaputt machen&lt;/h2&gt;
&lt;p&gt;Wie haben wir jetzt das Ganze simuliert, sprich, kaputt gemacht? Hierzu eignet sich der Befehl &lt;code&gt;dd&lt;/code&gt; ausgezeichnet:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ dd if=/dev/urandom of=24578 bs=1 seek=353874683 count=128 conv=notrunc
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Wenn man das dann auf die Fehlermeldung am Anfang zurück rechnet sieht man, dass es passt:&lt;/p&gt;
&lt;p&gt;Block Nr. 43197 x 8 k/block ➜ 353'869'824 Anfangsadresse, das Ende liegt bei: 353'878'015 und überschrieben haben wir ab Adresse 353'874'683 128 bytes.&lt;/p&gt;
&lt;p&gt;Jetzt versuchen wir die Daten in der Datenbank noch/wieder zu lesen und erhalten genau die richtige Fehlermeldung:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;test=# SELECT * FROM test;
ERROR: invalid page in block 43197 of relation &amp;#34;base/24576/24578&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Falls man mal noch einen Blick ins Error Log wirft, findet man dort ebenfalls dieselbe Fehlermeldung:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;LOG: page verification failed, calculated checksum 26618 but expected 52580
CONTEXT: I/O worker executing I/O on behalf of process 595983
LOG: invalid page in block 43197 of relation &amp;#34;base/24576/24578&amp;#34;
CONTEXT: I/O worker executing I/O on behalf of process 595983
ERROR: invalid page in block 43197 of relation &amp;#34;base/24576/24578&amp;#34;
STATEMENT: SELECT * FROM test;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Eine weitere Möglichkeit, Checksummen-Fehler zu überprüfen besteht mit der folgenden Abfrage:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# SELECT datid, datname, conflicts, checksum_failures, checksum_last_failure
 FROM pg_stat_database;
 datid | datname | conflicts | checksum_failures | checksum_last_failure 
-------+-----------+-----------+-------------------+-------------------------------
 0 | | 0 | 0 | 
 5 | postgres | 0 | 0 | 
 1 | template1 | 0 | 0 | 
 4 | template0 | 0 | 0 | 
 49204 | dba | 0 | 0 | 
 24576 | test | 0 | 27 | 2026-08-06 18:47:58.777398+02
 57405 | osm_ch | 0 | 0 | 
 57406 | osm_chx | 0 | 0 | 
 57408 | osm | 0 | 0 | 
 77107 | enswitch | 0 | 0 | 
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="korruption-eingrenzen"&gt;Korruption eingrenzen&lt;/h2&gt;
&lt;p&gt;Gehen wir mal davon aus, dass wir KEINE Backups haben (muss man aktiv selber tun und prüfen bzw. testen) und/oder das WAL-Archiving nicht aktiviert ist (default off) und die Änderungen seit dem letzten Backup (von heute Morgen um 02:00) nicht verloren gehen sollen&amp;hellip;&lt;/p&gt;
&lt;p&gt;Wie kann ich also die aktuellen Daten noch retten? &lt;code&gt;pg_dump&lt;/code&gt; wird ebenfalls fehlschlagen (macht das Selbe wie &lt;code&gt;SELECT&lt;/code&gt;). &lt;code&gt;pg_basebackup&lt;/code&gt; wird auch melden, dass die Checksumme nicht stimmt und ebenfalls fehlschlagen. Zudem komme ich so immer noch nicht wieder an meine Daten ran.&lt;/p&gt;
&lt;p&gt;Wir müssen also technisch mehr oder weniger Zeile für Zeile bis vor die Korruption rausfieseln. Das Ende der Korruption finden. Und ab da ebenfalls Zeile für Zeile rausfieseln.&lt;/p&gt;
&lt;p&gt;In der Praxis machen wir das, indem wir uns von oben und unten an die Korruption herantasten, indem wir immer jeweils bis zur Hälfte prüfen. [ &lt;a href="https://en.wikipedia.org/wiki/Binary_search" target="_blank" title="Binary-Search"&gt;3&lt;/a&gt; ]&lt;/p&gt;
&lt;figure&gt;
 &lt;p&gt;&lt;a href="https://www.fromdual.com/blog/postgresql/recovering-postgresql-data-when-blocks-are-corrupt/corruption_schema_606x256.webp" title="volle Grösse"&gt;&lt;img src="https://www.fromdual.com/blog/postgresql/recovering-postgresql-data-when-blocks-are-corrupt/corruption_schema_606x256.webp" alt="block corruption in file"&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;figcaption&gt;Block Korruption in PostgreSQL Tabellen finden&lt;/figcaption&gt;
&lt;/figure&gt;&lt;br&gt;
&lt;p&gt;Dazu müssen wir zuerst mal das obere und untere &amp;ldquo;Ende&amp;rdquo; unserer Tabelle finden. Hierzu eignet sich typischer Weise der Primary Key, welcher in unserem Fall die Spalte &lt;code&gt;id&lt;/code&gt; vom Type &lt;code&gt;serial&lt;/code&gt; ist und auf welchem damit eine &lt;code&gt;SEQUENCE&lt;/code&gt; liegt:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;test=# SELECT MIN(id), MAX(id) FROM test;
 min | max 
-----+----------
 11 | 75930859
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dann geht die Sucherei los:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;test=# SELECT * FROM test WHERE id &amp;lt; 40000000 ORDER BY id;
ERROR: invalid page in block 43197 of relation &amp;#34;base/24576/24578&amp;#34;
test=# SELECT * FROM test WHERE id &amp;lt; 20000000 ORDER BY id;
ERROR: invalid page in block 43197 of relation &amp;#34;base/24576/24578&amp;#34;
test=# SELECT * FROM test WHERE id &amp;lt; 10000000 ORDER BY id;
ERROR: invalid page in block 43197 of relation &amp;#34;base/24576/24578&amp;#34;
test=# SELECT * FROM test WHERE id &amp;lt; 5000000 ORDER BY id;
ERROR: invalid page in block 43197 of relation &amp;#34;base/24576/24578&amp;#34;
test=# SELECT * FROM test WHERE id &amp;lt; 2500000 ORDER BY id;
 id | data | ts 
---------+-------------------------------+----------------------------
 11 | Some text | 2026-07-23 17:46:13.710454
 61 | Some data to blow table up... | 2026-03-18 17:29:49.408976
test=# SELECT * FROM test WHERE id BETWEEN 2500000 AND 5000000 ORDER BY id;
ERROR: invalid page in block 43197 of relation &amp;#34;base/24576/24578&amp;#34;
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Der Anfang unserer Korruption liegt also irgendwo im Bereich zwischen Zeile 2'500'000 und 5'000'000.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Bereich von&lt;/th&gt;
					&lt;th&gt;Beriech bis&lt;/th&gt;
					&lt;th&gt;Resultat&lt;/th&gt;
					&lt;th&gt;Anzahl Zeilen&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;2500000&lt;/td&gt;
					&lt;td&gt;5000000&lt;/td&gt;
					&lt;td&gt;ERROR&lt;/td&gt;
					&lt;td&gt;2'500'000&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;2500000&lt;/td&gt;
					&lt;td&gt;3750000&lt;/td&gt;
					&lt;td&gt;OK&lt;/td&gt;
					&lt;td&gt;1'250'000&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;3750000&lt;/td&gt;
					&lt;td&gt;4250000&lt;/td&gt;
					&lt;td&gt;OK&lt;/td&gt;
					&lt;td&gt;500'000&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4250000&lt;/td&gt;
					&lt;td&gt;4600000&lt;/td&gt;
					&lt;td&gt;OK&lt;/td&gt;
					&lt;td&gt;350'000&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4600000&lt;/td&gt;
					&lt;td&gt;4800000&lt;/td&gt;
					&lt;td&gt;ERROR&lt;/td&gt;
					&lt;td&gt;200'000&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4600000&lt;/td&gt;
					&lt;td&gt;4700000&lt;/td&gt;
					&lt;td&gt;ERROR&lt;/td&gt;
					&lt;td&gt;100'000&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4600000&lt;/td&gt;
					&lt;td&gt;4650000&lt;/td&gt;
					&lt;td&gt;ERROR&lt;/td&gt;
					&lt;td&gt;50'000&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4600000&lt;/td&gt;
					&lt;td&gt;4625000&lt;/td&gt;
					&lt;td&gt;ERROR&lt;/td&gt;
					&lt;td&gt;25'000&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4600000&lt;/td&gt;
					&lt;td&gt;4612500&lt;/td&gt;
					&lt;td&gt;OK&lt;/td&gt;
					&lt;td&gt;12'500&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4612500&lt;/td&gt;
					&lt;td&gt;4619000&lt;/td&gt;
					&lt;td&gt;OK&lt;/td&gt;
					&lt;td&gt;6'500&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4619000&lt;/td&gt;
					&lt;td&gt;4622000&lt;/td&gt;
					&lt;td&gt;OK&lt;/td&gt;
					&lt;td&gt;3'000&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4622000&lt;/td&gt;
					&lt;td&gt;4623500&lt;/td&gt;
					&lt;td&gt;ERROR&lt;/td&gt;
					&lt;td&gt;1'500&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4622000&lt;/td&gt;
					&lt;td&gt;4622750&lt;/td&gt;
					&lt;td&gt;ERROR&lt;/td&gt;
					&lt;td&gt;750&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4622350&lt;/td&gt;
					&lt;td&gt;4622750&lt;/td&gt;
					&lt;td&gt;OK&lt;/td&gt;
					&lt;td&gt;400&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4622175&lt;/td&gt;
					&lt;td&gt;4622350&lt;/td&gt;
					&lt;td&gt;ERROR&lt;/td&gt;
					&lt;td&gt;175&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4622265&lt;/td&gt;
					&lt;td&gt;4622350&lt;/td&gt;
					&lt;td&gt;OK&lt;/td&gt;
					&lt;td&gt;85&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4622175&lt;/td&gt;
					&lt;td&gt;4622265&lt;/td&gt;
					&lt;td&gt;ERROR&lt;/td&gt;
					&lt;td&gt;90&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4622220&lt;/td&gt;
					&lt;td&gt;4622265&lt;/td&gt;
					&lt;td&gt;OK&lt;/td&gt;
					&lt;td&gt;45&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4622175&lt;/td&gt;
					&lt;td&gt;4622220&lt;/td&gt;
					&lt;td&gt;ERROR&lt;/td&gt;
					&lt;td&gt;45&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4622175&lt;/td&gt;
					&lt;td&gt;4622188&lt;/td&gt;
					&lt;td&gt;ERROR&lt;/td&gt;
					&lt;td&gt;13&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4622188&lt;/td&gt;
					&lt;td&gt;4622220&lt;/td&gt;
					&lt;td&gt;OK&lt;/td&gt;
					&lt;td&gt;32&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4622181&lt;/td&gt;
					&lt;td&gt;4622188&lt;/td&gt;
					&lt;td&gt;ERROR&lt;/td&gt;
					&lt;td&gt;7&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4622175&lt;/td&gt;
					&lt;td&gt;4622181&lt;/td&gt;
					&lt;td&gt;ERROR&lt;/td&gt;
					&lt;td&gt;6&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4622170&lt;/td&gt;
					&lt;td&gt;4622175&lt;/td&gt;
					&lt;td&gt;ERROR&lt;/td&gt;
					&lt;td&gt;5&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4622120&lt;/td&gt;
					&lt;td&gt;4622130&lt;/td&gt;
					&lt;td&gt;&amp;hellip;&lt;/td&gt;
					&lt;td&gt;10&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Dann gehen wir in den Fein-Suche-Bereich über:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;test=# SELECT * FROM test WHERE id = 4622079;
 id | data | ts 
---------+-------------------------------+----------------------------
 4622079 | Some data to blow table up... | 2026-03-18 17:31:39.104354
(1 row)
&lt;/code&gt;&lt;/pre&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Zeile&lt;/th&gt;
					&lt;th&gt;Resultat&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&amp;hellip;&lt;/td&gt;
					&lt;td&gt;OK&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4622079&lt;/td&gt;
					&lt;td&gt;OK&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4622080&lt;/td&gt;
					&lt;td&gt;ERROR&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4622081&lt;/td&gt;
					&lt;td&gt;ERROR&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&amp;hellip;&lt;/td&gt;
					&lt;td&gt;ERROR&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4622186&lt;/td&gt;
					&lt;td&gt;ERROR&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4622187&lt;/td&gt;
					&lt;td&gt;OK&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&amp;hellip;&lt;/td&gt;
					&lt;td&gt;OK&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Das selbe Spielchen machen wir jetzt noch von &amp;ldquo;oben&amp;rdquo; und gelangen zum Schluss an einen Wertebereich der Korruption von 4'622'080 bis 4'622'186. Es sind also erst mal 107 Rows innerhalb dieser Korruption.&lt;/p&gt;
&lt;h2 id="daten-retten"&gt;Daten retten&lt;/h2&gt;
&lt;p&gt;Zum Retten der Daten erstellen wir zuerst einmal eine exakte Kopie unserer Tabelle (&lt;strong&gt;Achtung&lt;/strong&gt;: Daran denken, genügend Diskplatz bereit zu stellen!):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;test=# CREATE TABLE test_copy (LIKE test INCLUDING ALL);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Und bringen erst mal alle unsere Daten in Sicherheit:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;test=# INSERT INTO test_copy SELECT * FROM test WHERE id &amp;lt; 4622080;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Den unteren Teil der Daten können wir noch mittels eines &amp;ldquo;sequential scans&amp;rdquo; retten:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;test=# EXPLAIN SELECT * FROM test WHERE id &amp;lt; 4622080;
 QUERY PLAN 
------------------------------------------------------------------
 Seq Scan on test (cost=0.00..1581458.80 rows=71211823 width=36)
 Filter: (id &amp;lt; 4622080)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Beim oberen Teil der Daten geht das dann nicht mehr. Hier müssen wir den Planner zu einem &amp;ldquo;index scan&amp;rdquo; zwingen:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;test=# SET enable_seqscan = off;
SET

test=# EXPLAIN SELECT * FROM test WHERE id &amp;gt; 4622186;
 QUERY PLAN 
------------------------------------------------------------------------------------
 Index Scan using test_pkey on test (cost=0.57..2819292.47 rows=71211823 width=36)
 Index Cond: (id &amp;gt; 4622186)

test=# INSERT INTO test_copy SELECT * FROM test WHERE id &amp;gt; 4622186;

test=# SET enable_seqscan = on;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Somit hätten wir erst mal alle Daten in Sicherheit gebracht, welche ausserhalb des korrupten Blocks liegen. Jetzt stellt sich natürlich die Frage, ob noch mehr geht?&lt;/p&gt;
&lt;h2 id="mehr-daten-retten"&gt;Mehr Daten retten&lt;/h2&gt;
&lt;p&gt;Zu diesem Zweck erstellen wir eine zweite Tabelle und ignorieren die Checksummen-Fehler. &lt;strong&gt;Achtung&lt;/strong&gt;: Ab diesem Punkt können die Checksummen-Fehler plötzlich &amp;ldquo;magisch&amp;rdquo; verschwinden die Korruptionen sind aber dadurch nicht weg sondern werden einfach nicht mehr festegestellt. [ &lt;a href="https://thebuild.com/blog/all-your-gucs-in-a-row-ignore_checksum_failure/" target="_blank" title="All Your GUCs in a Row: ignore_checksum_failure"&gt;4&lt;/a&gt; ]&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;test=# CREATE TABLE test_copy2 (LIKE test INCLUDING ALL);

test=# SET ignore_checksum_failure = on;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dann kopieren wir unsere Rows häppchenweise von Zeile 4622080 bis 4622186 weg:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;test=# INSERT INTO test_copy2 SELECT * FROM test WHERE id &amp;gt;= 4622080 and id &amp;lt; 4622090;
test=# INSERT INTO test_copy2 SELECT * FROM test WHERE id &amp;gt;= 4622090 and id &amp;lt; 4622100;
test=# INSERT INTO test_copy2 SELECT * FROM test WHERE id &amp;gt;= 4622100 and id &amp;lt; 4622110;
test=# INSERT INTO test_copy2 SELECT * FROM test WHERE id &amp;gt;= 4622110 and id &amp;lt; 4622120;
test=# INSERT INTO test_copy2 SELECT * FROM test WHERE id &amp;gt;= 4622120 and id &amp;lt; 4622123;
test=# --&amp;gt; Here is the hole!
test=# INSERT INTO test_copy2 SELECT * FROM test WHERE id &amp;gt; 4622127 and id &amp;lt;= 4622186;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dann überprüfen wir noch mal die Wertebereiche:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;test=# SELECT * FROM test_copy WHERE id between 4622075 and 4622080;
 id | data | ts 
---------+-------------------------------+----------------------------
 4622075 | Some data to blow table up... | 2026-03-18 17:31:39.104354
 4622076 | Some data to blow table up... | 2026-03-18 17:31:39.104354
 4622077 | Some data to blow table up... | 2026-03-18 17:31:39.104354
 4622078 | Some data to blow table up... | 2026-03-18 17:31:39.104354
 4622079 | Some data to blow table up... | 2026-03-18 17:31:39.104354

test=# SELECT * FROM test_copy2 WHERE id between 4622075 and 4622085;
 id | data | ts 
---------+-------------------------------+----------------------------
 4622080 | Some data to blow table up... | 2026-03-18 17:31:39.104354
 4622081 | Some data to blow table up... | 2026-03-18 17:31:39.104354
 4622082 | Some data to blow table up... | 2026-03-18 17:31:39.104354
 4622083 | Some data to blow table up... | 2026-03-18 17:31:39.104354
 4622084 | Some data to blow table up... | 2026-03-18 17:31:39.104354
 4622085 | Some data to blow table up... | 2026-03-18 17:31:39.104354

test=# SELECT * FROM test_copy2 WHERE id between 4622180 and 4622190;
 id | data | ts 
---------+-------------------------------+----------------------------
 4622180 | Some data to blow table up... | 2026-03-18 17:31:39.104354
 4622181 | Some data to blow table up... | 2026-03-18 17:31:39.104354
 4622182 | Some data to blow table up... | 2026-03-18 17:31:39.104354
 4622183 | Some data to blow table up... | 2026-03-18 17:31:39.104354
 4622184 | Some data to blow table up... | 2026-03-18 17:31:39.104354
 4622185 | Some data to blow table up... | 2026-03-18 17:31:39.104354
 4622186 | Some data to blow table up... | 2026-03-18 17:31:39.104354

test=# SELECT * FROM test_copy WHERE id between 4622180 and 4622190;
 id | data | ts 
---------+-------------------------------+----------------------------
 4622187 | Some data to blow table up... | 2026-03-18 17:31:39.104354
 4622188 | Some data to blow table up... | 2026-03-18 17:31:39.104354
 4622189 | Some data to blow table up... | 2026-03-18 17:31:39.104354
 4622190 | Some data to blow table up... | 2026-03-18 17:31:39.104354
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Und fügen die beiden Datensets wieder zusammen:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;test=# INSERT INTO test_copy SELECT * FROM test_copy2;
INSERT 0 104

test=# DROP TABLE test_copy2;
DROP TABLE

test=# DROP TABLE test CASCADE;
NOTICE: drop cascades to default value for column id of table test_copy
DROP TABLE

test=# ALTER TABLE test_copy RENAME TO test;
ALTER TABLE

test=# CREATE SEQUENCE public.test_id_seq
 AS integer
 RESTART WITH 75930860
 INCREMENT BY 1
 NO MINVALUE
 NO MAXVALUE
 CACHE 1;

test=# ALTER SEQUENCE public.test_id_seq OWNER TO dba;
test=# ALTER SEQUENCE public.test_id_seq OWNED BY public.test.id;
test=# ALTER TABLE ONLY public.test ALTER COLUMN id SET DEFAULT nextval(&amp;#39;public.test_id_seq&amp;#39;::regclass);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Spätestens jetzt aber ist es allerhöchste Zeit, sich ernsthaft Gedanken um ein Backup zu machen&amp;hellip;&lt;/p&gt;
&lt;h2 id="quellen"&gt;Quellen&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Christophe Pettus, PostgreSQL Experts, 2026-08-05: &lt;a href="https://thebuild.com/blog/all-your-gucs-in-a-row-ignore_checksum_failure/" target="_blank"&gt;All Your GUCs in a Row: ignore_checksum_failure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Christophe Pettus, PostgreSQL Experts, 2026-08-06: &lt;a href="https://thebuild.com/blog/all-your-gucs-in-a-row-ignore_invalid_pages/" target="_blank"&gt;All Your GUCs in a Row: ignore_invalid_pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Ashutosh Sharma &lt;a href="https://www.postgresql.org/docs/current/pgsurgery.html" target="_blank"&gt;pg_surgery — perform low-level surgery on relation data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;PostgreSQL Server Configuration: &lt;a href="https://www.postgresql.org/docs/current/runtime-config-developer.html#GUC-ZERO-DAMAGED-PAGES" target="_blank"&gt;zero_damaged_pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/current/pageinspect.html" target="_blank"&gt;&lt;code&gt;pageinspect&lt;/code&gt; — low-level inspection of database pages&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>PostgreSQL Dokumentation noch besser machen</title><link>https://www.fromdual.com/de/blog/postgresql/postgresql-dokumentation-noch-besser-machen/</link><pubDate>Mon, 10 Aug 2026 10:50:00 +0200</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/postgresql/postgresql-dokumentation-noch-besser-machen/</guid><description>&lt;p&gt;Bei unseren Arbeiten zum Thema &amp;ldquo;PostgreSQL für Delfine und Seelöwen&amp;rdquo; habe ich die PostgreSQL Dokumentation zum Thema &lt;a href="https://www.postgresql.org/docs/current/warm-standby.html" target="_blank" title="Log-Shipping Standby Servers"&gt;Replikation&lt;/a&gt; durchgeackert.&lt;/p&gt;
&lt;p&gt;Da ich bei diesem Thema noch nicht ganz so fit bin, schaue ich bei einigen Stichworten (Parametern, Funktionen, etc.) immer mal wieder gerne nach, was diese genau bedeuten oder wie sie genau funktionieren (&lt;a href="https://en.wikipedia.org/wiki/RTFM" target="_blank"&gt;RTFM&lt;/a&gt;!). Genau dafür wurden ja ursprünglich mal die &lt;strong&gt;Links&lt;/strong&gt; erfunden, welche zuerst &lt;a href="https://en.wikipedia.org/wiki/Gopher_(protocol)" target="_blank"&gt;Gopher&lt;/a&gt; und später das Internet/WWW (http) so populär gemacht haben.&lt;/p&gt;
&lt;p&gt;Leider fehlen aber in besagter Dokumentation oft diese Links, was den Lesefluss behindert.&lt;/p&gt;
&lt;p&gt;Zum Glück aber ist PostgreSQL ein Open Source Projekt und Mitarbeit soll ja hochwillkommen sein! Also könnte ich ja, statt nur dumm über die Dokumentation rumzumosern, die entsprechenden fehlenden Links selber hinzufügen. Aber wie geht das denn genau in einem für mich neuen und daher noch etwas ungewohnten Ökosystem? Dabei hat mir ein Artikel von Elizabeth Christensen von Crunchy Data mit dem Titel: &lt;a href="https://www.crunchydata.com/blog/contributing-to-postgres-101-a-beginners-experience" target="_blank"&gt;Contributing to Postgres 101: A Beginner&amp;rsquo;s Experience&lt;/a&gt; beim Einstieg geholfen.&lt;/p&gt;
&lt;p&gt;Da ich selber Null Ahnung vom Programmieren habe, sehe ich beim Dokumentation Verbessern eine gute Möglichkeit, mich aktiv ins Projekt einzubringen und mitzuhelfen&amp;hellip;&lt;/p&gt;
&lt;h2 id="postgresql-dokumentation-verbessern"&gt;PostgreSQL Dokumentation verbessern&lt;/h2&gt;
&lt;p&gt;Die PostgreSQL Dokumentation liegt direkt im Server-Repository. Also ziehen wir uns als erstes mal das Git-Repository vom PostgreSQL Server runter:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ git clone http://git.postgresql.org/git/postgresql.git
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Die nächste Herausforderung besteht darin, die richtige Datei zu finden:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ cd postgresql/doc/src/sgml
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Hierbei hilft mit der &lt;code&gt;grep&lt;/code&gt; Befehl in all seinen Spielarten weiter:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ grep -r &amp;#39;Planning for High Availability&amp;#39; *.sgml
high-availability.sgml: &amp;lt;title&amp;gt;Planning for High Availability&amp;lt;/title&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Das richtige Dokument scheint &lt;code&gt;high-availability.sgml&lt;/code&gt; zu sein. Die PostgreSQL Dokumentation selbst ist in &lt;a href="https://en.wikipedia.org/wiki/Standard_Generalized_Markup_Language" target="_blank"&gt;SGML&lt;/a&gt; verfasst, welche ähnlich wie HTML und nicht sonderlich schwer zu erlernen ist.&lt;/p&gt;
&lt;p&gt;Diese SGML-Dateien lassen sich gut mit dem Editor der Wahl und entsprechendem Code-Highlighting lesen und ändern.&lt;/p&gt;
&lt;p&gt;Dann geht es daran, die einzelnen Stichworte zu überprüfen, ob sie schon korrekt mit Markups versehen sind und wenn ja, diese mit Links zu versehen:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Stichwort&lt;/th&gt;
					&lt;th&gt;Markups&lt;/th&gt;
					&lt;th&gt;Links&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;synchronous_standby_names&lt;/td&gt;
					&lt;td&gt;&lt;b&gt;&amp;lt;varname&amp;gt;&lt;/b&gt;synchronous_standby_names&lt;b&gt;&amp;lt;/varname&amp;gt;&lt;/b&gt;&lt;/td&gt;
					&lt;td&gt;&amp;lt;xref linkend=&amp;quot;&lt;strong&gt;guc-synchronous-standby-names&lt;/strong&gt;&amp;quot;/&amp;gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;archive_command&lt;/td&gt;
					&lt;td&gt;&lt;b&gt;&amp;lt;varname&amp;gt;&lt;/b&gt;archive_command&lt;b&gt;&amp;lt;/varname&amp;gt;&lt;/b&gt;&lt;/td&gt;
					&lt;td&gt;&amp;lt;xref linkend=&amp;quot;&lt;strong&gt;guc-archive-command&lt;/strong&gt;&amp;quot;/&amp;gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;archive_library&lt;/td&gt;
					&lt;td&gt;&lt;b&gt;&amp;lt;varname&amp;gt;&lt;/b&gt;archive_library&lt;b&gt;&amp;lt;/varname&amp;gt;&lt;/b&gt;&lt;/td&gt;
					&lt;td&gt;&amp;lt;xref linkend=&amp;quot;&lt;strong&gt;guc-archive-library&lt;/strong&gt;&amp;quot;/&amp;gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;synchronous_commit&lt;/td&gt;
					&lt;td&gt;&lt;b&gt;&amp;lt;varname&amp;gt;&lt;/b&gt;synchronous_commit&lt;b&gt;&amp;lt;/varname&amp;gt;&lt;/b&gt;&lt;/td&gt;
					&lt;td&gt;&amp;lt;xref linkend=&amp;quot;&lt;strong&gt;guc-synchronous-commit&lt;/strong&gt;&amp;quot;/&amp;gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;pg_receivewal&lt;/td&gt;
					&lt;td&gt;&lt;b&gt;&amp;lt;command&amp;gt;&lt;/b&gt;pg_receivewal&lt;b&gt;&amp;lt;/command&amp;gt;&lt;/b&gt;&lt;/td&gt;
					&lt;td&gt;&amp;lt;xref linkend=&amp;quot;&lt;strong&gt;app-pgreceivewal&lt;/strong&gt;&amp;quot;/&amp;gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;pg_recvlogical&lt;/td&gt;
					&lt;td&gt;&lt;b&gt;&amp;lt;command&amp;gt;&lt;/b&gt;pg_recvlogical&lt;b&gt;&amp;lt;/command&amp;gt;&lt;/b&gt;&lt;/td&gt;
					&lt;td&gt;&amp;lt;xref linkend=&amp;quot;&lt;strong&gt;app-pgrecvlogical&lt;/strong&gt;&amp;quot;/&amp;gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;pg_backup_stop&lt;/td&gt;
					&lt;td&gt;&lt;b&gt;&amp;lt;function&amp;gt;&lt;/b&gt;pg_backup_stop()&lt;b&gt;&amp;lt;/function&amp;gt;&lt;/b&gt;&lt;/td&gt;
					&lt;td&gt;&lt;b&gt;&amp;lt;link linkend=&amp;ldquo;pg-backup-stop&amp;rdquo;&amp;gt;&lt;/b&gt;&amp;lt;function&amp;gt;pg_backup_stop()&amp;lt;/function&amp;gt;&lt;b&gt;&amp;lt;/link&amp;gt;&lt;/b&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;pg_backup_start&lt;/td&gt;
					&lt;td&gt;&lt;b&gt;&amp;lt;function&amp;gt;&lt;/b&gt;pg_backup_start()&lt;b&gt;&amp;lt;/function&amp;gt;&lt;/b&gt;&lt;/td&gt;
					&lt;td&gt;&lt;b&gt;&amp;lt;link linkend=&amp;ldquo;pg-backup-start&amp;rdquo;&amp;gt;&lt;/b&gt;&amp;lt;function&amp;gt;pg_backup_start()&amp;lt;/function&amp;gt;&lt;b&gt;&amp;lt;/link&amp;gt;&lt;/b&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;pg_switch_wal&lt;/td&gt;
					&lt;td&gt;&lt;b&gt;&amp;lt;function&amp;gt;&lt;/b&gt;pg_switch_wal()&lt;b&gt;&amp;lt;/function&amp;gt;&lt;/b&gt;&lt;/td&gt;
					&lt;td&gt;&lt;b&gt;&amp;lt;link linkend=&amp;ldquo;pg_switch_wal&amp;rdquo;&amp;gt;&lt;/b&gt;&amp;lt;function&amp;gt;pg_switch_wal()&amp;lt;/function&amp;gt;&lt;b&gt;&amp;lt;/link&amp;gt;&lt;/b&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Achtung&lt;/strong&gt;: Hierbei ist zu beachten, dass die Stichworte mit &amp;ldquo;_&amp;rdquo; (Unterstrich) und die Links mit &amp;ldquo;-&amp;rdquo; (Bindestrich) geschrieben werden.&lt;/p&gt;
&lt;p&gt;Beim Bauen der Dokumentation ist dann noch aufgefallen, dass einige Ziele von Links (&lt;code&gt;id&lt;/code&gt;) gar nicht gesetzt waren, also mussten diese auch noch angepasst werden:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; &amp;lt;row&amp;gt;
- &amp;lt;entry role=&amp;#34;func_table_entry&amp;#34;&amp;gt;&amp;lt;para role=&amp;#34;func_signature&amp;#34;&amp;gt;
+ &amp;lt;entry id=&amp;#34;pg-backup-start&amp;#34; role=&amp;#34;func_table_entry&amp;#34;&amp;gt;&amp;lt;para role=&amp;#34;func_signature&amp;#34;&amp;gt;
 &amp;lt;indexterm&amp;gt;
 &amp;lt;primary&amp;gt;pg_backup_start&amp;lt;/primary&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="qualitätssicherung"&gt;Qualitätssicherung&lt;/h2&gt;
&lt;p&gt;Wenn alle Änderungen vorgenommen sind, geht es an die Qualitätskontrolle. Dazu baut man die Dokumentation lokal:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ cd postgresql
$ ./configure
$ cd doc
$ make
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Wie das im genau im Detail funktioniert, ist &lt;a href="https://www.postgresql.org/docs/18/docguide-build.html" target="_blank" title="Building the Documentation with Make"&gt;hier&lt;/a&gt; beschrieben.&lt;/p&gt;
&lt;p&gt;Falls der Build noch Fehler findet, werden diese angezeigt und der Build wird abgebrochen. Falls alles sauber durch läuft, kann man mit dem Browser seiner Wahl, jetzt noch schauen, ob auch alles wirklich so funktioniert, wie man sich das vorstellt:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ firefox src/sgml/html/warm-standby.html
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Was ich später noch gefunden habe:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Building the documentation can take very long. But there is a method to just check the correct syntax of the documentation files, which only takes a few seconds: [ &lt;a href="https://www.postgresql.org/docs/18/docguide-build.html#DOCGUIDE-BUILD-SYNTAX-CHECK" target="_blank" title="Syntax Check"&gt;5&lt;/a&gt; ]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ make check
make -C ../src/backend generated-headers
make[1]: Entering directory &amp;#39;/home/oli/fromdual/postgresql/docu/postgresql/src/backend&amp;#39;
make -C ../include/catalog generated-headers
make[2]: Entering directory &amp;#39;/home/oli/fromdual/postgresql/docu/postgresql/src/include/catalog&amp;#39;
make[2]: Nothing to be done for &amp;#39;generated-headers&amp;#39;.
make[2]: Leaving directory &amp;#39;/home/oli/fromdual/postgresql/docu/postgresql/src/include/catalog&amp;#39;
make -C nodes generated-header-symlinks
make[2]: Entering directory &amp;#39;/home/oli/fromdual/postgresql/docu/postgresql/src/backend/nodes&amp;#39;
make[2]: Nothing to be done for &amp;#39;generated-header-symlinks&amp;#39;.
make[2]: Leaving directory &amp;#39;/home/oli/fromdual/postgresql/docu/postgresql/src/backend/nodes&amp;#39;
make -C utils generated-header-symlinks
make[2]: Entering directory &amp;#39;/home/oli/fromdual/postgresql/docu/postgresql/src/backend/utils&amp;#39;
make -C adt jsonpath_gram.h
make[3]: Entering directory &amp;#39;/home/oli/fromdual/postgresql/docu/postgresql/src/backend/utils/adt&amp;#39;
make[3]: &amp;#39;jsonpath_gram.h&amp;#39; is up to date.
make[3]: Leaving directory &amp;#39;/home/oli/fromdual/postgresql/docu/postgresql/src/backend/utils/adt&amp;#39;
make[2]: Leaving directory &amp;#39;/home/oli/fromdual/postgresql/docu/postgresql/src/backend/utils&amp;#39;
make[1]: Leaving directory &amp;#39;/home/oli/fromdual/postgresql/docu/postgresql/src/backend&amp;#39;
rm -rf &amp;#39;/home/oli/fromdual/postgresql/docu/postgresql&amp;#39;/tmp_install
/usr/bin/mkdir -p &amp;#39;/home/oli/fromdual/postgresql/docu/postgresql&amp;#39;/tmp_install/log
make -C &amp;#39;..&amp;#39; DESTDIR=&amp;#39;/home/oli/fromdual/postgresql/docu/postgresql&amp;#39;/tmp_install install &amp;gt;&amp;#39;/home/oli/fromdual/postgresql/docu/postgresql&amp;#39;/tmp_install/log/install.log 2&amp;gt;&amp;amp;1
make -j1 checkprep &amp;gt;&amp;gt;&amp;#39;/home/oli/fromdual/postgresql/docu/postgresql&amp;#39;/tmp_install/log/install.log 2&amp;gt;&amp;amp;1
PATH=&amp;#34;/home/oli/fromdual/postgresql/docu/postgresql/tmp_install/usr/local/pgsql/bin:/home/oli/fromdual/postgresql/docu/postgresql/doc:$PATH&amp;#34; LD_LIBRARY_PATH=&amp;#34;/home/oli/fromdual/postgresql/docu/postgresql/tmp_install/usr/local/pgsql/lib:$LD_LIBRARY_PATH&amp;#34; INITDB_TEMPLATE=&amp;#39;/home/oli/fromdual/postgresql/docu/postgresql&amp;#39;/tmp_install/initdb-template initdb --auth trust --no-sync --no-instructions --lc-messages=C --no-clean &amp;#39;/home/oli/fromdual/postgresql/docu/postgresql&amp;#39;/tmp_install/initdb-template &amp;gt;&amp;gt;&amp;#39;/home/oli/fromdual/postgresql/docu/postgresql&amp;#39;/tmp_install/log/initdb-template.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="patch-einreichen"&gt;Patch einreichen&lt;/h2&gt;
&lt;p&gt;Wenn alles wie vorgesehen und zur Zufriedenheit funktioniert, kann man dann daran gehen, den Patch zu erstellen um ihn einzureichen:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ git commit -m &amp;#39;some references on variables and functions added&amp;#39;
$ git format-patch -1 HEAD
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dabei wird eine Datei erstellt, die den Commit-Kommentar enhält: &lt;code&gt;0001-some-references-on-variables-and-functions-added.patch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Jetzt ist es im PostgreSQL-Projekt anscheinend so, dass man keinen Merge-Request erstellt um den Patch in den Quellcode zurück gelangen zu lassen sondern so, dass der Patch an die entsprechende Mailingliste geschickt werden muss und anschliessend von einem Entwickler, der Merge/Commit-Rechte hat, in den Haupt-Baum eingepflegt wird. Ich habe mich jetzt mal mit &amp;ldquo;meinem&amp;rdquo; Commiter darauf geeinigt, dass wir die Diskussion über meinen Patch auf der &lt;a href="https://www.postgresql.org/list/pgsql-docs/" target="_blank"&gt;pgsql-docs&lt;/a&gt; Mailingliste führen.&lt;/p&gt;
&lt;p&gt;Mal schauen, wie es weiter geht und wie weit ich mit meinem Patch komme&amp;hellip;&lt;/p&gt;</description></item><item><title>Künstlich verzögerte Replikation mit PostgreSQL</title><link>https://www.fromdual.com/de/blog/postgresql/kuenstlich-verzoegerte-replikation-mit-postgresql/</link><pubDate>Fri, 31 Jul 2026 11:59:00 +0200</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/postgresql/kuenstlich-verzoegerte-replikation-mit-postgresql/</guid><description>&lt;p&gt;Bei unseren Arbeiten zum Thema &amp;ldquo;PostgreSQL für Delfine und Seelöwen&amp;rdquo; habe ich die PostgreSQL Dokumentation zum Thema &lt;a href="https://www.postgresql.org/docs/current/warm-standby.html" target="_blank" title="Log-Shipping Standby Servers"&gt;Replikation&lt;/a&gt; durchgeackert.
Was mir dabei aufgefallen ist, ist das PostgreSQL KEINE künstlich verzögerte Replikation kennt, zumindest was diese Doku anbelangt.&lt;/p&gt;
&lt;p&gt;Nachdem ich mir bereits Gedanken darüber gemacht habe, wie man eventuell mit PostgreSQL Bordmitteln, eine künstlich verzögerte Replikation basteln könnte, habe ich doch noch mal die Suchmaschine meiner Wahl bemüht und, siehe da, beim zweiten Anlauf hat es dann doch noch geklappt: Ich wurde fündig! [ 2019: &lt;a href="https://about.gitlab.com/blog/delayed-replication-for-disaster-recovery-with-postgresql/" target="_blank" title="How we used delayed replication for disaster recovery with PostgreSQL"&gt;1&lt;/a&gt; ]&lt;/p&gt;
&lt;p&gt;Die entsprechende Replikations-Konfiguration heisst: &lt;code&gt;recovery_min_apply_delay&lt;/code&gt; [ &lt;a href="https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-RECOVERY-MIN-APPLY-DELAY" target="_blank" title="recovery_min_apply_delay"&gt;2&lt;/a&gt; ]. Leider fehlt das in der Dokumentation zum Thema &amp;ldquo;Replikation&amp;rdquo;. Ich habe mal bei der PostgreSQL Community einen Verbesserungsvorschlag eingereicht. Mal schauen, wie weit ich da komme&amp;hellip;&lt;/p&gt;
&lt;h2 id="anwendungsfälle"&gt;Anwendungsfälle&lt;/h2&gt;
&lt;p&gt;Aber warum will man überhaupt eine Replikation verzögern? Dazu sind mir bisher unterschiedliche Anwendungsfälle begegnet:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Am häufigsten wird dieses Setup gegen Ups!-Queries verwendet. Ein Fehler auf dem Primary würde bei einer normalen Replikation unmittelbar auf den Standby Servern einschlagen und auf diesen ebenfalls die Daten zerstören. Mit der verzögerten Replikation erhält man Zeit, den Fehler zu analysieren, allenfalls die Daten aus dem Standby zur Rettung herbeizuziehen oder den Stanby bis vor das Ups-Query aufholen zu lassen und dann auf den Standby zu schwenken. Siehe hierzu auch: &lt;a href="https://www.fromdual.com/de/blog/postgresql/postgresql-point-in-time-recovery-bei-ups-queries/" target="_blank"&gt;PostgreSQL Point-in-Time-Recovery bei Ups-Queries&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ein weiterer Anwendungsfall ist, einer bestimmten Nutzergruppe gezielt alte Daten zur Verfügung zu stellen, um sie bei entsprechender Bezahlung auf die aktuellen Daten zugreifen zu lassen (Freemium Geschäftsmodell) z. B. bei Börsenhandel, Computerwetten, Computerspielen, Sportresultaten, Job- oder sonstigen Angeboten oder Wetterdaten.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ein dritter Anwendungsfall wäre, ein System zu testen, wie es sich bei Replikationsverzögerungen verhält. Bei einer asynchronen Replikation kann es potentiell immer zu Verzögerungen zwischen Primary und Standby kommen. Künstlich eine so hohe Last zu erzeugen, dass dieses Verhalten simuliert werden kann, ist oft schwierig. Mit der künstlich verzögerten Replikation kann man das Verhalten der Anwendung so gezielt testen und allenfalls die Anwendung robuster machen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Der letzte Anwendungsfall wäre, schauen zu können, wie die Datenbank in der Vergangenheit ausgesehen hat, ohne ein Backup wieder einspielen zu müssen. Wenn man die Replikation zum Beispiel mit einer Verzögerung von einer Woche konfiguriert (bei PostgreSQL sind maximal knapp 40 Tage (2&lt;sup&gt;32&lt;/sup&gt; Sekunden) möglich), kann man in der Folgewoche noch schauen, ob die Daten nach dem Change am Wochenende noch korrekt sind.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="aufsetzen-der-primary-instanz"&gt;Aufsetzen der Primary Instanz&lt;/h2&gt;
&lt;p&gt;Der &lt;strong&gt;Primary&lt;/strong&gt; muss zusätzlich wie folgt konfiguriert werden (Datenbank-Neustart erforderlich!):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#
# postgresql.conf
#
cluster_name = &amp;#39;pg18p&amp;#39;
listen_addresses = &amp;#39;*&amp;#39;
archive_mode = on
archive_command = &amp;#39;test ! -f /mnt/backup/wal_archive/%f &amp;amp;&amp;amp; cp %p /mnt/backup/wal_archive/%f &amp;amp;&amp;amp; sync /mnt/backup/wal_archive/%f&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Damit alte WALs auf dem Primary nicht zu früh gelöscht werden, arbeiten wir hier mit einem &amp;ldquo;Replication Slot&amp;rdquo;. Dieser muss zuerst angelegt werden:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# SELECT * FROM pg_create_physical_replication_slot(&amp;#39;slot_for_pg18s1&amp;#39;);
 slot_name | lsn 
-----------------+-----
 slot_for_pg18s1 | 

postgres=# SELECT slot_name, slot_type, active, wal_status, failover, synced
 FROM pg_replication_slots
;
 slot_name | slot_type | active | wal_status | failover | synced 
-----------------+-----------+--------+------------+----------+--------
 slot_for_pg18s1 | physical | f | | f | f
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Natürlich braucht es auch noch einen Replikations-User:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# CREATE ROLE replication WITH LOGIN PASSWORD &amp;#39;secret&amp;#39; REPLICATION;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;welcher natürlich auch von remote zugreifen können muss:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#
# pg_hba.conf
#
# TYPE DATABASE USER ADDRESS METHOD
host replication replication 10.223.125.0/24 scram-sha-256
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;und ein anschliessendes Aktivieren der Konfiguration:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# SELECT pg_reload_conf();
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="aufsetzen-der-standby-instanz"&gt;Aufsetzen der Standby Instanz&lt;/h2&gt;
&lt;p&gt;Dann bauen wir den verzögerten &lt;strong&gt;Standby&lt;/strong&gt; wie folgt auf:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ sudo systemctl stop postgresql
$ PGDATA=&amp;#39;/var/lib/postgresql/18/main&amp;#39;
$ rm -rf ${PGDATA}/*
$ pg_basebackup --user=replication --host=10.223.125.65 --format=plain --pgdata=${PGDATA}/
$ touch ${PGDATA}/standby.signal
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;passen die PostgreSQL Konfiguration wie folgt an:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#
# postgresql.conf
#
cluster_name = &amp;#39;pg18s1&amp;#39;
archive_cleanup_command = &amp;#39;pg_archivecleanup /mnt/backup/wal_archive %r&amp;#39;

recovery_target_timeline = latest # default
primary_conninfo = &amp;#39;host=10.223.125.65 port=5432 user=replication options=&amp;#39;&amp;#39;-c wal_sender_timeout=5000&amp;#39;&amp;#39;&amp;#39;
recovery_min_apply_delay = &amp;#39;5min&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Das Passwort kann in der Datei &lt;code&gt;~/.pgpass&lt;/code&gt; hinterlegt werden:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;10.223.125.65:5432:replication:replication:secret
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dann starten wir die Standby Instanz und zum Schluss überprüfen wir, ob alles sauber funktioniert (ggf. &lt;code&gt;recovery_min_apply_delay&lt;/code&gt; warten):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# SELECT pg_is_in_recovery();
 pg_is_in_recovery 
-------------------
 t

postgres=# SELECT CURRENT_TIMESTAMP AS now, pg_last_xact_replay_timestamp() AS last_xact_timestamp
 , age(CURRENT_TIMESTAMP, pg_last_xact_replay_timestamp())
;
 now | last_xact_timestamp | age 
------------------------------+-------------------------------+-----------------
 2026-07-29 11:46:21.92429+00 | 2026-07-29 11:41:21.919321+00 | 00:00:00.004969
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="das-ups-query"&gt;Das Ups-Query&lt;/h2&gt;
&lt;p&gt;Jetzt passiert auf dem &lt;strong&gt;Primary&lt;/strong&gt; das Ups-Query:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;test=# UPDATE test SET data = &amp;#39;alles kaputt!&amp;#39;;
UPDATE 673
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nach etwas Schreckensstarre ermitteln wir die Zeit (&lt;strong&gt;Achtung&lt;/strong&gt;: UTC!):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;test=# SELECT current_timestamp;
 current_timestamp 
-------------------------------
 2026-07-31 14:10:30.083692+00
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="stoppen-der-replikation"&gt;Stoppen der Replikation&lt;/h3&gt;
&lt;p&gt;Sobald das Ups-Query aufgetreten ist, müssen wir sofort die Replikation auf dem Standby stoppen:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# SELECT pg_is_in_recovery();
 pg_is_in_recovery 
-------------------
 t

postgres=# SELECT pg_get_wal_replay_pause_state();
 pg_get_wal_replay_pause_state 
-------------------------------
 not paused

postgres=# SELECT pg_wal_replay_pause();
 pg_wal_replay_pause 
---------------------
 
(1 row)

postgres=# SELECT pg_get_wal_replay_pause_state();
 pg_get_wal_replay_pause_state 
-------------------------------
 paused

postgres=# SELECT pg_is_wal_replay_paused();
 pg_is_wal_replay_paused 
-------------------------
 t
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Allenfalls muss auch die Applikation angehalten werden, da ein weiteres Laufen lassen aus Business Sicht keinen Sinn macht.&lt;/p&gt;
&lt;p&gt;Anschliessend haben wir Zeit, den Zeitpunkt des Ups-Queries zu ermitteln.&lt;/p&gt;
&lt;p&gt;Nach weiterer Recherche und etwas Reserve legen wir den Zeitpunkt des Ups-Queries auf NACH 2026-07-31 14:10:00+00 fest!&lt;/p&gt;
&lt;h3 id="aufholen-bis-zum-ups-query"&gt;Aufholen bis zum Ups-Query&lt;/h3&gt;
&lt;p&gt;Dann lassen wir den verzögerten &lt;strong&gt;Standby&lt;/strong&gt; aufholen bis zum Ups-Query:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#
# postgresql.conf
#
# recovery_min_apply_delay = &amp;#39;5min&amp;#39; # Achtung: auskommentieren!
recovery_target_time = &amp;#39;2026-07-31 14:10:00+00&amp;#39;
# recovery_target_xid = &amp;#39;135586&amp;#39;
# recovery_target_lsn = &amp;#39;0/312DF748&amp;#39;
recovery_target_inclusive = off
recovery_target_action = &amp;#39;pause&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;und machen die &lt;strong&gt;Standby&lt;/strong&gt; Datenbank nach Überprüfung zum Primary mit anschliessendem Failover:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# SELECT pg_promote();
 pg_promote 
------------
 t
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In unserem Beispiel mussten wir mit 5 Rows (= 5 Sekunden) Datenverlust leben. Wer es genauer braucht, der muss mehr Zeit in das Bestimmen von &lt;code&gt;recovery_target_*&lt;/code&gt; investieren. Wie das genau geht erklären wir hier: &lt;a href="https://www.fromdual.com/de/blog/postgresql/postgresql-point-in-time-recovery-bei-ups-queries/" target="_blank"&gt;PostgreSQL Point-in-Time-Recovery bei Ups-Queries&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Backups mit der MySQL Clone Operation</title><link>https://www.fromdual.com/de/blog/backups-mit-mysql-clone/</link><pubDate>Mon, 27 Jul 2026 16:04:00 +0200</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/backups-mit-mysql-clone/</guid><description>&lt;p&gt;Kürzlich haben wir das PostgreSQL Backup-Tool &lt;code&gt;pg_basebackup&lt;/code&gt; getestet und dort grossen Gefallen an der remote-Backup Funktionalität gefunden, welche sowohl ein physisches lokales als auch ein physisches remote Backup erlaubt.&lt;/p&gt;
&lt;p&gt;Dabei hat sich bei uns die Frage gestellt, ob ein physisches remote Backup auch mit der &amp;ldquo;neuen&amp;rdquo; MySQL Server Clone-Funktionalität möglich ist, welche mit MySQL 8.0.17 (Juli 2019) dazu gekommen ist.&lt;/p&gt;
&lt;p&gt;Mit der MySQL Clone-Operation kann sowohl eine lokale als auch eine remote Kopie der Datenbank erstellt werden. Die ursprüngliche Idee für dieses Feature war wahrscheinlich, Knoten im InnoDB Cluster automatisiert zu erstellen (analog zum Percona XtraDB Cluster SST).&lt;/p&gt;
&lt;p&gt;Die bei der Clone-Operation verwendete Terminologie:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Donor (Geber, Quell-Datenbank der Daten)&lt;/li&gt;
&lt;li&gt;Recipient (Empfänger, Ziel der Daten(-Bank))&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Die Clone-Operation wird vom Recipient aus gestartet. Die Daten können ins eigene, alternativ aber auch in ein anderes Verzeichnis geclont werden.&lt;/p&gt;
&lt;h2 id="vorbereitungen"&gt;Vorbereitungen&lt;/h2&gt;
&lt;p&gt;Das Plugin muss sowohl auf dem Donor als auch auf dem Recipient installiert sein.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; INSTALL PLUGIN clone SONAME &amp;#39;mysql_clone.so&amp;#39;;
Query OK, 0 rows affected (0.01 sec)

SQL&amp;gt; SELECT PLUGIN_NAME, PLUGIN_STATUS
 FROM INFORMATION_SCHEMA.PLUGINS
 WHERE PLUGIN_NAME = &amp;#39;clone&amp;#39;
;
+-------------+---------------+
| PLUGIN_NAME | PLUGIN_STATUS |
+-------------+---------------+
| clone | ACTIVE |
+-------------+---------------+
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Wenn das Laden des Plugins beim Neustart erzwungen werden soll, muss es wie folgt in der MySQL Konfigurationsdatei (&lt;code&gt;my.cnf&lt;/code&gt;) konfiguriert werden:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[mysqld]
plugin_load_add = mysql_clone.so
clone = FORCE_PLUS_PERMANENT
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="lokales-backup-mittels-clone"&gt;Lokales Backup mittels Clone&lt;/h2&gt;
&lt;p&gt;Diese Variante kann als Ersatz einer physischen Backup Lösung (&lt;code&gt;xtrabackup&lt;/code&gt; oder MySQL Enterprise Backup (&lt;code&gt;mysql_backup&lt;/code&gt;)) dienen.
Auf der Datenbank, welche in diesem Fall als Recipient agiert, wird folgender Befehl ausgeführt:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; CLONE LOCAL DATA DIRECTORY = &amp;#39;/mnt/backup/mysql_clone&amp;#39;;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Was bei der Clone-Operation noch fehlt sind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Die ganzen TLS Schlüssel (&lt;code&gt;*.pem&lt;/code&gt; Files).&lt;/li&gt;
&lt;li&gt;Die Datei &lt;code&gt;auto.cnf&lt;/code&gt; welche die &lt;code&gt;server_uuid&lt;/code&gt; enthält.&lt;/li&gt;
&lt;li&gt;Die Datei &lt;code&gt;mysqld-auto.cnf&lt;/code&gt; welche dynamisch geänderte, persistierte Server Konfigurationsvariablen enthält.&lt;/li&gt;
&lt;li&gt;Die &lt;code&gt;mysql_upgrade_history&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Die MySQL Konfigurationsdatei (&lt;code&gt;my.cnf&lt;/code&gt;) sowie&lt;/li&gt;
&lt;li&gt;die Binary Logs.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ cp ${datadir}/*auto.cnf ${datadir}/mysql_upgrade_history ${datadir}/*.pem /mnt/backup/mysql_clone/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Das Wiederherstellen der Datenbank ist recht einfach:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ systemctl stop mysql
$ rm -rf ${datadir}/*
$ cp -a /mnt/backup/mysql_clone/* ${datadir}/
$ chown -R mysql: ${datadir}/*
$ systemctl start mysql
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Der Ordner &lt;code&gt;#clone&lt;/code&gt; wird durch die Clone-Operation erstellt und kann ignoriert, darf aber nicht gelöscht werden.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ ls -lad /mnt/backup/mysql_clone/*
...
drwxr-x--- 2 dba dba 4096 Jul 27 14:52 &amp;#39;#clone&amp;#39;
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Er wird beim Starten der MySQL Datenbank automatisch entfernt. Wenn man ihn trotzdem löscht erhält man folgende Fehlermeldungen:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[System] [MY-013576] [InnoDB] InnoDB initialization has started.
[System] [MY-013577] [InnoDB] InnoDB initialization has ended.
mysqld: Can&amp;#39;t create/write to file &amp;#39;./performance_schema/clone_status_385.sdi&amp;#39; (OS errno 2 - No such file or directory)
mysqld: Can&amp;#39;t create file &amp;#39;./performance_schema/clone_status_385.sdi&amp;#39; (errno: 2 - No such file or directory)
[ERROR] [MY-013272] [Clone] Plugin Clone reported: &amp;#39;Client: PFS table creation failed.&amp;#39;
[ERROR] [MY-010202] [Server] Plugin &amp;#39;clone&amp;#39; init function returned error.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Die für das Point-in-Time-Recovery erforderliche Binary Log Position kann wie folgt ermittelt werden:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; SELECT BINLOG_FILE, BINLOG_POSITION FROM performance_schema.clone_status;
+-------------------------------+-----------------+
| BINLOG_FILE | BINLOG_POSITION |
+-------------------------------+-----------------+
| boss_percona-84_binlog.000003 | 1231898 |
+-------------------------------+-----------------+
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="remote-backup-mittels-clone"&gt;Remote Backup mittels Clone&lt;/h2&gt;
&lt;p&gt;Um einen remote Backup mittels der Clone-Funktionalität erstellen zu können braucht es eine minimal funktionale MySQL Datenbank auf dem remote System. Ein simpler Prozess/ein simples Tool reicht dazu leider nicht.&lt;/p&gt;
&lt;p&gt;Auf dem Donor braucht es einen User mit den folgenden Privilegien:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; CREATE USER &amp;#39;backup_user&amp;#39;@&amp;#39;%&amp;#39; IDENTIFIED BY &amp;#39;secret&amp;#39;;
SQL&amp;gt; GRANT BACKUP_ADMIN ON *.* TO &amp;#39;backup_user&amp;#39;@&amp;#39;%&amp;#39;;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Zudem muss auf dem Recipient noch der mögliche Donor eingetragen werden:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; SET GLOBAL clone_valid_donor_list = &amp;#39;192.168.1.129:3306&amp;#39;;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dann erfolgt das Backup remote wie folgt:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; CLONE INSTANCE FROM &amp;#39;backup_user&amp;#39;@&amp;#39;192.168.1.129&amp;#39;:3306 IDENTIFIED BY &amp;#39;secret&amp;#39;
DATA DIRECTORY = &amp;#39;/mnt/backup/mysql_clone&amp;#39;;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Die weiter oben beschriebenen, noch fehlenden Dateien, müssen jetzt ebenfalls noch irgendwie kopiert werden:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ scp mysql@192.168.1.129:${datadir}/*auto.cnf /mnt/backup/mysql_clone/
$ scp mysql@192.168.1.129:${datadir}/mysql_upgrade_history /mnt/backup/mysql_clone/
$ scp mysql@192.168.1.129:${datadir}/*.pem /mnt/backup/mysql_clone/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Das Wiederherstellen der Datenbank erfolgt analog zur Beschreibung oben.&lt;/p&gt;
&lt;h2 id="fazit"&gt;Fazit&lt;/h2&gt;
&lt;p&gt;Die MySQL Clone-Operation ist ein cooles Feature, welches ich lange vernachlässigt habe, weil mir nicht in den Sinn kam, dass man es auch für Backzwecke nutzen könnte.&lt;/p&gt;
&lt;p&gt;Es würde mich nicht wundern, wenn die MySQL Entwickler nach PostgreSQL &lt;code&gt;pg_basebackup&lt;/code&gt; geschielt haben, als sie dieses Feature implementiert haben.&lt;/p&gt;
&lt;p&gt;In MariaDB fehlt dieses Feature leider, meines Wissens, noch gänzlich. Schade!&lt;/p&gt;
&lt;h2 id="quellen"&gt;Quellen&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Allgemein: &lt;a href="https://dev.mysql.com/doc/refman/9.7/en/clone-plugin.html" target="_blank"&gt;The Clone Plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Für das Clone-Backup gibt es einige wenige, minimale Einschränkungen, welche hier beschrieben sind: &lt;a href="https://dev.mysql.com/doc/refman/9.7/en/clone-plugin-limitations.html" target="_blank"&gt;Clone Plugin Limitations&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Das Überwachen des Clone-Backups ist hier beschrieben: &lt;a href="https://dev.mysql.com/doc/refman/9.7/en/clone-plugin-monitoring.html" target="_blank"&gt;Monitoring Cloning Operations&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Das Tuning des Clone-Backups ist hier beschrieben: &lt;a href="https://dev.mysql.com/doc/refman/9.7/en/clone-plugin-option-variable-reference.html" target="_blank"&gt;Clone System Variable Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Arunjith Aravindan, Percona, 2023-08-08: &lt;a href="https://www.percona.com/blog/provisioning-replication-with-clone-plugin/" target="_blank"&gt;Provisioning Replication With Clone Plugin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>PostgreSQL Point-in-Time-Recovery bei Ups-Queries</title><link>https://www.fromdual.com/de/blog/postgresql/postgresql-point-in-time-recovery-bei-ups-queries/</link><pubDate>Wed, 22 Jul 2026 07:26:00 +0200</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/postgresql/postgresql-point-in-time-recovery-bei-ups-queries/</guid><description>&lt;p&gt;Bei unseren Arbeiten zum Thema &amp;ldquo;PostgreSQL für Delfine und Seelöwen&amp;rdquo; bin ich darauf gestossen, dass im PostgreSQL Universum um das Thema Point-in-Time-Recovery auf ein spezifisches Statement/eine spezifische Transaktion ein Bogen gemacht wird. Und wenn man Beispiele findet, werden vor allem DDL und nicht DML Statements betrachtet.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you want to recover to some previous point in time (say, right before the junior DBA &lt;strong&gt;dropped your main transaction table&lt;/strong&gt;), just specify the required stopping point. You can specify the stop point, known as the “recovery target”, either by date/time, named restore point or by completion of a specific transaction ID. As of this writing &lt;strong&gt;only the date/time and named restore point options are very usable&lt;/strong&gt;, since there are no tools to help you identify with any accuracy which transaction ID to use. [ &lt;a href="https://www.postgresql.org/docs/current/continuous-archiving.html#BACKUP-PITR-RECOVERY" title="Recovering Using a Continuous Archive Backup"&gt;1&lt;/a&gt; ]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In dem Universum, aus dem ich komme, kennt man das anders. Hier ist es absolut kein Problem ein Point-in-Time-Recovery auf ein Statement oder eine Transaktion genau zu machen. Sofern man sein Handwerk beherrscht.&lt;/p&gt;
&lt;p&gt;Jetzt sticht mich aber der Hafer, ob das in dieser, für mich neuen Welt, nicht auch geht&amp;hellip;?&lt;/p&gt;
&lt;h2 id="die-testumgebung"&gt;Die Testumgebung&lt;/h2&gt;
&lt;p&gt;Wie aus unseren Schulungen bekannt, machen wir solche Experimente auf unserer &lt;code&gt;test&lt;/code&gt; Tabelle immer unter Last (&lt;code&gt;insert_test.sh&lt;/code&gt;):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;test=# DELETE FROM test WHERE id BETWEEN 10 AND 20;
DELETE 11
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="der-alarm"&gt;Der Alarm&lt;/h2&gt;
&lt;p&gt;Jetzt kommt die Meldung an den DBA: &amp;ldquo;&lt;em&gt;Ups! Es ist etwas kaputt gegangen! Kannst Du das bitte wieder reparieren?&lt;/em&gt;&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Welche Informationen brauchen wir also, um hier helfen zu können?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Wann ist es, möglichst genau, kaputt gegangen?
&lt;ul&gt;
&lt;li&gt;Am 22. Juli, ca. 10:55/CEST ➜ &lt;strong&gt;Achtung&lt;/strong&gt;: PostgreSQL scheint hier immer in UTC zu arbeiten, also: 08:55/UTC.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Wie, mit welchem Statement und auf welche Datenbank ist es kaputt gegangen?
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;delete from test where id between 10 and 20&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Datenbank: &lt;code&gt;test&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Wie viele Rows wurden (ungefähr) gelöscht?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Neben den üblichen Diskussionen über Datenbank stilllegen, Applikation stilllegen, etc. damit nicht noch mehr Unheil angerichtet wird, geht es jetzt darum, wie man die Datenbank wieder in den Zustand von vor dem Ups-Query bringt.&lt;/p&gt;
&lt;h2 id="vorbereitungen"&gt;Vorbereitungen&lt;/h2&gt;
&lt;p&gt;Um herauszufinden, wo GENAU wir beim Point-in-Time-Recovery stoppen wollen, brauchen wir die archivierten WAL Files. Und mit dem Tool &lt;code&gt;pg_waldump&lt;/code&gt; gucken wir dann in die WAL Files rein.&lt;/p&gt;
&lt;p&gt;Auf noch &amp;ldquo;aktive&amp;rdquo; WAL Files sollten wir nicht zugreifen,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Can give wrong results when the server is running. [ &lt;a href="https://www.postgresql.org/docs/current/pgwaldump.html" title="pg_waldump Notes"&gt;2&lt;/a&gt; ]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;das gibt potentiell falsche Resultate und Fehler:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ pg_waldump 000000010000000000000033 &amp;gt;/dev/null
pg_waldump: error: error in WAL record at 0/330B6CF8: invalid record length at 0/330B6D20: expected at least 24, got 0

$ pg_waldump 000000010000000000000034 &amp;gt;/dev/null
pg_waldump: error: could not find a valid record after 0/34000000
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Um das sicher zu stellen rotieren wir das aktuelle WAL File weg, damit es archiviert wird:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# SELECT pg_switch_wal();
 pg_switch_wal 
---------------
 0/3228AB50
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Zuerst wollen wir wissen, welche WAL Files in dem betreffendem Zeitraum in Betracht kommen (&lt;strong&gt;Achtung&lt;/strong&gt;: Server läuft ebenfalls in UTC!):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ PGDATA=&amp;#39;/var/lib/postgresql/18/main&amp;#39;
$ WALARCHIVE=&amp;#39;/mnt/backup/wal_archive&amp;#39;
$ PATH=&amp;#34;${PATH}:/usr/lib/postgresql/18/bin&amp;#34;

$ ll ${WALARCHIVE}/????????????????????????
-rw------- 1 postgres postgres 16777216 Jul 22 08:32 /mnt/backup/wal_archive/00000001000000000000002F
-rw------- 1 postgres postgres 16777216 Jul 22 08:47 /mnt/backup/wal_archive/000000010000000000000030
-rw------- 1 postgres postgres 16777216 Jul 22 09:02 /mnt/backup/wal_archive/000000010000000000000031
-rw------- 1 postgres postgres 16777216 Jul 22 09:11 /mnt/backup/wal_archive/000000010000000000000032
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Etwas genauer sehen wir es, wen wir in die WAL Files reinschauen (wahrscheinlich geht das eleganter?):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ for file in ${WALARCHIVE}/???????????????????????? ; do
 echo ${file}
 pg_waldump ${file} | head | grep COMMIT
done

/mnt/backup/wal_archive/000000010000000000000030
rmgr: Transaction len (rec/tot): 34/ 34, tx: 109112, lsn: 0/300000E0, prev 0/300000A0, desc: COMMIT 2026-07-22 08:32:31.317115 UTC
rmgr: Transaction len (rec/tot): 34/ 34, tx: 109113, lsn: 0/300001C0, prev 0/30000180, desc: COMMIT 2026-07-22 08:32:31.368472 UTC
rmgr: Transaction len (rec/tot): 34/ 34, tx: 109114, lsn: 0/300002A0, prev 0/30000260, desc: COMMIT 2026-07-22 08:32:31.427410 UTC
/mnt/backup/wal_archive/000000010000000000000031
rmgr: Transaction len (rec/tot): 34/ 34, tx: 127114, lsn: 0/31001D78, prev 0/31000FF8, desc: COMMIT 2026-07-22 08:47:31.912547 UTC
rmgr: Transaction len (rec/tot): 34/ 34, tx: 127115, lsn: 0/31001E58, prev 0/31001E18, desc: COMMIT 2026-07-22 08:47:31.971700 UTC
rmgr: Transaction len (rec/tot): 34/ 34, tx: 127116, lsn: 0/31001F38, prev 0/31001EF8, desc: COMMIT 2026-07-22 08:47:32.026471 UTC
/mnt/backup/wal_archive/000000010000000000000032
rmgr: Transaction len (rec/tot): 34/ 34, tx: 145081, lsn: 0/320000E0, prev 0/320000A0, desc: COMMIT 2026-07-22 09:02:31.648356 UTC
rmgr: Transaction len (rec/tot): 34/ 34, tx: 145082, lsn: 0/320001C0, prev 0/32000180, desc: COMMIT 2026-07-22 09:02:31.689970 UTC
rmgr: Transaction len (rec/tot): 34/ 34, tx: 145083, lsn: 0/320002A0, prev 0/32000260, desc: COMMIT 2026-07-22 09:02:31.749528 UTC
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Wenn die Zeitangabe zur Katastrophe stimmt (08:55/CEST), dann müsste das Statement in WAL 000000010000000000000031 liegen&amp;hellip;&lt;/p&gt;
&lt;p&gt;Zur Sicherheit machen wir einen kurzen Check:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ pg_waldump ${WALARCHIVE}/000000010000000000000031 | grep DELETE
rmgr: Heap len (rec/tot): 59/ 8179, tx: 135586, lsn: 0/312DF748, prev 0/312DF720, desc: DELETE xmax: 135586, off: 10, infobits: [KEYS_UPDATED], flags: 0x01, blkref #0: rel 1663/21506/21508 blk 0 FPW
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E1758, prev 0/312DF748, desc: DELETE xmax: 135586, off: 11, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E1790, prev 0/312E1758, desc: DELETE xmax: 135586, off: 12, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E17C8, prev 0/312E1790, desc: DELETE xmax: 135586, off: 13, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E1800, prev 0/312E17C8, desc: DELETE xmax: 135586, off: 14, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E1838, prev 0/312E1800, desc: DELETE xmax: 135586, off: 15, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E1870, prev 0/312E1838, desc: DELETE xmax: 135586, off: 16, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E18A8, prev 0/312E1870, desc: DELETE xmax: 135586, off: 17, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E18E0, prev 0/312E18A8, desc: DELETE xmax: 135586, off: 18, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E1918, prev 0/312E18E0, desc: DELETE xmax: 135586, off: 19, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E1950, prev 0/312E1918, desc: DELETE xmax: 135586, off: 20, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Glück gehabt! Da gibt es &lt;code&gt;DELETE&lt;/code&gt;s drin und zwar genau 11! Wir könnten also schon nahe dran sein&amp;hellip;&lt;/p&gt;
&lt;p&gt;Um genauer filtern zu können brauchen wir jetzt noch:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;die OID des Tablespaces&lt;/li&gt;
&lt;li&gt;die OID der Datenbank&lt;/li&gt;
&lt;li&gt;die OID der Tabelle&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# SELECT oid, spcname AS tablespace, spcowner::regrole AS owner
 FROM pg_tablespace
;
 oid | tablespace | owner 
------+------------+----------
 1663 | pg_default | postgres
 1664 | pg_global | postgres

postgres=# SELECT oid, datname AS database
 FROM pg_database
 WHERE oid = 21506
;
 oid | database 
-------+-----------
 5 | postgres
 16388 | enswitch
 1 | template1
 4 | template0
 21498 | oli
 21506 | test

postgres=# \connect test
test=# SELECT c.oid, ns.nspname AS schema, c.relname AS object_name, c.relkind
 FROM pg_class AS c
 JOIN pg_namespace AS ns ON ns.oid = c.relnamespace
 WHERE c.relkind = &amp;#39;r&amp;#39; -- Ordinary Table
 AND c.relname = &amp;#39;test&amp;#39;
;
 oid | schema | object_name | relkind 
-------+--------+-------------+---------
 21508 | public | test | r
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Jetzt können wir auf Tabelle genau filtern:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ pg_waldump --relation=1663/21506/21508 --rmgr=Heap ${WALARCHIVE}/000000010000000000000031 | grep DELETE
rmgr: Heap len (rec/tot): 59/ 8179, tx: 135586, lsn: 0/312DF748, prev 0/312DF720, desc: DELETE xmax: 135586, off: 10, infobits: [KEYS_UPDATED], flags: 0x01, blkref #0: rel 1663/21506/21508 blk 0 FPW
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E1758, prev 0/312DF748, desc: DELETE xmax: 135586, off: 11, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E1790, prev 0/312E1758, desc: DELETE xmax: 135586, off: 12, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E17C8, prev 0/312E1790, desc: DELETE xmax: 135586, off: 13, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E1800, prev 0/312E17C8, desc: DELETE xmax: 135586, off: 14, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E1838, prev 0/312E1800, desc: DELETE xmax: 135586, off: 15, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E1870, prev 0/312E1838, desc: DELETE xmax: 135586, off: 16, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E18A8, prev 0/312E1870, desc: DELETE xmax: 135586, off: 17, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E18E0, prev 0/312E18A8, desc: DELETE xmax: 135586, off: 18, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E1918, prev 0/312E18E0, desc: DELETE xmax: 135586, off: 19, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E1950, prev 0/312E1918, desc: DELETE xmax: 135586, off: 20, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Das gibt jetzt genau die 11 Rows auf der Tabelle &lt;code&gt;test&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Jetzt schauen wir das Ganze mit dem Betrachter unserer Wahl noch etwas genauer an (am besten Filtern auf LSN). Wir sehen, die Transaktion vorher und unsere Transaktion bis zum &lt;code&gt;COMMIT&lt;/code&gt; und das ganze als rückwärts-verkettete Liste (&lt;code&gt;prev&lt;/code&gt; ➜ &lt;code&gt;lsn&lt;/code&gt;)?&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rmgr: Transaction len (rec/tot): 34/ 34, tx: 135585, lsn: 0/312DF720, prev 0/312DF6E0, desc: COMMIT 2026-07-22 08:54:34.033961 UTC
rmgr: Heap len (rec/tot): 59/ 8179, tx: 135586, lsn: 0/312DF748, prev 0/312DF720, desc: DELETE xmax: 135586, off: 10, infobits: [KEYS_UPDATED], flags: 0x01, blkref #0: rel 1663/21506/21508 blk 0 FPW
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E1758, prev 0/312DF748, desc: DELETE xmax: 135586, off: 11, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E1790, prev 0/312E1758, desc: DELETE xmax: 135586, off: 12, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E17C8, prev 0/312E1790, desc: DELETE xmax: 135586, off: 13, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E1800, prev 0/312E17C8, desc: DELETE xmax: 135586, off: 14, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E1838, prev 0/312E1800, desc: DELETE xmax: 135586, off: 15, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E1870, prev 0/312E1838, desc: DELETE xmax: 135586, off: 16, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E18A8, prev 0/312E1870, desc: DELETE xmax: 135586, off: 17, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E18E0, prev 0/312E18A8, desc: DELETE xmax: 135586, off: 18, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E1918, prev 0/312E18E0, desc: DELETE xmax: 135586, off: 19, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Heap len (rec/tot): 54/ 54, tx: 135586, lsn: 0/312E1950, prev 0/312E1918, desc: DELETE xmax: 135586, off: 20, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/21506/21508 blk 0
rmgr: Transaction len (rec/tot): 34/ 34, tx: 135586, lsn: 0/312E1988, prev 0/312E1950, desc: COMMIT 2026-07-22 08:54:34.052628 UTC
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Unsere Ups-Query Transaktion hat also die XID 135586 und fängt bei der LSN 0/312DF748 an. Da wir ja die Transaktion beim Recovery NICHT mehr haben wollen, ist das Recovery dann exklusiv!&lt;/p&gt;
&lt;p&gt;Somit hätten wir also bereits die &lt;code&gt;recovery_target&lt;/code&gt; Informationen beisammen:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#
# postgres.conf
#
recovery_target_xid = &amp;#39;135586&amp;#39;
recovery_target_lsn = &amp;#39;0/312DF748&amp;#39;
recovery_target_inclusive = off
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="recovery-zur-xid"&gt;Recovery zur XID&lt;/h2&gt;
&lt;p&gt;Jetzt können wir das Point-in-Time-Recovery mit XID als Target ausführen. Das sieht dann im Log wie folgt aus:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;LOG: starting PostgreSQL 18.4 (Debian 18.4-1.pgdg13+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
LOG: listening on Unix socket &amp;#34;/var/run/postgresql/.s.PGSQL.5432&amp;#34;
LOG: database system was interrupted; last known up at 2026-07-21 14:21:59 UTC
cp: cannot stat &amp;#39;/mnt/backup/wal_archive/00000002.history&amp;#39;: No such file or directory
LOG: starting backup recovery with redo LSN 0/21000108, checkpoint LSN 0/2100BAF0, on timeline ID 1
LOG: restored log file &amp;#34;000000010000000000000021&amp;#34; from archive
LOG: starting point-in-time recovery to XID 135586
LOG: redo starts at 0/21000108
LOG: restored log file &amp;#34;000000010000000000000022&amp;#34; from archive
LOG: completed backup recovery with redo LSN 0/21000108 and end LSN 0/2100C070
LOG: consistent recovery state reached at 0/2100C070
LOG: database system is ready to accept read-only connections
LOG: restored log file &amp;#34;000000010000000000000023&amp;#34; from archive
LOG: restored log file &amp;#34;000000010000000000000024&amp;#34; from archive
LOG: restored log file &amp;#34;000000010000000000000025&amp;#34; from archive
LOG: restored log file &amp;#34;000000010000000000000026&amp;#34; from archive
LOG: restored log file &amp;#34;000000010000000000000027&amp;#34; from archive
LOG: restored log file &amp;#34;000000010000000000000028&amp;#34; from archive
LOG: restored log file &amp;#34;000000010000000000000029&amp;#34; from archive
LOG: restored log file &amp;#34;00000001000000000000002A&amp;#34; from archive
LOG: restored log file &amp;#34;00000001000000000000002B&amp;#34; from archive
LOG: restored log file &amp;#34;00000001000000000000002C&amp;#34; from archive 
LOG: restored log file &amp;#34;00000001000000000000002D&amp;#34; from archive
LOG: restored log file &amp;#34;00000001000000000000002E&amp;#34; from archive
LOG: restored log file &amp;#34;00000001000000000000002F&amp;#34; from archive
LOG: restored log file &amp;#34;000000010000000000000030&amp;#34; from archive
LOG: restored log file &amp;#34;000000010000000000000031&amp;#34; from archive
LOG: recovery stopping before commit of transaction 135586, time 2026-07-22 08:54:34.052628+00
LOG: pausing at the end of recovery
HINT: Execute pg_wal_replay_resume() to promote.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dann prüfen wir zuerst, ob das Ups-Query Rückgängig gemacht wurde und allenfalls, bis wohin die Daten noch/wieder da sind. Wenn das Resultat zufriedenstellend ist kann die Datenbank wieder aus dem Recovery Mode in Primary Mode promoted werden:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;test=# SELECT pg_is_in_recovery();
 pg_is_in_recovery 
-------------------
 t

test=# SELECT pg_promote();
 pg_promote 
------------
 t
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ein Blick ins Log zeigt uns, dass alles geklappt hat:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;LOG: received promote request
LOG: redo done at 0/312E1988 system usage: CPU: user: 0.12 s, system: 0.07 s, elapsed: 169.82 s
LOG: last completed transaction was at log time 2026-07-22 08:54:34.033961+00
cp: cannot stat &amp;#39;/mnt/backup/wal_archive/00000002.history&amp;#39;: No such file or directory 
LOG: selected new timeline ID: 2
cp: cannot stat &amp;#39;/mnt/backup/wal_archive/00000001.history&amp;#39;: No such file or directory 
LOG: archive recovery complete
LOG: checkpoint starting: force
LOG: database system is ready to accept connections
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="recovery-zur-lsn"&gt;Recovery zur LSN&lt;/h2&gt;
&lt;p&gt;Ob das Ganz mit LSN auch funktioniert, wollten wir natürlich ebenfalls ausprobieren. Das sieht dann im Log wie folgt aus:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;LOG: starting PostgreSQL 18.4 (Debian 18.4-1.pgdg13+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
LOG: listening on Unix socket &amp;#34;/var/run/postgresql/.s.PGSQL.5432&amp;#34;
LOG: database system was interrupted; last known up at 2026-07-21 14:21:59 UTC
cp: cannot stat &amp;#39;/mnt/backup/wal_archive/00000002.history&amp;#39;: No such file or directory
LOG: starting backup recovery with redo LSN 0/21000108, checkpoint LSN 0/2100BAF0, on timeline ID 1
LOG: restored log file &amp;#34;000000010000000000000021&amp;#34; from archive
LOG: starting point-in-time recovery to WAL location (LSN) &amp;#34;0/312DF748&amp;#34;
LOG: redo starts at 0/21000108
LOG: restored log file &amp;#34;000000010000000000000022&amp;#34; from archive
LOG: completed backup recovery with redo LSN 0/21000108 and end LSN 0/2100C070
LOG: consistent recovery state reached at 0/2100C070
LOG: database system is ready to accept read-only connections
LOG: restored log file &amp;#34;000000010000000000000023&amp;#34; from archive
LOG: restored log file &amp;#34;000000010000000000000024&amp;#34; from archive
LOG: restored log file &amp;#34;000000010000000000000025&amp;#34; from archive
LOG: restored log file &amp;#34;000000010000000000000026&amp;#34; from archive
LOG: restored log file &amp;#34;000000010000000000000027&amp;#34; from archive
LOG: restored log file &amp;#34;000000010000000000000028&amp;#34; from archive
LOG: restored log file &amp;#34;000000010000000000000029&amp;#34; from archive
LOG: restored log file &amp;#34;00000001000000000000002A&amp;#34; from archive
LOG: restored log file &amp;#34;00000001000000000000002B&amp;#34; from archive
LOG: restored log file &amp;#34;00000001000000000000002C&amp;#34; from archive 
LOG: restored log file &amp;#34;00000001000000000000002D&amp;#34; from archive
LOG: restored log file &amp;#34;00000001000000000000002E&amp;#34; from archive
LOG: restored log file &amp;#34;00000001000000000000002F&amp;#34; from archive
LOG: restored log file &amp;#34;000000010000000000000030&amp;#34; from archive
LOG: restored log file &amp;#34;000000010000000000000031&amp;#34; from archive
LOG: recovery stopping before WAL location (LSN) &amp;#34;0/312DF748&amp;#34;
LOG: pausing at the end of recovery
HINT: Execute pg_wal_replay_resume() to promote.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Und dann, nach Prüfen und Promoten geht es wie folgt weiter:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;LOG: received promote request 
LOG: redo done at 0/312DF748 system usage: CPU: user: 0.13 s, system: 0.05 s, elapsed: 90.70 s
LOG: last completed transaction was at log time 2026-07-22 08:54:34.033961+00
cp: cannot stat &amp;#39;/mnt/backup/wal_archive/00000002.history&amp;#39;: No such file or directory 
LOG: selected new timeline ID: 2
cp: cannot stat &amp;#39;/mnt/backup/wal_archive/00000001.history&amp;#39;: No such file or directory 
LOG: archive recovery complete
LOG: checkpoint starting: force
LOG: database system is ready to accept connections
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="nacharbeiten"&gt;Nacharbeiten&lt;/h2&gt;
&lt;p&gt;Ob und wie man die Daten die NACH dem Ups-Query angefallen sind, retten kann und will, ist dann noch mal eine ganz andere Geschichte&amp;hellip;&lt;/p&gt;
&lt;p&gt;Zur Sicherheit: Die betroffene Datenbank vor dem Restore/Recovery noch mal wegsichern, vielleicht können oder müssen wir da noch Daten rausfieseln?&lt;/p&gt;</description></item><item><title>Datenbank Index Optimierer</title><link>https://www.fromdual.com/de/blog/datenbank-index-optimierer/</link><pubDate>Wed, 15 Jul 2026 11:54:00 +0200</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/datenbank-index-optimierer/</guid><description>&lt;p&gt;Kürzlich hat mich ein Kunde gefragt, ob das &amp;ldquo;aufwändige&amp;rdquo; Index-Prüfen nicht einem Index-Optimierer überlassen werden könnte. Klar geht das&amp;hellip;&lt;/p&gt;
&lt;p&gt;Was wollen wir den genau überprüfen?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tabellen ohne Primary Key&lt;/li&gt;
&lt;li&gt;Doppelte Indices&lt;/li&gt;
&lt;li&gt;Teilweise redundante Indices&lt;/li&gt;
&lt;li&gt;Ungenutzte Indices&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="mariadb-mysql-und-percona-server"&gt;MariaDB, MySQL und Percona Server&lt;/h2&gt;
&lt;h3 id="tabellen-ohne-primary-key"&gt;Tabellen ohne Primary Key&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; SELECT DISTINCT t.table_schema, t.table_name
 FROM information_schema.tables AS t
 LEFT JOIN information_schema.columns AS c ON t.table_schema = c.table_schema AND t.table_name = c.table_name
 AND c.column_key = &amp;#34;PRI&amp;#34;
 WHERE t.table_schema NOT IN (&amp;#39;information_schema&amp;#39;, &amp;#39;mysql&amp;#39;, &amp;#39;performance_schema&amp;#39;)
 AND c.table_name IS NULL AND t.table_type NOT IN(&amp;#39;VIEW&amp;#39;, &amp;#39;SEQUENCE&amp;#39;)
 AND t.table_schema = &amp;#39;testtest&amp;#39;
;
+--------------+------------+
| table_schema | table_name |
+--------------+------------+
| testtest | archived |
+--------------+------------+
1 row in set
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Quelle: &lt;a href="https://www.fromdual.com/blog/mysql-performance-schema-hints/#tables-without-primary-key" target="_blank"&gt;Tables without a Primary Key&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="doppelte-indices"&gt;Doppelte Indices&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; SELECT table_name, redundant_index_name, redundant_index_columns, dominant_index_name, dominant_index_columns, sql_drop_index
 FROM sys.schema_redundant_indexes
 WHERE redundant_index_columns = dominant_index_columns
 AND table_schema = &amp;#39;testtest&amp;#39;
;
+------------+----------------------+-------------------------+---------------------+------------------------+------------------------------------------------------+
| table_name | redundant_index_name | redundant_index_columns | dominant_index_name | dominant_index_columns | sql_drop_index |
+------------+----------------------+-------------------------+---------------------+------------------------+------------------------------------------------------+
| archived | dupl2 | category_id | dupl1 | category_id | ALTER TABLE `testtest`.`archived` DROP INDEX `dupl2` |
+------------+----------------------+-------------------------+---------------------+------------------------+------------------------------------------------------+
1 row in set
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Quelle: &lt;a href="https://www.fromdual.com/blog/mysql-performance-schema-hints/#duplicate-and-redundant-indices" target="_blank"&gt;Duplicate and redundant indices&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="teilweise-redundante-indices"&gt;Teilweise redundante Indices&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; SELECT table_name, redundant_index_name, redundant_index_columns, dominant_index_name, dominant_index_columns, sql_drop_index
 FROM sys.schema_redundant_indexes
 WHERE table_schema = &amp;#39;testtest&amp;#39;
;
+-------------------+----------------------+-------------------------+---------------------+---------------------------------+-------------------------------------------------------------------+
| table_name | redundant_index_name | redundant_index_columns | dominant_index_name | dominant_index_columns | sql_drop_index |
+-------------------+----------------------+-------------------------+---------------------+---------------------------------+-------------------------------------------------------------------+
| access | customer | customer | customer_2 | customer,callerid_internal | ALTER TABLE `testtest`.`access` DROP INDEX `customer` |
| access | customer | customer | customer_3 | customer,callerid_external | ALTER TABLE `testtest`.`access` DROP INDEX `customer` |
| active_customers | uniqueid | uniqueid | PRIMARY | uniqueid,scustomer | ALTER TABLE `testtest`.`active_customers` DROP INDEX `uniqueid` |
| analytics_include | analytics | analytics | PRIMARY | analytics,feature,dtype,dnumber | ALTER TABLE `testtest`.`analytics_include` DROP INDEX `analytics` |
| archived | dupl2 | category_id | dupl1 | category_id | ALTER TABLE `testtest`.`archived` DROP INDEX `dupl2` |
...
| texts_media | uniqueid | uniqueid | PRIMARY | uniqueid,filename | ALTER TABLE `testtest`.`texts_media` DROP INDEX `uniqueid` |
| unlimited_access | customer | customer | customer_2 | customer,callerid_internal | ALTER TABLE `testtest`.`unlimited_access` DROP INDEX `customer` |
| unlimited_access | customer | customer | customer_3 | customer,callerid_external | ALTER TABLE `testtest`.`unlimited_access` DROP INDEX `customer` |
+-------------------+----------------------+-------------------------+---------------------+---------------------------------+-------------------------------------------------------------------+
26 rows in set
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Quelle: &lt;a href="https://www.fromdual.com/blog/mysql-performance-schema-hints/#duplicate-and-redundant-indices" target="_blank"&gt;Duplicate and redundant indices&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="ungenutzte-indices"&gt;Ungenutzte Indices&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; SELECT object_name, index_name
 FROM sys.schema_unused_indexes
 WHERE object_schema = &amp;#39;testtest&amp;#39;
;
+------------------------+------------------------+
| object_name | index_name |
+------------------------+------------------------+
| access | customer_3 |
| access | customer_2 |
| actions | class |
| actions | action |
| active | channel |
...
| urls | customer |
| voucher_batches | customer |
| vouchers | batch |
+------------------------+------------------------+
413 rows in set
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Achtung&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bei MariaDB muss das &lt;code&gt;PERFORMANCE_SCHEMA&lt;/code&gt; zuerst eingeschaltet werden.&lt;/li&gt;
&lt;li&gt;Die Informationen sind korrekt seit dem letzten Datenbank-Neustart. Wurde ein Index das letzte mal VOR dem letzten Neustart genutzt, wir er hier als ungenutzt angezeigt.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Quelle: &lt;a href="https://www.fromdual.com/blog/mysql-performance-schema-hints/#unused-indexes" target="_blank"&gt;Unused indexes&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="und-jetzt-mit-postgresql"&gt;Und jetzt mit PostgreSQL&lt;/h2&gt;
&lt;h3 id="tabellen-ohne-primary-key-1"&gt;Tabellen ohne Primary Key&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; SELECT tab.table_schema, tab.table_name
 FROM information_schema.tables tab
 LEFT JOIN information_schema.table_constraints tco
 ON tab.table_schema = tco.table_schema
 AND tab.table_name = tco.table_name 
 AND tco.constraint_type = &amp;#39;PRIMARY KEY&amp;#39;
 WHERE tab.table_type = &amp;#39;BASE TABLE&amp;#39;
 AND tab.table_schema NOT IN (&amp;#39;pg_catalog&amp;#39;, &amp;#39;information_schema&amp;#39;)
 AND tco.constraint_name IS NULL
 ORDER BY table_schema, table_name
;
 table_schema | table_name 
--------------+------------
 public | archived
(1 row)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Quelle: &lt;a href="https://dataedo.com/kb/query/postgresql/find-tables-without-primary-keys" target="_blank"&gt;Find tables without primary keys (PKs) in PostgreSQL database&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="doppelte-indices-1"&gt;Doppelte Indices&lt;/h3&gt;
&lt;p&gt;Basierend auf dem MySQL &lt;code&gt;sys&lt;/code&gt; Schema:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; WITH schema_flattened_keys AS (
 SELECT sai.relid, sai.indexrelid
 , sai.schemaname AS table_schema, sai.relname AS table_name, sai.indexrelname AS index_name
 , CASE pi.indisunique WHEN &amp;#39;f&amp;#39; THEN 1 ELSE 0 END AS non_unique
 , index_columns.columns AS index_columns
 FROM pg_stat_all_indexes AS sai
 JOIN pg_index AS pi ON pi.indexrelid = sai.indexrelid
 JOIN (
 SELECT attrelid, string_agg(attname, &amp;#39;,&amp;#39; ORDER BY attnum ASC) AS columns
 FROM pg_attribute GROUP BY attrelid
 ) AS index_columns ON index_columns.attrelid = sai.indexrelid
 WHERE sai.schemaname NOT IN (&amp;#39;pg_toast&amp;#39;, &amp;#39;pg_catalog&amp;#39;)
)
SELECT redundant_keys.table_schema AS table_schema, redundant_keys.table_name AS table_name, redundant_keys.index_name AS redundant_index_name
 , redundant_keys.index_columns AS redundant_index_columns, redundant_keys.non_unique AS redundant_index_non_unique
 , dominant_keys.index_name AS dominant_index_name, dominant_keys.index_columns AS dominant_index_columns, dominant_keys.non_unique AS dominant_index_non_unique
 , CONCAT(&amp;#39;ALTER TABLE &amp;#39;, redundant_keys.table_schema, &amp;#39;.&amp;#39;, redundant_keys.table_name, &amp;#39; DROP INDEX &amp;#39;, redundant_keys.index_name, &amp;#39;&amp;#39;) AS sql_drop_index
 FROM schema_flattened_keys redundant_keys
 JOIN schema_flattened_keys dominant_keys ON redundant_keys.table_schema = dominant_keys.table_schema AND redundant_keys.table_name = dominant_keys.table_name
 WHERE (redundant_keys.index_name &amp;lt;&amp;gt; dominant_keys.index_name
 AND ((redundant_keys.index_columns = dominant_keys.index_columns)
 AND ((redundant_keys.non_unique &amp;gt; dominant_keys.non_unique) OR (redundant_keys.non_unique = dominant_keys.non_unique))
 )
 OR ((POSITION(CONCAT(redundant_keys.index_columns,&amp;#39;,&amp;#39;) IN dominant_keys.index_columns) = 1) AND (redundant_keys.non_unique = 1))
 OR ((POSITION(CONCAT(dominant_keys.index_columns,&amp;#39;,&amp;#39;) IN redundant_keys.index_columns) = 1) AND (dominant_keys.non_unique = 0))
 )
 AND redundant_keys.index_columns = dominant_keys.index_columns
;
 table_schema | table_name | redundant_index_name | redundant_index_columns | redundant_index_non_unique | dominant_index_name | dominant_index_columns | dominant_index_non_unique | sql_drop_index 
--------------+------------+----------------------+-------------------------+----------------------------+---------------------+------------------------+---------------------------+----------------------------------------------
 public | archived | dupl1 | category_id | 1 | dupl2 | category_id | 1 | ALTER TABLE public.archived DROP INDEX dupl1
 public | archived | dupl2 | category_id | 1 | dupl1 | category_id | 1 | ALTER TABLE public.archived DROP INDEX dupl2
(2 rows)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Quelle: &lt;a href="https://www.fromdual.com/blog/mysql-performance-schema-hints/#duplicate-and-redundant-indices" target="_blank"&gt;Duplicate and redundant indices&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="teilweise-redundante-indices-1"&gt;Teilweise redundante Indices&lt;/h3&gt;
&lt;p&gt;Basierend auf dem MySQL &lt;code&gt;sys&lt;/code&gt; Schema:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; WITH schema_flattened_keys AS (
 SELECT sai.relid, sai.indexrelid
 , sai.schemaname AS table_schema, sai.relname AS table_name, sai.indexrelname AS index_name
 , CASE pi.indisunique WHEN &amp;#39;f&amp;#39; THEN 1 ELSE 0 END AS non_unique
 , index_columns.columns AS index_columns
 FROM pg_stat_all_indexes AS sai
 JOIN pg_index AS pi ON pi.indexrelid = sai.indexrelid
 JOIN (
 SELECT attrelid, string_agg(attname, &amp;#39;,&amp;#39; ORDER BY attnum ASC) AS columns
 FROM pg_attribute GROUP BY attrelid
 ) AS index_columns ON index_columns.attrelid = sai.indexrelid
 WHERE sai.schemaname NOT IN (&amp;#39;pg_toast&amp;#39;, &amp;#39;pg_catalog&amp;#39;)
)
SELECT redundant_keys.table_schema AS table_schema, redundant_keys.table_name AS table_name, redundant_keys.index_name AS redundant_index_name
 , redundant_keys.index_columns AS redundant_index_columns, redundant_keys.non_unique AS redundant_index_non_unique
 , dominant_keys.index_name AS dominant_index_name, dominant_keys.index_columns AS dominant_index_columns, dominant_keys.non_unique AS dominant_index_non_unique
 , CONCAT(&amp;#39;ALTER TABLE &amp;#39;, redundant_keys.table_schema, &amp;#39;.&amp;#39;, redundant_keys.table_name, &amp;#39; DROP INDEX &amp;#39;, redundant_keys.index_name, &amp;#39;&amp;#39;) AS sql_drop_index
 FROM schema_flattened_keys redundant_keys
 JOIN schema_flattened_keys dominant_keys ON redundant_keys.table_schema = dominant_keys.table_schema AND redundant_keys.table_name = dominant_keys.table_name
 WHERE (redundant_keys.index_name &amp;lt;&amp;gt; dominant_keys.index_name
 AND ((redundant_keys.index_columns = dominant_keys.index_columns)
 AND ((redundant_keys.non_unique &amp;gt; dominant_keys.non_unique) OR (redundant_keys.non_unique = dominant_keys.non_unique))
 )
 OR ((POSITION(CONCAT(redundant_keys.index_columns,&amp;#39;,&amp;#39;) IN dominant_keys.index_columns) = 1) AND (redundant_keys.non_unique = 1))
 OR ((POSITION(CONCAT(dominant_keys.index_columns,&amp;#39;,&amp;#39;) IN redundant_keys.index_columns) = 1) AND (dominant_keys.non_unique = 0))
 )
;
 table_schema | table_name | redundant_index_name | redundant_index_columns | redundant_index_non_unique | dominant_index_name | dominant_index_columns | dominant_index_non_unique | sql_drop_index 
--------------+-----------------------+------------------------------------------+-------------------------+----------------------------+-------------------------------------------------+-----------------------------------------+---------------------------+---------------------------------------------------------------------------------------------
 public | numbers | numbers_customer_idx | customer | 1 | numbers_customer_text_dtype_text_dnumber_idx | customer,text_dtype,text_dnumber | 1 | ALTER TABLE public.numbers DROP INDEX numbers_customer_idx
 public | numbers | numbers_customer_idx | customer | 1 | numbers_customer_fax_dtype_fax_dnumber_idx | customer,fax_dtype,fax_dnumber | 1 | ALTER TABLE public.numbers DROP INDEX numbers_customer_idx
 public | numbers | numbers_customer_idx | customer | 1 | numbers_pkey | customer,stype,snumber | 0 | ALTER TABLE public.numbers DROP INDEX numbers_customer_idx
 public | numbers | numbers_dtype_idx | dtype | 1 | numbers_dtype_dnumber_idx | dtype,dnumber | 1 | ALTER TABLE public.numbers DROP INDEX numbers_dtype_idx
 public | number_callers | number_callers_dtype_idx | dtype | 1 | number_callers_dtype_dnumber_idx | dtype,dnumber | 1 | ALTER TABLE public.number_callers DROP INDEX number_callers_dtype_idx
 public | prefixes | prefixes_customer_idx | customer | 1 | prefixes_customer_dtype_dnumber_idx | customer,dtype,dnumber | 1 | ALTER TABLE public.prefixes DROP INDEX prefixes_customer_idx
 public | number_times | number_times_dtype_idx | dtype | 1 | number_times_dtype_dnumber_idx | dtype,dnumber | 1 | ALTER TABLE public.number_times DROP INDEX number_times_dtype_idx
 public | phones | phones_customer_idx | customer | 1 | phones_customer_callerid_location_idx | customer,callerid_location | 1 | ALTER TABLE public.phones DROP INDEX phones_customer_idx
 public | phones | phones_customer_idx | customer | 1 | phones_customer_callerid_external_idx | customer,callerid_external | 1 | ALTER TABLE public.phones DROP INDEX phones_customer_idx
 public | phones | phones_customer_idx | customer | 1 | phones_customer_callerid_internal_idx | customer,callerid_internal | 1 | ALTER TABLE public.phones DROP INDEX phones_customer_idx
 public | phones_hardware | phones_hardware_phone_idx | phone | 1 | phones_hardware_phone_hardware_address_idx | phone,hardware_address | 0 | ALTER TABLE public.phones_hardware DROP INDEX phones_hardware_phone_idx
 public | speeddials | speeddials_stype_idx | stype | 1 | speeddials_stype_snumber_idx | stype,snumber | 1 | ALTER TABLE public.speeddials DROP INDEX speeddials_stype_idx
 public | speeddials | speeddials_dtype_idx | dtype | 1 | speeddials_dtype_dnumber_idx | dtype,dnumber | 1 | ALTER TABLE public.speeddials DROP INDEX speeddials_dtype_idx
 public | mailbox_destinations | mailbox_destinations_context_mailbox_idx | context,mailbox | 1 | mailbox_destinations_pkey | context,mailbox,dcustomer,dtype,dnumber | 0 | ALTER TABLE public.mailbox_destinations DROP INDEX mailbox_destinations_context_mailbox_idx
 public | outgroup_times | outgroup_times_outgroup_idx | outgroup | 1 | outgroup_times_outgroup_name_idx | outgroup,name | 0 | ALTER TABLE public.outgroup_times DROP INDEX outgroup_times_outgroup_idx
 public | ingroup_times | ingroup_times_ingroup_idx | ingroup | 1 | ingroup_times_ingroup_name_idx | ingroup,name | 0 | ALTER TABLE public.ingroup_times DROP INDEX ingroup_times_ingroup_idx
 public | active_customers | active_customers_uniqueid_idx | uniqueid | 1 | active_customers_pkey | uniqueid,scustomer | 0 | ALTER TABLE public.active_customers DROP INDEX active_customers_uniqueid_idx
 public | access | access_customer_idx | customer | 1 | access_customer_callerid_external_idx | customer,callerid_external | 1 | ALTER TABLE public.access DROP INDEX access_customer_idx
 public | access | access_customer_idx | customer | 1 | access_customer_callerid_internal_idx | customer,callerid_internal | 1 | ALTER TABLE public.access DROP INDEX access_customer_idx
 public | unlimited_access | unlimited_access_customer_idx | customer | 1 | unlimited_access_customer_callerid_external_idx | customer,callerid_external | 1 | ALTER TABLE public.unlimited_access DROP INDEX unlimited_access_customer_idx
 public | unlimited_access | unlimited_access_customer_idx | customer | 1 | unlimited_access_customer_callerid_internal_idx | customer,callerid_internal | 1 | ALTER TABLE public.unlimited_access DROP INDEX unlimited_access_customer_idx
 public | texts | texts_dcustomer_idx | dcustomer | 1 | texts_dcustomer_dtype_dnumber_idx | dcustomer,dtype,dnumber | 1 | ALTER TABLE public.texts DROP INDEX texts_dcustomer_idx
 public | texts_media | texts_media_uniqueid_idx | uniqueid | 1 | texts_media_pkey | uniqueid,filename | 0 | ALTER TABLE public.texts_media DROP INDEX texts_media_uniqueid_idx
 public | number_calleridgroups | number_calleridgroups_dtype_idx | dtype | 1 | number_calleridgroups_dtype_dnumber_idx | dtype,dnumber | 1 | ALTER TABLE public.number_calleridgroups DROP INDEX number_calleridgroups_dtype_idx
 public | analytics_include | analytics_i | analytics | 1 | analytics_include_pkey | analytics,feature,dtype,dnumber | 0 | ALTER TABLE public.analytics_include DROP INDEX analytics_i
 public | archived | dupl1 | category_id | 1 | dupl2 | category_id | 1 | ALTER TABLE public.archived DROP INDEX dupl1
 public | archived | dupl2 | category_id | 1 | dupl1 | category_id | 1 | ALTER TABLE public.archived DROP INDEX dupl2
(27 rows)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Quelle: &lt;a href="https://www.fromdual.com/blog/mysql-performance-schema-hints/#duplicate-and-redundant-indices" target="_blank"&gt;Duplicate and redundant indices&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="ungenutzte-indices-1"&gt;Ungenutzte Indices&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; SELECT relid::regclass AS table, indexrelid::regclass AS index
 , pg_size_pretty(pg_relation_size(indexrelid::regclass)) AS index_size
 , idx_tup_read, idx_tup_fetch, idx_scan
 FROM pg_stat_user_indexes 
 JOIN pg_index USING (indexrelid) 
 WHERE idx_scan = 0 
 AND indisunique IS FALSE
;
 table | index | index_size | idx_tup_read | idx_tup_fetch | idx_scan 
------------------------+-----------------------------------------------------------------+------------+--------------+---------------+----------
 customers | customers_prefix_idx | 16 kB | 0 | 0 | 0
 customers | customers_parent_idx | 16 kB | 0 | 0 | 0
 customers | customers_email_idx | 16 kB | 0 | 0 | 0
 customers | customers_affiliate_customer_idx | 16 kB | 0 | 0 | 0
 customers | customers_bill_ref_idx | 16 kB | 0 | 0 | 0
...
 analytics_include | analytics_i | 8192 bytes | 0 | 0 | 0
 archived | dupl1 | 8192 bytes | 0 | 0 | 0
 archived | dupl2 | 8192 bytes | 0 | 0 | 0
(413 rows)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Quellen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://wiki.postgresql.org/wiki/Index_Maintenance#Unused_Indexes" target="_blank"&gt;Unused Indexes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jmorano.moretrix.com/2014/02/postgresql-monitor-unused-indexes/" target="_blank"&gt;Postgresql: Monitor unused indexes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="pg-assistant"&gt;PG Assistant&lt;/h3&gt;
&lt;p&gt;An den &lt;a href="https://2026.pgday.ch/schedule/" target="_blank"&gt;Swiss PGDay2026&lt;/a&gt;(s) hat Bertrand Hartwig sein Tool &lt;a href="https://github.com/beh74/pgassistant-community" target="_blank"&gt;PG Assistant&lt;/a&gt; vorgestellt. In diesem Zusammenhang wollte ich es gleich mal ausprobieren&amp;hellip;&lt;/p&gt;
&lt;p&gt;Fehlende Primary Keys und doppelte Indices konnte PG Assistant finden. Teilweise redundante Indices oder ungenutzte Indices hat er mir nicht angezeigt, kann aber auch an mir liegen&amp;hellip;&lt;/p&gt;
&lt;figure&gt;
 &lt;a href="https://www.fromdual.com/images/pgAssistant_Screenshot_20260715_111018.png" title="volle Grösse"&gt;&lt;img src="https://www.fromdual.com/images/pgAssistant_Screenshot_20260715_111018_640x509.png" alt="pgAssistant-1"&gt;&lt;/a&gt;
 &lt;figcaption&gt;PG Assistant: Dashboard / Dev advisor&lt;/figcaption&gt;
&lt;/figure&gt;&lt;br&gt;
&lt;figure&gt;
 &lt;a href="https://www.fromdual.com/images/pgAssistant_Screenshot_20260715_111135.png" title="volle Grösse"&gt;&lt;img src="https://www.fromdual.com/images/pgAssistant_Screenshot_20260715_111135_640x533.png" alt="pgAssistant-2"&gt;&lt;/a&gt;
 &lt;figcaption&gt;PG Assistant: Global Advisor / Dev advisor&lt;/figcaption&gt;
&lt;/figure&gt;&lt;br&gt;
&lt;figure&gt;
 &lt;a href="https://www.fromdual.com/images/pgAssistant_Screenshot_20260715_111230.png" title="volle Grösse"&gt;&lt;img src="https://www.fromdual.com/images/pgAssistant_Screenshot_20260715_111230_640x532.png" alt="pgAssistant-3"&gt;&lt;/a&gt;
 &lt;figcaption&gt;PG Assistant: Strictly duplicate unused index&lt;/figcaption&gt;
&lt;/figure&gt;&lt;br&gt;
&lt;h4 id="intallation-von-pg-assistant"&gt;Intallation von PG Assistant&lt;/h4&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ apt update
$ apt install python3 python3.13-venv unzip pip
$ wget https://github.com/beh74/pgassistant-community/archive/refs/heads/main.zip
$ unzip main.zip 
$ cd pgassistant-community-main/
$ python3 -m venv env
$ source env/bin/activate
$ pip3 install -r requirements.txt
$ export FLASK_APP=run.py
$ flask run --host=0.0.0.0 --port=80
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dann mit dem Web-Browser auf die angezeigte URL verbinden.&lt;/p&gt;
&lt;p&gt;In der Datenbank muss ein User angelegt:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; CREATE ROLE pgassistant WITH LOGIN SUPERUSER PASSWORD &amp;#39;secret&amp;#39;;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;sowie die &lt;code&gt;pg_hba.conf&lt;/code&gt; angepasst werden.&lt;/p&gt;
&lt;h2 id="nachtrag"&gt;Nachtrag&lt;/h2&gt;
&lt;p&gt;Ungenutzte Indices findet man mit dem PG Assitant wie folgt: Database Objects ➜ Indexes ➜ Status: Unused ➜ &amp;ldquo;NO INDEX ACTIVITY&amp;rdquo;&lt;/p&gt;
&lt;figure&gt;
 &lt;a href="https://www.fromdual.com/images/pgAssistant_Screenshot_20260716_093730.png" title="volle Grösse"&gt;&lt;img src="https://www.fromdual.com/images/pgAssistant_Screenshot_20260716_093730_640x459.png" alt="pgAssistant-4"&gt;&lt;/a&gt;
 &lt;figcaption&gt;PG Assistant: Unused Indexes&lt;/figcaption&gt;
&lt;/figure&gt;&lt;br&gt;</description></item><item><title>MariaDB verkürzt Wartungszeitraum von 5 auf 3 Jahre</title><link>https://www.fromdual.com/de/blog/mariadb-verkuerzt-wartungszeitraum-von-5-auf-3-jahre/</link><pubDate>Wed, 10 Jun 2026 21:37:00 +0200</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/mariadb-verkuerzt-wartungszeitraum-von-5-auf-3-jahre/</guid><description>&lt;p&gt;Irgendwie ist die Meldung an mir vorbei gegangen: MariaDB hat den Wartungszeitraum für die Long-Term Releases des MariaDB Community Servers von 5 auf 3 Jahre verkürzt. OK, ist ja nicht weiter verwunderlich, ich war ja gut einen Monat offline&amp;hellip;&lt;/p&gt;
&lt;h2 id="mariadb-server-lts-release-wartungszeiträume"&gt;MariaDB Server LTS Release Wartungszeiträume&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Release&lt;/th&gt;
					&lt;th&gt;GA Datum&lt;/th&gt;
					&lt;th&gt;EoL Datum&lt;/th&gt;
					&lt;th&gt;Dauer&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;12.3&lt;/td&gt;
					&lt;td&gt;28 May 2026&lt;/td&gt;
					&lt;td&gt;Jun 2029&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;3 Jahre&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;11.8&lt;/td&gt;
					&lt;td&gt;4 Jun 2025&lt;/td&gt;
					&lt;td&gt;4 Jun 2028&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;3 Jahre&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;11.4&lt;/td&gt;
					&lt;td&gt;29 May 2024&lt;/td&gt;
					&lt;td&gt;29 May 2029&lt;/td&gt;
					&lt;td&gt;5 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;10.11&lt;/td&gt;
					&lt;td&gt;16 Feb 2023&lt;/td&gt;
					&lt;td&gt;16 Feb 2028&lt;/td&gt;
					&lt;td&gt;5 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;10.6&lt;/td&gt;
					&lt;td&gt;6 Jul 2021&lt;/td&gt;
					&lt;td&gt;6 Jul 2026&lt;/td&gt;
					&lt;td&gt;5 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;10.5&lt;/td&gt;
					&lt;td&gt;24 Jun 2020&lt;/td&gt;
					&lt;td&gt;24 Jun 2025&lt;/td&gt;
					&lt;td&gt;5 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;10.4&lt;/td&gt;
					&lt;td&gt;18 Jun 2019&lt;/td&gt;
					&lt;td&gt;18 Jun 2024&lt;/td&gt;
					&lt;td&gt;5 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;10.3&lt;/td&gt;
					&lt;td&gt;25 May 2018&lt;/td&gt;
					&lt;td&gt;25 May 2023&lt;/td&gt;
					&lt;td&gt;5 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;10.2&lt;/td&gt;
					&lt;td&gt;23 May 2017&lt;/td&gt;
					&lt;td&gt;23 May 2022&lt;/td&gt;
					&lt;td&gt;5 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;10.1&lt;/td&gt;
					&lt;td&gt;17 Oct 2015&lt;/td&gt;
					&lt;td&gt;17 Oct 2020&lt;/td&gt;
					&lt;td&gt;5 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;10.0&lt;/td&gt;
					&lt;td&gt;31 Mar 2014&lt;/td&gt;
					&lt;td&gt;31 Mar 2019&lt;/td&gt;
					&lt;td&gt;5 Jahre&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Quelle: &lt;a href="https://mariadb.org/about/#maintenance-policy" target="_blank"&gt;MariaDB Server long-term release maintenance periods&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ich bin ja mal gespannt, wie das die ganzen Distributionen lösen? Die haben nämlich einen deutlich längeren Wartungszeitraum.&lt;/p&gt;
&lt;p&gt;Und wie machen es denn die Mitbewerber, die anderen Datenbanken?&lt;/p&gt;
&lt;h2 id="debian"&gt;Debian&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Debian Long Term Support (LTS) is a project to extend the lifetime of all Debian stable releases to (at least) 5 years.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Quelle: &lt;a href="https://wiki.debian.org/LTS" target="_blank"&gt;Debian Long Term Support&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Version&lt;/th&gt;
					&lt;th&gt;Name&lt;/th&gt;
					&lt;th&gt;Release&lt;/th&gt;
					&lt;th&gt;ext-LTS&lt;/th&gt;
					&lt;th&gt;EoL&lt;/th&gt;
					&lt;th&gt;Dauer&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Debian 13&lt;/td&gt;
					&lt;td&gt;trixie&lt;/td&gt;
					&lt;td&gt;2025-08-09&lt;/td&gt;
					&lt;td&gt;2030-07-01&lt;/td&gt;
					&lt;td&gt;2035-06-30&lt;/td&gt;
					&lt;td&gt;5 / 10 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Debian 12&lt;/td&gt;
					&lt;td&gt;bookworm&lt;/td&gt;
					&lt;td&gt;2023-06-10&lt;/td&gt;
					&lt;td&gt;2028-07-01&lt;/td&gt;
					&lt;td&gt;2033-06-30&lt;/td&gt;
					&lt;td&gt;5 / 10 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Debian 11&lt;/td&gt;
					&lt;td&gt;bullseye&lt;/td&gt;
					&lt;td&gt;2021-08-14&lt;/td&gt;
					&lt;td&gt;2026-09-01&lt;/td&gt;
					&lt;td&gt;2031-06-30&lt;/td&gt;
					&lt;td&gt;5 / 10 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Debian 10&lt;/td&gt;
					&lt;td&gt;buster&lt;/td&gt;
					&lt;td&gt;2019-06-06&lt;/td&gt;
					&lt;td&gt;2024-07-01&lt;/td&gt;
					&lt;td&gt;2029-06-30&lt;/td&gt;
					&lt;td&gt;5 / 10 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Debian 9&lt;/td&gt;
					&lt;td&gt;stretch&lt;/td&gt;
					&lt;td&gt;2017-06-17&lt;/td&gt;
					&lt;td&gt;2022-07-01&lt;/td&gt;
					&lt;td&gt;2027-06-30&lt;/td&gt;
					&lt;td&gt;5 / 10 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Debian 8&lt;/td&gt;
					&lt;td&gt;jessie&lt;/td&gt;
					&lt;td&gt;2015-04-26&lt;/td&gt;
					&lt;td&gt;2020-07-01&lt;/td&gt;
					&lt;td&gt;2025-06-30&lt;/td&gt;
					&lt;td&gt;5 / 10 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Debian 7&lt;/td&gt;
					&lt;td&gt;wheezy&lt;/td&gt;
					&lt;td&gt;2013-05-04&lt;/td&gt;
					&lt;td&gt;2018-06-01&lt;/td&gt;
					&lt;td&gt;2020-06-30&lt;/td&gt;
					&lt;td&gt;5 / 7 Jahre&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Quelle: &lt;a href="https://wiki.debian.org/LTS/Extended" target="_blank"&gt;Extended Long Term Support&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="ubuntu"&gt;Ubuntu&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Version&lt;/th&gt;
					&lt;th&gt;Name&lt;/th&gt;
					&lt;th&gt;Release&lt;/th&gt;
					&lt;th&gt;End of Support&lt;/th&gt;
					&lt;th&gt;EoL&lt;/th&gt;
					&lt;th&gt;Dauer&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Ubuntu 26.04 LTS&lt;/td&gt;
					&lt;td&gt;Resolute Raccoon&lt;/td&gt;
					&lt;td&gt;23. April 2026&lt;/td&gt;
					&lt;td&gt;May 2031&lt;/td&gt;
					&lt;td&gt;April 2041&lt;/td&gt;
					&lt;td&gt;5 / 15 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Ubuntu 24.04 LTS&lt;/td&gt;
					&lt;td&gt;Noble Numbat&lt;/td&gt;
					&lt;td&gt;25. April 2024&lt;/td&gt;
					&lt;td&gt;June 2029&lt;/td&gt;
					&lt;td&gt;April 2039&lt;/td&gt;
					&lt;td&gt;5 / 15 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Ubuntu 22.04 LTS&lt;/td&gt;
					&lt;td&gt;Jammy Jellyfish&lt;/td&gt;
					&lt;td&gt;21. April 2022&lt;/td&gt;
					&lt;td&gt;June 2027&lt;/td&gt;
					&lt;td&gt;April 2037&lt;/td&gt;
					&lt;td&gt;5 / 15 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Ubuntu 20.04 LTS&lt;/td&gt;
					&lt;td&gt;Focal Fossa&lt;/td&gt;
					&lt;td&gt;23. April 2020&lt;/td&gt;
					&lt;td&gt;May 2025&lt;/td&gt;
					&lt;td&gt;April 2035&lt;/td&gt;
					&lt;td&gt;5 / 15 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Ubuntu 18.04 LTS&lt;/td&gt;
					&lt;td&gt;Bionic Beaver&lt;/td&gt;
					&lt;td&gt;26. April 2018&lt;/td&gt;
					&lt;td&gt;June 2023&lt;/td&gt;
					&lt;td&gt;April 2033&lt;/td&gt;
					&lt;td&gt;5 / 15 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Ubuntu 16.04 LTS&lt;/td&gt;
					&lt;td&gt;Xenial Xerus&lt;/td&gt;
					&lt;td&gt;21. April 2016&lt;/td&gt;
					&lt;td&gt;April 2021&lt;/td&gt;
					&lt;td&gt;April 2031&lt;/td&gt;
					&lt;td&gt;5 / 15 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Ubuntu 14.04 LTS&lt;/td&gt;
					&lt;td&gt;Trusty Tahr&lt;/td&gt;
					&lt;td&gt;17. April 2014&lt;/td&gt;
					&lt;td&gt;April 2019&lt;/td&gt;
					&lt;td&gt;April 2029&lt;/td&gt;
					&lt;td&gt;5 / 15 Jahre&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Quelle: &lt;a href="https://documentation.ubuntu.com/project/release-team/list-of-releases/" target="_blank"&gt;List of releases&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="rocky-linux"&gt;Rocky Linux&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Release&lt;/th&gt;
					&lt;th&gt;Codename&lt;/th&gt;
					&lt;th&gt;Release Date&lt;/th&gt;
					&lt;th&gt;Active Support End&lt;/th&gt;
					&lt;th&gt;End of Life&lt;/th&gt;
					&lt;th&gt;Dauer&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Rocky Linux 10&lt;/td&gt;
					&lt;td&gt;Red Quartz&lt;/td&gt;
					&lt;td&gt;June 11, 2025&lt;/td&gt;
					&lt;td&gt;May 31, 2030&lt;/td&gt;
					&lt;td&gt;May 31, 2035&lt;/td&gt;
					&lt;td&gt;5 / 10 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Rocky Linux 9&lt;/td&gt;
					&lt;td&gt;Blue Onyx&lt;/td&gt;
					&lt;td&gt;July 14, 2022&lt;/td&gt;
					&lt;td&gt;May 31, 2027&lt;/td&gt;
					&lt;td&gt;May 31, 2032&lt;/td&gt;
					&lt;td&gt;5 / 10 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Rocky Linux 8&lt;/td&gt;
					&lt;td&gt;Green Obsidian&lt;/td&gt;
					&lt;td&gt;May 1, 2021&lt;/td&gt;
					&lt;td&gt;May 31, 2024&lt;/td&gt;
					&lt;td&gt;May 31, 2029&lt;/td&gt;
					&lt;td&gt;3 / 8 Jahre&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Quelle: &lt;a href="https://wiki.rockylinux.org/rocky/version/#current-supported-releases" target="_blank"&gt;Rocky Linux Release and Version Guide&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="oracle--mysql-releases"&gt;Oracle / MySQL Releases&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Release&lt;/th&gt;
					&lt;th&gt;GA Date&lt;/th&gt;
					&lt;th&gt;Premier Support End&lt;/th&gt;
					&lt;th&gt;Extended Support End&lt;/th&gt;
					&lt;th&gt;Dauer&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;MySQL 9.7&lt;/td&gt;
					&lt;td&gt;Apr 2026&lt;/td&gt;
					&lt;td&gt;Apr 2031&lt;/td&gt;
					&lt;td&gt;Apr 2034&lt;/td&gt;
					&lt;td&gt;5 / 8 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;MySQL 8.4&lt;/td&gt;
					&lt;td&gt;Apr 2024&lt;/td&gt;
					&lt;td&gt;Apr 2029&lt;/td&gt;
					&lt;td&gt;Apr 2032&lt;/td&gt;
					&lt;td&gt;5 / 8 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;MySQL 8.0&lt;/td&gt;
					&lt;td&gt;Apr 2018&lt;/td&gt;
					&lt;td&gt;Apr 2025&lt;/td&gt;
					&lt;td&gt;Apr 2026&lt;/td&gt;
					&lt;td&gt;7 Jahre / 8 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;MySQL 5.7&lt;/td&gt;
					&lt;td&gt;Oct 2015&lt;/td&gt;
					&lt;td&gt;Oct 2020&lt;/td&gt;
					&lt;td&gt;Oct 2023&lt;/td&gt;
					&lt;td&gt;5 Jahre / 8 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;MySQL 5.6&lt;/td&gt;
					&lt;td&gt;Feb 2013&lt;/td&gt;
					&lt;td&gt;Feb 2018&lt;/td&gt;
					&lt;td&gt;Feb 2021&lt;/td&gt;
					&lt;td&gt;5 Jahre / 8 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;MySQL 5.5&lt;/td&gt;
					&lt;td&gt;Dec 2010&lt;/td&gt;
					&lt;td&gt;Dec 2015&lt;/td&gt;
					&lt;td&gt;Dec 2018&lt;/td&gt;
					&lt;td&gt;5 Jahre / 8 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;MySQL 5.1&lt;/td&gt;
					&lt;td&gt;Dec 2008&lt;/td&gt;
					&lt;td&gt;Dec 2013&lt;/td&gt;
					&lt;td&gt;Not Available&lt;/td&gt;
					&lt;td&gt;5 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;MySQL 5.0&lt;/td&gt;
					&lt;td&gt;Oct 2005&lt;/td&gt;
					&lt;td&gt;Dec 2011&lt;/td&gt;
					&lt;td&gt;Not Available&lt;/td&gt;
					&lt;td&gt;6 Jahre&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Quelle: &lt;a href="https://www.oracle.com/us/support/library/lifetime-support-technology-069183.pdf" target="_blank"&gt;Oracle Lifetime Support Policy&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="percona"&gt;Percona&lt;/h2&gt;
&lt;p&gt;Percona Distribution for PostgreSQL (PDPG) und Percona Server for MySQL (PS): Mindestens 5 Jahre, wenn ich die Support-Matrix richtig interpretiere&amp;hellip;&lt;/p&gt;
&lt;p&gt;Quelle: &lt;a href="https://www.percona.com/release-lifecycle-overview/" target="_blank"&gt;Percona Release Lifecycle Overview&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="oursql--villagesql"&gt;OurSQL / VillageSQL&lt;/h2&gt;
&lt;p&gt;Noch keine fertige Software vorhanden und somit noch keine Support Policies, sofern mir bekannt. Sofern das überhaupt mal vorgesehen ist?&lt;/p&gt;
&lt;p&gt;Quelle: &lt;a href="https://oursqlfoundation.org/" target="_blank"&gt;OurSQL&lt;/a&gt; und &lt;a href="https://villagesql.com/" target="_blank"&gt;VillageSQL&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="postgresql"&gt;PostgreSQL&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Version&lt;/th&gt;
					&lt;th&gt;First Release&lt;/th&gt;
					&lt;th&gt;Final Release&lt;/th&gt;
					&lt;th&gt;Dauer&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;18&lt;/td&gt;
					&lt;td&gt;September 25, 2025&lt;/td&gt;
					&lt;td&gt;November 14, 2030&lt;/td&gt;
					&lt;td&gt;5 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;17&lt;/td&gt;
					&lt;td&gt;September 26, 2024&lt;/td&gt;
					&lt;td&gt;November 8, 2029&lt;/td&gt;
					&lt;td&gt;5 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;16&lt;/td&gt;
					&lt;td&gt;September 14, 2023&lt;/td&gt;
					&lt;td&gt;November 9, 2028&lt;/td&gt;
					&lt;td&gt;5 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;15&lt;/td&gt;
					&lt;td&gt;October 13, 2022&lt;/td&gt;
					&lt;td&gt;November 11, 2027&lt;/td&gt;
					&lt;td&gt;5 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;14&lt;/td&gt;
					&lt;td&gt;September 30, 2021&lt;/td&gt;
					&lt;td&gt;November 12, 2026&lt;/td&gt;
					&lt;td&gt;5 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;13&lt;/td&gt;
					&lt;td&gt;September 24, 2020&lt;/td&gt;
					&lt;td&gt;November 13, 2025&lt;/td&gt;
					&lt;td&gt;5 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;12&lt;/td&gt;
					&lt;td&gt;October 3, 2019&lt;/td&gt;
					&lt;td&gt;November 21, 2024&lt;/td&gt;
					&lt;td&gt;5 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;11&lt;/td&gt;
					&lt;td&gt;October 18, 2018&lt;/td&gt;
					&lt;td&gt;November 9, 2023&lt;/td&gt;
					&lt;td&gt;5 Jahre&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;10&lt;/td&gt;
					&lt;td&gt;October 5, 2017&lt;/td&gt;
					&lt;td&gt;November 10, 2022&lt;/td&gt;
					&lt;td&gt;5 Jahre&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Quelle: &lt;a href="https://www.postgresql.org/support/versioning/" target="_blank"&gt;Versioning Policy&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="weitere-quellen"&gt;Weitere Quellen&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/docs/release-notes/community-server/10.6/what-is-mariadb-106" target="_blank"&gt;MariaDB 10.6 Changes &amp;amp; Improvements&lt;/a&gt;&lt;br&gt;
&lt;em&gt;MariaDB 10.6 is a long-term maintenance stable version. The first stable release was in July 2021, and it will be maintained until July 2026.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/docs/release-notes/community-server/10.11/what-is-mariadb-1011" target="_blank"&gt;MariaDB 10.11 Changes &amp;amp; Improvements&lt;/a&gt;&lt;br&gt;
&lt;em&gt;MariaDB 10.11 is a long-term maintenance release series, maintained until February 2028.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/docs/release-notes/community-server/11.4/what-is-mariadb-114" target="_blank"&gt;MariaDB 11.4 Changes &amp;amp; Improvements&lt;/a&gt;&lt;br&gt;
&lt;em&gt;MariaDB 11.4 is a current long-term series, maintained until May 2029.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/docs/release-notes/community-server/11.8/what-is-mariadb-118" target="_blank"&gt;MariaDB 11.8 Changes &amp;amp; Improvements&lt;/a&gt;&lt;br&gt;
&lt;em&gt;MariaDB 11.8 is a long-term release, maintained until June 2028.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/docs/release-notes/community-server/12.3/mariadb-12.3-changes-and-improvements" target="_blank"&gt;MariaDB 12.3 Changes &amp;amp; Improvements&lt;/a&gt;&lt;br&gt;
&lt;em&gt;MariaDB 12.3 is a long term release, maintained until June 2029.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Wie lädt man Daten am schnellsten in die Datenbank?</title><link>https://www.fromdual.com/de/blog/daten-schnell-in-die-datenbank-laden/</link><pubDate>Tue, 10 Feb 2026 22:48:00 +0100</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/daten-schnell-in-die-datenbank-laden/</guid><description>&lt;p&gt;Beim letzten Kunden hatten wir wirklich ein paar spannende Fragen zu lösen! Insbesondere auch, weil die Datenbank nicht ganz klein war.&lt;/p&gt;
&lt;p&gt;Hier kurz einige Eckdaten: CPU: 2 Sockel x 24 Kerne x 2 Threads = 96 vCores, 756 G RAM, 2 x 10 Tbyte PCIe SSD im RAID-10 und 7 Tbyte Daten, einige Tausend Mandanten, stark wachsend.&lt;/p&gt;
&lt;p&gt;Der aktuelle Durchsatz: 1 M &lt;code&gt;SELECT&lt;/code&gt;/min, 56 k &lt;code&gt;INSERT&lt;/code&gt;/min, 44 k &lt;code&gt;UPDATE&lt;/code&gt;/min , 7 k &lt;code&gt;DELETE&lt;/code&gt;/min gemittelt über 30 Tage. Tendenz stark steigend. Applikation und Queries nicht durchgängig optimiert. Datenbank Konfiguration: &amp;ldquo;state of the art&amp;rdquo; nicht mit Benchmarks verifiziert. CPU-Auslastung ca. 50% im Schnitt, in der Spitze mehr. I/O System hat noch Luft nach oben.&lt;/p&gt;
&lt;p&gt;Der Kunde sammelt Positions- und sonstige Gerätedaten ein und speichert diese in der Datenbank. Also ein klassisches IoT Problem (mit Zeitreihen, Index Clustered Table, etc.).&lt;/p&gt;
&lt;p&gt;Die Frage, die er gestellt hat, ist: Wie kriege ich am schnellsten Daten von einer Tabelle (pending data, eine Art Queue) in eine andere Tabelle (final data, pro Mandant) kopiert.&lt;/p&gt;
&lt;p&gt;Der Datenfluss sieht in etwa wie folgt aus:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;+------------+
| IoT Device |--+
+------------+ \
 \ +-----+
+------------+ \ | AS | +--------------+ Processing +------------+
| IoT Device |------+--&amp;gt;| |--&amp;gt;| Pending data |-------------&amp;gt;| Final data |
+------------+ / | 400 | +--------------+ of data +------------+
 / +-----+
+------------+ /
| IoT Device |--+
+------------+
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;3 verschiedene Varianten, die Daten zu kopieren, standen zur Auswahl.&lt;/p&gt;
&lt;h2 id="variante-1-insert-und-delete-einfachste-form"&gt;Variante 1: &lt;code&gt;INSERT&lt;/code&gt; und &lt;code&gt;DELETE&lt;/code&gt; (einfachste Form)&lt;/h2&gt;
&lt;p&gt;Die einfachste Variante ist simples &lt;code&gt;INSERT&lt;/code&gt; und &lt;code&gt;DELETE&lt;/code&gt;. Diese Variante ist insbesondere darum problematisch, weil MariaDB/MySQL und PostgreSQL per default &lt;code&gt;AUTOCOMMIT&lt;/code&gt; eingeschaltet haben (&lt;a href="https://dev.mysql.com/doc/refman/8.4/en/innodb-autocommit-commit-rollback.html" target="_blank"&gt;hier&lt;/a&gt;, &lt;a href="https://mariadb.com/docs/server/reference/sql-statements/transactions/start-transaction" target="_blank"&gt;hier&lt;/a&gt;, &lt;a href="https://www.postgresql.org/docs/current/ecpg-sql-set-autocommit.html" target="_blank"&gt;hier&lt;/a&gt; und &lt;a href="https://www.cybertec-postgresql.com/en/disabling-autocommit-in-postgresql-can-damage-your-health/" target="_blank"&gt;hier&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Damit man sich das ein bisschen besser vorstellen kann, hier etwas Pseudocode dazu:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// 20k rows
for (i = 1; i &amp;lt;= 2000; i++) {

 SELECT * FROM pending LIMIT 10;
 foreach ( row ) {
 INSERT INTO final;
 -- implicit COMMIT
 DELETE FROM pending WHERE id = row[id];
 -- implicit COMMIT
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Wenn wir also 20 k Zeilen umkopieren wollen, verursacht diese Variante: 40 k &lt;code&gt;COMMIT&lt;/code&gt;s (&lt;code&gt;fsync&lt;/code&gt;) und 42 k Netzwerk-Roundtrips!&lt;/p&gt;
&lt;h2 id="variante-2-start-transaction-und-insert-und-delete"&gt;Variante 2: &lt;code&gt;START TRANSACTION&lt;/code&gt; und &lt;code&gt;INSERT&lt;/code&gt; und &lt;code&gt;DELETE&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Diese Variante wird von versierteren Datenbank-Entwicklern verwendet. Hier der passende Pseudocode dazu:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// 20k rows
for (i = 1; i &amp;lt;= 2000; i++) {

 SELECT * FROM pending LIMIT 10;
 START TRANSACTION;
 foreach ( row ) {
 INSERT INTO final;
 DELETE FROM pending WHERE id = row[id];
 }
 COMMIT;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Wenn wir in diesem Beispiel 20 k Zeilen umkopieren wollen verursacht diese Variante nur noch 2 k &lt;code&gt;COMMIT&lt;/code&gt;s (&lt;code&gt;fsync&lt;/code&gt;)! Also 20 mal weniger! Aber dafür 46 k Netzwerk-Roundtrips (10% mehr).&lt;/p&gt;
&lt;h2 id="variante-3-start-transaction-und-optimiertes-insert-und-delete"&gt;Variante 3: &lt;code&gt;START TRANSACTION&lt;/code&gt; und optimiertes &lt;code&gt;INSERT&lt;/code&gt; und &lt;code&gt;DELETE&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Diese Variante ist programmiertechnisch ein klein wenig anspruchsvoller. Sie wird verwendet, wenn man den Grenzen des Möglich etwas näher kommen will. Hier der Pseudocode dazu:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// 20k rows
for (i = 1; i &amp;lt;= 2000; i++) {

 SELECT * FROM pending LIMIT 10;
 START TRANSACTION;
 INSERT INTO final (), (), (), (), (), (), (), (), (), ();
 DELETE FROM pending WHERE id = IN (...);
 COMMIT;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Und diese 3. Variante verursacht bei 20 k Zeilen ebenfalls nur noch 2 k &lt;code&gt;COMMIT&lt;/code&gt;&amp;rsquo;s (&lt;code&gt;fsync&lt;/code&gt;), spart sich aber die Schleife über die &lt;code&gt;INSERT&lt;/code&gt; und &lt;code&gt;DELETE&lt;/code&gt; Statements in der Datenbank. Wir sparen also einerseits Netzwerk Round-Trips (nur noch 10 k) und CPU-Cycles auf der Datenbank (welche sich schlecht skalieren lassen) zum Parsen der Queries.&lt;/p&gt;
&lt;h2 id="test-set-up"&gt;Test Set-up&lt;/h2&gt;
&lt;p&gt;Um das Ganze zu testen haben wir ein kleines Script vorbereitet: &lt;a href="https://www.fromdual.com/code-examples/load_data.php.txt"&gt;load_data.php&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="vorbereitung-und-ausführung-mit-mariadbmysql"&gt;Vorbereitung und Ausführung mit MariaDB/MySQL&lt;/h3&gt;
&lt;p&gt;Mit folgenden Befehlen kann man diese Tests selber ausführen:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; CREATE DATABASE test;
SQL&amp;gt; CREATE USER &amp;#39;app&amp;#39;@&amp;#39;127.0.0.1&amp;#39; IDENTIFIED BY &amp;#39;secret&amp;#39;;
SQL&amp;gt; GRANT ALL ON *.* TO &amp;#39;app&amp;#39;@&amp;#39;127.0.0.1&amp;#39;;

$ ./load_data.php --database-type=mysql --database=test --host=127.0.0.1 --port=3306 --user=app --password=secret --prepare

$ for i in $(seq 5) ; do
 ./load_data.php --database-type=mysql --database=test --host=127.0.0.1 --port=3306 --user=app --password=secret --run --variant=1
 ./load_data.php --database-type=mysql --database=test --host=127.0.0.1 --port=3306 --user=app --password=secret --run --variant=2
 ./load_data.php --database-type=mysql --database=test --host=127.0.0.1 --port=3306 --user=app --password=secret --run --variant=3
done

$ ./load_data.php --database-type=mysql --database=test --host=127.0.0.1 --port=3306 --user=app --password=secret --clean-up
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Die Messwerte kann sich jeder selbst mit dem entsprechenden Test-Skript erarbeiten.&lt;/p&gt;
&lt;h3 id="vorbereitung-und-ausführung-mit-postgresql"&gt;Vorbereitung und Ausführung mit PostgreSQL&lt;/h3&gt;
&lt;p&gt;Mit folgenden Befehlen kann man diese Tests selber ausführen:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres# CREATE DATABASE test;
postgres# CREATE USER app PASSWORD &amp;#39;secret&amp;#39;;
postgres# GRANT ALL ON DATABASE test TO app;
postgres# GRANT ALL ON SCHEMA public TO app;

$ ./load_data.php --database-type=postgresql --database=test --host=127.0.0.1 --port=5432 --user=app --password=secret --prepare

$ for i in $(seq 5) ; do
 ./load_data.php --database-type=postgresql --database=test --host=127.0.0.1 --port=5432 --user=app --password=secret --run --variant=1
 ./load_data.php --database-type=postgresql --database=test --host=127.0.0.1 --port=5432 --user=app --password=secret --run --variant=2
 ./load_data.php --database-type=postgresql --database=test --host=127.0.0.1 --port=5432 --user=app --password=secret --run --variant=3
done

$ ./load_data.php --database-type=postgresql --database=test --host=127.0.0.1 --port=5432 --user=app --password=secret --clean-up
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Die Messwerte kann sich jeder selbst mit dem entsprechenden Test-Skript erarbeiten.&lt;/p&gt;
&lt;h2 id="resultate"&gt;Resultate&lt;/h2&gt;
&lt;p&gt;Um unnötige Diskussionen zu vermeiden, haben wir hier &amp;ldquo;nur&amp;rdquo; die relative Performance (Laufzeit) aufgeführt, wie es MarkC in letzter Zeit auch macht. Unsere Messwerte können gerne bilateral zur Verfügung stellen. Sie lassen sich aber einfach mit dem Test-Skript selber reproduzieren.&lt;/p&gt;
&lt;p&gt;Weniger ist besser:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th&gt;Variante 1&lt;/th&gt;
					&lt;th&gt;Variante 2&lt;/th&gt;
					&lt;th&gt;Variante 3&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;MariaDB 11.8, avg(5)&lt;/td&gt;
					&lt;td&gt;100.0%&lt;/td&gt;
					&lt;td&gt;7.5%&lt;/td&gt;
					&lt;td&gt;6.2%&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;PostgreSQL 19dev, avg(5)&lt;/td&gt;
					&lt;td&gt;100.0%&lt;/td&gt;
					&lt;td&gt;11.2%&lt;/td&gt;
					&lt;td&gt;7.0%&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Achtung&lt;/strong&gt;: Die Werte von MariaDB/MySQL und PostgreSQL lassen sich NICHT direkt vergleichen!&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;Und hier noch die graphische Auswertung:&lt;/p&gt;
&lt;img src="https://www.fromdual.com/images/mariadb-data-load.png" alt="mariadb"&gt;
&lt;p&gt; &lt;/p&gt;
&lt;img src="https://www.fromdual.com/images/postgresql-data-load.png" alt="postgresql"&gt;
&lt;h2 id="bemerkungen"&gt;Bemerkungen&lt;/h2&gt;
&lt;p&gt;Mit schnelleren Disks, wäre der Unterschied zwischen 1 und 2/3 wahrscheinlich nicht mehr ganz so gravierend ausgefallen. Tests beim Kunden haben &amp;ldquo;nur&amp;rdquo; ca. Faktor 5 (statt Faktor 9 bis 16) unterschied gezeigt.&lt;/p&gt;
&lt;p&gt;Es gibt sicher weitere Möglichkeiten, wie man diesen Lade-Vorgang noch optimieren kann. Hier kommen mir in den Sinn:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;INSERT INTO ... SELECT * FROM&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LOAD DATA INFILE&lt;/code&gt;/&lt;code&gt;COPY&lt;/code&gt;, wenn möglich&lt;/li&gt;
&lt;li&gt;Prepared Statements&lt;/li&gt;
&lt;li&gt;Server side Stored Language (SQL/PSM, PL/pgSQL, &amp;hellip;) :-(&lt;/li&gt;
&lt;li&gt;PDO-Fetch der Resultate?&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vielleicht sollte ich mich mal um einen Profiler kümmern (&lt;a href="https://xdebug.org/" target="_blank"&gt;Xdebug&lt;/a&gt; oder &lt;a href="https://www.php.net/manual/en/book.xhprof.php" target="_blank"&gt;xhprof&lt;/a&gt;)?&lt;/p&gt;
&lt;h2 id="weitere-beiträge"&gt;Weitere Beiträge&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/blog/load-csv-files-into-the-database/"&gt;Load CSV files into the database&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/blog/mariadb-prepared-statements-transactions-and-multi-row-inserts/"&gt;MariaDB Prepared Statements, Transactions and Multi-Row Inserts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/blog/how-good-is-mysql-insert-trigger-performance/"&gt;How good is MySQL INSERT TRIGGER performance&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>MariaDB hat das Konzept des dynamisch konfigurierbaren Buffer Pools kaputt gemacht!</title><link>https://www.fromdual.com/de/blog/mariadb-dynamisch-konfigurierbare-buffer-pool-kaputt/</link><pubDate>Sun, 08 Feb 2026 20:25:00 +0100</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/mariadb-dynamisch-konfigurierbare-buffer-pool-kaputt/</guid><description>&lt;h2 id="problembeschreibung"&gt;Problembeschreibung&lt;/h2&gt;
&lt;p&gt;MySQL hat mit 5.7.5 im September 2014 den dynamisch konfigurierbaren InnoDB Buffer Pool eingeführt (&lt;a href="https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-5.html" target="_blank"&gt;hier&lt;/a&gt; und &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-buffer-pool-resize.html#innodb-buffer-pool-online-resize" target="_blank"&gt;hier&lt;/a&gt;):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The innodb_buffer_pool_size configuration option can be set dynamically using a SET statement, allowing you to resize the buffer pool without restarting the server. For example:&lt;br&gt;&lt;br&gt;
mysql&amp;gt; SET GLOBAL innodb_buffer_pool_size=402653184;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;MariaDB 10.2.2 hat dieses Feature im September 2016 übernommen (&lt;a href="https://mariadb.com/docs/release-notes/community-server/old-releases/10.2/10.2.2#notable-changes" target="_blank"&gt;Quelle&lt;/a&gt;):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;InnoDB was merged from MySQL-5.7.14 (XtraDB is disabled in MariaDB-10.2.2 pending a similar merge)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Das Problematische ist einerseits, dass dieses Feature jetzt nicht mehr funktioniert wie bis anhin und nicht mehr funktioniert wie erwartet. Anderseits haben sie das Verhalten im Frühling 2025 innerhalb einer Major Release Serie (LTS) geändert, was meiner Meinung nach ein absolutes no-go ist (&lt;a href="https://mariadb.com/docs/server/server-usage/storage-engines/innodb/innodb-buffer-pool#buffer-pool-changes" target="_blank"&gt;Quelle&lt;/a&gt;):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;From MariaDB 10.11.12 / 11.4.6 / 11.8.2, there are significant changes to the InnoDB buffer pool behavior.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Und zudem ist die Beschreibung dazu recht dürftig (&lt;a href="https://mariadb.com/docs/release-notes/community-server/10.11/10.11.12#innodb" target="_blank"&gt;Quelle&lt;/a&gt;):&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;decreasing innodb_buffer_pool_size at runtime does not release memory (MDEV-32339)&lt;/li&gt;
&lt;li&gt;reorganise innodb buffer pool (and remove buffer pool chunks) (MDEV-29445)&lt;/li&gt;
&lt;li&gt;The Linux memory pressure interface, which could previously not be disabled and could cause performance anomalies, was rewritten and is disabled by default. (MDEV-34863)&lt;/li&gt;
&lt;li&gt;Server crashes when resizing default innodb buffer pool after setting innodb-buffer-pool-chunk-size to 1M (MDEV-34677)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Im entsprechende Worklog (&lt;a href="https://jira.mariadb.org/browse/MDEV-36197" target="_blank"&gt;MDEV-36197&lt;/a&gt;) beschreibt MarkoM zudem ein anderes Verhalten:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;innodb_buffer_pool_size_auto_max (my proposal for this task) would set the maximum for the automation (default: 0 to disable the logic).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;was meiner Meinung nach wesentlich sinnvoller gewesen wäre.&lt;/p&gt;
&lt;h2 id="wie-ging-es-früher"&gt;Wie ging es früher?&lt;/h2&gt;
&lt;p&gt;Wie ging es früher bei MariaDB und heute immer noch bei MySQL:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; SHOW GLOBAL VARIABLES LIKE &amp;#39;innodb_buffer_pool%size&amp;#39;;
+-------------------------------+-----------+
| Variable_name | Value |
+-------------------------------+-----------+
| innodb_buffer_pool_chunk_size | 2097152 |
| innodb_buffer_pool_size | 134217728 |
+-------------------------------+-----------+

SQL&amp;gt; SET GLOBAL innodb_buffer_pool_size = @@innodb_buffer_pool_chunk_size * 128;

SQL&amp;gt; SHOW GLOBAL VARIABLES LIKE &amp;#39;innodb_buffer_pool%size&amp;#39;;
+-------------------------------+-----------+
| Variable_name | Value |
+-------------------------------+-----------+
| innodb_buffer_pool_chunk_size | 2097152 |
| innodb_buffer_pool_size | 268435456 |
+-------------------------------+-----------+
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Somit alles OK. Funktioniert wie erwartet und wie gewohnt.&lt;/p&gt;
&lt;h2 id="was-macht-mariadb-heute"&gt;Was macht MariaDB heute?&lt;/h2&gt;
&lt;p&gt;Was passiert heute bei MariaDB:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; SHOW GLOBAL VARIABLES LIKE &amp;#39;innodb_buffer_pool%size%&amp;#39;;
+----------------------------------+-----------+
| Variable_name | Value |
+----------------------------------+-----------+
| innodb_buffer_pool_chunk_size | 0 |
| innodb_buffer_pool_size | 134217728 |
| innodb_buffer_pool_size_auto_min | 134217728 |
| innodb_buffer_pool_size_max | 134217728 |
+----------------------------------+-----------+

SQL&amp;gt; SET GLOBAL innodb_buffer_pool_size = 256*1024*1024;
Query OK, 0 rows affected, 1 warning (0.000 sec)

SQL&amp;gt; show warnings;
+---------+------+----------------------------------------------------------------+
| Level | Code | Message |
+---------+------+----------------------------------------------------------------+
| Warning | 1292 | Truncated incorrect innodb_buffer_pool_size value: &amp;#39;268435456&amp;#39; |
+---------+------+----------------------------------------------------------------+

SQL&amp;gt; SHOW GLOBAL VARIABLES LIKE &amp;#39;innodb_buffer_pool%size%&amp;#39;;
+----------------------------------+-----------+
| Variable_name | Value |
+----------------------------------+-----------+
| innodb_buffer_pool_chunk_size | 0 |
| innodb_buffer_pool_size | 134217728 |
| innodb_buffer_pool_size_auto_min | 134217728 |
| innodb_buffer_pool_size_max | 134217728 |
+----------------------------------+-----------+
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Also gar nichts! Nicht mal einen Fehler, nur eine Warnung. Und wenn ich nicht genau schaue, merke ich nicht mal, dass da was nicht geklappt hat.&lt;/p&gt;
&lt;p&gt;Im MariaDB Error Log steht:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[Note] InnoDB: Memory pressure event disregarded; innodb_buffer_pool_size=128m, innodb_buffer_pool_size_auto_min=128m
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Wenn man dann etwas auf die Suche geht, findet man heraus, das hier etwas geändert hat: &lt;a href="https://mariadb.com/docs/server/server-usage/storage-engines/innodb/innodb-buffer-pool#buffer-pool-changes" target="_blank"&gt;Buffer Pool Changes&lt;/a&gt; und versucht intuitiv:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; SET GLOBAL innodb_buffer_pool_size_max = 256*1024*1024;
ERROR 1238 (HY000): Variable &amp;#39;innodb_buffer_pool_size_max&amp;#39; is a read only variable
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Geht aber auch nicht.&lt;/p&gt;
&lt;p&gt;Das bedeutet, man muss die Datenbank neu starten! Und das möglicherweise genau in dem Augenblick, in welchem man den Neustart eigentlich gar nicht gebrauchen kann und dieses Feature nötig hätte&amp;hellip;&lt;/p&gt;
&lt;p&gt;In der Doku steht dann auch (&lt;a href="https://mariadb.com/docs/server/server-usage/storage-engines/innodb/innodb-system-variables#innodb_buffer_pool_size_max" target="_blank" title="innodb_buffer_pool_size_max"&gt;Quelle&lt;/a&gt;):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Default Value: specified by the initial value of innodb_buffer_pool_size, rounded up to the block size of that variable. See the section about buffer pool changes in MariaDB 10.11.12, 11.4.6, and 11.8.2.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;und (&lt;a href="https://mariadb.com/docs/server/server-usage/storage-engines/innodb/innodb-buffer-pool#buffer-pool-changes" target="_blank" title="Buffer Pool Changes"&gt;Quelle&lt;/a&gt;):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If innodb_buffer_pool_size_max is 0 or not specified, it defaults to the innodb_buffer_pool_size value.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Das heisst, ich muss mir wieder vorher Gedanken machen, wie gross ich &lt;code&gt;innodb_buffer_pool_size_max&lt;/code&gt; machen soll und kann erst dann nachträglich im Betrieb korrigieren, sollte ich es vergessen oder mich verschätzt haben.&lt;/p&gt;
&lt;p&gt;Dies ist meiner Meinung nach ein vollkommener Rückschritt aus betrieblicher Sicht. Wahrscheinlich ist das wieder eine Implementierung für irgendwelche Cloud-only as a Service Lösungen (Enterprise?).&lt;/p&gt;
&lt;p&gt;Mein Vorschlage ist: Entweder, wie im MDEV vorgeschlagen: 0 soll dieses Feature ausschalten und das Verhalten wie bisher sein oder aber, der Default-Wert soll auf 75% der RAM-Size gesetzt werden, so wie es &lt;code&gt;innodb_dedicated_server&lt;/code&gt; bei MySQL macht.&lt;/p&gt;
&lt;p&gt;Ich habe mich erdreistet hier mal einen Bug zu eröffnen: &lt;a href="https://jira.mariadb.org/browse/MDEV-38779" target="_blank"&gt;New InnoDB Buffer Pool autosize feature not so optimal implemented&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;FedericoR hat mit verdankenswerter Weise noch folgen Link empfohlen: &lt;a href="https://www.mail-archive.com/developers@lists.mariadb.org/msg00822.html" target="_blank" title="MariaDB developers mailing list"&gt;Issues with new buffer pool configuration in MariaDB Minors (10.11.12/13/14, 11.4.6/7/8, 11.8.2/3)&lt;/a&gt;. Ich schein nich der Einzige zu sein, dem dies Änderung auf diese Art sauer aufgestossen ist&amp;hellip;&lt;/p&gt;
&lt;h2 id="wie-macht-das-postgresql"&gt;Wie macht das PostgreSQL?&lt;/h2&gt;
&lt;p&gt;PostgreSQL ist zur Zeit (noch) nicht in der Lage, &lt;code&gt;shared_buffers&lt;/code&gt; dynamisch zu ändern. Der Default ist üblicherweise 128M. Hier gilt die Faustregel, ähnlich wie bei MyISAM, 25 - 40% vom RAM. Das Fehlen dieses Features ist bei PostgreSQL aber wahrscheinlich nicht so gravierend, da sich PostgreSQL stark auf den Filesystem Cache verlässt, ähnlich wie MyISAM.&lt;/p&gt;
&lt;p&gt;Quelle: &lt;a href="https://www.postgresql.org/docs/current/runtime-config-resource.html#RUNTIME-CONFIG-RESOURCE-MEMORY" target="_blank"&gt;Resource Consumption&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="kommentare"&gt;Kommentare&lt;/h2&gt;
&lt;p&gt;Siehe &lt;a href="https://www.fromdual.com/blog/mariadb-dynamically-configurable-buffer-pool-broken/#mariadb-dynamically-configurable-buffer-pool-broken/%23comment-1049"&gt;hier&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Wie viel Platz braucht NULL?</title><link>https://www.fromdual.com/de/blog/wie-viel-platz-braucht-null/</link><pubDate>Sun, 08 Feb 2026 10:36:00 +0100</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/wie-viel-platz-braucht-null/</guid><description>&lt;p&gt;Beim letzten Beratungseinsatz beim Kunden, kam dieser freudestrahlend auf mich zu mit der Bemerkung: Er habe meinen Rat befolgt und sämtiche Primary Key Spalten von &lt;code&gt;BIGINT&lt;/code&gt; (8 byte) auf &lt;code&gt;INT&lt;/code&gt; (4 byte) geändert und das habe viel gebracht! Seine MySQL 8.4er Datenbank sei jetzt um 750 Gbyte kleiner geworden (von 5.5 Tbyte). Schön!&lt;/p&gt;
&lt;p&gt;Und ja, ich weiss, dass wiederspricht den Empfehlungen einiger meiner PostgreSQL Kollegen (&lt;a href="https://www.crunchydata.com/blog/postgres-serials-should-be-bigint-and-how-to-migrate" target="_blank"&gt;hier&lt;/a&gt; und &lt;a href="https://www.cybertec-postgresql.com/en/uuid-serial-or-identity-columns-for-postgresql-auto-generated-primary-keys/#should-i-use-integerserial-or-bigintbigserial-for-my-auto-generated-primary-key" target="_blank"&gt;hier&lt;/a&gt;). In der MySQL-Welt wird eher auf solche Dinge Wert gelegt (&lt;a href="https://dev.mysql.com/doc/refman/8.4/en/data-size.html" target="_blank"&gt;Quelle&lt;/a&gt;):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Use the most efficient (smallest) data types possible. MySQL has many specialized types that save disk space and memory. For example, use the smaller integer types if possible to get smaller tables&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Zudem funktioniert InnoDB ein klein wening anders (Index Clusterd Table und Primary Key in allen Secondary Keys) als PostgreSQL (Heap Table, Indices mit Row Pointer (&lt;code&gt;ctid&lt;/code&gt;)).&lt;/p&gt;
&lt;p&gt;Aber das ist eigentlich nicht das Thema. Sofort im Anschluss kam er nämlich mit der Frage, ob das AusNULLen von Spalten vom Typ &lt;code&gt;DOUBLE&lt;/code&gt; (8 byte, in PostgreSQL-Sprech &lt;code&gt;DOUBLE PRECISION&lt;/code&gt;) auch Platzeinsparungen brächte oder ob er die Spalten lieber gleich droppen solle. Meine erste reflexartige Antwort bei &lt;code&gt;DOUBLE&lt;/code&gt; war: &lt;code&gt;NULL&lt;/code&gt; bringt was, gefolgt von &lt;code&gt;OPTIMIZE TABLE&lt;/code&gt; (in PostgreSQL-Sprech &lt;code&gt;VACUUM FULL&lt;/code&gt;). Der zweite Gedanke war aber, &lt;code&gt;DOUBLE&lt;/code&gt; ist ein Datentyp fixer länger, gilt das mit &lt;code&gt;NULL&lt;/code&gt; da auch oder nur bei Datentypen mit variabler Länge? Vorsicht ist die Mutter der Porzelankiste! Liebe zuerst mal das Manual konsultieren&amp;hellip;&lt;/p&gt;
&lt;p&gt;Und dort steht (&lt;a href="https://dev.mysql.com/doc/refman/8.4/en/data-size.html" target="_blank"&gt;Quelle&lt;/a&gt;):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Declare columns to be NOT NULL if possible. It makes SQL operations faster, by enabling better use of indexes and eliminating overhead for testing whether each value is NULL. You also save some storage space, one bit per column. If you really need NULL values in your tables, use them. Just avoid the default setting that allows NULL values in every column.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;und (&lt;a href="https://dev.mysql.com/doc/refman/8.4/en/innodb-row-format.html" target="_blank"&gt;Quelle&lt;/a&gt;):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The variable-length part of the record header contains a bit vector for indicating NULL columns. &amp;hellip; Columns that are NULL do not occupy space other than the bit in this vector. The variable-length part of the header also contains the lengths of variable-length columns. Each length takes one or two bytes, depending on the maximum length of the column. If all columns in the index are NOT NULL and have a fixed length, the record header has no variable-length part.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="versuch-mit-mariadbmysql"&gt;Versuch mit MariaDB/MySQL&lt;/h2&gt;
&lt;h3 id="versuchsaufbau"&gt;Versuchsaufbau&lt;/h3&gt;
&lt;p&gt;Irgendwie ist mir das etwas zu komplizert beschrieben. Eine kleine Skizze würde vielleicht helfen? Also machen wir einen Versuch:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; -- DROP TABLE IF EXISTS tracking;

SQL&amp;gt; CREATE TABLE tracking (
 id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT
, d0 DOUBLE, d1 DOUBLE, d2 DOUBLE, d3 DOUBLE, d4 DOUBLE
, d5 DOUBLE, d6 DOUBLE, d7 DOUBLE, d8 DOUBLE, d9 DOUBLE
);

SQL&amp;gt; INSERT INTO tracking SELECT NULL, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0;
SQL&amp;gt; INSERT INTO tracking SELECT NULL, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 FROM tracking;
... bis 16 M rows
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Die Tabelle wird sowohl bei MariaDB als auch bei MySQL bei 16 M Rows ca. 1.8 Gbyte gross. Da dieses Informationen im &lt;code&gt;INFORMATION_SCHEMA&lt;/code&gt; nur sehr unpräzise angegeben werden schauen wir auf dem Filesystem nach:&lt;/p&gt;
&lt;p&gt;MariaDB 11.8:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; system ls -l tracking.ibd
-rw-rw---- 1 mysql mysql 1206 Feb 7 10:28 tracking.frm
-rw-rw---- 1 mysql mysql 1933574144 Feb 7 10:32 tracking.ibd
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;MySQL 8.4:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; system ls -l tracking.ibd
-rw-r----- 1 mysql mysql 1929379840 Feb 7 10:33 tracking.ibd
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="defragementieren-der-tabelle"&gt;Defragementieren der Tabelle&lt;/h3&gt;
&lt;p&gt;Dann &amp;ldquo;defragmentieren&amp;rdquo; wir die Tabelle mit dem &lt;code&gt;OPTIMIZE TABLE&lt;/code&gt; Befehl:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; OPTIMIZE TABLE tracking;
+---------------+----------+----------+-------------------------------------------------------------------+
| Table | Op | Msg_type | Msg_text |
+---------------+----------+----------+-------------------------------------------------------------------+
| test.tracking | optimize | note | Table does not support optimize, doing recreate + analyze instead |
| test.tracking | optimize | status | OK |
+---------------+----------+----------+-------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Achtung&lt;/strong&gt;: Die Tabelle wir einmal umkopiert! Es braucht also kurzzeitig die doppelte Menge an Diskplatz! Dies können wird schön beobachten während der &lt;code&gt;OPTIMIZE TABLE&lt;/code&gt; Befehls läuft:&lt;/p&gt;
&lt;p&gt;MariaDB:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ watch -d -n 1 &amp;#39;ls -l trac* \#*&amp;#39;
-rw-rw---- 1 mysql mysql 1206 Feb 7 10:39 &amp;#39;#sql-alter-d57-8c.frm&amp;#39;
-rw-rw---- 1 mysql mysql 968884224 Feb 7 10:39 &amp;#39;#sql-alter-d57-8c.ibd&amp;#39;
-rw-rw---- 1 mysql mysql 1206 Feb 7 10:28 tracking.frm
-rw-rw---- 1 mysql mysql 1933574144 Feb 7 10:32 tracking.ibd
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;MySQL:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ watch -d -n 1 &amp;#39;ls -l trac* \#*&amp;#39;
-rw-r----- 1 mysql mysql 369098752 Feb 7 10:40 #sql-ib1594-4164062678.ibd
-rw-r----- 1 mysql mysql 1929379840 Feb 7 10:33 tracking.ibd
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Das Resultat ist dann aber erstaunlich! Bei MariaDB ist die Tabelle in etwas gleich gross geblieben:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;-rw-rw---- 1 mysql mysql 1206 Feb 7 10:39 tracking.frm
-rw-rw---- 1 mysql mysql 1912602624 Feb 7 10:39 tracking.ibd
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Bei MySQL hingegen ist die Tabelle nach dem &amp;ldquo;defragmentieren&amp;rdquo; sogar angewachsen und zwar um ca. 14%:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;-rw-r----- 1 mysql mysql 2197815296 Feb 7 10:41 tracking.ibd
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Wenn wir den &lt;code&gt;OPTIMIZE TABLE&lt;/code&gt; Befehl nochmal ausführen bleibt die Grösse sowohl bei MariaDB als auch MySQL konstant:&lt;/p&gt;
&lt;p&gt;MariaDB:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;-rw-rw---- 1 mysql mysql 1206 Feb 7 10:46 tracking.frm
-rw-rw---- 1 mysql mysql 1912602624 Feb 7 10:48 tracking.ibd
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;MySQL:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;-rw-r----- 1 mysql mysql 2197815296 Feb 7 10:48 tracking.ibd
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="versuch-1-ausnullen"&gt;Versuch 1: AusNULLen&lt;/h3&gt;
&lt;p&gt;Jetzt &lt;code&gt;NULL&lt;/code&gt;en wir die Werte aus:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; UPDATE tracking
SET d0 = NULL, d1 = NULL, d2 = NULL, d3 = NULL, d4 = NULL
 , d5 = NULL, d6 = NULL, d7 = NULL, d8 = NULL, d9 = NULL
;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nach diesem Schritt sind die Grössen der Dateien sogar noch etwas gewachsen:&lt;/p&gt;
&lt;p&gt;MariaDB (+1.3%):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;-rw-rw---- 1 mysql mysql 1206 Feb 7 10:49 tracking.frm
-rw-rw---- 1 mysql mysql 1937768448 Feb 7 11:04 tracking.ibd
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;MySQL (+0.2%):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;-rw-r----- 1 mysql mysql 2202009600 Feb 7 11:04 tracking.ibd
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Anschliessend defragemntieren wir die Tabelle erneut mit dem &lt;code&gt;OPTIMIZE TABLE&lt;/code&gt; Befehl. Die Tabellen schrumpfen wie erwartet.&lt;/p&gt;
&lt;p&gt;MariaDB (auf 23%):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;-rw-rw---- 1 mysql mysql 1206 Feb 7 11:09 tracking.frm
-rw-rw---- 1 mysql mysql 448790528 Feb 7 11:10 tracking.ibd
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;MySQL (auf 24%):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;-rw-r----- 1 mysql mysql 520093696 Feb 7 11:10 tracking.ibd
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ein erneutes &lt;code&gt;OPTIMIZE TABLE&lt;/code&gt; bringt anschliessend KEINE Veränderung mehr in der Dateigrösse&amp;hellip;&lt;/p&gt;
&lt;h3 id="versuch-2-löschen-der-spalten"&gt;Versuch 2: Löschen der Spalten&lt;/h3&gt;
&lt;p&gt;Jetzt probieren wir das Ganze nochmal mit dem &lt;code&gt;DROP COLUMN&lt;/code&gt; Befehl. Die Ausganglage ist wieder die selbe wie oben beschrieben:&lt;/p&gt;
&lt;p&gt;MariaDB:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;-rw-rw---- 1 mysql mysql 1206 Feb 7 11:15 tracking.frm
-rw-rw---- 1 mysql mysql 1933574144 Feb 7 11:18 tracking.ibd
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;MySQL:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;-rw-r----- 1 mysql mysql 1929379840 Feb 7 11:19 tracking.ibd
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nach dem &lt;code&gt;OPTIMIZE TABLE&lt;/code&gt; Befehl sehen die Werte ähnlich aus wie im ersten Versuch:&lt;/p&gt;
&lt;p&gt;MariaDB:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;-rw-rw---- 1 mysql mysql 1206 Feb 7 11:20 tracking.frm
-rw-rw---- 1 mysql mysql 1912602624 Feb 7 11:21 tracking.ibd
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;MySQL:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;-rw-r----- 1 mysql mysql 2197815296 Feb 7 11:21 tracking.ibd
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ein erneutes &lt;code&gt;OPTIMIZE TABLE&lt;/code&gt; bring ebenfalls keine weiteren Veränderungen mehr, wie oben:&lt;/p&gt;
&lt;p&gt;MariaDB:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;-rw-rw---- 1 mysql mysql 1206 Feb 7 11:22 tracking.frm
-rw-rw---- 1 mysql mysql 1912602624 Feb 7 11:23 tracking.ibd
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;MySQL:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;-rw-r----- 1 mysql mysql 2197815296 Feb 7 11:24 tracking.ibd
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Und jetzt der eigentliche zweite Versuch mit Löschen der Spalten:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; ALTER TABLE tracking
 DROP COLUMN d0, DROP COLUMN d1, DROP COLUMN d2, DROP COLUMN d3, DROP COLUMN d4
, DROP COLUMN d5, DROP COLUMN d6, DROP COLUMN d7, DROP COLUMN d8, DROP COLUMN d9
;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Als erstes stellen wir fest, dass der Befehl &lt;code&gt;INSTANTANEOUS&lt;/code&gt; ist, als keinerlei Änderung an den Daten vornimmt sondern nur Änderung an den Metadaten. Das ist auf der einen Seite gut, da der Einfluss auf die Applikation dadurch minimal gehalten wird.. Auf der anderen Seite bedeutet das ja auch: Keine Platzersparnis.&lt;/p&gt;
&lt;p&gt;Also rücken wir dem Ganzen wieder mit dem &lt;code&gt;OPTIMIZE TABLE&lt;/code&gt; Befehl zu Leibe:&lt;/p&gt;
&lt;p&gt;MariaDB (auf 93%):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;-rw-rw---- 1 mysql mysql 925 Feb 7 11:28 tracking.frm
-rw-rw---- 1 mysql mysql 415236096 Feb 7 11:29 tracking.ibd
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;MySQL (auf 92%):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;-rw-r----- 1 mysql mysql 478150656 Feb 7 11:28 tracking.ibd
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="fazit"&gt;Fazit&lt;/h3&gt;
&lt;p&gt;Sowohl das ausNULLen der Spalten als auch das Löschen der Spalten bringen signifikante Platzersparnis. Wobei das Löschen der Spalten nochmal ca. 7% mehr bringt als das ausNULLen. Wenn es applikatorisch möglich ist, sollte man daher nicht mehr benötigte Spalen löschen, wenn nicht möglich, diese zumindest ausNULLen.&lt;/p&gt;
&lt;h2 id="versuch-mit-postgresql"&gt;Versuch mit PostgreSQL&lt;/h2&gt;
&lt;p&gt;Und jetzt schauen wir uns das Ganze noch bei PostgreSQL 19devel an.&lt;/p&gt;
&lt;h3 id="versuchsaufbau-1"&gt;Versuchsaufbau&lt;/h3&gt;
&lt;p&gt;Der Versuchsaufbau erfolgt analog zu MariaDB/MySQL:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# -- DROP TABLE IF EXISTS tracking;

postgres=# CREATE TABLE tracking (
 id SERIAL PRIMARY KEY
, d0 DOUBLE PRECISION, d1 DOUBLE PRECISION, d2 DOUBLE PRECISION, d3 DOUBLE PRECISION, d4 DOUBLE PRECISION
, d5 DOUBLE PRECISION, d6 DOUBLE PRECISION, d7 DOUBLE PRECISION, d8 DOUBLE PRECISION, d9 DOUBLE PRECISION
);

postgres=# \timing

postgres=# INSERT INTO tracking (d0, d1, d2, d3, d4, d5, d6, d7, d8, d9)
 SELECT 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0;
postgres=# INSERT INTO tracking (d0, d1, d2, d3, d4, d5, d6, d7, d8, d9)
 SELECT 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 FROM tracking;
... bis 16 M rows
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Zuerst wollen wir mal wissen, wie gross die Tabelle eigentlich geworden ist. PostgreSQL scheint diese Informationen sehr genau zu kennen:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# SELECT pg_relation_size(&amp;#39;tracking&amp;#39;) AS tab_siz
 , pg_size_pretty(pg_relation_size(&amp;#39;tracking&amp;#39;)) AS tab_siz_prtty
 , pg_indexes_size(&amp;#39;tracking&amp;#39;) AS idx_siz
 , pg_size_pretty(pg_indexes_size(&amp;#39;tracking&amp;#39;)) AS idx_siz_prtty
 , pg_relation_size(&amp;#39;tracking&amp;#39;) + pg_indexes_size(&amp;#39;tracking&amp;#39;) AS tab_and_idx_siz
 , pg_size_pretty(pg_relation_size(&amp;#39;tracking&amp;#39;) + pg_indexes_size(&amp;#39;tracking&amp;#39;)) AS tab_and_idx_siz_prtty
 , pg_total_relation_size(&amp;#39;tracking&amp;#39;) AS tot_rel_siz
 , pg_size_pretty(pg_total_relation_size(&amp;#39;tracking&amp;#39;)) AS tot_rel_siz_prtty
;
 tab_siz | tab_siz_prtty | idx_siz | idx_siz_prtty | tab_and_idx_siz | tab_and_idx_siz_prtty | tot_rel_siz | tot_rel_siz_prtty
------------+---------------+-----------+---------------+-----------------+-----------------------+-------------+-------------------
 1963417600 | 1872 MB | 376856576 | 359 MB | 2340274176 | 2232 MB | 2340798464 | 2232 MB
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dann wollen wir wissen, wo im Filesystem diese Dateien zu finden sind:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# SELECT oid AS db_oid FROM pg_database WHERE datname = current_database();
 db_oid
--------
 5

postgres=# SELECT oid AS table_oid, relname, relnamespace, relfilenode
 FROM pg_class WHERE relname = &amp;#39;tracking&amp;#39;;
 table_oid | relname | relnamespace | relfilenode
-----------+----------+--------------+-------------
 40965 | tracking | 2200 | 40965

postgres=# SELECT i.indexrelid::regclass as index_name, i.indexrelid as index_oid
 FROM pg_index i
 JOIN pg_class c ON i.indrelid = c.oid
 WHERE c.relname = &amp;#39;tracking&amp;#39;;
 index_name | index_oid
---------------+-----------
 tracking_pkey | 40970

postgres=# SELECT pg_relation_filepath(&amp;#39;tracking&amp;#39;);
 pg_relation_filepath
----------------------
 base/5/40965
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Tabellen- und Indexgrösse im Filesystem:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ ls -ltr 40965* 40970*
-rw------- 1 mysql mysql 40960 Feb 7 18:33 40965_vm
-rw------- 1 mysql mysql 499712 Feb 7 18:33 40965_fsm
-rw------- 1 mysql mysql 889675776 Feb 7 18:34 40965.1
-rw------- 1 mysql mysql 1073741824 Feb 7 18:34 40965
-rw------- 1 mysql mysql 376856576 Feb 7 18:35 40970
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;*_fsm&lt;/code&gt; bedeutet &amp;ldquo;free space map&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;*_vm&lt;/code&gt; bedeutet &amp;ldquo;visibility map&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;*.1&lt;/code&gt; bedeutet 2. Segment des Objekts (Tabelle oder Index)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PostgreSQL scheint per default mit Segmenten von 1 Gbyte Grösse zu arbeiten und im Unterschied zu MariaDB/MySQL (&lt;code&gt;INFORMATION_SCHEMA&lt;/code&gt;) sehr genau zu wissen, wie gross seine Dateien sind. Und die Diskrepanz von oben (zwischen &lt;code&gt;tot_rel_siz&lt;/code&gt; und &lt;code&gt;tab_and_idx_siz&lt;/code&gt;) lässt sich durch die &lt;code&gt;fsm&lt;/code&gt; und die &lt;code&gt;vm&lt;/code&gt;-Dateien erklären.&lt;/p&gt;
&lt;p&gt;Das PostgreSQL Equivalent zum MariaDB/MySQL &lt;code&gt;OPTIMIZE TABLE&lt;/code&gt; ist der Befehl &lt;code&gt;VACUUM FULL&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# VACUUM FULL tracking;

$ ls -ltr
-rw------- 1 mysql mysql 40960 Feb 7 18:39 40965_vm
-rw------- 1 mysql mysql 499712 Feb 7 18:39 40965_fsm
-rw------- 1 mysql mysql 1073741824 Feb 7 18:39 40965
-rw------- 1 mysql mysql 889675776 Feb 7 18:39 40965.1
-rw------- 1 mysql mysql 1073741824 Feb 7 18:39 40972
-rw------- 1 mysql mysql 889675776 Feb 7 18:39 40972.1
-rw------- 1 mysql mysql 0 Feb 7 18:39 40975

...

-rw------- 1 mysql mysql 49152 Feb 7 18:39 2704
-rw------- 1 mysql mysql 32768 Feb 7 18:39 2703
-rw------- 1 mysql mysql 32768 Feb 7 18:39 2696
-rw------- 1 mysql mysql 65536 Feb 7 18:39 2674
-rw------- 1 mysql mysql 81920 Feb 7 18:39 2673
-rw------- 1 mysql mysql 98304 Feb 7 18:39 2659
-rw------- 1 mysql mysql 139264 Feb 7 18:39 2658
-rw------- 1 mysql mysql 24576 Feb 7 18:39 2619_fsm
-rw------- 1 mysql mysql 163840 Feb 7 18:39 2619
-rw------- 1 mysql mysql 106496 Feb 7 18:39 2608
-rw------- 1 mysql mysql 491520 Feb 7 18:39 1249
-rw------- 1 mysql mysql 122880 Feb 7 18:39 1247
-rw------- 1 mysql mysql 32768 Feb 7 18:39 2662
-rw------- 1 mysql mysql 114688 Feb 7 18:39 1259
-rw------- 1 mysql mysql 16384 Feb 7 18:39 3455
-rw------- 1 mysql mysql 49152 Feb 7 18:39 2663
-rw------- 1 mysql mysql 1073741824 Feb 7 18:39 40972
-rw------- 1 mysql mysql 889675776 Feb 7 18:39 40972.1
-rw------- 1 mysql mysql 376864768 Feb 7 18:39 40975
-rw------- 1 mysql mysql 0 Feb 7 18:39 40965
-rw------- 1 mysql mysql 0 Feb 7 18:39 40970
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Das erste, was man feststellt, ist, dass PostgreSQL ganz viele Dateien anlangt und die &amp;ldquo;free space map&amp;rdquo; Datei ist verschwunden. Die Tabellen-Segmente sind dafür, im Unterschied zu MariaDB/MySQL gleich gross geblieben. Zudem sieht man, dass die alte Tabelle &amp;ldquo;verschwunden&amp;rdquo; (40965, 40970) und eine neue entstanden (40972 und 40975) ist. Der &lt;code&gt;VACUUM FULL&lt;/code&gt; Befehl bei PostgreSQL erstellt also ebenfalls, wie bei MariaDB/MySQL eine Kopie der Daten.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# SELECT pg_relation_size(&amp;#39;tracking&amp;#39;) AS tab_siz
 , pg_size_pretty(pg_relation_size(&amp;#39;tracking&amp;#39;)) AS tab_siz_prtty
 , pg_indexes_size(&amp;#39;tracking&amp;#39;) AS idx_siz
 , pg_size_pretty(pg_indexes_size(&amp;#39;tracking&amp;#39;)) AS idx_siz_prtty
 , pg_relation_size(&amp;#39;tracking&amp;#39;) + pg_indexes_size(&amp;#39;tracking&amp;#39;) AS tab_and_idx_siz
 , pg_size_pretty(pg_relation_size(&amp;#39;tracking&amp;#39;) + pg_indexes_size(&amp;#39;tracking&amp;#39;)) AS tab_and_idx_siz_prtty
 , pg_total_relation_size(&amp;#39;tracking&amp;#39;) AS tot_rel_siz
 , pg_size_pretty(pg_total_relation_size(&amp;#39;tracking&amp;#39;)) AS tot_rel_siz_prtty
;
 tab_siz | tab_siz_prtty | idx_siz | idx_siz_prtty | tab_and_idx_siz | tab_and_idx_siz_prtty | tot_rel_siz | tot_rel_siz_prtty
------------+---------------+-----------+---------------+-----------------+-----------------------+-------------+-------------------
 1963417600 | 1872 MB | 376864768 | 359 MB | 2340282368 | 2232 MB | 2340282368 | 2232 MB
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Um zu verstehen, welche Dateien/Objekte denn sonst noch so alles angelangt wurden, hilft folgendes Query:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# SELECT c.oid, c.relname, ns.nspname
FROM pg_class AS c
JOIN pg_namespace AS ns ON ns.oid = c.relnamespace
WHERE c.oid IN (2704, 2703, 2696, 2674, 2673, 2659, 2658, 2619, 2608, 1249, 1247, 40972, 2662, 1259, 3455, 2663, 40975, 40965, 40970)
;
 oid | relname | nspname
-------+-----------------------------------+------------
 40965 | tracking | public
 40970 | tracking_pkey | public
 2619 | pg_statistic | pg_catalog
 1247 | pg_type | pg_catalog
 2703 | pg_type_oid_index | pg_catalog
 2704 | pg_type_typname_nsp_index | pg_catalog
 2658 | pg_attribute_relid_attnam_index | pg_catalog
 2659 | pg_attribute_relid_attnum_index | pg_catalog
 2662 | pg_class_oid_index | pg_catalog
 2663 | pg_class_relname_nsp_index | pg_catalog
 3455 | pg_class_tblspc_relfilenode_index | pg_catalog
 2696 | pg_statistic_relid_att_inh_index | pg_catalog
 2673 | pg_depend_depender_index | pg_catalog
 2674 | pg_depend_reference_index | pg_catalog
 1249 | pg_attribute | pg_catalog
 1259 | pg_class | pg_catalog
 2608 | pg_depend | pg_catalog

postgres=# SELECT i.indexrelid::regclass as index_name, i.indexrelid as index_oid, ns.nspname
 FROM pg_index i
 JOIN pg_class c ON i.indrelid = c.oid
 JOIN pg_namespace AS ns ON ns.oid = c.relnamespace
 WHERE c.oid IN (2704, 2703, 2696, 2674, 2673, 2659, 2658, 2619, 2608, 1249, 1247, 40972, 2662, 1259, 3455, 2663, 40975, 40965, 40970)
;
 index_name | index_oid | nspname
-----------------------------------+-----------+------------
 pg_type_typname_nsp_index | 2704 | pg_catalog
 pg_attribute_relid_attnam_index | 2658 | pg_catalog
 tracking_pkey | 40970 | public
 pg_class_relname_nsp_index | 2663 | pg_catalog
 pg_class_tblspc_relfilenode_index | 3455 | pg_catalog
 pg_type_oid_index | 2703 | pg_catalog
 pg_attribute_relid_attnum_index | 2659 | pg_catalog
 pg_statistic_relid_att_inh_index | 2696 | pg_catalog
 pg_depend_depender_index | 2673 | pg_catalog
 pg_depend_reference_index | 2674 | pg_catalog
 pg_class_oid_index | 2662 | pg_catalog
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="versuch-1-ausnullen-1"&gt;Versuch 1: AusNULLen&lt;/h3&gt;
&lt;p&gt;Dann &lt;code&gt;NULL&lt;/code&gt;en wir die Spalten bei PostgreSQL ebenfalls aus. Die Sicht aufs Datei-System sparen wir uns ab hier, da PostgreSQL die Dateigrössen ja genau zu kennen scheint, wie wir oben gesehen haben:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# UPDATE tracking
SET d0 = NULL, d1 = NULL, d2 = NULL, d3 = NULL, d4 = NULL
 , d5 = NULL, d6 = NULL, d7 = NULL, d8 = NULL, d9 = NULL
;

postgres=# SELECT pg_relation_size(&amp;#39;tracking&amp;#39;) AS tab_siz
 , pg_size_pretty(pg_relation_size(&amp;#39;tracking&amp;#39;)) AS tab_siz_prtty
 , pg_indexes_size(&amp;#39;tracking&amp;#39;) AS idx_siz
 , pg_size_pretty(pg_indexes_size(&amp;#39;tracking&amp;#39;)) AS idx_siz_prtty
 , pg_relation_size(&amp;#39;tracking&amp;#39;) + pg_indexes_size(&amp;#39;tracking&amp;#39;) AS tab_and_idx_siz
 , pg_size_pretty(pg_relation_size(&amp;#39;tracking&amp;#39;) + pg_indexes_size(&amp;#39;tracking&amp;#39;)) AS tab_and_idx_siz_prtty
 , pg_total_relation_size(&amp;#39;tracking&amp;#39;) AS tot_rel_siz
 , pg_size_pretty(pg_total_relation_size(&amp;#39;tracking&amp;#39;)) AS tot_rel_siz_prtty
;
 tab_siz | tab_siz_prtty | idx_siz | idx_siz_prtty | tab_and_idx_siz | tab_and_idx_siz_prtty | tot_rel_siz | tot_rel_siz_prtty
------------+---------------+-----------+---------------+-----------------+-----------------------+-------------+-------------------
 2695716864 | 2571 MB | 753696768 | 719 MB | 3449413632 | 3290 MB | 3450101760 | 3290 MB
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Hier sehen wir, dass die Tabellen-Segmente massiv anwachsen (+37%), was in der PostgreSQL Terminologie als &amp;ldquo;bloat&amp;rdquo; bezeichnet wird. Die MVCC Implementierung von PostgreSQL speichert sowohl die alte auch nie neue Version der Row &amp;ldquo;in-place&amp;rdquo; also direkt in der Tabelle im Gegensatz zu MariaDB/MySQL welche die alte Version im UNDO Space und die neue Row &amp;ldquo;in-place&amp;rdquo; speichert. Auch die Index-Datei vergrössert sich signifikant (+100%). Dazu müssen wir noch weiter forschen, warum das so ist. Zudem wird wieder eine &amp;ldquo;free space map&amp;rdquo; erstellt (Differenz zwischen &lt;code&gt;tot_rel_siz&lt;/code&gt; und &lt;code&gt;tab_and_idx_siz&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Ein anschliessendes &lt;code&gt;VACUUM FULL&lt;/code&gt; verkleinert die Tabelle (auf 28%) und den Index (auf 50%) wieder im Bezug auf die vorherige Grösse:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# VACUUM FULL tracking;

postgres=# SELECT pg_relation_size(&amp;#39;tracking&amp;#39;) AS tab_siz
 , pg_size_pretty(pg_relation_size(&amp;#39;tracking&amp;#39;)) AS tab_siz_prtty
 , pg_indexes_size(&amp;#39;tracking&amp;#39;) AS idx_siz
 , pg_size_pretty(pg_indexes_size(&amp;#39;tracking&amp;#39;)) AS idx_siz_prtty
 , pg_relation_size(&amp;#39;tracking&amp;#39;) + pg_indexes_size(&amp;#39;tracking&amp;#39;) AS tab_and_idx_siz
 , pg_size_pretty(pg_relation_size(&amp;#39;tracking&amp;#39;) + pg_indexes_size(&amp;#39;tracking&amp;#39;)) AS tab_and_idx_siz_prtty
 , pg_total_relation_size(&amp;#39;tracking&amp;#39;) AS tot_rel_siz
 , pg_size_pretty(pg_total_relation_size(&amp;#39;tracking&amp;#39;)) AS tot_rel_siz_prtty
;
 tab_siz | tab_siz_prtty | idx_siz | idx_siz_prtty | tab_and_idx_siz | tab_and_idx_siz_prtty | tot_rel_siz | tot_rel_siz_prtty
-----------+---------------+-----------+---------------+-----------------+-----------------------+-------------+-------------------
 742916096 | 709 MB | 376864768 | 359 MB | 1119780864 | 1068 MB | 1119780864 | 1068 MB
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;und auch in Bezug zur ursprünglichen Grösse wird die Tabelle (auf 38%) und der Index (auf 100%) wieder kleiner. Warum der Index gleich gross geblieben ist und nur die Tabelle geschrumpft ist, muss noch recherchiert werden&amp;hellip;&lt;/p&gt;
&lt;h3 id="versuch-2-löschen-der-spalten-1"&gt;Versuch 2: Löschen der Spalten&lt;/h3&gt;
&lt;p&gt;Anschliessend werden die Spalten noch mit &lt;code&gt;DROP COLUMN&lt;/code&gt; gelöscht.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# ALTER TABLE tracking
 DROP COLUMN d0, DROP COLUMN d1, DROP COLUMN d2, DROP COLUMN d3, DROP COLUMN d4
, DROP COLUMN d5, DROP COLUMN d6, DROP COLUMN d7, DROP COLUMN d8, DROP COLUMN d9
;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Da die Rückmeldung sofort kam, kann davon ausgegangen werden, dass diese Operation ebenfalls instant erfolgt. Leider habe ich auf die Schnelle nichts in der PostgreSQL Dokumentation dazu gefunden.&lt;/p&gt;
&lt;p&gt;An der Grösse hat sich nichts signifikant geändert, was bei einer Instant-Operation ja eigentlich auch zu erwarten ist. Dass sich jedoch nach dem &lt;code&gt;VACUUM FULL&lt;/code&gt; Befehl die Grösse nicht mehr geändert hat, hat doch ein wenig erstaunt:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; tab_siz | tab_siz_prtty | idx_siz | idx_siz_prtty | tab_and_idx_siz | tab_and_idx_siz_prtty | tot_rel_siz | tot_rel_siz_prtty
-----------+---------------+-----------+---------------+-----------------+-----------------------+-------------+-------------------
 742916096 | 709 MB | 376864768 | 359 MB | 1119780864 | 1068 MB | 1120010240 | 1068 MB

postgres=# VACUUM FULL tracking;

 tab_siz | tab_siz_prtty | idx_siz | idx_siz_prtty | tab_and_idx_siz | tab_and_idx_siz_prtty | tot_rel_siz | tot_rel_siz_prtty
-----------+---------------+-----------+---------------+-----------------+-----------------------+-------------+-------------------
 742916096 | 709 MB | 376864768 | 359 MB | 1119780864 | 1068 MB | 1119780864 | 1068 MB
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="bemerkungen"&gt;Bemerkungen&lt;/h2&gt;
&lt;p&gt;Locking bei PostgreSQL funktioniert wie folgt:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;VACUUM&lt;/code&gt; Nebenläufige DML Befehle sind möglich ähnlich wie beim MariaDB/MySQL &lt;code&gt;OPTIMIZE TABLE&lt;/code&gt; Befehl. Das Resultat ist aber nicht ganz das Selbe.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;VACUUM FULL&lt;/code&gt; verursacht einen &lt;code&gt;ACCESS EXCLUSIVE&lt;/code&gt; Lock. Ähnlich wie bei MariaDB/MySQL 5.5 und älter beim &lt;code&gt;OPTIMIZE TABLE&lt;/code&gt; Befehl. DML und &lt;code&gt;SELECT&lt;/code&gt; Befehle sind NICHT zulässig.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="quellen"&gt;Quellen&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://neon.com/postgresql/postgresql-administration/postgresql-database-indexes-table-size" target="_blank"&gt;How to Get Sizes of Database Objects in PostgreSQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/current/storage-file-layout.html" target="_blank"&gt;Database File Layout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/current/functions-admin.html" target="_blank"&gt;System Administration Functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/current/sql-cluster.html" target="_blank"&gt;CLUSTER&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/current/sql-vacuum.html" target="_blank"&gt;VACUUM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/current/explicit-locking.html" target="_blank"&gt;Explicit Locking&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="zusatzversuche"&gt;Zusatzversuche&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Statt &lt;code&gt;0.0&lt;/code&gt; wurde &lt;code&gt;NULL&lt;/code&gt; in die Spalten &lt;code&gt;d0&lt;/code&gt; - &lt;code&gt;d9&lt;/code&gt; abgefüllt. Die Tabelle blieb klein (&lt;code&gt;tot_rel_siz_prtty = 1068 MB&lt;/code&gt;). Es lohnt sich also auch bei PostgreSQL &lt;code&gt;NULL&lt;/code&gt; statt Dummy-Werte zu speichern.&lt;/li&gt;
&lt;li&gt;Die Spalten &lt;code&gt;d0&lt;/code&gt; - &lt;code&gt;d9&lt;/code&gt; wurden mit &lt;code&gt;DOUBLE PRECISION NOT NULL&lt;/code&gt; angelegt und die Werte &lt;code&gt;0.0&lt;/code&gt; abgefüllt. Keinen Effekt: Die Tabelle blieb gross (&lt;code&gt;tot_rel_siz_prtty = 2232 MB&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>Jemand löscht meine Shared Memory Segmente!</title><link>https://www.fromdual.com/de/blog/postgresql/geloeschte-postgresql-shared-memory-segmente/</link><pubDate>Sat, 07 Feb 2026 19:58:00 +0100</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/postgresql/geloeschte-postgresql-shared-memory-segmente/</guid><description>&lt;p&gt;Wenn wir mit PostgreSQL unter unserem &lt;a href="https://www.fromdual.com/myenv/"&gt;myEnv&lt;/a&gt; arbeiten, kriegen wir regelmässig Shared Memory Segment Fehler. Beispiel:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;psql: error: connection to server on socket &amp;#34;/tmp/.s.PGSQL.5433&amp;#34; failed:
FATAL: could not open shared memory segment &amp;#34;/PostgreSQL.4220847662&amp;#34;:
No such file or directory
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;oder wir sehen ähnliche Meldungen im PostgreSQL Error Log:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ERROR: could not open shared memory segment &amp;#34;/PostgreSQL.4220847662&amp;#34;:
No such file or directory
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Weil ich ein MariaDB/MySQL Admin bin, kenne ich mich nicht so gut mit Shared Memory Problemen aus (MariaDB/MySQL arbeitet nicht mit Shared Memory). Zum Glück hat uns eine Suche im Internet auf eine Fährte geführt (&lt;a href="https://www.postgresql.org/message-id/56A52018.1030001%40gmx.net" target="_blank" title="Re: systemd deletes shared memory segment in /dev/shm/Postgresql.NNNNNN"&gt;Quelle&lt;/a&gt;). Dort wird vermerkt:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The documentation of systemd states that this only happens for
non-system users. Can you check whether your &amp;ldquo;postgres&amp;rdquo; user (or
whatever you are using) is a system user?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="linux-system-user"&gt;Linux System User&lt;/h2&gt;
&lt;p&gt;Zuerst musste ich mal herausfinden was ein System User unter Linux überhaupt ist. Eine Antwort habe ich hier gefunden: &lt;a href="https://unix.stackexchange.com/questions/80277/whats-the-difference-between-a-normal-user-and-a-system-user" target="_blank"&gt;What&amp;rsquo;s the difference between a normal user and a system user?&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;That is not a technical difference but an organizational decision. E.g. it makes sense to show normal users in a login dialog (so that you can click them instead of having to type the user name) but it wouldn&amp;rsquo;t to show system accounts (the UIDs under which daemons and other automatic processes run) there.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Der LSB Standard meint dazu: &lt;a href="https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/uidrange.html" target="_blank"&gt;User ID Ranges&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The system User IDs from 0 to 99 should be statically allocated by the system, and shall not be created by applications.&lt;br&gt;
The system User IDs from 100 to 499 should be reserved for dynamic allocation by system administrators and post install scripts using useradd.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Auf meinem Ubuntu System sieht das wie folgt aus:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ grep SYS_ /etc/login.defs
#SYS_UID_MIN 100
#SYS_UID_MAX 999
#SYS_GID_MIN 100
#SYS_GID_MAX 999
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Für den PostgreSQL User würde das ja auch stimmen:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ id postgres
uid=130(postgres) gid=142(postgres) groups=142(postgres),116(ssl-cert)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Da aber die betroffene PostgreSQL Instanz unter unserm &lt;a href="https://www.fromdual.com/myenv/"&gt;myEnv&lt;/a&gt; läuft, ist das was anderes:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ id dba
uid=1001(dba) gid=1001(dba) groups=1001(dba)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Jetzt haben wir also zwei Möglickeiten:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Wir ändern &lt;code&gt;SYS_UID_MAX&lt;/code&gt; und &lt;code&gt;SYS_GID_MAX&lt;/code&gt; auf 1001 (einfache Variante).&lt;/li&gt;
&lt;li&gt;Oder wir ändern die &lt;code&gt;UID&lt;/code&gt; und die &lt;code&gt;GID&lt;/code&gt; unseres Users &lt;code&gt;dba&lt;/code&gt; auf unter 1000.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="einfache-variante-ändern-von-sys_uid_max-und-sys_gid_max-auf-1001"&gt;Einfache Variante: Ändern von &lt;code&gt;SYS_UID_MAX&lt;/code&gt; und &lt;code&gt;SYS_GID_MAX&lt;/code&gt; auf 1001&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# /etc/login.defs
SYS_UID_MAX 1001
SYS_GID_MAX 1001
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Zur Sicherheit wurde die Maschine neu gebootet. Das ha aber nicht geholfen: Nach kurzer Zeit treten wieder die selber Fehler auf.&lt;/p&gt;
&lt;h2 id="kompliziertere-variante-ändern-der-uid-von-1001-auf-990"&gt;Kompliziertere Variante: Ändern der &lt;code&gt;UID&lt;/code&gt; von 1001 auf 990&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ cat /etc/passwd
...
polkitd:x:997:997:User for polkitd:/:/usr/sbin/nologin
systemd-coredump:x:998:998:systemd Core Dumper:/:/usr/sbin/nologin
tomcat:x:999:999:Apache Tomcat:/:/sbin/nologin
oli:x:1000:1000:Oli Sennhauser,,,:/home/oli:/bin/bash
dba:x:1001:1001:DBA user:/home/dba:/bin/bash
...

$ id dba
uid=1001(dba) gid=1001(dba) groups=1001(dba)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dazu müssen alle Prozesse dieses Users gestoppt sein!&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ usermod --uid 990 dba
$ groupmod --gid 990 dba

$ find / -user 1001 -exec chown --no-dereference dba {} \;
$ find / -group 1001 -exec chgrp --no-dereference dba {} \;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Damit scheint das Problem gelöst zu sein!&lt;/p&gt;
&lt;h2 id="zusatzinformationen"&gt;Zusatzinformationen&lt;/h2&gt;
&lt;p&gt;Eine weiter oben erwähnte Quelle hat noch empfohlen, beim &lt;code&gt;systemd-logind&lt;/code&gt; folgende Parameter zu setzen:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# /etc/systemd/system/systemd-logind.service.d/override.conf
RemoveIPC=no
RuntimeDirectorySize=1%
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Diese Massnahme ist jetzt aber nicht mehr notwendig, das es auch ohne diese Änderung funktioniert.&lt;/p&gt;</description></item><item><title>CSV Dateien in die Datenbank laden</title><link>https://www.fromdual.com/de/blog/csv-dateien-in-die-datenbank-laden/</link><pubDate>Fri, 06 Feb 2026 17:12:00 +0100</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/csv-dateien-in-die-datenbank-laden/</guid><description>&lt;p&gt;Kürzlich wollte ich für eine persönliche kleine Spielerei die Wohnorte der Vereinsmitlieder meines Vereins auf einer Karte darstellen (&lt;a href="https://www.shinguz.ch/computer/gis/igoc-mitglieder/" target="_blank"&gt;IGOC Mitglieder&lt;/a&gt;). Die Adressen der Vereinsmitglieder waren mir bekannt. Nicht aber die Koordinaten der Wohnorte.&lt;/p&gt;
&lt;p&gt;Also machte ich mich auf die Suche nach den Koordinaten und wurde beim Bundesamt für Landestopografie (&lt;a href="https://www.swisstopo.admin.ch/de" target="_blank"&gt;swisstopo&lt;/a&gt;) fündig.&lt;/p&gt;
&lt;p&gt;Die Daten werden dort als CSV Datei zur Verfügung gestellt. Details hier: &lt;a href="https://www.shinguz.ch/computer/gis/schweizer-ortschafts-koordinaten/" target="_blank"&gt;Schweizer Ortschafts-Koordinaten&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Wie lädt man jetzt diese Daten in eine Datenbank?&lt;/p&gt;
&lt;h2 id="laden-der-daten-mit-mariadbmysql"&gt;Laden der Daten mit MariaDB/MySQL&lt;/h2&gt;
&lt;p&gt;MariaDB und MySQL haben dafür den Befehl &lt;code&gt;LOAD DATA INFILE&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; DROP TABLE IF EXISTS wgs84;

SQL&amp;gt; -- SET GLOBAL local_infile = ON; -- Only needed with MySQL

SQL&amp;gt; CREATE TABLE wgs84 (
 ortschaftsname VARCHAR(32)
, plz4 SMALLINT
, zusatzziffer SMALLINT
, zip_id SMALLINT UNSIGNED
, gemeindename VARCHAR(32)
, bfs_nr SMALLINT
, kantonskuerzel CHAR(2)
, adressenanteil varchar(8)
, e DOUBLE
, n DOUBLE
, sprache VARCHAR(8)
, validity VARCHAR(12)
);

SQL&amp;gt; -- TRUNCATE TABLE wgs84;

SQL&amp;gt; LOAD DATA LOCAL INFILE &amp;#39;/tmp/AMTOVZ_CSV_WGS84/AMTOVZ_CSV_WGS84.csv&amp;#39;
INTO TABLE wgs84
FIELDS TERMINATED BY &amp;#39;;&amp;#39;
LINES TERMINATED BY &amp;#39;\r\n&amp;#39;
IGNORE 1 LINES
;
Query OK, 5713 rows affected
Records: 5713 Deleted: 0 Skipped: 0 Warnings: 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Anschliessend kann man die Daten in der Datenbank abfragen:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; SELECT * FROM wgs84 ORDER BY ortschaftsname LIMIT 5;
+----------------+------+--------------+--------+--------------+--------+----------------+----------------+-------------------+--------------------+---------+------------+
| ortschaftsname | plz4 | zusatzziffer | zip_id | gemeindename | bfs_nr | kantonskuerzel | adressenanteil | e | n | sprache | validity |
+----------------+------+--------------+--------+--------------+--------+----------------+----------------+-------------------+--------------------+---------+------------+
| Aadorf | 8355 | 0 | 4672 | Aadorf | 4551 | TG | 96.802 % | 8.903193007810433 | 47.491079014637265 | de | 2008-07-01 |
| Aadorf | 8355 | 0 | 4672 | Elgg | 294 | ZH | 3.198 % | 8.89206766645808 | 47.4933781685032 | de | 2008-07-01 |
| Aarau | 5000 | 0 | 2913 | Aarau | 4001 | AG | 99.713 % | 8.048148371736266 | 47.38973523857376 | de | 2008-07-01 |
| Aarau | 5000 | 0 | 2913 | Suhr | 4012 | AG | 0.287 % | 8.059410934099922 | 47.383298214804334 | de | 2008-07-01 |
| Aarau | 5004 | 0 | 2932 | Aarau | 4001 | AG | 100 % | 8.060698546432551 | 47.400587704180744 | de | 2008-07-01 |
+----------------+------+--------------+--------+--------------+--------+----------------+----------------+-------------------+--------------------+---------+------------+
5 rows in set
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Bzw. etwas präziser:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; SELECT ortschaftsname AS city, plz4 AS city_code, e AS lon, n AS lat
 FROM wgs84 WHERE plz4 IN (8280, 4663, 6043);
+-------------+-----------+-------------------+--------------------+
| city | city_code | lon | lat |
+-------------+-----------+-------------------+--------------------+
| Aarburg | 4663 | 7.904271716719409 | 47.321443418782955 |
| Aarburg | 4663 | 7.889249714098425 | 47.313536073562474 |
| Aarburg | 4663 | 7.880309179095798 | 47.31255194439023 |
| Adligenswil | 6043 | 8.364849060491428 | 47.07037816052481 |
| Kreuzlingen | 8280 | 9.173740257895282 | 47.64491046067056 |
| Kreuzlingen | 8280 | 9.159171428030783 | 47.654149879509134 |
| Kreuzlingen | 8280 | 9.204470741840725 | 47.639949130372145 |
+-------------+-----------+-------------------+--------------------+
7 rows in set (0.003 sec)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Das ausmisten der Duplikate überlasse ich der geneigten Lesering oder dem geneigten Leser&amp;hellip; :-)&lt;/p&gt;
&lt;p&gt;Soweit so gut, jetzt zu den Feinheiten:&lt;/p&gt;
&lt;h3 id="unterschiede-zwischen-mariadb-und-mysql"&gt;Unterschiede zwischen MariaDB und MySQL&lt;/h3&gt;
&lt;p&gt;Das oben beschriebene Verfahren funktioniert einwandfrei bei MariaDB 11.4 und 11.8. Bei MySQL 8.4 gibt es kleine Unterschiede:&lt;/p&gt;
&lt;p&gt;Die erste Fehlermeldung, die das Laden verhindert ist diese:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ERROR 3948 (42000): Loading local data is disabled; this must be enabled on both the client and server sides
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Sie kann relativ einfach umgangen werden mit dem Befehl:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; SET GLOBAL local_infile = ON;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Beim nächsten Versuch wird man wie folgt scheitern:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ERROR 2068 (HY000): LOAD DATA LOCAL INFILE file request rejected due to restrictions on access.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dieses Problem kann gelöst werden, indem man den MySQL Client wie folgt startet:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ mysql --local-infile=1 --user=root test
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="quellen"&gt;Quellen&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;MariaDB: &lt;a href="https://mariadb.com/docs/server/reference/sql-statements/data-manipulation/inserting-loading-data/load-data-into-tables-or-index/load-data-infile" target="_blank"&gt;LOAD DATA INFILE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;MySQL: &lt;a href="https://dev.mysql.com/doc/refman/8.4/en/load-data.html" target="_blank"&gt;LOAD DATA Statement&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="laden-der-daten-mit-postgresql"&gt;Laden der Daten mit PostgreSQL&lt;/h2&gt;
&lt;p&gt;PostgreSQL hat hierfür den Befehl &lt;code&gt;COPY ... FROM&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# DROP TABLE IF EXISTS wgs84;

postgres=# CREATE TABLE wgs84 (
 ortschaftsname VARCHAR(32)
, plz4 SMALLINT
, zusatzziffer SMALLINT
, zip_id INT
, gemeindename VARCHAR(32)
, bfs_nr SMALLINT
, kantonskuerzel CHAR(2)
, adressenanteil varchar(8)
, e DOUBLE PRECISION
, n DOUBLE PRECISION
, sprache VARCHAR(8)
, validity VARCHAR(12)
);

postgres=# -- TRUNCATE TABLE wgs84;

postgres=# COPY wgs84
FROM &amp;#39;/tmp/AMTOVZ_CSV_WGS84/AMTOVZ_CSV_WGS84.csv&amp;#39;
DELIMITER &amp;#39;;&amp;#39;
CSV HEADER
;
COPY 5713
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Auch hier erhalten wir das Resultat wie erwartet in der für PostgreSQL üblichen Form:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# SELECT ortschaftsname AS city, plz4 AS city_code, e AS lon, n AS lat
 FROM wgs84 WHERE plz4 IN (8280, 4663, 6043);
 city | city_code | lon | lat
-------------+-----------+-------------------+--------------------
 Aarburg | 4663 | 7.904271716719409 | 47.321443418782955
 Aarburg | 4663 | 7.889249714098425 | 47.313536073562474
 Aarburg | 4663 | 7.880309179095798 | 47.31255194439023
 Adligenswil | 6043 | 8.36487538940682 | 47.07037794822416
 Kreuzlingen | 8280 | 9.173740257895282 | 47.64491046067056
 Kreuzlingen | 8280 | 9.159171428030783 | 47.654149879509134
 Kreuzlingen | 8280 | 9.204470741840725 | 47.639949130372145
(7 rows)
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="quellen-1"&gt;Quellen&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;PostgreSQL: &lt;a href="https://www.postgresql.org/docs/current/sql-copy.html" target="_blank"&gt;COPY&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="kleine-unterschiede-zwischen-mariadbmysql-und-postgresql"&gt;Kleine Unterschiede zwischen MariaDB/MySQL und PostgreSQL&lt;/h2&gt;
&lt;p&gt;Grundsätzlich lautet der Ladebefehl in den beiden Datenbankwelten gänzlich unterschiedlich.&lt;/p&gt;
&lt;p&gt;Bei MariaDB und PostgreSQL laufen die Befehle &amp;lsquo;out-of-the-box&amp;rsquo;. MySQL hat hier noch zwei zusätzliche Sicherheitshürden eingebaut.&lt;/p&gt;
&lt;p&gt;PostgreSQL kennt keine &lt;code&gt;UNSIGNED&lt;/code&gt; Integer Datentypen, daher muss der nächstgrössere Datentyp (&lt;code&gt;INT&lt;/code&gt;) verwendet werden, was ein klein wenig weniger platzsparender ist als bei MariaDB/MySQL.&lt;/p&gt;
&lt;h2 id="bemerkungen"&gt;Bemerkungen&lt;/h2&gt;
&lt;p&gt;Als wir den selben Test vor ein paar wenigen Tagen gemacht hatten, gab es noch eine Fehler beim Laden. Es scheint also, dass sich die Datenquelle ebenfalls minimal geändert hat&amp;hellip;&lt;/p&gt;
&lt;p&gt;Ich habe auf die Schnelle nicht herausgefunden, ob es zu diesen Lade-Befehlen einen SQL-Standard gibt und wenn ja, ob MariaDB/MySQL oder PostgreSQL hier Standard konform sind.&lt;/p&gt;
&lt;p&gt;Und natürlich gibt es noch andere Möglichkeiten, wie man seine CSV Daten in die Datenbank rein kriegt&amp;hellip;&lt;/p&gt;
&lt;p&gt;Die Tools &lt;code&gt;mariadb-import&lt;/code&gt;/&lt;code&gt;mysqlimport&lt;/code&gt; verwendet man, wenn man das von der Kommandozeile aus machen möchte. Die CSV Storage Engine kann ebenfalls dazu missbraucht werden (Details siehe &lt;a href="https://www.fromdual.com/blog/csv-storage-engine/"&gt;hier&lt;/a&gt;). Eine offiziell unterstütze Variante ist ist die MariaDB CONNECT Storage Engine mit dem CSV Type (siehe &lt;a href="https://mariadb.com/docs/server/server-usage/storage-engines/connect/connect-table-types/connect-csv-and-fmt-table-types" target="_blank"&gt;hier&lt;/a&gt;):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; INSTALL SONAME &amp;#39;ha_connect&amp;#39;;

SQL&amp;gt; CREATE TABLE wgs84_fdw
ENGINE = CONNECT
table_type = CSV
file_name=&amp;#39;/tmp/AMTOVZ_CSV_WGS84/AMTOVZ_CSV_WGS84.csv&amp;#39;
header = 1
sep_char = &amp;#39;;&amp;#39;
quoted = 0;

SQL&amp;gt; INSERT INTO wgs84 SELECT * FROM wgs84_fdw;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So wie es aussieht, wird die CONNECT Storage Engine von MariaDB leider nicht mehr weiter supportet?!? Und auch das Tool &lt;code&gt;mydumper&lt;/code&gt;/&lt;code&gt;myloader&lt;/code&gt; scheint mit CSV Dateien umgehen zu können.&lt;/p&gt;
&lt;p&gt;Und natürlich kann das ganze auch applikatorisch gelöst werden&amp;hellip;&lt;/p&gt;
&lt;p&gt;Bei PostgreSQL gibt es folgende Möglichkeiten:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# \copy wgs84 FROM &amp;#39;/tmp/AMTOVZ_CSV_WGS84/AMTOVZ_CSV_WGS84.csv&amp;#39; DELIMITER &amp;#39;;&amp;#39; CSV HEADER
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;dann von der Shell aus:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ psql --user=dba -c &amp;#34;\copy wgs84 FROM &amp;#39;/tmp/AMTOVZ_CSV_WGS84/AMTOVZ_CSV_WGS84.csv&amp;#39; DELIMITER &amp;#39;;&amp;#39; CSV HEADER&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Und die Variante über den Foreign Data Wrapper (FWD). Habe ich aber nicht ausprobiert:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;postgres=# CREATE EXTENSION postgres_fdw;

postgres=# CREATE SERVER foreign_server
 FOREIGN DATA WRAPPER postgres_fdw
 OPTIONS (
 datasource &amp;#39;CSV:/tmp/AMTOVZ_CSV_WGS84/AMTOVZ_CSV_WGS84.csv&amp;#39;,
 format &amp;#39;CSV&amp;#39;
 )
;

postgres=# CREATE USER MAPPING FOR local_user
 SERVER foreign_server
 OPTIONS (user &amp;#39;foreign_user&amp;#39;, password &amp;#39;password&amp;#39;)
;

postgres=# CREATE FOREIGN TABLE foreign_table (
 id integer NOT NULL,
 data text
)
 SERVER foreign_server
 OPTIONS (schema_name &amp;#39;some_schema&amp;#39;, table_name &amp;#39;some_table&amp;#39;)
;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="nachtrag"&gt;Nachtrag&lt;/h2&gt;
&lt;p&gt;Der MariaDB/MySQL Datentyp &lt;code&gt;DOUBLE&lt;/code&gt; heist in ProsgreSQL &lt;code&gt;DOUBLE PRECISION&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Attribute Promotion und Demotion im MariaDB Galera Cluster</title><link>https://www.fromdual.com/de/blog/attribute-promotion-und-demotion-im-mariadb-galera-cluster/</link><pubDate>Fri, 28 Nov 2025 15:32:49 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/attribute-promotion-und-demotion-im-mariadb-galera-cluster/</guid><description>&lt;p&gt;In der MariaDB Master/Slave Replikation gibt es ein Feature welches sich &lt;a href="https://mariadb.com/docs/server/ha-and-performance/standard-replication/replication-when-the-primary-and-replica-have-different-table-definitions" target="_blank"&gt;Attribute Promotion/Demotion&lt;/a&gt; nennt.&lt;/p&gt;
&lt;p&gt;Das kann man in etwa übersetzten mit Spalten Erweiterung/Einschränkung.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2 id="use-case-des-kunden"&gt;Use case des Kunden&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Bei früheren Schema-Änderungen hat er immer Probleme gekriegt was bis zum Totalausfall des Clusters für mehrere Stunden geführt hat.&lt;/p&gt;
&lt;p&gt;Der Kunde meint, dass niemals Spalten gelöscht und neue Spalten immer nur am Ende einer Tabelle hinzugefügt werden.&lt;/p&gt;
&lt;p&gt;Und das NICHT sichergestellt werden kann, dass wärend des Rolling-Schema-Upgrades keine schreibenden Verbindungen mehr existieren.&lt;/p&gt;
&lt;p&gt;Verwendet wird das PHP ORM-Framework &lt;a href="https://www.doctrine-project.org/" target="_blank"&gt;Doctrine&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="was-sagt-die-mariadb-dokumentation-dazu"&gt;Was sagt die MariaDB Dokumentation dazu?&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Quelle: &lt;a href="https://mariadb.com/docs/galera-cluster/galera-management/general-operations/performing-schema-upgrades-in-galera-cluster#rolling-schema-upgrade-rsu" target="_blank"&gt;Rolling Schema Upgrade (RSU)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Beim Replizieren mit unterschiedlichen Tabellenstrukturen steht dazu nur Allgemeines zur Replikation aber nichts Spezifisches zu Galera (ob es geht oder nicht):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Quelle: &lt;a href="https://mariadb.com/docs/server/ha-and-performance/standard-replication/replication-when-the-primary-and-replica-have-different-table-definitions" target="_blank"&gt;Replication When the Primary and Replica Have Different Table Definitions&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Für das Attribute Promotion/Demotion Feature gibt es einen speziellen MariaDB Server Konfigurationsparameter der das verhalten steuert: &lt;code&gt;slave_type_conversions&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Wenn man hier zwischen den Zeilen liest, könnte das auch für Galera Cluster funktionieren:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Determines the type conversion mode on the replica when using row-based replication, including replications in MariaDB Galera cluster.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Quelle: &lt;a href="https://mariadb.com/docs/server/ha-and-performance/standard-replication/replication-and-binary-log-system-variables#slave_type_conversions" target="_blank"&gt;&lt;code&gt;slave_type_conversions&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="testplanung"&gt;Testplanung&lt;/h2&gt;
&lt;p&gt;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&amp;hellip;&lt;/p&gt;
&lt;p&gt;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!&lt;/p&gt;
&lt;p&gt;Da die Situation nicht klar ist und die Dokumentation nichts eindeutiges her gibt wurde getestet.&lt;/p&gt;
&lt;p&gt;Um mögliche bereits gefixte MariaDB Bugs zu vermeiden wurde die neuste MariaDB 11.8.5 LTS Version verwendet.&lt;/p&gt;
&lt;p&gt;An speziellen Datenbank Konfigurationsparameter wurde verwendet:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;slave_type_conversions = 'ALL_NON_LOSSY,ALL_LOSSY'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unsere Test-Tabelle sieht wie üblich wie folgt aus:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;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`)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Und Testdaten wurden wie folgt generiert:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INSERT INTO test VALUES (NULL, 'Some data to fill table up', NOW());
... -- 9 x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Befehle zur Überwachung der Tests:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SHOW GLOBAL VARIABLES LIKE 'slave_type_conversions';
SQL&amp;gt; SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment';
SQL&amp;gt; SHOW CREATE TABLE test&amp;lt;br&amp;gt;G
SQL&amp;gt; CHECKSUM TABLE test;
SQL&amp;gt; SELECT * FROM test;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="testen"&gt;Testen&lt;/h2&gt;
&lt;h3 id="test-1-attribute-promotion-mit-dml-remote-auf-node-c"&gt;Test 1: Attribute Promotion mit DML remote (auf Node C)&lt;/h3&gt;
&lt;p&gt;Dies ist der einfachste Fall: Eine Spalte wird hinzugefügt und ein Default-Wert festgelegt. Änderungen auf der Tabelle erfolgen auf einem anderen Knoten:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nodeA&amp;gt; SET SESSION wsrep_OSU_method = 'RSU';
nodeA&amp;gt; ALTER TABLE test ADD COLUMN c1 VARCHAR(64) NOT NULL DEFAULT 'foo';
nodeA&amp;gt; SET SESSION wsrep_OSU_method = 'TOI';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MariaDB Error Log Datei:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[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
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dann die weiteren Befehle:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nodeC&amp;gt; INSERT INTO test VALUES (NULL, 'Some data to fill table up', NOW());
nodeC&amp;gt; UPDATE test SET data = 'Some data changed' WHERE id = 10;
nodeC&amp;gt; DELETE FROM test WHERE id = 19;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anschliessend wurde der &lt;code&gt;ALTER TABLE&lt;/code&gt; Befehl auf Knoten 2 und 3 ausgeführt.&lt;/p&gt;
&lt;p&gt;Alle 3 Operationen haben einwandfrei funktioniert. Der Cluster ist noch voll funktionsfähig. Anschliessend:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; ALTER TABLE test DROP COLUMN c1;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;um das System für weitere Tests wieder in seinen Anfangszustand zurück zu überführen.&lt;/p&gt;
&lt;h3 id="test-2-attribute-promotion-mit-dml-remote-auf-node-b"&gt;Test 2: Attribute Promotion mit DML remote (auf Node B)&lt;/h3&gt;
&lt;p&gt;Wenn man das gleiche Experiment auf Knoten B macht, fliegt einem der Cluster um die Ohren!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nodeB&amp;gt; INSERT INTO test VALUES (NULL, 'Some data to fill table up', NOW());

nodeC&amp;gt; SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment';
+---------------------------+--------------+
| Variable_name | Value |
+---------------------------+--------------+
| wsrep_local_state_comment | Inconsistent |
+---------------------------+--------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MariaDB Error Log Datei:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Note] WSREP: Member 1(Node A) initiates vote on 22db9ea1-cb7b-11f0-b26e-6fbce72757f9:45,dbc9a6ea898b6a29: Got error 171 &amp;quot;The event was corrupt, leading to illegal data being read&amp;quot; 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 &amp;quot;The event was corrupt, leading to illegal data being read&amp;quot; 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)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Das Ganze wurde in verschiedenen Varianten getestet und der Knoten wird mal inkonsistent und mal nicht.&lt;/p&gt;
&lt;p&gt;Der inkonsistente Knoten B wird über einen erzwungenen SST wieder in den Cluster synchronisiert.&lt;/p&gt;
&lt;h3 id="test-3-attribute-promotion-mit-dml-remote-auf-node-c"&gt;Test 3: Attribute Promotion mit DML remote (auf Node C)&lt;/h3&gt;
&lt;p&gt;Dieser Fall ist etwas heikler, da kein Default-Wert vorgegeben wird.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nodeA&amp;gt; SET SESSION wsrep_OSU_method = 'RSU';
nodeA&amp;gt; ALTER TABLE test ADD COLUMN c1 VARCHAR(64) NOT NULL; -- DEFAULT ''
nodeA&amp;gt; SET SESSION wsrep_OSU_method = 'TOI';

nodeC&amp;gt; INSERT INTO test VALUES (NULL, 'Some data to fill table up', NOW());
nodeC&amp;gt; UPDATE test SET data = 'Some data changed' WHERE id = 13;
nodeC&amp;gt; DELETE FROM test WHERE id = 22;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anschliessend wurde der &lt;code&gt;ALTER TABLE&lt;/code&gt; Befehl auf Knoten 2 und 3 ausgeführt.&lt;/p&gt;
&lt;p&gt;Alle 3 Operationen haben einwandfrei funktioniert. Der Cluster ist noch voll funktionsfähig.&lt;/p&gt;
&lt;h3 id="test-4-attribute-promotion-mit-dml-remote-auf-node-b"&gt;Test 4: Attribute Promotion mit DML remote (auf Node B)&lt;/h3&gt;
&lt;p&gt;Gleicher Test aber der Befehl &lt;code&gt;ALTER TABLE ADD COLUMN&lt;/code&gt; wird auf Knoten A und der DML Befehl auf Knoten B ausgeführt.&lt;/p&gt;
&lt;p&gt;Knoten A und C werden &amp;ldquo;Inconsistent&amp;rdquo; und Knoten B ist noch &amp;ldquo;Synced&amp;rdquo;.&lt;/p&gt;
&lt;h3 id="test-5-attribute-promotion-mit-dml-lokal-auf-node-a"&gt;Test 5: Attribute Promotion mit DML lokal (auf Node A)&lt;/h3&gt;
&lt;p&gt;Analoger Test aber der DML Befehl wird lokal, auf dem selben Knoten wie der DDL Befehl, ausgeführt.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nodeA&amp;gt; SET SESSION wsrep_OSU_method = 'RSU';
nodeA&amp;gt; ALTER TABLE test ADD COLUMN c1 VARCHAR(64) NOT NULL; -- DEFAULT ''
nodeA&amp;gt; SET SESSION wsrep_OSU_method = 'TOI';

nodeA&amp;gt; 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&amp;gt; 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&amp;gt; INSERT INTO test (id, data, ts, c1) VALUES (NULL, 'Some data to fill table up', NOW(), '');

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

nodeA&amp;gt; SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment';
+---------------------------+--------------+
| Variable_name | Value |
+---------------------------+--------------+
| wsrep_local_state_comment | Inconsistent |
+---------------------------+--------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cluster Knoten wurde inkonsistent. Interessanter weise hat bei weiteren Test das Ganze plötzlich geklappt! Also vollständig unvorhersagbar&amp;hellip;&lt;/p&gt;
&lt;h3 id="test-6-ddl-auf-2-knoten"&gt;Test 6: DDL auf 2 Knoten&lt;/h3&gt;
&lt;p&gt;Neue Fragestellung: Was passiert nachdem der DDL Befehl auf 2 Knoten ausgeführt wurde und dann DML Befehle auftreten?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nodeA&amp;gt; SET SESSION wsrep_OSU_method = 'RSU';
nodeA&amp;gt; ALTER TABLE test ADD COLUMN c1 VARCHAR(64) NOT NULL DEFAULT 'foo';
nodeA&amp;gt; SET SESSION wsrep_OSU_method = 'TOI';

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

nodeC&amp;gt; INSERT INTO test (id, data, ts) VALUES (NULL, 'Some data to fill table up', NOW());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;klappt, aber:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nodeB&amp;gt; INSERT INTO test (id, data, ts) VALUES (NULL, 'Some data to fill table up', NOW());

root@localhost [test]&amp;gt; SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment';
+---------------------------+--------------+
| Variable_name | Value |
+---------------------------+--------------+
| wsrep_local_state_comment | Inconsistent |
+---------------------------+--------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="test-7-update-und-delete-befehle-vom-selben-knoten"&gt;Test 7: UPDATE und DELETE Befehle vom selben Knoten&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;nodeA&amp;gt; SET SESSION wsrep_OSU_method = 'RSU';
nodeA&amp;gt; ALTER TABLE test ADD COLUMN c1 VARCHAR(64) NOT NULL DEFAULT 'foo';
nodeA&amp;gt; SET SESSION wsrep_OSU_method = 'TOI';

nodeA&amp;gt; UPDATE test SET data = 'Some data changed' WHERE id = 16;

nodeA&amp;gt; SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment';
+---------------------------+--------------+
| Variable_name | Value |
+---------------------------+--------------+
| wsrep_local_state_comment | Inconsistent |
+---------------------------+--------------+

nodeA&amp;gt; DELETE FROM test WHERE id = 25;
nodeA&amp;gt; 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'
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Prozessliste von Knoten C:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nodeC&amp;gt; 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 |
+----+-------------+-----------+------+---------+------+-------------------------+----------------------------------------------------------------------------------------+----------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Knoten C ist aber immer noch Synced:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nodeC&amp;gt; SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment';
+---------------------------+--------+
| Variable_name | Value |
+---------------------------+--------+
| wsrep_local_state_comment | Synced |
+---------------------------+--------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Herunterfahren des Knotens für zu folgender Fehlermeldung:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nodeC&amp;gt; SQL&amp;gt; shutdown;
ERROR 1047 (08S01): WSREP has not yet prepared node for application use
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anschliessend war der ganze Cluster nicht mehr nutzbar. Und musste neu gestartet werden (Bootstrap).&lt;/p&gt;
&lt;h2 id="zusammenfassung"&gt;Zusammenfassung&lt;/h2&gt;
&lt;h2&gt;&lt;/h2&gt;
&lt;p&gt;Wir haben zu dieser Thematik einen Bug bei MariaDB eröffnet: &lt;a href="https://jira.mariadb.org/browse/MDEV-38215" target="_blank"&gt;Attribute Promotion/Demotion in Galera Cluster&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Weitere Tests wurden vorerst nicht durchgeführt, da dieses Feature in der getesteten Version zu instabil ist und &lt;code&gt;DROP COLUMN&lt;/code&gt; kein Use Case unseres Kunden ist.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fazit&lt;/strong&gt;: 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 (&lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;) auf den zu ändernden Tabellen mit der untersuchten Version NICHT tun!&lt;/p&gt;
&lt;p&gt;Weitere Quellen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://galeracluster.com/documentation/html_docs_2023/_sources/documentation/inconsistency-voting.rst.txt" target="_blank"&gt;Galera Cluster Inconsistency Voting protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.percona.com/blog/inconsistent-voting-in-percona-xtradb-cluster/" target="_blank"&gt;Inconsistent Voting in Percona XtraDB Cluster&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>MariaDB Honeypot</title><link>https://www.fromdual.com/de/blog/mariadb-honeypot/</link><pubDate>Tue, 04 Mar 2025 16:27:44 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/mariadb-honeypot/</guid><description>&lt;p&gt;Bei unseren &lt;a href="https://www.fromdual.com/de/mysql-mariadb-fuer-fortgeschrittene-schulung"&gt;MariaDB für Fortgeschrittene Schulungen&lt;/a&gt;, welche wir in etwa alle zwei Monate halten, verwenden wir Maschinen, welche mit einer öffentlichen IP-Adresse direkt dem Internet ausgesetzt sind.
&lt;strong&gt;Achtung&lt;/strong&gt;: Man sollte NIE eine Datenbank ungeschützt direkt dem Internet aussetzen!
Typischerweise dauert es keine 72 Stunden (3 Tage) bis wir ersten Zugriffsversuchen von aussen ausgesetzt sind.&lt;/p&gt;
&lt;p&gt;Dies sieht dann im MariaDB Error Log in etwa wie folgt aus:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Warning] Aborted connection 22939 to db: 'unconnected' user: 'unauthenticated' host: '118.193.58.125' (This connection closed normally without authentication)
[Warning] Aborted connection 22940 to db: 'unconnected' user: 'unauthenticated' host: '118.193.58.125' (This connection closed normally without authentication)
[Warning] Access denied for user ''@'118.193.58.125' (using password: NO)
[Warning] Access denied for user 'root'@'118.193.58.125' (using password: YES)
[Warning] Access denied for user 'root'@'118.193.58.125' (using password: YES)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Zuerst wird einmal probiert, ob da eine Datenbank lauscht und wie diese antwortet. Anschliessend wird auf verschiedene Arten versucht in die Datenbank einzudringen. So wie es aussieht, gibt es verschiedene Beprobungs- und Angriffsmuster. Der Anonymous User (&lt;code&gt;''@'%'&lt;/code&gt;) und &lt;code&gt;'root'@'%'&lt;/code&gt; werden mit und ohne Passwort geprüft.
Ob noch weitere User probiert werden, muss sich noch über einen längeren Beobachtungszeitraum zeigen.&lt;/p&gt;
&lt;p&gt;Und so sieht das aus Sicht des MariaDB General Query Logs aus:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;287793 Connect root@196.251.91.77 on using TCP/IP
287793 Connect Access denied for user 'root'@'196.251.91.77' (using password: NO)
287794 Connect root@196.251.91.77 on using TCP/IP
287794 Connect Access denied for user 'root'@'196.251.91.77' (using password: YES)
287796 Connect root@196.251.91.77 on using TCP/IP
287796 Connect Access denied for user 'root'@'196.251.91.77' (using password: YES)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="vorbereiten-des-honeypots"&gt;Vorbereiten des Honeypots&lt;/h2&gt;
&lt;p&gt;Nach Beendigung der Schulung brauchen wir die Maschinen nicht mehr, sie werden abgebaut. Daher hat es mich zum Schluss gereizt, auszuprobieren, was passiert, wenn ein Zugriffsversuch erfolgreich ist. Um das zu testen wurde der User &lt;code&gt;'root'@'%'&lt;/code&gt; ohne Passwort wie folgt angelegt und ihm alle Rechte auf das &lt;code&gt;test&lt;/code&gt; Schema gegeben:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; CREATE USER 'root'@'%';
SQL&amp;gt; GRANT ALL ON test.* TO 'root'@'%';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;und sowohl das MariaDB General Log eingeschaltet als auch das MariaDB Error Log gesprächiger gemacht:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# my.cnf

[server]

general_log_file = /var/log/mysql/general.log
general_log = on
log_error = /var/log/mysql/error.log
log_warnings = 9 # too much!
bind_address = *
skip_name_resolve = on # How much info do we loose?
# skip_grant_tables
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anschliessend hiess es nur noch sich auf die Lauer legen und abwarten was passiert&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TODO&lt;/strong&gt;: Es wäre hier noch spannend zu sehen, mit welchen Passwörtern versucht wird, in die Datenbank zu gelangen. Mal schauen, ob man das rausfinden kann ohne den MariaDB Quellcode zu patchen&amp;hellip;? Ev. sollte man zusätzlich noch mit &lt;code&gt;wireshark&lt;/code&gt; oder &lt;code&gt;tcpdump&lt;/code&gt; versuchen, die Passwörter ersichtlich zu machen.&lt;/p&gt;
&lt;h2 id="die-erste-fliege-schwirrt-an"&gt;Die erste Fliege schwirrt an&lt;/h2&gt;
&lt;p&gt;Dann scheint die erste Fliege (aus Amsterdam, Niederlande) zum Honigtopf zu gelangen. Zuerst haben wir eine Warnung, dass der Reverse-Lookup der IP Adresse scheitert:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Warning] Hostname 'no-reverse-dns-configured.com' does not resolve to '94.102.49.155'.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Aber dann geht sie rein. Tut aber nichts Spannendes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;250228 16:06:32 1222 Connect root@94.102.49.155 on using TCP/IP
 1222 Query SHOW databases
 1222 Query SHOW tables IN information_schema
 1222 Query SHOW tables IN test
 1222 Query SHOW VARIABLES
250228 16:06:33 1222 Quit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Was kriegt die Fliege zu sehen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SHOW databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| test |
+--------------------+

SQL&amp;gt; SHOW tables IN information_schema;
+-------------------------------+
| Tables_in_information_schema |
+-------------------------------+
| ALL_PLUGINS |
| APPLICABLE_ROLES |
| CHARACTER_SETS |
| ... |
| INNODB_TABLESPACES_ENCRYPTION |
| INNODB_LOCK_WAITS |
| THREAD_POOL_STATS |
+-------------------------------+
82 rows in set (0.000 sec)

SQL&amp;gt; SHOW tables IN test;
+----------------+
| Tables_in_test |
+----------------+
| test |
+----------------+

SQL&amp;gt; SHOW VARIABLES;
+----------------------------------------------------------+------------+
| Variable_name | Value |
+----------------------------------------------------------+------------+
| allow_suspicious_udfs | OFF |
| alter_algorithm | DEFAULT |
| analyze_sample_percentage | 100.000000 |
| ... | |
| wsrep_sync_wait | 0 |
| wsrep_trx_fragment_size | 0 |
| wsrep_trx_fragment_unit | bytes |
+----------------------------------------------------------+------------+
686 rows in set (0.003 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Möglicherweise ist hier der Algorithmus des Angriffs schlau genug und stellt fest, dass sich ein Angriff nicht lohnt?&lt;/p&gt;
&lt;p&gt;Dann warten wir auf die nächste Fliege&amp;hellip;&lt;/p&gt;
&lt;h2 id="die-nächste-fliege-kommt-geflogen"&gt;Die nächste Fliege kommt geflogen&lt;/h2&gt;
&lt;p&gt;Und da sieht man sie auch schon (diesmal aus USA, Minneapolis) im MariaDB Error Log:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Warning] Hostname 'undefined.hostname.localhost' does not resolve to '196.251.83.136'.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Jetzt wird es spannen, im MariaDB General Query Log zu sehen, was genau passiert?&lt;/p&gt;
&lt;p&gt;Zuerst wird eine Verbindung aufgebaut und offen gehalten (Fuss in der Angel behalten?):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;250228 16:06:55 67 Connect root@196.251.83.136 on using TCP/IP
 67 Query SET AUTOCOMMIT=0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dann, 15 Sekunden später wird eine Verbindung auf und wieder zu gemacht (sicherstellen, dass man auch wirklich erfolgreich war?):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;250228 16:07:10 68 Connect root@196.251.83.136 on using TCP/IP
 68 Query SET AUTOCOMMIT=0
 68 Quit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sofort danach werden alle Schemas abgefragt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 69 Connect root@196.251.83.136 on using TCP/IP
 69 Query SET AUTOCOMMIT=0
 69 Query SHOW DATABASES
 69 Quit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dann wird wieder eine Verbindung auf- und wieder abgebaut:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 70 Connect root@196.251.83.136 on using TCP/IP
 70 Query SET AUTOCOMMIT=0
 70 Quit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anschliessend wird geprüft, wie gross das Schema ist. Wahrscheinlich um sicherzustellen, dass es nicht zu gross ist? Dann die Tabellen abgefragt. Die Verbindung wird offen gehalten, es wird 2 Sekunden später damit weitergearbeitet.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 71 Connect root@196.251.83.136 on using TCP/IP
 71 Query SET AUTOCOMMIT=0
 71 Query SELECT SUM(data_length + index_length) FROM information_schema.tables WHERE table_schema = 'test'
 71 Query USE `test`
 71 Query SHOW tables
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anschliessend wir ein &lt;code&gt;mariadb-dump&lt;/code&gt; Imitat? mit Version ≥ 10.1 gestartet um &lt;strong&gt;die ersten 10!!! Zeilen der Tabelle&lt;/strong&gt; &lt;code&gt;aaa_payload&lt;/code&gt; im Schema &lt;code&gt;test&lt;/code&gt; zu dumpen.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;250228 16:07:11 72 Connect root@196.251.83.136 on using TCP/IP
 72 Query /*!40100 SET @@SQL_MODE='' */
 72 Query /*!100100 SET @@MAX_STATEMENT_TIME=0.000000 */
 72 Query /*!100100 SET WAIT_TIMEOUT=DEFAULT */
 72 Query set optimizer_switch='semijoin=off'
 72 Query SELECT LOGFILE_GROUP_NAME, FILE_NAME, TOTAL_EXTENTS, INITIAL_SIZE, ENGINE, EXTRA FROM INFORMATION_SCHEMA.FILES WHERE FILE_TYPE = 'UNDO LOG' AND FILE_NAME IS NOT NULL AND LOGFILE_GROUP_NAME IS NOT NULL AND LOGFILE_GROUP_NAME IN (SELECT DISTINCT LOGFILE_GROUP_NAME FROM INFORMATION_SCHEMA.FILES WHERE FILE_TYPE = 'DATAFILE' AND TABLESPACE_NAME IN (SELECT DISTINCT TABLESPACE_NAME FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_SCHEMA='test' AND TABLE_NAME IN ('aaa_payload'))) GROUP BY LOGFILE_GROUP_NAME, FILE_NAME, ENGINE, TOTAL_EXTENTS, INITIAL_SIZE ORDER BY LOGFILE_GROUP_NAME
 72 Query SELECT DISTINCT TABLESPACE_NAME, FILE_NAME, LOGFILE_GROUP_NAME, EXTENT_SIZE, INITIAL_SIZE, ENGINE FROM INFORMATION_SCHEMA.FILES WHERE FILE_TYPE = 'DATAFILE' AND TABLESPACE_NAME IN (SELECT DISTINCT TABLESPACE_NAME FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_SCHEMA='test' AND TABLE_NAME IN ('aaa_payload')) ORDER BY TABLESPACE_NAME, LOGFILE_GROUP_NAME
 72 Query set optimizer_switch=default
 72 Init DB test
 72 Query SHOW VARIABLES LIKE 'lower_case_table_names'
 72 Query SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = DATABASE() AND table_name = 'aaa_payload'
 72 Query SELECT engine, table_type FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = DATABASE() AND table_name = 'aaa_payload'
 72 Query SELECT engine, table_type FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = DATABASE() AND table_name = 'aaa_payload'
 72 Query SELECT engine, table_type FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = DATABASE() AND table_name = 'aaa_payload'
 72 Query SET SQL_QUOTE_SHOW_CREATE=1
 72 Query show fields from `aaa_payload`
 72 Query SELECT /*!40001 SQL_NO_CACHE */ `id`, `name` FROM `aaa_payload` WHERE 1 LIMIT 10
 72 Quit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dann das selbe nochmal mit einer Tabelle namens &lt;code&gt;bbb_payload&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 73 Connect root@196.251.83.136 on using TCP/IP
 73 Query /*!40100 SET @@SQL_MODE='' */
 73 Query /*!100100 SET @@MAX_STATEMENT_TIME=0.000000 */
 73 Query /*!100100 SET WAIT_TIMEOUT=DEFAULT */
 73 Query set optimizer_switch='semijoin=off'
 73 Query SELECT LOGFILE_GROUP_NAME, FILE_NAME, TOTAL_EXTENTS, INITIAL_SIZE, ENGINE, EXTRA FROM INFORMATION_SCHEMA.FILES WHERE FILE_TYPE = 'UNDO LOG' AND FILE_NAME IS NOT NULL AND LOGFILE_GROUP_NAME IS NOT NULL AND LOGFILE_GROUP_NAME IN (SELECT DISTINCT LOGFILE_GROUP_NAME FROM INFORMATION_SCHEMA.FILES WHERE FILE_TYPE = 'DATAFILE' AND TABLESPACE_NAME IN (SELECT DISTINCT TABLESPACE_NAME FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_SCHEMA='test' AND TABLE_NAME IN ('bbb_payload'))) GROUP BY LOGFILE_GROUP_NAME, FILE_NAME, ENGINE, TOTAL_EXTENTS, INITIAL_SIZE ORDER BY LOGFILE_GROUP_NAME
 73 Query SELECT DISTINCT TABLESPACE_NAME, FILE_NAME, LOGFILE_GROUP_NAME, EXTENT_SIZE, INITIAL_SIZE, ENGINE FROM INFORMATION_SCHEMA.FILES WHERE FILE_TYPE = 'DATAFILE' AND TABLESPACE_NAME IN (SELECT DISTINCT TABLESPACE_NAME FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_SCHEMA='test' AND TABLE_NAME IN ('bbb_payload')) ORDER BY TABLESPACE_NAME, LOGFILE_GROUP_NAME
 73 Query set optimizer_switch=default
 73 Init DB test
 73 Query SHOW VARIABLES LIKE 'lower_case_table_names'
250228 16:07:12 73 Query SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = DATABASE() AND table_name = 'bbb_payload'
 73 Query SELECT engine, table_type FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = DATABASE() AND table_name = 'bbb_payload'
 73 Query SELECT engine, table_type FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = DATABASE() AND table_name = 'bbb_payload'
 73 Query SELECT engine, table_type FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = DATABASE() AND table_name = 'bbb_payload'
 73 Query SET SQL_QUOTE_SHOW_CREATE=1
 73 Query show fields from `bbb_payload`
 73 Query SELECT /*!40001 SQL_NO_CACHE */ `id`, `name` FROM `bbb_payload` WHERE 1 LIMIT 10
 73 Quit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Und schiesslich unsere Haupttabelle &lt;code&gt;test&lt;/code&gt; aber auch hier &lt;strong&gt;nur die ersten 10!!! Zeilen&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 74 Connect root@196.251.83.136 on using TCP/IP
 74 Query /*!40100 SET @@SQL_MODE='' */
 74 Query /*!100100 SET @@MAX_STATEMENT_TIME=0.000000 */
 74 Query /*!100100 SET WAIT_TIMEOUT=DEFAULT */
 74 Query set optimizer_switch='semijoin=off'
 74 Query SELECT LOGFILE_GROUP_NAME, FILE_NAME, TOTAL_EXTENTS, INITIAL_SIZE, ENGINE, EXTRA FROM INFORMATION_SCHEMA.FILES WHERE FILE_TYPE = 'UNDO LOG' AND FILE_NAME IS NOT NULL AND LOGFILE_GROUP_NAME IS NOT NULL AND LOGFILE_GROUP_NAME IN (SELECT DISTINCT LOGFILE_GROUP_NAME FROM INFORMATION_SCHEMA.FILES WHERE FILE_TYPE = 'DATAFILE' AND TABLESPACE_NAME IN (SELECT DISTINCT TABLESPACE_NAME FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_SCHEMA='test' AND TABLE_NAME IN ('test'))) GROUP BY LOGFILE_GROUP_NAME, FILE_NAME, ENGINE, TOTAL_EXTENTS, INITIAL_SIZE ORDER BY LOGFILE_GROUP_NAME
 74 Query SELECT DISTINCT TABLESPACE_NAME, FILE_NAME, LOGFILE_GROUP_NAME, EXTENT_SIZE, INITIAL_SIZE, ENGINE FROM INFORMATION_SCHEMA.FILES WHERE FILE_TYPE = 'DATAFILE' AND TABLESPACE_NAME IN (SELECT DISTINCT TABLESPACE_NAME FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_SCHEMA='test' AND TABLE_NAME IN ('test')) ORDER BY TABLESPACE_NAME, LOGFILE_GROUP_NAME
 74 Query set optimizer_switch=default
 74 Init DB test
 74 Query SHOW VARIABLES LIKE 'lower_case_table_names'
 74 Query SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = DATABASE() AND table_name = 'test'
 74 Query SELECT engine, table_type FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = DATABASE() AND table_name = 'test'
 74 Query SELECT engine, table_type FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = DATABASE() AND table_name = 'test'
 74 Query SELECT engine, table_type FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = DATABASE() AND table_name = 'test'
 74 Query SET SQL_QUOTE_SHOW_CREATE=1
 74 Query show fields from `test`
 74 Query SELECT /*!40001 SQL_NO_CACHE */ `id`, `data`, `ts` FROM `test` WHERE 1 LIMIT 10
 74 Quit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dann geht es wieder mit Connection 71 weiter: Die 3 Tabellen &lt;code&gt;test&lt;/code&gt;, &lt;code&gt;bbb_payload&lt;/code&gt; und &lt;code&gt;aaa_payload&lt;/code&gt; werden gelöscht:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 71 Query USE `test`
 71 Query SHOW TABLES
 71 Query SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME = 'test' AND REFERENCED_TABLE_NAME IS NOT NULL
 71 Query SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME = 'bbb_payload' AND REFERENCED_TABLE_NAME IS NOT NULL
250228 16:07:13 71 Query SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME = 'aaa_payload' AND REFERENCED_TABLE_NAME IS NOT NULL
 71 Query DROP TABLE `test`
 71 Query DROP TABLE `bbb_payload`
 71 Query DROP TABLE `aaa_payload`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dann wird eine Tabelle namens &lt;code&gt;RECOVER_YOUR_DATA&lt;/code&gt; erstellt und mit dem Text versehen, wie man das Lösegeld bezahlen soll. Die Höhe beträgt 0.0101 Bitcoin was zur Zeit ca. EUR 863.40 entspricht. Wohlgemerkt, sie haben nur die ersten 10 Zeilen gedumpt und anschliessend gelöscht!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 71 Query CREATE TABLE IF NOT EXISTS RECOVER_YOUR_DATA (text VARCHAR(255))
 71 Query INSERT INTO RECOVER_YOUR_DATA (text) VALUES ('All your data is backed up. You must pay 0.0101 BTC to bc1qm0v2r0mmx3py3h7fzkerd9a6rzdrpw5afqacen In 48 hours, your data will be publicly disclosed and deleted. (more information: go to https://is.gd/yotuqu)')
 71 Query INSERT INTO RECOVER_YOUR_DATA (text) VALUES ('After payment send mail to us: rambler+2r8qm@onionmail.org and we will provide a link for you to download your data. Your DBCODE is: 2R8QM')
 71 Query COMMIT
 71 Quit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hinter dem Link ist folgender Text zu finden:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Please take note of the following:

We are aware that you have accessed this guide.
This offer stands for 24hs

After 72 hours, we cannot guarantee that we will be able to send the data to you.
The only way to recover your data is by making the payment. We will not provide the data for free.

Data leakage is a serious legal violation. Rest assured, the incident will remain confidential, and your data is protected.

After your payment is completed, all data downloaded from you will be deleted from our servers, government agencies, competitors, contractors, and local media are currently unaware of the incident.

If you pay we guarantee that your data will not be sold on Darkweb resources and will not be used to attack your company, employees, or counterparties in the future and the full database dump will be sent to you.

If you have not contacted us within two days from the time of the incident, we will consider the transaction incomplete. Your data will then be sent to any interested parties. This is your responsibility.

If you are a system administrator or programmer and your boss is unaware of this incident, we will contact them after 48 hours.

If you are unable to contact us using the provided email, please visit https://getsession.org/ and download the Session Messenger. Add us using the following Session ID for a smoother conversation and better negotiation:
Session ID:
05a5ba6491a15908207cce6e257b3316cd11cb2575f75194d3c59c37de68eaf55a

After payment, please provide us with a screenshot or proof of payment.
Once the payment is confirmed, we will send you a download link for your data. We will also delete our copy of the data.

IMPORTANT!!
DO NOT FORGET TO INCLUDE YOUR DBCODE IN YOUR MAIL OR MESSAGE YOU SEND TO US

The only accepted payment method is Bitcoin.
Be advised: PayPal, WeTransfer, Alipay, credit cards, and other methods will not be accepted.
If you prefer to pay with another cryptocurrency, please contact us to make arrangements.

If you don't have Bitcoin, you can purchase it using a credit card from the following websites:

MoonPay: https://www.moonpay.com/buy
Paybis: https://paybis.com/
Changelly: https://changelly.com/buy

Alternatively, you can buy Bitcoin using other payment methods from the following platforms (some of them work in China):

Coinbase: https://www.coinbase.com/
Paxful: https://paxful.com/
Binance: https://www.binance.com/
Crypto.com: https://www.crypto.com/
Huobi: https://www.huobi.com/
OKCoin: https://www.okcoin.com/
BTCC: https://www.btcc.com/
Paybis: https://paybis.com/
Coinmama: https://coinmama.com/
Bitfinex: https://www.bitfinex.com/
For users in China, Bitcoin can be purchased with Alipay from:

CoinCola: https://www.coincola.com/?lang=zh-HK
BitValve: https://www.bitvalve.com/buy-bitcoin/alipay
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Und dieser Text wir in die Tabelle &lt;code&gt;RECOVER_YOUR_DATA&lt;/code&gt; geschrieben:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SELECT * FROM RECOVER_YOUR_DATA;
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| text |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| All your data is backed up. You must pay 0.0101 BTC to bc1qm0v2r0mmx3py3h7fzkerd9a6rzdrpw5afqacen In 48 hours, your data will be publicly disclosed and deleted. (more information: go to https://is.gd/yotuqu) |
| After payment send mail to us: rambler+2r8qm@onionmail.org and we will provide a link for you to download your data. Your DBCODE is: 2R8QM |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anschliessend wird das Schema test (wo die Tabelle &lt;code&gt;RECOVER_YOUR_DATA&lt;/code&gt; drin ist?) gelöscht.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 75 Connect root@196.251.83.136 on using TCP/IP
 75 Query SET AUTOCOMMIT=0
 75 Query DROP DATABASE `test`
 75 Query COMMIT
 75 Quit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Und ein neues Schema erstellt mit dem Namen &lt;code&gt;RECOVER_YOUR_DATA&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 76 Connect root@196.251.83.136 on using TCP/IP
 76 Query SET AUTOCOMMIT=0
 76 Query CREATE DATABASE IF NOT EXISTS RECOVER_YOUR_DATA
 76 Quit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Irgendwie scheint mir der Zugriff nicht logisch und hat noch Potential. Da die Tabelle &lt;code&gt;RECOVER_YOUR_DATA&lt;/code&gt; fehlt (gelöscht mit Schema &lt;code&gt;test&lt;/code&gt;) kann man ja gar nicht mehr bezahlen, falls man wollte&amp;hellip;&lt;/p&gt;
&lt;p&gt;Möglicherweise scheint der Entwickler auch nicht mit einem limitierten &lt;code&gt;root&lt;/code&gt; Account gerechnet zu haben und das Tool verhält sich daher falsch? Siehe auch die drei Wiederholungen beim Dumpen der Tabellen.&lt;/p&gt;
&lt;h2 id="woher-kommt-der-zugriff"&gt;Woher kommt der Zugriff?&lt;/h2&gt;
&lt;p&gt;Ich weiss nicht, wie gut man verschleiern kann woher man kommt und wie gut die Geo-Auflösung der IP-Adressen ist, davon habe ich zu wenig Ahnung. Eine Recherche hat ergeben, dass die &lt;a href="https://whatismyipaddress.com/ip/196.251.83.136" target="_blank"&gt;IP aus Amsterdam (Niederlande)&lt;/a&gt; kommt.&lt;/p&gt;
&lt;p&gt;Nach weiterer Suche stellte sich heraus, dass die IP einer Organisation Namens &lt;a href="https://myip.ms/view/ip_owners/1629066/Internet_Secuirty_Ekabi.html" target="_blank"&gt;Internet Secuirty Ekabi&lt;/a&gt; (achtung Schreibfehler wie im Original!) gehört mit einer Adresse aus der USA und der &lt;a href="https://whois.ipip.net/AS401115" target="_blank"&gt;Location auf den Seychellen&lt;/a&gt;.
Wenn mir noch jemand mehr Tipps geben kann, was man da alles rausfinden kann, wäre ich sehr dankbar dafür!&lt;/p&gt;
&lt;h2 id="woher-stammen-die-ip-adressen"&gt;Woher stammen die IP Adressen?&lt;/h2&gt;
&lt;p&gt;Im kurzen beobachteten Zeitraum haben wir Zugriffe aus folgenden Regionen beobachtet:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Alibaba Cloud, Singapore&lt;/li&gt;
&lt;li&gt;Google Belgium, Brussels&lt;/li&gt;
&lt;li&gt;Data-Center Imaqliq Ltd., Russia, St. Petersburg&lt;/li&gt;
&lt;li&gt;Alibaba Cloud, Japan, Tokyo&lt;/li&gt;
&lt;li&gt;FiberXpress BV, Netherlands, Amsterdam&lt;/li&gt;
&lt;li&gt;M247 Europe SRL, United Kingdom, Manchester&lt;/li&gt;
&lt;li&gt;Hetzner Online GmbH, Finland, Helsinki&lt;/li&gt;
&lt;li&gt;Internet Secuirty Ekabi, Netherlands, Amsterdam (2 x)&lt;/li&gt;
&lt;li&gt;Internet Security Cheapyhost, Netherlands, Amsterdam (9 x)&lt;/li&gt;
&lt;li&gt;Internet Security Nybula, Netherlands, Amsterdam (2 x)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="zugriffsmuster"&gt;Zugriffsmuster&lt;/h2&gt;
&lt;p&gt;Alle Zugriffsversuche erfolgen OHNE SSL/TLS.&lt;/p&gt;
&lt;p&gt;Fingerprint aus Sicht des MariaDB General Query Logs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;9 x
Connect root@34.140.63.218 on using TCP/IP
Connect Access denied for user 'root'@'34.140.63.218' (using password: NO)

1 x
Connect root@45.135.95.25 on using TCP/IP
Connect Access denied for user 'root'@'45.135.95.25' (using password: NO)
Connect root@45.135.95.25 on using TCP/IP
Connect Access denied for user 'root'@'45.135.95.25' (using password: NO)

4 x
Connect root@196.251.118.8 on using TCP/IP
Connect Access denied for user 'root'@'196.251.118.8' (using password: NO)
Connect root@196.251.118.8 on using TCP/IP
Connect Access denied for user 'root'@'196.251.118.8' (using password: YES)
Connect root@196.251.118.8 on using TCP/IP
... mit 2 Wiederholungen

1 x
Connect root@94.102.49.155 on using TCP/IP
Connect Access denied for user 'root'@'94.102.49.155' (using password: NO)
Connect root@94.102.49.155 on using TCP/IP
Connect Access denied for user 'root'@'94.102.49.155' (using password: YES)
... mit 6 Wiederholungen

2 x
Connect root@196.251.91.19 on using TCP/IP
Connect Access denied for user 'root'@'196.251.91.19' (using password: NO)
Connect root@196.251.91.19 on using TCP/IP
Connect Access denied for user 'root'@'196.251.91.19' (using password: YES)
... mit 28 Wiederholungen

1 x
Connect root@157.180.29.231 on using TCP/IP
Connect Access denied for user 'root'@'157.180.29.231' (using password: NO)
Connect root@157.180.29.231 on using TCP/IP
Connect Access denied for user 'root'@'157.180.29.231' (using password: NO)
Connect root@157.180.29.231 on using TCP/IP
Connect Access denied for user 'root'@'157.180.29.231' (using password: YES)
Connect root@157.180.29.231 on using TCP/IP
Connect Access denied for user 'root'@'157.180.29.231' (using password: YES)
Mit jeweils zeitlichem Abstand

2 x
Connect root@196.251.86.26 on using TCP/IP
Connect Access denied for user 'root'@'196.251.86.26' (using password: NO)
Connect root@196.251.86.26 on using TCP/IP
Connect Access denied for user 'root'@'196.251.86.26' (using password: YES)
Connect root@196.251.86.26 on using TCP/IP
... mit 38 Wiederholungen
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Es kann natürlich sein, dass einige dieser Zugriffsversuche dieselben Werkzeuge verwenden und einfach nach einer unterschiedlichen Anzahl Versuche abgebrochen wurde und die Tools damit ein unterschiedliches Muster erzeugt haben.&lt;/p&gt;
&lt;h2 id="zugriffsmuster-aus-sicht-des-mariadb-error-logs"&gt;Zugriffsmuster aus Sicht des MariaDB Error Logs&lt;/h2&gt;
&lt;h3 id="mögliche-zugriffe-auf-das-galera-protokoll"&gt;Mögliche Zugriffe auf das Galera Protokoll&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;[Warning] WSREP: Failed to unserialize message. This may be a result of corrupt message, port scanner or another application connecting to group communication port.
[Warning] WSREP: Failed to unserialize message. This may be a result of corrupt message, port scanner or another application connecting to group communication port.
[Warning] WSREP: Unsupported/unrecognized gmcast protocol version: {
 at ./gcomm/src/gmcast_message.hpp:unserialize():331
 at ./gcomm/src/gmcast.cpp:handle_up():1494
[Warning] WSREP: Failed to unserialize message. This may be a result of corrupt message, port scanner or another application connecting to group communication port.
[Warning] WSREP: Failed to unserialize message. This may be a result of corrupt message, port scanner or another application connecting to group communication port.
[Warning] WSREP: Failed to unserialize message. This may be a result of corrupt message, port scanner or another application connecting to group communication port.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;und:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Warning] WSREP: Failed to unserialize message. This may be a result of corrupt message, port scanner or another application connecting to group communication port.
[Warning] WSREP: Failed to unserialize message. This may be a result of corrupt message, port scanner or another application connecting to group communication port.
[Warning] WSREP: Failed to unserialize message. This may be a result of corrupt message, port scanner or another application connecting to group communication port.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ob dies ein Zugriffsversuch war oder ob wir nur bei uns im Netzwerk oder Galera Cluster ein Problem hatten muss noch verifiziert werden.&lt;/p&gt;
&lt;h2 id="probleme-mit-der-namensauflösung"&gt;Probleme mit der Namensauflösung&lt;/h2&gt;
&lt;p&gt;Um mehr und zusätzliche Informationen zu erhalten wurde die Datenbank OHNE &lt;code&gt;skip_name_resolve&lt;/code&gt; betrieben. Dies führt zu verschiedenen Warnung betreffend Namensauflösung (vorwärts und rückwärts).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Warning] Hostname 'no-reverse-dns-configured.com' does not resolve to '94.102.49.155'.
[Warning] Host name 'scanner-28.ch1.censys-scanner.com' could not be resolved: Name or service not known
[Warning] IP address '34.140.170.97' has been resolved to the host name '97.170.140.34.bc.googleusercontent.com', which resembles IPv4-address itself.
[Warning] IP address '165.154.100.58' could not be resolved: Name or service not known
[Warning] IP address '104.193.135.104' could not be resolved: Temporary failure in name resolution
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Zudem sind verschiedene Scanner-Systeme aufgefallen, die teilweise richtig aufgelöst haben: security.ipip.net coop.net {ch1|hk2}.censys-scanner.com&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fazit&lt;/strong&gt;: &lt;code&gt;slog_warnings = 9&lt;/code&gt; ist zu hoch, &lt;code&gt;skip_name_resolve&lt;/code&gt; aktivieren.&lt;/p&gt;
&lt;h2 id="port-probing"&gt;Port probing&lt;/h2&gt;
&lt;p&gt;Einfaches Port probing (Abklopfen der Ports) kann wie folgt erfolgen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# netcat -z -n -v 10.116.63.139 3300-3310
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Im MariaDB Error Log auf Datenbank Seite sieht man dann wie folgt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Warning] Could not read packet: fd: 56 state: 1 read_length: 4 errno: 110 vio_errno: 1159 length: 0
[Warning] Aborted connection 52 to db: 'unconnected' user: 'unauthenticated' host: '_gateway.incus' (This connection closed normally without authentication)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Diese Port probes werden im MariaDB Gerneral Query Log schon gar nicht erst angezeigt.&lt;/p&gt;
&lt;h2 id="verschiedene-varianten-und-muster-des-port-probings"&gt;Verschiedene Varianten und Muster des Port probings&lt;/h2&gt;
&lt;p&gt;Folgende 3 Muster des Port probings sind uns aufgefallen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Warning] Could not read packet: fd: 63 state: 1 read_length: 4 errno: 104 vio_errno: 1158 length: -1
[Warning] Aborted connection 76 to db: 'unconnected' user: 'unauthenticated' host: '45.142.193.153' (This connection closed normally without authentication)

198.235.24.140 115.231.78.10
[Warning] Could not read packet: fd: 63 state: 1 read_length: 4 errno: 11 vio_errno: 1158 length: 0
[Warning] Aborted connection 50 to db: 'unconnected' user: 'unauthenticated' host: '20.65.194.133' (This connection closed normally without authentication)

[Warning] Could not write packet: fd: 64 state: 1 errno: 104 vio_errno: 1160 length: 42
[ERROR] mariadbd: Got an error writing communication packets
[Warning] Aborted connection 55 to db: 'unconnected' user: 'unauthenticated' host: 'connecting host' (This connection closed normally without authentication)
[Warning] Could not read packet: fd: 64 state: 1 read_length: 4 errno: 11 vio_errno: 1158 length: 0
[Warning] Aborted connection 56 to db: 'unconnected' user: 'unauthenticated' host: '137.184.75.161' (This connection closed normally without authentication)
[Warning] Could not read packet: fd: 64 state: 1 read_length: 4 errno: 0 vio_errno: 1158 length: 0
[Warning] Aborted connection 57 to db: 'unconnected' user: 'unauthenticated' host: '165.22.191.252' (This connection closed normally without authentication)
[Warning] Could not read packet: fd: 87 state: 1 read_length: 4 errno: 0 vio_errno: 1158 length: 0
[Warning] Aborted connection 58 to db: 'unconnected' user: 'unauthenticated' host: '165.22.188.115' (This connection closed normally without authentication)
[Warning] Could not read packet: fd: 64 state: 1 read_length: 196974 errno: 11 vio_errno: 1158 length: 0
[Warning] Aborted connection 59 to db: 'unconnected' user: 'unauthenticated' host: '165.22.191.252' (This connection closed normally without authentication)
[Warning] Could not read packet: fd: 64 state: 1 read_length: 196974 errno: 11 vio_errno: 1158 length: 0
[Warning] Aborted connection 60 to db: 'unconnected' user: 'unauthenticated' host: '165.22.191.252' (This connection closed normally without authentication)
[Warning] Could not read packet: fd: 64 state: 1 read_length: 197053 errno: 11 vio_errno: 1158 length: 0
[Warning] Aborted connection 61 to db: 'unconnected' user: 'unauthenticated' host: '165.22.191.252' (This connection closed normally without authentication)
[Warning] Could not read packet: fd: 64 state: 1 read_length: 197067 errno: 11 vio_errno: 1158 length: 0
[Warning] Aborted connection 62 to db: 'unconnected' user: 'unauthenticated' host: '165.22.191.252' (This connection closed normally without authentication)
[Warning] Could not read packet: fd: 64 state: 1 read_length: 196986 errno: 11 vio_errno: 1158 length: 0
[Warning] Aborted connection 63 to db: 'unconnected' user: 'unauthenticated' host: '165.22.191.252' (This connection closed normally without authentication)
[Warning] Could not read packet: fd: 64 state: 1 read_length: 131449 errno: 11 vio_errno: 1158 length: 0
[Warning] Aborted connection 64 to db: 'unconnected' user: 'unauthenticated' host: '165.22.191.252' (This connection closed normally without authentication)
[Warning] Could not read packet: fd: 64 state: 1 read_length: 65900 errno: 11 vio_errno: 1158 length: 0
[Warning] Aborted connection 65 to db: 'unconnected' user: 'unauthenticated' host: '165.22.191.252' (This connection closed normally without authentication)
[Warning] Could not read packet: fd: 64 state: 1 read_length: 65900 errno: 11 vio_errno: 1158 length: 0
[Warning] Aborted connection 66 to db: 'unconnected' user: 'unauthenticated' host: '165.22.191.252' (This connection closed normally without authentication)
[Warning] Could not read packet: fd: 64 state: 1 read_length: 65910 errno: 11 vio_errno: 1158 length: 0
[Warning] Aborted connection 67 to db: 'unconnected' user: 'unauthenticated' host: '165.22.191.252' (This connection closed normally without authentication)
[Warning] Could not read packet: fd: 64 state: 1 read_length: 65887 errno: 11 vio_errno: 1158 length: 0
[Warning] Aborted connection 68 to db: 'unconnected' user: 'unauthenticated' host: '165.22.191.252' (This connection closed normally without authentication)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Was im dritten Fall genau probiert wurde, entzieht sich noch meiner Kenntnis.&lt;/p&gt;
&lt;p&gt;Und hier noch die Aufschlüsselung der Fehlermeldungen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# perror 104
OS error code 104: Connection reset by peer

# perror 11
OS error code 11: Resource temporarily unavailable

# perror 1158
MariaDB error code 1158 (ER_NET_READ_ERROR): Got an error reading communication packets
Learn more: https://mariadb.com/kb/en/e1158/

# perror 1160
MariaDB error code 1160 (ER_NET_ERROR_ON_WRITE): Got an error writing communication packets
Learn more: https://mariadb.com/kb/en/e1160/
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="out-of-order-packets-4-x"&gt;Out of order packets (4 x)&lt;/h2&gt;
&lt;p&gt;Ein weiteres Muster, welches wir gesehen habe, sind Pakete in falscher Reihenfolge. Ob das Absicht ist oder mit dem Netzwerk zwischen Angreifer und Datenbank zu tun hat kann zur Zeit nicht gesagt werden (die IPs sollen aus Italien, USA und 2 x Schweden stammen).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quellen&lt;/strong&gt;: 45.91.171.169 45.147.250.222 20.168.122.53 91.223.169.88&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Beispiel&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ERROR] mariadbd: Got packets out of order
[Warning] Aborted connection 49 to db: 'unconnected' user: 'unauthenticated' host: '103.45.246.42' (This connection closed normally without authentication)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="unvollständiger-verbindungsaufbau-51-x"&gt;Unvollständiger Verbindungsaufbau (51 x)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Quellen&lt;/strong&gt;: 80.82.70.133 194.165.16.167 162.142.125.208 205.210.31.204 104.248.130.34 198.235.24.96 209.38.99.93 171.36.7.2 34.77.151.17 118.193.33.60 147.185.132.118 88.214.25.121 198.235.24.129 188.166.68.252 118.193.43.158 206.189.5.176 205.210.31.178 165.22.187.120 45.142.193.153 196.251.90.186 134.209.221.50 89.185.82.115 104.248.229.49 198.235.24.72 35.205.56.72 198.235.24.28 43.248.108.8 167.71.184.54 205.210.31.70 154.212.141.215 89.248.174.130 154.212.141.212 198.235.24.164 34.140.35.166 167.94.146.56 167.94.145.101 167.94.145.98 103.149.26.234 167.94.146.49 137.184.64.140 167.94.145.104 167.94.146.59 170.64.154.53 165.22.188.115 185.47.172.136 199.45.154.148 162.142.125.207 162.142.125.47 147.185.132.108 20.171.28.254 103.203.57.18&lt;/p&gt;
&lt;p&gt;und ebenfalls von diesen Netzen (Internet-Monitoring und AWS):&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quellen&lt;/strong&gt;: {larry|sharon|susan}.probe.onyphe.net {poetic|glowing|principled&lt;br&gt;].monitoring.internet-measurement.com prod-{boron|barium}-{sfo2|us-central|us-east|nyc1|us-southeast}-{&lt;br&gt;d{2,3}}.{do|li}.binaryedge.ninja azpd{&lt;br&gt;w{5,8}}.stretchoid.com scan-{&lt;br&gt;d{2}&lt;br&gt;[a-z&lt;br&gt;]}.shadowserver.org pdcscan{2,3}.scanning.cybcube.com ec2-{&lt;br&gt;w&lt;br&gt;*}&lt;br&gt;[us-east-2&lt;br&gt;]?.compute&lt;br&gt;[-1&lt;br&gt;]?.amazonaws.com&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Beispiel&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Warning] Aborted connection 325749 to db: 'unconnected' user: 'unauthenticated' host: '137.184.64.140' (This connection closed normally without authentication)
... 0, 1, 4, 5, 8, 9, 10 Wiederholungen
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="zugriffsversuch-mittels-regulärem-connect"&gt;Zugriffsversuch mittels regulärem Connect&lt;/h2&gt;
&lt;h3 id="versuche-ohne-passwort-40x"&gt;Versuche OHNE Passwort (40x)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Quellen&lt;/strong&gt;: 196.251.91.50 196.251.114.6 196.251.90.139 196.251.91.43 196.251.114.27 196.251.114.6 196.251.90.186 196.251.114.27 196.251.91.89 196.251.91.47 196.251.91.50 196.251.90.139 196.251.91.69 196.251.91.89 196.251.114.10 196.251.114.6 196.251.91.90 196.251.91.87 196.251.115.18 196.251.114.98 196.251.115.18 196.251.91.89 196.251.115.26 196.251.83.97 196.251.91.53 196.251.90.139 196.251.90.186 34.140.170.97 196.251.91.50 196.251.91.90 196.251.85.11 196.251.91.55 196.251.83.125 196.251.91.80 196.251.83.97 196.251.91.78&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Beispiel&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Warning] Access denied for user 'root'@'196.251.91.69' (using password: NO)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="versuche-ohne-und-dann-mit-passwort-28-x"&gt;Versuche OHNE und dann MIT Passwort (28 x)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Quellen&lt;/strong&gt;: 165.154.172.87 165.154.164.92 128.14.237.43 196.251.69.185 37.19.221.171 196.251.118.8 196.251.91.78 196.251.69.185 196.251.91.77 196.251.91.19 196.251.91.78 196.251.91.44 196.251.118.47 196.251.91.32 45.129.56.161 196.251.91.27 196.251.91.19 196.251.91.19 146.70.132.164 196.251.86.26 196.251.91.78 196.251.69.185 196.251.83.136 196.251.91.52 38.240.225.39 196.251.91.27 196.251.69.185 196.251.91.51&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Beispiel&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Warning] Access denied for user 'root'@'196.251.80.168' (using password: NO)
[Warning] Access denied for user 'root'@'196.251.80.168' (using password: YES)
... 1, 2, 3, 13, 27, 28, 37 Wiederholungen
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="komplexere-zugriffsversuche-mittels-port-probing-und-regulärem-connect-15-x"&gt;Komplexere Zugriffsversuche mittels Port probing und regulärem Connect (15 x)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Quellen&lt;/strong&gt;: 8.219.222.66 47.250.81.7 8.219.222.66 34.76.203.56 8.221.136.6 47.254.192.213 (6 x)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Beispiel&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Warning] Aborted connection 1134 to db: 'unconnected' user: 'unauthenticated' host: '47.254.192.213' (This connection closed normally without authentication)
[Warning] Access denied for user 'root'@'47.254.192.213' (using password: NO)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Quelle&lt;/strong&gt;: 45.150.237.21 (1 x)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Beispiel&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Warning] Aborted connection 22965 to db: 'unconnected' user: 'unauthenticated' host: '45.150.237.21' (This connection closed normally without authentication)
... 9 Wiederholungen
[Warning] Access denied for user ''@'45.150.237.21' (using password: NO)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Quellen&lt;/strong&gt;: 101.36.122.183 152.32.150.7 152.32.245.170 118.193.58.125 165.154.48.24 107.150.117.219 116.90.238.220 165.154.100.58 (8 x)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Beispiel&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Warning] Aborted connection 2816 to db: 'unconnected' user: 'unauthenticated' host: '165.154.100.58' (This connection closed normally without authentication)
... 0, 1 Wiederholungen
[Warning] Access denied for user ''@'165.154.100.58' (using password: NO)
[Warning] Access denied for user 'root'@'165.154.100.58' (using password: YES)
... 0, 48 Widerholungen
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="komplexere-zugriffsversuche-mittels-regulärem-connect-und-port-probing-3-x"&gt;Komplexere Zugriffsversuche mittels regulärem Connect und Port probing (3 x)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Quelle&lt;/strong&gt;: 104.193.135.104 (1 x)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Beispiel&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Warning] Access denied for user 'root'@'104.193.135.104' (using password: NO)
[Warning] Aborted connection 47 to db: 'unconnected' user: 'unauthenticated' host: '104.193.135.104' (This connection closed normally without authentication)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Quelle&lt;/strong&gt;: 196.251.91.18 (1 x)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Beispiel&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Warning] Access denied for user 'root'@'196.251.91.18' (using password: NO)
[Warning] Access denied for user 'root'@'196.251.91.18' (using password: YES)
... 3 Wiederholungen
[Warning] Aborted connection 1013 to db: 'unconnected' user: 'unauthenticated' host: '196.251.91.18' (This connection closed normally without authentication)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Quelle&lt;/strong&gt;: 94.102.49.155 (1 x)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Beispiel&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Warning] Access denied for user 'root'@'94.102.49.155' (using password: NO)
[Warning] Access denied for user 'root'@'94.102.49.155' (using password: YES)
... 1 Wiederholungen
[Warning] Aborted connection 1389 to db: 'unconnected' user: 'unauthenticated' host: '94.102.49.155' (This connection closed normally without authentication)
[Warning] Access denied for user 'root'@'94.102.49.155' (using password: YES)
... 3 Wiederholungen
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Diese Muster sind im beobachteten Zeitraum selten aufgetreten.&lt;/p&gt;
&lt;h2 id="fazit"&gt;Fazit&lt;/h2&gt;
&lt;p&gt;Lösegeld bei Cybercrime zu zahlen lohnt sich nicht!&lt;/p&gt;
&lt;h2 id="outlook--todoes"&gt;Outlook / Todoes&lt;/h2&gt;
&lt;p&gt;Weiter Punkte die man das nächste mal klären, verfeinern und optimieren könnte:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Passwörter herausfinden, mit welchen probiert wird. Ev. MariaDB patchen? (&lt;code&gt;sql_acl.cc&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Etwas mehr spannende Daten zur Verfügung stellen und schauen was dann genau passiert.&lt;/li&gt;
&lt;li&gt;Mehrere verschiedene Angriffe aufzeichnen (auf IP gefiltert?). Ev. aus verschiedenen Ländern (USA, China, Russland, Ukraine, &amp;hellip;)&lt;/li&gt;
&lt;li&gt;Man könnte auch versuchen den Honeypot mit &lt;code&gt;skip_grant_tables&lt;/code&gt; zu betreiben um Zugriffe mit Passwort zu ermöglichen?&lt;/li&gt;
&lt;li&gt;&lt;code&gt;log_warnings&lt;/code&gt; is mit 9 zu verbose eingestellt. Ev. reicht der Default?&lt;/li&gt;
&lt;li&gt;Zugriff mit SSL only? Schauen ob TLS schon jemand kann.&lt;/li&gt;
&lt;li&gt;Galera Protokoll? War das wirklich ein Angriff oder nur ein Problem im Netzwerk/mit dem Galera Cluster?&lt;/li&gt;
&lt;li&gt;Das selbe Spiel mit MySQL machen um zu sehen, ob hier andere Angriffsmuster erfolgen.&lt;/li&gt;
&lt;li&gt;Welche DB user werden sonst noch attakiert (CMS)?&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Wie verhält sich Galera Cluster mit vielen Knoten?</title><link>https://www.fromdual.com/de/blog/wie-verhaelt-sich-galera-cluster-mit-vielen-knoten/</link><pubDate>Fri, 24 Jan 2025 17:29:33 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/wie-verhaelt-sich-galera-cluster-mit-vielen-knoten/</guid><description>&lt;p&gt;Kürzlich hatte ich die Gelegenheit ganz viele Linux Systeme (VMs mit Rocky Linux 9) aus einer unserer regelmässig stattfindenden &lt;a href="https://www.fromdual.com/de/galera-cluster-fuer-mysql-mariadb-schulung" title="Übersicht über die FromDual Galera Cluster Schulung"&gt;Galera Cluster Schulungen&lt;/a&gt; eine Woche lang ganz für mich alleine zur freien Verfügung zu haben. Und auf den Maschinen war auch schon ein MariaDB 11.4.4 mit Galera Cluster installiert.&lt;/p&gt;
&lt;p&gt;Da ich schon lange mal ausprobieren wollte, wie sich ein Galera Cluster mit zunehmender Anzahl Knoten verhält, war jetzt die Gelegenheit dies mal auszuprobieren.&lt;/p&gt;
&lt;p&gt;Die folgenden Fragestellung sollten beantwortet werden:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Wie verhält sich der Durchsatz eines Galera Clusters in Abhängigkeit der Anzahl Galera-Knoten?&lt;/li&gt;
&lt;li&gt;Mit welcher Konfiguration erhalten wir den grössten Durchsatz?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Insgesamt wurde mit 5 verschiedenen Versuchs-Parameter experimentiert:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Anzahl Galera Knoten.&lt;/li&gt;
&lt;li&gt;Anzahl Client Maschinen (= Instanzen).&lt;/li&gt;
&lt;li&gt;Anzahl Threads pro Client (&lt;code&gt;--threads=&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Anzahl Galera Threads (&lt;code&gt;wsrep_slave_threads&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Laufzeit der Tests. Dieser Parameter wurde variiert weil einige Tests während des Laufs abgebrochen sind. Möglicherweise kann mit einer kleineren Rate (&lt;code&gt;--rate&lt;/code&gt;) im Lasttest dieser Parameter eliminiert werden. Wie sich zeigte, hatte er sehr wohl einen Einfluss auf das Resultat bzw. den gemessenen Durchsatz (z.B. Test 4b und 5 bzw. 18 und 19).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Insgesamt wurde 35 verschiedene Tests gefahren. Siehe &lt;a href="https://www.fromdual.com/de/blog/wie-verhaelt-sich-galera-cluster-mit-vielen-knoten/#rohdaten"&gt;Rohdaten&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="durchsatz-in-abhängigkeit-der-anzahl-galera-knoten"&gt;Durchsatz in Abhängigkeit der Anzahl Galera Knoten&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://www.fromdual.com/sites/default/files/galera_node_scaling_graph1.png" alt="graph"&gt;&lt;/p&gt;
&lt;table&gt;
&lt;caption&gt;Throughput related to # nodes&lt;/caption&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;# gal nodes&lt;/th&gt;
&lt;th&gt;# threads/client&lt;/th&gt;
&lt;th&gt;runtime [s]&lt;/th&gt;
&lt;th&gt;tps&lt;/th&gt;
&lt;th&gt;runtime [s]&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;7&lt;/th&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;596.3&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;8&lt;/th&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;567.8&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;9&lt;/th&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;531.9&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;11&lt;/th&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;495.2&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;12&lt;/th&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;492.2&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;13&lt;/th&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;502.9&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;14&lt;/th&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;459.5&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;15&lt;/th&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;458.6&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;16&lt;/th&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;429.2&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Throughput related to &lt;br&gt;# nodes&lt;/p&gt;
&lt;p&gt;Der Durchsatz im Galera Cluster nahm leicht von 600 tps auf 430 tps ab (28%) wenn die Anzahl Knoten von 1 auf 9 erhöht wurde.&lt;/p&gt;
&lt;h2 id="durchsatz-in-abhängigkeit-der-anzahl-verbindungen"&gt;Durchsatz in Abhängigkeit der Anzahl Verbindungen&lt;/h2&gt;
&lt;p&gt;Hier wurde vor allem mit der Anzahl der Clients und der Threads pro Client variiert. Bei 30 - 40 Connections scheint in diesem Setup das Optimum zu liegen. Das variieren der Anzahl Galera Threads (&lt;code&gt;wsrep_slave_threads&lt;/code&gt;) scheint in unserem Fall nicht sonderlich viel bewirkt zu haben. Wesentlich mehr als 1200 tps scheint das System nicht herzugeben. Insbesondere haben auch die Maschinen der beschriebene Galera Knoten nicht mehr allzu viel CPU Idle Zeit gehabt.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.fromdual.com/sites/default/files/galera_node_scaling_graph2.png" alt="graph"&gt;&lt;/p&gt;
&lt;table&gt;
&lt;caption&gt;Total # connections vs. throughput&lt;/caption&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;# client nodes&lt;/th&gt;
&lt;th&gt;# threads/client&lt;/th&gt;
&lt;th&gt;# con tot&lt;/th&gt;
&lt;th&gt;# gal threads&lt;/th&gt;
&lt;th&gt;runtime [s]&lt;/th&gt;
&lt;th&gt;tps&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;16&lt;/th&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;429.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;17&lt;/th&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;684.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;18&lt;/th&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;603.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;19&lt;/th&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;925.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;20&lt;/th&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;919.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;21&lt;/th&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;1081.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;22&lt;/th&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;1196.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;23&lt;/th&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;1132.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;23b&lt;/th&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;1106.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;24&lt;/th&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;1233.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;25&lt;/th&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;160&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;1095.7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Total &lt;br&gt;# connections vs. throughput&lt;/p&gt;
&lt;h2 id="durchsatz-in-abhängigkeit-aller-möglichen-parametern"&gt;Durchsatz in Abhängigkeit aller möglichen Parametern&lt;/h2&gt;
&lt;p&gt;Mit dem weiteren Variieren der Parameter insbesondere dem Reduzieren der Anzahl Galera Knoten von 9 auf 3 konnte der Durchsatz von knapp unter 1200 auf knapp über 1400 tps weiter erhöht werden.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.fromdual.com/sites/default/files/galera_node_scaling_graph3.png" alt="graph"&gt;&lt;/p&gt;
&lt;table&gt;
&lt;caption&gt;Throughput related to various different parameters&lt;/caption&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;# gal nodes&lt;/th&gt;
&lt;th&gt;# client nodes&lt;/th&gt;
&lt;th&gt;# threads/client&lt;/th&gt;
&lt;th&gt;# con tot&lt;/th&gt;
&lt;th&gt;tps&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;23&lt;/th&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;1132.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;23b&lt;/th&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;1106.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;24&lt;/th&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;1233.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;25&lt;/th&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;160&lt;/td&gt;
&lt;td&gt;1095.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;26&lt;/th&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;160&lt;/td&gt;
&lt;td&gt;1132.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;27&lt;/th&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;160&lt;/td&gt;
&lt;td&gt;1207.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;28&lt;/th&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;1333.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;29&lt;/th&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;1278.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;30&lt;/th&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;1281.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;31&lt;/th&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;1374.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;32&lt;/th&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;1304.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;33&lt;/th&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;1428.9&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Throughput related to various different parameters&lt;/p&gt;
&lt;p&gt;Es scheint also, mit gegebener Hardware irgendwo ein Optimum zu geben welches sich um 3 Galera Knoten und ca 40 Connections befindet. Genauere Abklärungen wären hier spannend&amp;hellip;&lt;/p&gt;
&lt;h2 id="statistische-versuchsplanung--design-of-experiments-doe"&gt;Statistische Versuchsplanung / Design of Experiments (DoE)&lt;/h2&gt;
&lt;p&gt;Hier wäre es noch spannend mit der Methode der statistischen Versuchsplanung zu arbeiten um dieses Optimum genauer zu bestimmen bzw. schneller zu finden.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mettler Toledo: &lt;a href="https://www.mt.com/de/de/home/applications/L1_AutoChem_Applications/L2_ReactionAnalysis/design-of-experiments-doe.html" target="_blank"&gt;DoE - Statistische Versuchsplanung&lt;/a&gt; - Ein statistischer Ansatz zur Reaktionsoptimierung.&lt;/li&gt;
&lt;li&gt;Wikipedia.de: &lt;a href="https://de.wikipedia.org/wiki/Statistische_Versuchsplanung" target="_blank"&gt;Statistische Versuchsplanung&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatab.de/tutorial/design-of-experiments" target="_blank"&gt;Versuchsplanung&lt;/a&gt; - DoE - Design of Experiments.&lt;/li&gt;
&lt;li&gt;Novustat: &lt;a href="https://novustat.com/statistik-blog/quality-engineering-statistische-versuchsplanung-unser-ultimativer-ueberblick.html" target="_blank"&gt;Quality Engineering: Statistische Versuchsplanung – Unser ultimativer Überblick!&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="harware-spezifikation"&gt;Harware Spezifikation&lt;/h2&gt;
&lt;p&gt;VM&amp;rsquo;s von Hetzner: CX22 (2 vCPU, 4 Gibyte RAM (effektiv: 3.5 Gibyte (warum das?)), 40 Gibyte Disk)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Architecture: x86_64
 CPU op-mode(s): 32-bit, 64-bit
 Address sizes: 40 bits physical, 48 bits virtual
 Byte Order: Little Endian
CPU(s): 2
 On-line CPU(s) list: 0,1
Vendor ID: GenuineIntel
 BIOS Vendor ID: QEMU
 Model name: Intel Xeon Processor (Skylake, IBRS, no TSX)
 BIOS Model name: NotSpecified
 CPU family: 6
 Model: 85
 Thread(s) per core: 1
 Core(s) per socket: 2
 Socket(s): 1
 Stepping: 4
 BogoMIPS: 4589.21
 Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2a
 pic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch cpuid_fault pti ssbd ibrs ibpb fsgsbase bmi1 avx2 smep bmi2 erms invpcid avx512f avx512dq rdseed adx smap clwb avx512cd avx512bw avx512vl xs
 aveopt xsavec xgetbv1 xsaves arat pku ospke md_clear
Virtualization features:
 Hypervisor vendor: KVM
 Virtualization type: full
Caches (sum of all):
 L1d: 64 KiB (2 instances)
 L1i: 64 KiB (2 instances)
 L2: 8 MiB (2 instances)
 L3: 16 MiB (1 instance)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="benchmark-tool--last-generator"&gt;Benchmark-Tool / Last-Generator&lt;/h2&gt;
&lt;p&gt;Als Last-Generator wurde &lt;code&gt;sysbench&lt;/code&gt; verwendet.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ dnf install epel-release
$ dnf install sysbench
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Jeder Client läuft auf seinem eigenen Schema um Galera Cluster Konflikte zu vermeiden. Dies ist in der Realität nicht in jedem Fall gegeben, stellt aber für Galera den optimalen Fall dar.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;SQL&amp;gt; CREATE DATABASE sbtest&amp;lt;n&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Jeder Client verbindet sich auf einen anderen Galera Knoten (1 - 6 Clients verteilt auf 1 - 9 Galera Knoten).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;GALERA_IP=&amp;lt;galera_ip&amp;gt;
DATABASE=sbtest&amp;lt;n&amp;gt;

$ sysbench oltp_common --mysql-host=${GALERA_IP} --mysql-user=app --mysql-password=secret --mysql-db=${DATABASE} --db-driver=mysql prepare
$ sysbench oltp_read_write --time=180 --db-driver=mysql --mysql-host=${GALERA_IP} --mysql-user=app --mysql-password=secret --mysql-db=${DATABASE} --threads=8 --rate=1000 --report-interval=1 run
$ sysbench oltp_common --mysql-host=${GALERA_IP} --mysql-user=app --mysql-password=secret --mysql-db=${DATABASE} --db-driver=mysql cleanup
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="mariadb-und-galera-konfiguration"&gt;MariaDB und Galera Konfiguration&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[server]

binlog_format = row
innodb_autoinc_lock_mode = 2
innodb_flush_log_at_trx_commit = 2
query_cache_size = 0
query_cache_type = 0

wsrep_on = on
wsrep_provider = /usr/lib64/galera-4/libgalera_smm.so
wsrep_cluster_address = &amp;#34;gcomm://10.0.0.2,10.0.0.3,10.0.0.4,10.0.0.5,10.0.0.6,10.0.0.7,10.0.0.8,10.0.0.9,10.0.0.10,10.0.0.11,10.0.0.12,10.0.0.13,10.0.0.14,10.0.0.15,10.0.0.16,10.0.0.17&amp;#34;
wsrep_cluster_name = &amp;#39;Galera Cluster&amp;#39;
wsrep_node_address = 10.0.0.2
wsrep_sst_method = rsync
wsrep_sst_auth = sst:secret
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="rohdaten"&gt;Rohdaten&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/sites/default/files/galera_node_scaling_raw_data.tar.gz"&gt;Rohdaten&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/sites/default/files/galera_node_scaling_data_summary.ods"&gt;Aufbereitete Daten&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Spielen mit MariaDB Vector für erste KI-Tests</title><link>https://www.fromdual.com/de/blog/spielen-mit-mariadb-vector-fuer-erste-ki-tests/</link><pubDate>Tue, 27 Aug 2024 21:50:13 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/spielen-mit-mariadb-vector-fuer-erste-ki-tests/</guid><description>&lt;p&gt;Künstliche Intelligenz (KI) und Vektor-Datenbanken sind heute in aller Munde. Da MariaDB demnächst auch mit Vektor-Datenbank-Funktionalität auf den Markt kommt, habe ich es als Datenbank-Berater für an der Zeit befunden mich etwas mit dem Thema zu beschäftigen, damit ich wenigstens einen Hauch Ahnung davon habe um was es geht&amp;hellip;&lt;/p&gt;
&lt;p&gt;Da ich nicht so der Theoretiker bin sondern eher gerne etwas praktisches mache, habe ich einen kleinen &amp;ldquo;KI&amp;rdquo; Prototypen gebaut, den jeder auf seinem Laptop (ohne GPU) sehr schnell und einfach nachbauen kann&amp;hellip;&lt;/p&gt;
&lt;p&gt;Ich habe mir auch erlaubt die Graphen aus dem Vortrag der MariaDB Foundation zu klauen (siehe Quellen am Ende).&lt;/p&gt;
&lt;h2 id="herunterladen-der-mariadb-datenbank-mit-vektor-funktionalität"&gt;Herunterladen der MariaDB Datenbank mit Vektor Funktionalität&lt;/h2&gt;
&lt;p&gt;Noch gib es keine MariaDB Pakete mit Vektor-Funktionalität, aber der &lt;a href="https://mariadb.org/download/?t=mariadb&amp;amp;p=mariadb&amp;amp;r=11.6.0+Vector&amp;amp;os=source" target="_blank" title="MariaDB Vector Quellcode"&gt;Quellcode&lt;/a&gt; ist bereits verfügbar. Also baut man sich die Binaries halt schnell selber. Dies hat auf meiner alten Kiste eine knappe Stunde gedauert. Wenn die Binaries gebaut sind, kann man sich einen Tarball draus machen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# tar xf mariadb-11.6.0_vector.tar.gz
# cd mariadb-11.6.0_vector/
# cmake .
# make
# make package
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anschliessend muss die MariaDB Datenbank nur noch gestartet werden.&lt;/p&gt;
&lt;h2 id="das-modell"&gt;Das Modell&lt;/h2&gt;
&lt;p&gt;Um das Konzept des Tokenisierens zu zeigen habe ich mir vorgenommen ein KI für URLs zu bauen und um das Konzept der verschiedenen Modelle und deren Verbesserungspotenzial zu zeigen habe ich eine ganz dummes Modell in PHP gebaut, welches ganz einfach und simple eine URL zerlegt.&lt;/p&gt;
&lt;p&gt;Die Frage, die mit diesem Modell beantwortet werden können sollte lautet: &amp;ldquo;Gib mir ähnliche URLs zur folgenden URL.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Die dazu gehörende Tabelle sieht wie folgt aus:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DROP TABLE IF EXISTS `urls`;
-- TRUNCATE TABLE is NOT sufficient!!!
CREATE TABLE `urls` (
 `id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY
, `url` varchar(1024) DEFAULT NULL
, `title` varchar(2000) DEFAULT NULL
, `embedding` blob NOT NULL
, VECTOR KEY `embedding` (`embedding`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Das Modell &lt;code&gt;fromdual_llm_v1&lt;/code&gt; kann man &lt;a href="https://www.fromdual.com/sites/default/files/fromdual_llm_v1.phpx"&gt;hier&lt;/a&gt; herunter laden.&lt;/p&gt;
&lt;p&gt;Wie das Ganze in etwa funktioniert kann man an diesem Schaubild der MariaDB Foundation entnehmen:&lt;/p&gt;
&lt;img src="https://www.fromdual.com/sites/default/files/vector_1.png" width="640" /&gt;
&lt;h2 id="trainieren-der-ki"&gt;Trainieren der KI&lt;/h2&gt;
&lt;p&gt;Dann wird die Datenbank trainiert: Die URL wird als gegeben angenommen und der Title kann z.B. über einen HTML Scraper ausgelesen werden. Hier 8 Trainings-Datensätze:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mariadb.org/download/?t=mariadb&amp;amp;p=mariadb&amp;amp;r=11.6.0+Vector&amp;amp;os=source" target="_blank"&gt;Download MariaDB Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/kb/en/creating-the-mariadb-binary-tarball/" target="_blank"&gt;Creating the MariaDB Binary Tarball&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.org/wp-content/uploads/2024/02/MariaDB-Vector.pdf" target="_blank"&gt;MariaDB Vector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/resources/blog/mariadb-vector-preview-is-out/" target="_blank"&gt;MariaDB Vector preview is out&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.org/projects/mariadb-vector/" target="_blank"&gt;MariaDB Vector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://metacpan.org/pod/Perl::Tokenizer" target="_blank"&gt;Perl::Tokenizer - A tiny Perl code tokenizer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.qwak.com/post/utilizing-llms-with-embedding-stores" target="_blank"&gt;Integrating Vector Databases with LLMs: A Hands-On Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/qwak-ai/qwak-examples/tree/main/qa_bot_falcon_chroma" target="_blank"&gt;LLM Model Enhanced with Vector DB&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Anschliessend werden mittels unseres Models die Vektoren generiert:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(./fromdual_llm_v1.php https://mariadb.org/download/?t=mariadb&amp;amp;p=mariadb&amp;amp;r=11.6.0+Vector&amp;amp;os=source
./fromdual_llm_v1.php https://mariadb.com/kb/en/creating-the-mariadb-binary-tarball/
./fromdual_llm_v1.php https://mariadb.org/wp-content/uploads/2024/02/MariaDB-Vector.pdf
./fromdual_llm_v1.php https://mariadb.com/resources/blog/mariadb-vector-preview-is-out/
./fromdual_llm_v1.php https://mariadb.org/projects/mariadb-vector/
./fromdual_llm_v1.php https://metacpan.org/pod/Perl::Tokenizer
./fromdual_llm_v1.php https://www.qwak.com/post/utilizing-llms-with-embedding-stores
./fromdual_llm_v1.php https://github.com/qwak-ai/qwak-examples/tree/main/qa_bot_falcon_chroma) | grep '^&amp;lt;br&amp;gt;['
[0.2, 0.0107421875, 0, 0, 0, 0.0006103515625, 0.00054931640625, 0]
[0.2, 0.0107421875, 0, 0, 0, 0.00262451171875, 0, 0]
[0.2, 0.0107421875, 0, 0, 0, 0.0028076171875, 0, 0]
[0.2, 0.0107421875, 0, 0, 0, 0.0028076171875, 0, 0]
[0.2, 0.0107421875, 0, 0, 0, 0.00152587890625, 0, 0]
[0.2, 0.01171875, 0, 0, 0, 0.001220703125, 0, 0]
[0.2, 0.01171875, 0, 0, 0, 0.0025634765625, 0, 0]
[0.2, 0.009765625, 0, 0, 0, 0.00323486328125, 0, 0]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mit diesen Vektoren wird jetzt die Datenbank gefüttert (trainiert):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INSERT INTO `urls` (id, url, title, embedding) VALUES (
 NULL
, 'https://mariadb.org/download/?t=mariadb&amp;amp;p=mariadb&amp;amp;r=11.6.0+Vector&amp;amp;os=source'
, 'Download MariaDB Server'
, VEC_FromText('[0.2, 0.0107421875, 0, 0, 0, 0.00262451171875, 0, 0]')
);

INSERT INTO `urls` (id, url, title, embedding) VALUES (
 NULL
, 'https://mariadb.com/kb/en/creating-the-mariadb-binary-tarball/'
, 'Creating the MariaDB Binary Tarball'
, VEC_FromText('[0.2, 0.0107421875, 0, 0, 0, 0.0006103515625, 0.00054931640625, 0]')
);

INSERT INTO `urls` (id, url, title, embedding) VALUES (
 NULL
, 'https://mariadb.org/wp-content/uploads/2024/02/MariaDB-Vector.pdf'
, 'MariaDB Vector'
, VEC_FromText('[0.2, 0.0107421875, 0, 0, 0, 0.0028076171875, 0, 0]')
);

INSERT INTO `urls` (id, url, title, embedding) VALUES (
 NULL
, 'https://mariadb.com/resources/blog/mariadb-vector-preview-is-out/'
, 'MariaDB Vector preview is out'
, VEC_FromText('[0.2, 0.0107421875, 0, 0, 0, 0.0028076171875, 0, 0]')
);

INSERT INTO `urls` (id, url, title, embedding) VALUES (
 NULL
, 'https://mariadb.org/projects/mariadb-vector/'
, 'MariaDB Vector'
, VEC_FromText('[0.2, 0.0107421875, 0, 0, 0, 0.00152587890625, 0, 0]')
);

INSERT INTO `urls` (id, url, title, embedding) VALUES (
 NULL
, 'https://metacpan.org/pod/Perl::Tokenizer'
, 'Perl::Tokenizer - A tiny Perl code tokenizer'
, VEC_FromText('[0.2, 0.01171875, 0, 0, 0, 0.001220703125, 0, 0]')
);

INSERT INTO `urls` (id, url, title, embedding) VALUES (
 NULL
, 'https://www.qwak.com/post/utilizing-llms-with-embedding-stores'
, 'Integrating Vector Databases with LLMs: A Hands-On Guide'
, VEC_FromText('[0.2, 0.01171875, 0, 0, 0, 0.0025634765625, 0, 0]')
);

INSERT INTO `urls` (id, url, title, embedding) VALUES (
 NULL
, 'https://github.com/qwak-ai/qwak-examples/tree/main/qa_bot_falcon_chroma'
, 'LLM Model Enhanced with Vector DB'
, VEC_FromText('[0.2, 0.009765625, 0, 0, 0, 0.00323486328125, 0, 0]')
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hier mal zuerst ein Überblick, was jetzt in der Datenbank drin steht:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT id, url, title, VEC_ToText(embedding)
 FROM urls
;
+----+-----------------------------------------------------------------------------+----------------------------------------------------------+---------------------------------------------------------------------------+
| id | url | title | VEC_ToText(embedding) |
+----+-----------------------------------------------------------------------------+----------------------------------------------------------+---------------------------------------------------------------------------+
| 1 | https://mariadb.org/download/?t=mariadb&amp;amp;p=mariadb&amp;amp;r=11.6.0+Vector&amp;amp;os=source | Download MariaDB Server | [0.200000,0.010742,0.000000,0.000000,0.000000,0.002625,0.000000,0.000000] |
| 2 | https://mariadb.com/kb/en/creating-the-mariadb-binary-tarball/ | Creating the MariaDB Binary Tarball | [0.200000,0.010742,0.000000,0.000000,0.000000,0.000610,0.000549,0.000000] |
| 3 | https://mariadb.org/wp-content/uploads/2024/02/MariaDB-Vector.pdf | MariaDB Vector | [0.200000,0.010742,0.000000,0.000000,0.000000,0.002808,0.000000,0.000000] |
| 4 | https://mariadb.com/resources/blog/mariadb-vector-preview-is-out/ | MariaDB Vector preview is out | [0.200000,0.010742,0.000000,0.000000,0.000000,0.002808,0.000000,0.000000] |
| 5 | https://mariadb.org/projects/mariadb-vector/ | MariaDB Vector | [0.200000,0.010742,0.000000,0.000000,0.000000,0.001526,0.000000,0.000000] |
| 6 | https://metacpan.org/pod/Perl::Tokenizer | Perl::Tokenizer - A tiny Perl code tokenizer | [0.200000,0.011719,0.000000,0.000000,0.000000,0.001221,0.000000,0.000000] |
| 7 | https://www.qwak.com/post/utilizing-llms-with-embedding-stores | Integrating Vector Databases with LLMs: A Hands-On Guide | [0.200000,0.011719,0.000000,0.000000,0.000000,0.002563,0.000000,0.000000] |
| 8 | https://github.com/qwak-ai/qwak-examples/tree/main/qa_bot_falcon_chroma | LLM Model Enhanced with Vector DB | [0.200000,0.009766,0.000000,0.000000,0.000000,0.003235,0.000000,0.000000] |
+----+-----------------------------------------------------------------------------+----------------------------------------------------------+---------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="suche-in-der-mariadb-vektor-datenbank"&gt;Suche in der MariaDB Vektor Datenbank&lt;/h2&gt;
&lt;p&gt;Jetzt kommt der spannend Teil der ganzen Geschichte: Finden wir auch was in unserer MariaDB Vektor Datenbank mit URLs?&lt;/p&gt;
&lt;p&gt;Wie das schematisch vor sich geht kann wieder dem Schaubild der MariaDB Foundation entnommen werden:&lt;/p&gt;
&lt;img src="https://www.fromdual.com/sites/default/files/vector_2.png" width="640" /&gt;
&lt;p&gt;Als erster Versuch ein perfekter Treffer:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./fromdual_llm_v1.php https://mariadb.org/projects/mariadb-vector/
[0.2, 0.0107421875, 0, 0, 0, 0.00152587890625, 0, 0]

SELECT id, url, title, VEC_ToText(embedding)
 FROM urls
 ORDER BY VEC_DISTANCE(embedding, VEC_FromText('[0.2, 0.0107421875, 0, 0, 0, 0.00152587890625, 0, 0]'))
LIMIT 3
;
+----+----------------------------------------------------------------+-----------------------------------------------+---------------------------------------------------------------------------+
| id | url | title | VEC_ToText(embedding) |
+----+----------------------------------------------------------------+-----------------------------------------------+---------------------------------------------------------------------------+
| 5 | https://mariadb.org/projects/mariadb-vector/ | MariaDB Vector | [0.200000,0.010742,0.000000,0.000000,0.000000,0.001526,0.000000,0.000000] |
| 6 | https://metacpan.org/pod/Perl::Tokenizer | Perl::Tokenizer - A tiny Perl code tokenizer. | [0.200000,0.011719,0.000000,0.000000,0.000000,0.001221,0.000000,0.000000] |
| 2 | https://mariadb.com/kb/en/creating-the-mariadb-binary-tarball/ | Creating the MariaDB Binary Tarball | [0.200000,0.010742,0.000000,0.000000,0.000000,0.000610,0.000549,0.000000] |
+----+----------------------------------------------------------------+-----------------------------------------------+---------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Die erste Zeile stimmt zu 100% überein. Dann werden die Resultate relativ schnell viel schlechter&amp;hellip;&lt;/p&gt;
&lt;p&gt;Zweiter Versuch eine ähnliche URL:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./fromdual_llm_v1.php https://mariadb.com/kb/en/e4201/
[0.2, 0.0107421875, 0, 0, 0, 0.00079345703125, 0, 0]

SELECT id, url, title, VEC_ToText(embedding)
 FROM urls
 ORDER BY VEC_DISTANCE(embedding, VEC_FromText('[0.2, 0.0107421875, 0, 0, 0, 0.00079345703125, 0, 0]'))
LIMIT 3
;
+----+----------------------------------------------------------------+-----------------------------------------------+---------------------------------------------------------------------------+
| id | url | title | VEC_ToText(embedding) |
+----+----------------------------------------------------------------+-----------------------------------------------+---------------------------------------------------------------------------+
| 2 | https://mariadb.com/kb/en/creating-the-mariadb-binary-tarball/ | Creating the MariaDB Binary Tarball | [0.200000,0.010742,0.000000,0.000000,0.000000,0.000610,0.000549,0.000000] |
| 5 | https://mariadb.org/projects/mariadb-vector/ | MariaDB Vector | [0.200000,0.010742,0.000000,0.000000,0.000000,0.001526,0.000000,0.000000] |
| 6 | https://metacpan.org/pod/Perl::Tokenizer | Perl::Tokenizer - A tiny Perl code tokenizer. | [0.200000,0.011719,0.000000,0.000000,0.000000,0.001221,0.000000,0.000000] |
+----+----------------------------------------------------------------+-----------------------------------------------+---------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hier würde ich unter den ersten 3 Treffern nur &lt;code&gt;mariadb&lt;/code&gt; URLs erwarten. Dies ist aber nicht der Fall. Hier hat unser Modell also noch Verbesserungspotential!&lt;/p&gt;
&lt;p&gt;Und noch eine andere ähnliche URL:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./fromdual_llm_v1.php https://mariadb.com/kb/en/vec_totext/
[0.2, 0.0107421875, 0, 0, 0, 0.0010986328125, 0, 0]

SELECT id, url, title, VEC_ToText(embedding)
 FROM urls
 ORDER BY VEC_DISTANCE(embedding, VEC_FromText('[0.2, 0.0107421875, 0, 0, 0, 0.0010986328125, 0, 0]'))
LIMIT 3
;
+----+----------------------------------------------------------------+-----------------------------------------------+---------------------------------------------------------------------------+
| id | url | title | VEC_ToText(embedding) |
+----+----------------------------------------------------------------+-----------------------------------------------+---------------------------------------------------------------------------+
| 5 | https://mariadb.org/projects/mariadb-vector/ | MariaDB Vector | [0.200000,0.010742,0.000000,0.000000,0.000000,0.001526,0.000000,0.000000] |
| 2 | https://mariadb.com/kb/en/creating-the-mariadb-binary-tarball/ | Creating the MariaDB Binary Tarball | [0.200000,0.010742,0.000000,0.000000,0.000000,0.000610,0.000549,0.000000] |
| 6 | https://metacpan.org/pod/Perl::Tokenizer | Perl::Tokenizer - A tiny Perl code tokenizer. | [0.200000,0.011719,0.000000,0.000000,0.000000,0.001221,0.000000,0.000000] |
+----+----------------------------------------------------------------+-----------------------------------------------+---------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Selbes Problem hier. Der Hostname wird zu wenig gewichtet. Wahrscheinlich kann/muss man hier mit der Streuung spielen welche &lt;code&gt;mariadb.org&lt;/code&gt; und &lt;code&gt;mariadb.com&lt;/code&gt; erzeugen.&lt;/p&gt;
&lt;p&gt;Und zu guter letzt eine URL die gar nicht im Datensatz vorkommt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./fromdual_llm_v1.php https://www.mongodb.com/blog/post/vector-search-llm-essentials-what-when-why
[0.2, 0.0146484375, 0, 0, 0, 0.00323486328125, 0, 0]
SELECT id, url, title, VEC_ToText(embedding)
 FROM urls
 ORDER BY VEC_DISTANCE(embedding, VEC_FromText('[0.2, 0.0146484375, 0, 0, 0, 0.00323486328125, 0, 0]'))
LIMIT 5
;
+----+-----------------------------------------------------------------------------+----------------------------------------------------------+---------------------------------------------------------------------------+
| id | url | title | VEC_ToText(embedding) |
+----+-----------------------------------------------------------------------------+----------------------------------------------------------+---------------------------------------------------------------------------+
| 7 | https://www.qwak.com/post/utilizing-llms-with-embedding-stores | Integrating Vector Databases with LLMs: A Hands-On Guide | [0.200000,0.011719,0.000000,0.000000,0.000000,0.002563,0.000000,0.000000] |
| 6 | https://metacpan.org/pod/Perl::Tokenizer | Perl::Tokenizer - A tiny Perl code tokenizer. | [0.200000,0.011719,0.000000,0.000000,0.000000,0.001221,0.000000,0.000000] |
| 3 | https://mariadb.org/wp-content/uploads/2024/02/MariaDB-Vector.pdf | MariaDB Vector | [0.200000,0.010742,0.000000,0.000000,0.000000,0.002808,0.000000,0.000000] |
| 4 | https://mariadb.com/resources/blog/mariadb-vector-preview-is-out/ | MariaDB Vector preview is out | [0.200000,0.010742,0.000000,0.000000,0.000000,0.002808,0.000000,0.000000] |
| 1 | https://mariadb.org/download/?t=mariadb&amp;amp;p=mariadb&amp;amp;r=11.6.0+Vector&amp;amp;os=source | Download MariaDB Server | [0.200000,0.010742,0.000000,0.000000,0.000000,0.002625,0.000000,0.000000] |
+----+-----------------------------------------------------------------------------+----------------------------------------------------------+---------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hier scheint das Resultat völlig willkürlich zu sein. Wenn man sich den Vektor der Abfrage mit den Vektoren der Resultate vergleicht macht die Reihenfolge aber schon Sinn&amp;hellip; Werden die Dimensionen im Vektor von links nach rechts ausgewertet? Es soll ja der Abstand von zwei Punkten im 8-dimensionalen Raum bestimmt werden&amp;hellip;&lt;/p&gt;
&lt;h2 id="verbesserungen-im-modell"&gt;Verbesserungen im Modell&lt;/h2&gt;
&lt;p&gt;Die Resultate unserer KI sind noch nicht so sonderlich berauschend. Einerseits liegt das sicher an der sehr beschränkten Datenmenge, andererseits haben wir auch sehr wichtige Kriterien in unserem Model noch nicht abgebildet oder völlig unsinnige Kriterien verwendet.&lt;/p&gt;
&lt;p&gt;Verbesserungsvorschläge für ein nächstes Modell: Der &lt;code&gt;hostname&lt;/code&gt; könnte zusätzlich noch tokenisiert werden, damit könnte man erreichen dass &lt;code&gt;mariadb.com&lt;/code&gt; und &lt;code&gt;mariadb.org&lt;/code&gt; näher beieinander liegen.&lt;/p&gt;
&lt;p&gt;Die Länge von &lt;code&gt;hostname&lt;/code&gt;, &lt;code&gt;path&lt;/code&gt;, &lt;code&gt;query&lt;/code&gt; und &lt;code&gt;fragment&lt;/code&gt; ist sicherlich kein sonderlich schlaues Kriterium um eine Änhlichkeit von URLs abzubilden. Hier wäre also wesentlich mehr Intelligenz im Modell vonnöten. Eine Funktion &lt;code&gt;1/CRC32(dim)&lt;/code&gt; liefert ev. bereits minimal bessere Resultate?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;titel&lt;/code&gt; könnte mit einbezogen werden, oder zumindest die wichtigsten Worte (Nomen, Verben) aus dem Titel.&lt;/p&gt;
&lt;p&gt;Der Dokumententyp (MIME type) könnte mit einbezogen werden: Ist ein PDF ähnlicher zu einem anderen PDF als zu einer CSV Datei oder einer statischen HMTL Seite oder einer dynamischen PHP Seite?&lt;/p&gt;
&lt;h2 id="punkte-die-beim-spielen-aufgefallen-sind"&gt;Punkte die beim Spielen aufgefallen sind&lt;/h2&gt;
&lt;p&gt;Die Anzahl der Dimensionen in einem Vektor scheinen beim ersten INSERT festgelegt zu werden. Wenn man anschliessend Daten mit einer anderen Vektorlänge eingibt erscheint folgender Fehler:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INSERT INTO products (name, description, embedding) VALUES (
 'Coffee Machine'
, 'Built to make the best coffee you can imagine'
, VEC_FromText('[0.2, 0.013671875, 0, 0, 0, 6.103515625E-5, 0, 0]')
);
ERROR 1366 (22007): Incorrect vector value: '...' for column `test`.`products`.`embedding` at row 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ein Änderen der Vector-Länge ist mit einem &lt;code&gt;TRUNCATE TABLE&lt;/code&gt; Befehl zur Zeit noch NICHT möglich. Die Tabelle muss gelöscht (&lt;code&gt;DROP TABLE&lt;/code&gt;) und wieder kreiert (&lt;code&gt;CREATE TABLE&lt;/code&gt;) werden.&lt;/p&gt;
&lt;p&gt;Das Suchen mit einem kürzeren Vector geht hingegen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT id, url, title, VEC_ToText(embedding)
 FROM urls
 ORDER BY VEC_DISTANCE(embedding, VEC_FromText('[0.2, 0.0107421875]'))
LIMIT 3
;
+----+-------------------------------------------------------------------------+-------------------------------------+---------------------------------------------------------------------------+
| id | url | title | VEC_ToText(embedding) |
+----+-------------------------------------------------------------------------+-------------------------------------+---------------------------------------------------------------------------+
| 8 | https://github.com/qwak-ai/qwak-examples/tree/main/qa_bot_falcon_chroma | LLM Model Enhanced with Vector DB | [0.200000,0.009766,0.000000,0.000000,0.000000,0.003235,0.000000,0.000000] |
| 2 | https://mariadb.com/kb/en/creating-the-mariadb-binary-tarball/ | Creating the MariaDB Binary Tarball | [0.200000,0.010742,0.000000,0.000000,0.000000,0.000610,0.000549,0.000000] |
| 5 | https://mariadb.org/projects/mariadb-vector/ | MariaDB Vector | [0.200000,0.010742,0.000000,0.000000,0.000000,0.001526,0.000000,0.000000] |
+----+-------------------------------------------------------------------------+-------------------------------------+---------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ob das Resultat so sinnvoll ist, kann ich (noch) nicht beurteilen.&lt;/p&gt;
&lt;h2 id="quellen"&gt;Quellen&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/kb/en/creating-the-mariadb-binary-tarball/"&gt;Creating the MariaDB Binary Tarball&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.org/wp-content/uploads/2024/02/MariaDB-Vector.pdf"&gt;MariaDB Vector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/resources/blog/mariadb-vector-preview-is-out/"&gt;MariaDB Vector preview is out&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.org/projects/mariadb-vector/"&gt;MariaDB Vector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://metacpan.org/pod/Perl::Tokenizer"&gt;Perl::Tokenizer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.qwak.com/post/utilizing-llms-with-embedding-stores"&gt;Integrating Vector Databases with LLMs: A Hands-On Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/qwak-ai/qwak-examples/tree/main/qa_bot_falcon_chroma"&gt;LLM Model Enhanced with Vector DB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@vipra_singh/building-llm-applications-vector-database-part-4-2bb29e7c798d"&gt;Building LLM Applications: Vector Database (Part 4)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@myscale/understanding-vector-indexing-a-comprehensive-guide-d1abe36ccd3c"&gt;Understanding Vector Indexing: A Comprehensive Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://christophergs.com/blog/understanding-llm-tokenization"&gt;The Technical User&amp;rsquo;s Introduction to LLM Tokenization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.blog/2023/10/09/from-prototype-to-production-vector-databases-in-generative-ai-applications/"&gt;From prototype to production: Vector databases in generative AI applications&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="nachtrag"&gt;Nachtrag&lt;/h2&gt;
&lt;p&gt;Wir haben unser Model verbessert (&lt;a href="https://www.fromdual.com/sites/default/files/fromdual_llm_v2.phpx"&gt;siehe v2&lt;/a&gt;). Jetzt sind die Resultate ein wenig besser/genauer.&lt;/p&gt;
&lt;p&gt;Für die Benutzung des Modells siehe: &lt;code&gt;./fromdual_llm_v2.php --help&lt;/code&gt;&lt;/p&gt;</description></item><item><title>Partieller physischer Datenbank-Restore für MariaDB und MySQL</title><link>https://www.fromdual.com/de/blog/partieller-physischer-datenbank-restore-fuer-mariadb-und-mysql/</link><pubDate>Mon, 01 Jul 2024 16:52:00 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/partieller-physischer-datenbank-restore-fuer-mariadb-und-mysql/</guid><description>&lt;h2 id="um-was-geht-es"&gt;Um was geht es?&lt;/h2&gt;
&lt;p&gt;Bei der Beschreibung von Backup- und /Restore-Szenarien wird in der Regel immer von einem vollständigen Backup (full backup) und einem vollständigen Restore (full restore) der Datenbankinstanz (&lt;code&gt;mariadbd&lt;/code&gt;/&lt;code&gt;mysqld&lt;/code&gt;) ausgegangen. Das bedeutet, dass die gesamte Datenbankinstanz inklusive aller Datenbanken (Schemata) gesichert und wiederhergestellt wird.&lt;/p&gt;
&lt;p&gt;In der Praxis sieht die Situation jedoch oft anders aus: Es soll nicht eine ganze Datenbankinstanz wiederhergestellt werden, sondern nur einzelne Datenbanken oder gar einzelne Tabellen, weil nur diese kaputt gegangen sind.&lt;/p&gt;
&lt;p&gt;Dies kann in vielen Fällen recht einfach mit den Tools &lt;code&gt;mariadb-dump&lt;/code&gt;/&lt;code&gt;mariadb&lt;/code&gt; oder &lt;code&gt;mysqldump&lt;/code&gt;/&lt;code&gt;mysql&lt;/code&gt; (logisches Backup) bewerkstelligt werden. Wenn die Datenbank oder die Tabelle jedoch sehr gross ist, wird die Wiederherstellung nicht in angemessener Zeit (einige Minuten bis wenige Stunden) abgeschlossen sein.&lt;/p&gt;
&lt;p&gt;Genau in diesem Fall kommt der sogenannte partielle physische Restore ins Spiel. Partiell steht für eine oder mehrere Tabellen (oder eine ganze Datenbank), physisch für: Es werden nicht einzelne SQL-Anweisungen ausgeführt, sondern die Datenfiles werden physisch zurückgespielt. In diesem Szenario können, eine entsprechende Infrastruktur vorausgesetzt, sehr schnell sehr grosse Datenbestände zurückgespielt werden. Faustregel: Auf fetter Hardware: 1 Tbyte pro Stunde. Auf diese Weise können Datenbank-Restores sehr schnell durchgeführt werden.&lt;/p&gt;
&lt;p&gt;MariaDB und MySQL bieten diese Funktionalität bereits von Haus aus an. Für einzelne Tabellen ist der Mechanismus einigermassen praktikabel (siehe &lt;a href="https://www.fromdual.com/xtrabackup_in_a_nutshell#pb_restore"&gt;Restore partial Backup&lt;/a&gt;). Für ganze Datenbanken mit möglicherweise Dutzenden oder Hunderten von Tabellen ist der Bord-Mechanismus jedoch sehr umständlich und fehleranfällig.&lt;/p&gt;
&lt;h2 id="anwendungsfall"&gt;Anwendungsfall&lt;/h2&gt;
&lt;p&gt;Genau hier kommt die neue Funktionalität des &lt;a href="https://www.fromdual.com/fromdual-backup-manager-2.3.0-has-been-released"&gt;FromDual Backup and Recovery Managers (&lt;code&gt;brman&lt;/code&gt;) v2.3.0&lt;/a&gt; ins Spiel: Er vereinfacht den partiellen physischen Datenbank-Restore erheblich.&lt;/p&gt;
&lt;p&gt;Ein zweites Szenario, in welchem diese neue Funktionalität ebenfalls genutzt werden kann, ist der Umzug einer grossen Datenbank von einer Datenbankinstanz in eine andere Datenbankinstanz (z. B. von Dev nach Prod).&lt;/p&gt;
&lt;h2 id="vorbereitungen-für-den-partiellen-physischen-datenbank-restore"&gt;Vorbereitungen für den partiellen physischen Datenbank-Restore&lt;/h2&gt;
&lt;p&gt;Um eine Datenbank wiederherstellen zu können, muss natürlich zunächst ein sauberes Backup vorliegen. Dieses kann entweder mit dem FromDual Backup Manager (&lt;code&gt;bman&lt;/code&gt;) erstellt werden:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PORT=3306
BACKUPNAME=bck_full_2024-07-01
BACKUPDIR=/tmp/bck

./brman/bin/bman --target=brman:secret@127.0.0.1:${PORT} --type=full --mode=physical --policy=daily --backupdir=${BACKUPDIR} --backup-name=${BACKUPNAME} --no-compress
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;oder man kann das Backup auch einfach mit den MariaDB- (&lt;code&gt;mariadb-backup&lt;/code&gt;) oder MySQL-Bordmitteln (&lt;code&gt;xtrabackup&lt;/code&gt;) erstellen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PORT=3306
BACKUPNAME=bck_full_2024-07-01
BACKUPDIR=/tmp/bck
POLICY=daily

mariadb-backup --user=brman --password=secret --host=127.0.0.1 --port=${PORT} --backup --target-dir=${BACKUPDIR}/${POLICY}/${BACKUPNAME}
mariadb-backup --user=brman --password=secret --host=127.0.0.1 --port=${PORT} --prepare --target-dir=${BACKUPDIR}/${POLICY}/${BACKUPNAME}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="partieller-physischer-datenbank-restore"&gt;Partieller physischer Datenbank-Restore&lt;/h2&gt;
&lt;p&gt;Um einen partiellen physischen Datenbank-Restore durchzuführen, muss die Datenbank, im Gegensatz zum vollständigen physischen Restore, laufen.&lt;/p&gt;
&lt;p&gt;Der partielle physische Datenbank-Restore ist dann einfach:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PORT=3306
DATADIR=/var/lib/mysql
BACKUPNAME=bck_full_2024-07-01
BACKUPDIR=/tmp/bck

./brman/bin/rman --target=brman:secret@127.0.0.1:${PORT} --type=schema --mode=physical --policy=daily --schema=test --log=/tmp/rman.log --backupdir=${BACKUPDIR} --datadir=${DATADIR} --backup-name=${BACKUPNAME}

...
Start restore at 2024-07-01 16:29:48
 Backup with tool mariabackup version 10.11.8 (from path /home/mysql/product/mariadb-10.11/bin/mariabackup).
 Parent: We are the parent. Our child is: 63712. Waiting for database daemon...
 Child: We are the child: Starting database daemon...
 Child: Change ownership of database files (/tmp/bck/daily/bck_full_2024-07-01) to mysql
 Child: /home/mysql/product/mariadb-10.11/bin/mariadbd --no-defaults --user=mysql --basedir=/home/mysql/product/mariadb-10.11 --datadir=/tmp/bck/daily/bck_full_2024-07-01 --log-error=/tmp/my.err --port=3360 --socket=/tmp/my.sock --lower-case-table-names=0
 Parent: Tables not InnoDB or sequences: 0
 Parent: Tables with partitions: 0
 Parent: Tables with full-text index: 0
 Parent: InnoDB table `test` found to restore
 Parent: Dump database test
 Parent: /home/mysql/product/mariadb-10.11/bin/mariadb-dump --user=brman --host=127.0.0.1 --port=3360 --routines --events --triggers --no-data --skip-lock-tables --add-drop-database --databases test
 Parent: Shutdown backup database.
 Restore empty database test
 Prepare and export tables: /home/mysql/product/mariadb-10.11/bin/mariabackup --user=brman --host=127.0.0.1 --port=3321 --prepare --export --databases=test --target-dir=/tmp/bck/daily/bck_full_2024-07-01
 SET SESSION foreign_key_checks = 0
 SET SESSION sql_log_bin = off

 Restore table test
 ALTER TABLE `test`.`test` DISCARD TABLESPACE
 cp /tmp/bck/daily/bck_full_2024-07-01/test/test.cfg /home/mysql/database/mariadb-1011/data/test/test.cfg
 cp /tmp/bck/daily/bck_full_2024-07-01/test/test.ibd /home/mysql/database/mariadb-1011/data/test/test.ibd
 chown mysql: /home/mysql/database/mariadb-1011/data/test/test.cfg /home/mysql/database/mariadb-1011/data/test/test.ibd
 ALTER TABLE `test`.`test` IMPORT TABLESPACE
 rm /home/mysql/database/mariadb-1011/data/test/test.cfg
 rm /tmp/bck/daily/bck_full_2024-07-01/test/test.cfg
 ----------------------------------------
 WARNING: You should restart the database now! Otherwise possible future backups may fail. See MDEV-34418 (https://jira.mariadb.org/browse/MDEV-34418).
 ----------------------------------------

 Restore time was: 0d 0h 0' 2&amp;quot;
End restore at 2024-07-01 16:29:50 (rc=0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bei MariaDB empfiehlt es sich, die Datenbank anschliessend neu zu starten, bis der Bug MDEV-34418: &lt;a href="https://jira.mariadb.org/browse/MDEV-34418" target="_blank"&gt;mariadb-backup fails on database which was partially restored with mariadb-backup&lt;/a&gt; behoben ist. Für MySQL kann dieser Schritt entfallen.&lt;/p&gt;
&lt;h2 id="einschränkungen"&gt;Einschränkungen&lt;/h2&gt;
&lt;p&gt;Zur Zeit sind noch folgende Einschränkungen beim partiellen physischen Restore von Datenbanken mit &lt;code&gt;rman&lt;/code&gt; zu beachten:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Es können nur ganze Datenbanken zurückgesichert werden. Das zurücksichern von einzelne Tabellen ist derzeit noch nicht implementiert. Verwenden Sie hierfür die Basis-Bordwerkzeuge.&lt;/li&gt;
&lt;li&gt;Die Wiederherstellung partitionierter Tabellen ist derzeit noch nicht implementiert. Verwenden Sie dazu die Basis-Bordwerkzeuge für partitionierte Tabellen.&lt;/li&gt;
&lt;li&gt;Ein anschliessendes Point-in-Time-Recovery der Datenbank ist noch nicht implementiert und muss manuell durchgeführt werden.&lt;/li&gt;
&lt;li&gt;Ein partielles physisches Datenbank-Restore für einen gesamten Galera-Cluster ist derzeit noch nicht implementiert und muss manuell durchgeführt werden. In diesem Fall wird ein Restore auf einen Galera-Knoten und eine anschliessende Synchronisation der anderen Knoten mittels SST empfohlen.&lt;/li&gt;
&lt;li&gt;Beim physischen partiellen Datenbank-Restore wird eine Pseudo-Instanz auf den Backup-Dateien gestartet. Diese Pseudoinstanz benötigt einen freien Port 3360.&lt;/li&gt;
&lt;li&gt;Die Backup-Dateien müssen bereits in einem konsistenten Zustand vorliegen (&lt;code&gt;--prepare&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Beim partiellen physischen Datenbank-Restore wird ein logisches Backup der Datenbank ohne die Daten auf der Pseudo-Instanz erstellt. Diese Sicherung wird auf die zu reparierende Instanz zurückgespielt. Das bedeutet, dass alle Objekte (Views, Trigger, Functions, Procedures, Events, etc.), die NACH dem vollständigen physischen Backup erstellt wurden, vor dem partiellen physischen Datenbank-Restore gelöscht werden und anschliessend nicht mehr vorhanden sind.&lt;/li&gt;
&lt;li&gt;Die ursprüngliche Datenbankinstanz, von der das Backup erstellt wurde, und die Instanz, auf der der Restore durchgeführt wird, müssen die gleiche Einstellung für &lt;code&gt;lower_case_table_names&lt;/code&gt; haben.&lt;/li&gt;
&lt;li&gt;Sowohl das Backup als auch die Datenbankinstanz und das &lt;code&gt;rman&lt;/code&gt;-Werkzeug müssen sich auf derselben Maschine befinden.&lt;/li&gt;
&lt;li&gt;Das Backup muss zur Zeit noch in unkomprimierter Form vorliegen.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="quellen"&gt;Quellen&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/xtrabackup_in_a_nutshell#partial"&gt;Partial Backup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/kb/en/partial-backup-and-restore-with-mariabackup/" target="_blank"&gt;Partial Backup and Restore with Mariabackup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/kb/en/innodb-file-per-table-tablespaces/#importing-transportable-tablespaces-for-non-partitioned-tables" target="_blank"&gt;Copying Transportable Tablespaces for Non-partitioned Tables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.mysql.com/doc/mysql-enterprise-backup/8.4/en/backup-partial-options.html" target="_blank"&gt;Partial Backup and Restore Options&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.percona.com/percona-xtrabackup/2.4/innobackupex/partial_backups_innobackupex.html#restoring-partial-backups" target="_blank"&gt;Partial Backups&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Verkleinern des InnoDB-System-Tablespaces</title><link>https://www.fromdual.com/de/blog/verkleinern-des-innodb-system-tablespaces/</link><pubDate>Wed, 12 Jun 2024 15:27:10 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/verkleinern-des-innodb-system-tablespaces/</guid><description>&lt;p&gt;Ein Feature, das mich im neuen MariaDB 11.4 LTS Release wirklich begeistert hat, ist das Verkleinern bzw. Schrumpfen des System-Tablespaces (&lt;code&gt;ibdata1&lt;/code&gt;). Auf dieses Feature habe ich seit ca. 2006 sehnsüchtig gewartet und nun ist es mit MariaDB 11.4 endlich gekommen.
Eigentlich gibt es dieses Feature schon seit dem &lt;a href="https://www.fromdual.com/enterprise-support-for-mariadb-and-mysql#mariadb-eol"&gt;MariaDB 11.2 IR&lt;/a&gt; (Juni 2023).&lt;/p&gt;
&lt;p&gt;Leider ist die Ankündigung dieses Features etwas zu kurz gekommen. In den MariaDB Release Notes heisst es lapidar:&lt;/p&gt;
&lt;figure&gt;
&lt;blockquote&gt;
&lt;p&gt;The InnoDB system tablespace is now shrunk by reclaiming unused space at startup (&lt;a href="https://jira.mariadb.org/browse/MDEV-14795" target="_blank"&gt;MDEV-14795&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figcaption&gt;Aus den &lt;a href="https://mariadb.com/kb/en/mariadb-11-2-0-release-notes/"&gt;MariaDB 11.2.0 Release Notes&lt;/a&gt;.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Die Gründe, warum dieses Datei ins Unermessliche wachsen kann, sind eigentlich schon lange bekannt und die Massnahmen dagegen sind auch klar (siehe &lt;a href="https://www.fromdual.com/de/blog/verkleinern-des-innodb-system-tablespaces/#quellen"&gt;Quellen&lt;/a&gt;). Nur sehen wir immer wieder MariaDB-Anwender draussen im Feld, die das Problem nicht oder zu spät auf dem Schirm hatten und nun mit einer viel zu grossen &lt;code&gt;ibdata1&lt;/code&gt;-Datei da stehen&amp;hellip;&lt;/p&gt;
&lt;h2 id="wie-kann-das-problem-provoziert-werden"&gt;Wie kann das Problem provoziert werden?&lt;/h2&gt;
&lt;p&gt;Das Problem kann provoziert werden, indem man eine Tabelle im System-Tablespace anlegt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SET global innodb_file_per_table = off;

SQL&amp;gt; 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`)
) ENGINE=InnoDB;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;und diese dann mit Daten befüllt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; INSERT INTO test
SELECT NULL, 'Some data to provoke huge data growth in system tablespace', NOW()
;

SQL&amp;gt; INSERT INTO test
SELECT NULL, 'Some data to provoke huge data growth in system tablespace', NOW()
 FROM test LIMIT 1000000
;

...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Während die Tabelle gefüllt wird, kann man auf dem Dateisystem beobachten, wie die Datei &lt;code&gt;ibdata1&lt;/code&gt; anschwillt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ while [ 1 ] ; do ll -h ibdata1 ; sleep 5 ; done
-rw-rw---- 1 mysql mysql 12M Jun 2 13:57 ibdata1
-rw-rw---- 1 mysql mysql 76M Jun 12 13:57 ibdata1
-rw-rw---- 1 mysql mysql 76M Jun 12 13:57 ibdata1
-rw-rw---- 1 mysql mysql 140M Jun 12 13:58 ibdata1
-rw-rw---- 1 mysql mysql 204M Jun 12 13:58 ibdata1
-rw-rw---- 1 mysql mysql 268M Jun 12 13:58 ibdata1
-rw-rw---- 1 mysql mysql 332M Jun 12 13:59 ibdata1
-rw-rw---- 1 mysql mysql 396M Jun 12 13:59 ibdata1
-rw-rw---- 1 mysql mysql 460M Jun 12 13:59 ibdata1
-rw-rw---- 1 mysql mysql 524M Jun 12 13:59 ibdata1
-rw-rw---- 1 mysql mysql 588M Jun 12 13:59 ibdata1
-rw-rw---- 1 mysql mysql 652M Jun 12 13:59 ibdata1
-rw-rw---- 1 mysql mysql 716M Jun 12 13:59 ibdata1
-rw-rw---- 1 mysql mysql 780M Jun 12 14:00 ibdata1
-rw-rw---- 1 mysql mysql 844M Jun 12 14:00 ibdata1
-rw-rw---- 1 mysql mysql 908M Jun 12 14:00 ibdata1
-rw-rw---- 1 mysql mysql 972M Jun 12 14:00 ibdata1
-rw-rw---- 1 mysql mysql 1.1G Jun 12 14:00 ibdata1
-rw-rw---- 1 mysql mysql 1.2G Jun 12 14:00 ibdata1
-rw-rw---- 1 mysql mysql 1.3G Jun 12 14:00 ibdata1
-rw-rw---- 1 mysql mysql 1.4G Jun 12 14:00 ibdata1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wenn die Datei &lt;code&gt;ibdata1&lt;/code&gt; gross genug ist, kann man die Tabelle vom System-Tablespace in einen dedizierten Tablespace verschieben:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SET global innodb_file_per_table = off;
SQL&amp;gt; ALTER TABLE test.test FORCE;
Query OK, 0 rows affected (33.764 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Und man sieht, wie die neue Datei aufgebaut wird:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ll -h ibdata1 test/*
-rw-rw---- 1 mysql mysql 1.4G Jun 12 14:01 ibdata1
-rw-rw---- 1 mysql mysql 1.1K Jun 12 14:01 test/#sql-alter-dca30-12.frm
-rw-rw---- 1 mysql mysql 696M Jun 12 14:01 test/#sql-alter-dca30-12.ibd
-rw-rw---- 1 mysql mysql 1.1K Jun 12 13:56 test/test.frm

-rw-rw---- 1 mysql mysql 1.4G Jun 12 14:01 ibdata1
-rw-rw---- 1 mysql mysql 1.1K Jun 12 14:01 test/test.frm
-rw-rw---- 1 mysql mysql 1.4G Jun 12 14:02 test/test.ibd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wir haben jetzt also einmal die Daten aber doppelte so viel Platz verbraucht.&lt;/p&gt;
&lt;h2 id="und-wie-kann-man-den-system-tablespace-wieder-verkleinern"&gt;Und wie kann man den System-Tablespace wieder verkleinern?&lt;/h2&gt;
&lt;p&gt;Diese Information ist leider etwas versteck und muss aus der Dokumentation und den MariaDB Jira Issues (siehe &lt;a href="https://www.fromdual.com/de/blog/verkleinern-des-innodb-system-tablespaces/#quellen"&gt;Quellen&lt;/a&gt;) zusammengesucht werden:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SET GLOBAL innodb_fast_shutdown=0;
SQL&amp;gt; SHUTDOWN;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Beim Herunterfahren sieht man die entsprechenden Einträge im MariaDB Error Log:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Note] bin/mariadbd (initiated by: root[root] @ localhost []): Normal shutdown
[Note] InnoDB: FTS optimize thread exiting.
[Note] InnoDB: Truncating system tablespace from 90880 to 768 pages
[Note] InnoDB: System tablespace truncated successfully
[Note] InnoDB: Starting shutdown...
[Note] InnoDB: Dumping buffer pool(s) to /home/mysql/database/mariadb-114/data/ib_buffer_pool
[Note] InnoDB: Restricted to 2016 pages due to innodb_buf_pool_dump_pct=25
[Note] InnoDB: Buffer pool(s) dump completed at 240612 14:11:11
[Note] InnoDB: Removed temporary tablespace data file: &amp;quot;./ibtmp1&amp;quot;
[Note] InnoDB: Shutdown completed; log sequence number 4011132308; transaction id 139
[Note] bin/mariadbd: Shutdown complete
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Und wenn man sich die Datei &lt;code&gt;ibdata1&lt;/code&gt; danach auf Platte anschaut, ist sie wieder so klein wie zu Beginn des Experiments:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ll ibdata1* -h
-rw-rw---- 1 mysql mysql 12M Jun 12 14:11 ibdata1
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="quellen"&gt;Quellen&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/undo-logs-in-innodb-system-tablespace-ibdata1"&gt;UNDO logs in InnoDB system tablespace ibdata1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/shrinking-innodb-system-tablespace-file-ibdata1-poc"&gt;Shrinking InnoDB system tablespace file ibdata1 PoC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/who-else-is-using-my-memory-file-system-cache-analysis"&gt;Who else is using my memory - File System Cache analysis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;InnoDB system tablespace cannot be shrunk (&lt;a href="https://jira.mariadb.org/browse/MDEV-14795" target="_blank"&gt;MDEV-14795&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;ibdata1 shrinking (&lt;a href="https://jira.mariadb.org/browse/MDEV-31462" target="_blank"&gt;MDEV-31462&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;InnoDB System Tablespaces - &lt;a href="https://mariadb.com/kb/en/innodb-system-tablespaces/#decreasing-the-size" target="_blank"&gt;Decreasing the Size&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>dbstat für MariaDB nach einem Monat produktiver Nutzung</title><link>https://www.fromdual.com/de/blog/dbstat-nach-einem-monat-produktiver-nutzung/</link><pubDate>Thu, 25 Apr 2024 12:39:58 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/dbstat-nach-einem-monat-produktiver-nutzung/</guid><description>&lt;h2 id="inhaltsverzeichnis"&gt;Inhaltsverzeichnis&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/dbstat-nach-einem-monat-produktiver-nutzung/#retrospect"&gt;Rückblick&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/dbstat-nach-einem-monat-produktiver-nutzung/#one-month-later"&gt;Einen Monat später&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/dbstat-nach-einem-monat-produktiver-nutzung/#table-size"&gt;Grösse der Tabellen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/dbstat-nach-einem-monat-produktiver-nutzung/#processlist"&gt;Prozessliste&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/dbstat-nach-einem-monat-produktiver-nutzung/#global-variables"&gt;Globale Variablen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/dbstat-nach-einem-monat-produktiver-nutzung/#locking"&gt;Metadata Lock und InnoDB Transaction Lock&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/dbstat-nach-einem-monat-produktiver-nutzung/#global-status"&gt;Globaler Status&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="rückblick"&gt;Rückblick&lt;/h2&gt;
&lt;p&gt;Nachdem wir vor gut 5 Wochen &lt;a href="https://www.fromdual.com/dbstat-fuer-mariadb-und-mysql"&gt;&lt;code&gt;dbstat&lt;/code&gt; für MariaDB (und MySQL)&lt;/a&gt; vorgestellt haben, haben wir es natürlich auch auf unseren Systemen ausgerollt um das Verhalten im täglichen Einsatz zu testen (&lt;a href="https://en.wikipedia.org/wiki/Eating_your_own_dog_food" target="_blank" title="Eating your own dog food auf Wikipedia"&gt;eat your own dog food&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Das ging soweit ganz gut, bis wir auf unserem MariaDB aktiv/passiv Master/Master Replikationscluster auf die Idee kamen, &lt;code&gt;dbstat&lt;/code&gt; auch auf dem passiven &lt;code&gt;dbstat&lt;/code&gt; Node zu aktivieren (eine ähnliche Situation hätte man auch bei einem Galera Cluster). Dabei stellten wir fest, dass das Design von &lt;code&gt;dbstat&lt;/code&gt; noch Potential hatte. Nachdem dieses Problem behoben war (v0.0.2 und v0.0.3) und auch das Problem gelöst war, wie man Events auf Master UND Slave aktivieren kann (&lt;a href="https://jira.mariadb.org/browse/MDEV-33782" target="_blank"&gt;MDEV-33782: Event is always disabled on slave&lt;/a&gt;), schien auf den ersten Blick alles in Ordnung. Leider haben wir bei der Korrektur nicht bedacht, dass auch die Daten hätten angepasst werden müssen. Dies hatte zur Folge, dass unsere Replikation über die Osterfeiertage zum Stillstand kam, was dann beim Aufholen zu einem weiteren Problem führte (&lt;a href="https://jira.mariadb.org/browse/MDEV-33923" target="_blank"&gt;MDEV-33923: MariaDB parallel replication causes Foreign Key errors&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Nachdem auch dieser kleine Zwischenfall behoben war lief &lt;code&gt;dbstat&lt;/code&gt; auf unserem Master/Master Replikationscluster seitdem einwandfrei&amp;hellip; Das Produkt &lt;code&gt;dbstat&lt;/code&gt; ist Open Source (GPLv2) und kann von &lt;a href="https://github.com/FromDual/dbstat" target="_blank" title="dbstat auf GitHub"&gt;GitHub heruntergeladen&lt;/a&gt; werden.&lt;/p&gt;
&lt;h2 id="einen-monat-später"&gt;Einen Monat später&lt;/h2&gt;
&lt;p&gt;Datenbanken sollten NICHT mit der Zeit wachsen sondern nur mit der Anzahl {Kunden, Produkte, etc.}, sobald das gewünschte Gleichgewicht (&lt;a href="https://www.merriam-webster.com/dictionary/steady state" target="_blank"&gt;steady state&lt;/a&gt;) erreicht ist. In unserer &lt;code&gt;dbstat&lt;/code&gt;-Installation haben wir diesen Wert auf 30 Tage gesetzt. Es wird also langsam an der Zeit, dass sich die Grösse von &lt;code&gt;dbstat&lt;/code&gt; stabilisiert und nicht weiter wächst&amp;hellip;&lt;/p&gt;
&lt;p&gt;Ausserdem wäre es spannend zu verstehen, welchen praktischen Nutzen &lt;code&gt;dbstat&lt;/code&gt; hat. Deshalb haben wir uns jetzt an die Arbeit gemacht und versuchen, die Ergebnisse von &lt;code&gt;dbstat&lt;/code&gt; auszuwerten.&lt;/p&gt;
&lt;p&gt;Hier zunächst noch einmal ein Überblick über die 11 laufenden Events:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SELECT db, name, definer, CONCAT(interval_value, ' ', interval_field) AS 'interval'
 , last_executed, ends, status
 FROM mysql.event
 ORDER BY db, name ASC
;
+--------+-------------------------+------------------+----------+---------------------+------+---------+
| db | name | definer | interval | last_executed | ends | status |
+--------+-------------------------+------------------+----------+---------------------+------+---------+
| dbstat | gather_global_status | dbstat@localhost | 1 MINUTE | 2024-04-24 07:44:14 | NULL | ENABLED |
| dbstat | gather_global_variables | dbstat@localhost | 1 MINUTE | 2024-04-24 07:44:32 | NULL | ENABLED |
| dbstat | gather_metadata_lock | dbstat@localhost | 1 MINUTE | 2024-04-24 07:44:47 | NULL | ENABLED |
| dbstat | gather_processlist | dbstat@localhost | 1 MINUTE | 2024-04-24 07:44:28 | NULL | ENABLED |
| dbstat | gather_table_size | dbstat@localhost | 1 DAY | 2024-04-24 00:04:00 | NULL | ENABLED |
| dbstat | gather_trx_and_lck | dbstat@localhost | 1 MINUTE | 2024-04-24 07:44:35 | NULL | ENABLED |
| dbstat | purge_global_status | dbstat@localhost | 1 MINUTE | 2024-04-24 07:44:08 | NULL | ENABLED |
| dbstat | purge_metadata_lock | dbstat@localhost | 5 MINUTE | 2024-04-24 07:44:37 | NULL | ENABLED |
| dbstat | purge_processlist | dbstat@localhost | 1 MINUTE | 2024-04-24 07:43:58 | NULL | ENABLED |
| dbstat | purge_table_size | dbstat@localhost | 5 MINUTE | 2024-04-24 07:40:04 | NULL | ENABLED |
| dbstat | purge_trx_and_lck | dbstat@localhost | 1 MINUTE | 2024-04-24 07:44:45 | NULL | ENABLED |
+--------+-------------------------+------------------+----------+---------------------+------+---------+
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="grösse-der-tabellen"&gt;Grösse der Tabellen&lt;/h2&gt;
&lt;p&gt;Zunächst ist das Wachstum von &lt;code&gt;dbstat&lt;/code&gt; selbst interessant. Aber natürlich kann diese Auswertung auch für jede andere Datenbank, Tabelle oder Catalog (&lt;a href="https://mariadb.com/kb/en/catalogs-overview" target="_blank"&gt;kommt in MariaDB 11.7?&lt;/a&gt;) durchgeführt werden:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SET SESSION sql_mode='STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,only_full_group_by';

SQL&amp;gt; SET @machine_name = @@hostname;

SQL&amp;gt; SELECT `table_schema`, SUBSTR(`ts`, 1, 10) AS date
 , ROUND(SUM(`data_length`)/1024/1024, 1) AS data_mb
 , ROUND(SUM(`index_length`)/1024/1024, 1) AS index_mb
 , ROUND(SUM(`data_free`)/1024/1024, 1) AS free_mb
 , ROUND((SUM(`data_length`) + SUM(`index_length`) + SUM(`data_free`))/1024/1024, 1) AS total_mb
 , ROUND(SUM(`table_rows`)/1000/1000, 1) AS rows_m
 FROM `table_size`
 WHERE `machine_name` = @machine_name
 AND `table_catalog` = 'def'
 AND `table_schema` = 'dbstat'
 GROUP BY `table_catalog`, `table_schema`, `date`
 ORDER BY `table_catalog`, `table_schema`, `date` ASC
;
+--------------+------------+---------+----------+---------+----------+--------+
| table_schema | date | data_mb | index_mb | free_mb | total_mb | rows_m |
+--------------+------------+---------+----------+---------+----------+--------+
| dbstat | 2024-03-26 | 762.8 | 1128.6 | 18.0 | 1909.4 | 10.9 |
| dbstat | 2024-03-27 | 835.8 | 1241.6 | 17.0 | 2094.4 | 11.1 |
| dbstat | 2024-03-28 | 837.8 | 1241.6 | 14.0 | 2093.4 | 11.8 |
| dbstat | 2024-03-29 | 960.7 | 1443.6 | 18.0 | 2422.4 | 14.2 |
| dbstat | 2024-03-30 | 960.7 | 1443.6 | 17.0 | 2421.4 | 15.0 |
| dbstat | 2024-03-31 | 1057.7 | 1604.6 | 20.0 | 2682.4 | 16.9 |
| dbstat | 2024-04-01 | 1057.7 | 1602.6 | 21.0 | 2681.4 | 17.6 |
| dbstat | 2024-04-02 | 1172.7 | 1797.6 | 22.0 | 2992.3 | 17.8 |
| dbstat | 2024-04-03 | 1442.8 | 2333.7 | 12.0 | 3788.5 | 22.8 |
| dbstat | 2024-04-04 | 1649.8 | 2723.7 | 13.0 | 4386.5 | 24.4 |
| dbstat | 2024-04-05 | 1649.8 | 2722.7 | 14.0 | 4386.5 | 26.0 |
| dbstat | 2024-04-06 | 1821.8 | 3034.8 | 13.0 | 4869.6 | 24.6 |
| dbstat | 2024-04-07 | 1821.8 | 3034.8 | 14.0 | 4870.6 | 26.2 |
| dbstat | 2024-04-08 | 1989.9 | 3344.8 | 12.0 | 5346.6 | 29.9 |
| dbstat | 2024-04-09 | 1990.9 | 3343.8 | 14.0 | 5348.6 | 31.5 |
| dbstat | 2024-04-10 | 2193.9 | 3712.8 | 13.0 | 5919.7 | 31.6 |
| dbstat | 2024-04-11 | 2193.9 | 3712.8 | 15.0 | 5921.7 | 31.1 |
| dbstat | 2024-04-12 | 2405.8 | 4119.1 | 12.0 | 6537.0 | 34.9 |
| dbstat | 2024-04-13 | 2405.8 | 4119.1 | 14.0 | 6538.9 | 35.7 |
| dbstat | 2024-04-14 | 2480.8 | 4278.9 | 15.0 | 6774.8 | 36.2 |
| dbstat | 2024-04-15 | 2560.8 | 4443.7 | 12.0 | 7016.5 | 37.5 |
| dbstat | 2024-04-16 | 2560.8 | 4443.7 | 12.0 | 7016.5 | 38.2 |
| dbstat | 2024-04-17 | 2640.8 | 4610.6 | 18.0 | 7269.4 | 38.5 |
| dbstat | 2024-04-18 | 2640.9 | 4611.6 | 14.0 | 7266.5 | 39.7 |
| dbstat | 2024-04-19 | 2743.9 | 4826.5 | 14.0 | 7584.3 | 36.9 |
| dbstat | 2024-04-20 | 2826.9 | 4995.5 | 14.0 | 7836.4 | 38.3 |
| dbstat | 2024-04-21 | 2830.9 | 4997.4 | 18.0 | 7846.3 | 39.2 |
| dbstat | 2024-04-22 | 2919.9 | 5177.4 | 14.0 | 8111.3 | 43.2 |
| dbstat | 2024-04-23 | 2923.0 | 5177.3 | 16.0 | 8116.3 | 44.1 |
| dbstat | 2024-04-24 | 3020.0 | 5376.3 | 16.0 | 8412.3 | 41.0 |
| dbstat | 2024-04-25 | 3024.0 | 5377.3 | 17.0 | 8418.3 | 40.9 |
+--------------+------------+---------+----------+---------+----------+--------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nimmt man zum Vergleich den Plattenplatz im O/S:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# du -shc *.ibd
8.6G global_status.ibd
308K global_variables.ibd
692K metadata_lock.ibd
97M processlist.ibd
18M table_size.ibd
212K trx_and_lck.ibd
8.7G total
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;sieht man, dass die Werte aus der Datenbank in etwa stimmen (5% Fehler)&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.fromdual.com/sites/default/files/size_of_db_dbstat.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Wichtig&lt;/strong&gt;: Die Datenbank &lt;code&gt;dbstat&lt;/code&gt; erreicht nach ca. einem Monat eine Grösse von ca. 9 Gbyte auf einem nicht besonders grossen Datenbanksystem.&lt;/p&gt;
&lt;p&gt;Man sieht auch, dass sich die Grösse der Datenbank gerade erst stabilisiert.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.fromdual.com/sites/default/files/number_of_rows.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Wenn man genauer wissen will, welche Tabellen für welchen Teil des Datenvolumens verantwortlich sind, kann man auch in die Daten hineinzoomen bzw. hineindrillen (&lt;a href="https://www.dictionary.com/browse/drill-down" target="_blank"&gt;drill down&lt;/a&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SELECT `table_name`, SUBSTR(`ts`, 1, 10) AS date
 , ROUND(`data_length`/1024/1024, 1) AS data_mb
 , ROUND(`index_length`/1024/1024, 1) AS index_mb
 , ROUND(`data_free`/1024/1024, 1) AS free_mb
 , ROUND((`data_length` + `index_length` + `data_free`)/1024/1024, 1) AS total_mb
 , ROUND((`data_length` + `index_length` + `data_free`)/1024/1024/8418.26*100, 1) AS pct
 , ROUND(`table_rows`/1000/1000, 1) AS rows_m
 FROM `table_size`
 WHERE `machine_name` = @machine_name
 AND `table_catalog` = 'def'
 AND `table_schema` = 'dbstat'
 AND SUBSTR(`ts`, 1, 10) = CURRENT_DATE()
 ORDER BY rows_m DESC
;
+------------------+------------+---------+----------+---------+----------+------+--------+
| table_name | date | data_mb | index_mb | free_mb | total_mb | pct | rows_m |
+------------------+------------+---------+----------+---------+----------+------+--------+
| global_status | 2024-04-25 | 2949.9 | 5356.9 | 5.0 | 8311.8 | 98.7 | 40.4 |
| processlist | 2024-04-25 | 68.2 | 17.1 | 7.0 | 92.2 | 1.1 | 0.4 |
| global_variables | 2024-04-25 | 0.1 | 0.1 | 0.0 | 0.2 | 0.0 | 0.0 |
| metadata_lock | 2024-04-25 | 0.4 | 0.2 | 0.0 | 0.6 | 0.0 | 0.0 |
| table_size | 2024-04-25 | 5.4 | 3.1 | 5.0 | 13.5 | 0.2 | 0.0 |
| trx_and_lck | 2024-04-25 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
+------------------+------------+---------+----------+---------+----------+------+--------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Anmerkung&lt;/strong&gt;: Bitte entschuldigen Sie die Nichverwendung der Window-Funktion!&lt;/p&gt;
&lt;p&gt;Der einzige wirkliche Treiber für die Datenmenge auf diesem System ist die Tabelle &lt;code&gt;global_status&lt;/code&gt;. Dies ist auch zu erwarten (siehe &lt;a href="https://www.fromdual.com/dbstat-fuer-mariadb-und-mysql#how-does-dbstat-work"&gt;Mengengerüst von &lt;code&gt;dbstat&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SELECT SUBSTR(ts, 1, 10) AS date, table_rows/1000/1000 AS k_rows
 , ROUND(data_length/1024/1024, 1) AS data_mb, ROUND(index_length/1024/1024, 1) AS index_mb, ROUND(data_free/1024/1024, 1) AS free_mb
 , ROUND((data_length + index_length + data_free)/1024/1024, 1) AS total_mb
 FROM table_size
 WHERE `machine_name` = @machine_name
 AND `table_catalog` = 'def'
 AND `table_schema` = 'dbstat'
 AND table_name = 'global_status'
 AND ts &amp;gt; DATE_SUB(CURRENT_DATE, INTERVAL 10 DAY)
;
+------------+-------------+---------+----------+---------+----------+
| date | k_rows | data_mb | index_mb | free_mb | total_mb |
+------------+-------------+---------+----------+---------+----------+
| 2024-04-15 | 37.13876300 | 2512.9 | 4433.0 | 4.0 | 6949.9 |
| 2024-04-16 | 37.94217200 | 2512.9 | 4433.0 | 4.0 | 6949.9 | + 0M
| 2024-04-17 | 38.19867500 | 2592.9 | 4600.0 | 7.0 | 7199.9 | + 250M
| 2024-04-18 | 39.39108500 | 2592.9 | 4600.0 | 5.0 | 7197.9 | - 2M
| 2024-04-19 | 36.52539600 | 2691.9 | 4813.0 | 5.0 | 7509.8 | + 312M
| 2024-04-20 | 37.99073500 | 2770.9 | 4980.9 | 6.0 | 7757.8 | + 248M
| 2024-04-21 | 38.79420200 | 2770.9 | 4980.9 | 7.0 | 7758.8 | + 1M
| 2024-04-22 | 42.82606200 | 2855.9 | 5158.9 | 6.0 | 8020.8 | + 263M
| 2024-04-23 | 43.62953000 | 2855.9 | 5158.9 | 7.0 | 8021.8 | + 1M
| 2024-04-24 | 40.54342200 | 2949.9 | 5356.9 | 7.0 | 8313.8 | + 292M
| 2024-04-25 | 40.43067700 | 2949.9 | 5356.9 | 5.0 | 8311.8 | - 2M
+------------+-------------+---------+----------+---------+----------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Anmerkung&lt;/strong&gt;: Sorry, ich sollte mich wirklich mit den Window-Funktionen vertraut machen&amp;hellip;&lt;/p&gt;
&lt;p&gt;Wenn wir die Daten etwas genauer analysieren, sehen wir, dass sich die Anzahl der Rows in den letzten 4 Tagen langsam stabilisiert hat (Achtung: &lt;code&gt;table_rows&lt;/code&gt; wird berechnet (aus der Anzahl der Blöcke und der durchschnittlichen Zeilenlänge?) und ist kein exakter Wert), aber die &amp;ldquo;Datenmenge&amp;rdquo; hat bis gestern weiter zugenommen, was wahrscheinlich auf das &amp;ldquo;Zerfleddern&amp;rdquo; der Tabellen und Indizes zurückzuführen ist&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.fromdual.com/sites/default/files/size_per_table.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Der Primärschlüssel der Tabelle &lt;code&gt;global_status&lt;/code&gt; wurde gewählt, um die &lt;a href="https://en.wikipedia.org/wiki/Locality_of_reference" target="_blank" title="Locality of reference on Wikipedia"&gt;Lokalisierung der Daten&lt;/a&gt; zu optimieren:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PRIMARY KEY (`machine_name`,`variable_name`,`ts`),
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Die Lage sollte sich in den nächsten Tagen beruhigen. In 2 bis 4 Wochen müssen wir die Lage erneut prüfen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Zusammenfassung&lt;/strong&gt;: Ich würde sagen, dass dieses Feature die Anforderungen erfüllt und hilft, das Datenwachstum zu verstehen.&lt;/p&gt;
&lt;h2 id="liste-der-prozesse"&gt;Liste der Prozesse&lt;/h2&gt;
&lt;p&gt;Da wir keine ernsthaften Lastprobleme in unseren Datenbanken haben, ist diese Funktion in unserem Fall nicht so interessant. Wir können zum Beispiel sehen, was eine (persistente) Verbindung gemacht hat:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SELECT connection_id, ts, command, time, state, SUBSTR(REGEXP_REPLACE(REPLACE(query, &amp;quot;&amp;lt;br&amp;gt;n&amp;quot;, ' '), '&amp;lt;br&amp;gt; +', ' '), 1, 64)
 FROM processlist
 WHERE machine_name = @machine_name
 AND command != 'Sleep'
 AND connection_id = @connection_id
 AND state NOT IN (
 'Waiting for next activation'
 , 'Master has sent all binlog to slave; waiting for more updates'
 , 'Waiting for master to send event'
 , 'Slave has read all relay log; waiting for more updates'
 )
 ORDER BY ts ASC
;
+---------------+---------------------+---------+-------+----------------+----------------------------------------------------------------------+
| connection_id | ts | command | time | state | SUBSTR(REGEXP_REPLACE(REPLACE(query, &amp;quot;&amp;lt;br&amp;gt;n&amp;quot;, ' '), '&amp;lt;br&amp;gt; +', ' '), 1, 64) |
+---------------+---------------------+---------+-------+----------------+----------------------------------------------------------------------+
| 18 | 2024-04-17 12:30:28 | Query | 0.029 | Sending data | select pp.item_preprocid,pp.itemid,pp.type,pp.params,pp.step,h.h |
| 18 | 2024-04-17 14:58:28 | Query | 0.009 | Writing to net | select itemtagid,itemid,tag,value from item_tag |
| 18 | 2024-04-18 06:24:28 | Query | 0.003 | Sending data | select pp.item_preprocid,pp.itemid,pp.type,pp.params,pp.step,h.h |
| 18 | 2024-04-18 11:34:28 | Query | 0.030 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-18 16:39:28 | Query | 0.006 | Sending data | select itemid,functionid,name,parameter,triggerid from functions |
| 18 | 2024-04-18 19:12:28 | Query | 0.014 | Sending data | select triggerid,description,expression,error,priority,type,valu |
| 18 | 2024-04-18 21:49:28 | Query | 0.004 | Writing to net | select i.itemid,i.hostid,i.templateid from items i inner join ho |
| 18 | 2024-04-19 00:21:28 | Query | 0.032 | Sending data | select pp.item_preprocid,pp.itemid,pp.type,pp.params,pp.step,h.h |
| 18 | 2024-04-19 02:59:28 | Query | 0.017 | Writing to net | select triggerid,description,expression,error,priority,type,valu |
| 18 | 2024-04-19 05:39:28 | Query | 0.052 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-19 08:19:28 | Query | 0.000 | Statistics | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-19 13:26:28 | Query | 0.075 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-19 15:57:28 | Query | 0.027 | Writing to net | select itemtagid,itemid,tag,value from item_tag |
| 18 | 2024-04-19 18:33:28 | Query | 0.010 | Sending data | select itemtagid,itemid,tag,value from item_tag |
| 18 | 2024-04-19 21:10:28 | Query | 0.008 | Sending data | select pp.item_preprocid,pp.itemid,pp.type,pp.params,pp.step,h.h |
| 18 | 2024-04-19 23:50:28 | Query | 0.067 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-20 02:28:28 | Query | 0.008 | Sending data | select triggerid,description,expression,error,priority,type,valu |
| 18 | 2024-04-20 05:08:28 | Query | 0.052 | Writing to net | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-20 07:44:28 | Query | 0.123 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-20 10:21:28 | Query | 0.144 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-20 12:55:28 | Query | 0.004 | Sending data | select i.itemid,i.hostid,i.templateid from items i where i.flags |
| 18 | 2024-04-20 15:35:28 | Query | 0.092 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-20 18:12:28 | Query | 0.041 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-20 20:47:28 | Query | 0.113 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-20 23:25:28 | Query | 0.101 | Writing to net | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-21 02:03:28 | Query | 0.120 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-21 04:42:28 | Query | 0.099 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-21 07:18:28 | Query | 0.015 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-21 12:32:28 | Query | 0.018 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-21 15:06:28 | Query | 0.091 | Writing to net | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-21 20:16:28 | Query | 0.012 | Sending data | select itemtagid,itemid,tag,value from item_tag |
| 18 | 2024-04-22 06:44:28 | Query | 0.161 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-22 09:21:28 | Query | 0.000 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-22 11:54:28 | Query | 0.020 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-22 14:23:28 | Query | 0.067 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-22 16:59:28 | Query | 0.128 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-22 22:05:28 | Query | 0.078 | Writing to net | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-23 00:38:28 | Query | 0.084 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-23 03:15:28 | Query | 0.098 | Writing to net | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-23 05:52:28 | Query | 0.000 | starting | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-23 08:27:28 | Query | 0.011 | Sending data | select pp.item_preprocid,pp.itemid,pp.type,pp.params,pp.step,h.h |
| 18 | 2024-04-23 10:58:28 | Query | 0.000 | Sending data | select i.itemid,i.hostid,i.templateid from items i inner join ho |
| 18 | 2024-04-23 13:31:28 | Query | 0.110 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-23 16:01:28 | Query | 0.023 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-23 18:35:28 | Query | 0.095 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-23 21:10:28 | Query | 0.017 | Writing to net | select itemtagid,itemid,tag,value from item_tag |
| 18 | 2024-04-23 23:44:28 | Query | 0.014 | Sending data | select triggerid,description,expression,error,priority,type,valu |
| 18 | 2024-04-24 02:21:28 | Query | 0.024 | Sending data | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
| 18 | 2024-04-24 07:33:28 | Query | 0.046 | Writing to net | select i.itemid,i.hostid,i.status,i.type,i.value_type,i.key_,i.s |
+---------------+---------------------+---------+-------+----------------+----------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wichtig ist auch, dass wir in dieser Auswertung nur die Einträge sehen, wenn der Thread ETWAS gemacht hat (State &lt;code&gt;Sleep&lt;/code&gt; haben wir ausgeblendet). Interessant ist auch, dass wir diese (persistente) Verbindung nicht vor dem 17. April sehen, aber ich habe im Moment KEINE Erklärung dafür aus operativer Sicht (Restart etc.). Wahrscheinlich muss die Anwendung (Zabbix) das erklären.&lt;/p&gt;
&lt;h2 id="globale-variablen"&gt;Globale Variablen&lt;/h2&gt;
&lt;p&gt;Interessant sind auch die Informationen in der Tabelle &lt;code&gt;global_variables&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SELECT variable_name, ts, variable_value
 FROM global_variables
 WHERE machine_name = @machine_name
 AND variable_name IN (
 SELECT variable_name
 FROM global_variables
 WHERE machine_name = @machine_name
 GROUP BY variable_name
 HAVING COUNT(*) &amp;gt; 1
 )
 ORDER BY ts, variable_name
;
+---------------------------+---------------------+----------------+
| variable_name | ts | variable_value |
+---------------------------+---------------------+----------------+
| auto_increment_increment | 2024-03-09 22:10:42 | 1 |
| auto_increment_offset | 2024-03-09 22:10:42 | 1 |
| read_only | 2024-03-09 22:10:42 | OFF |
| slave_parallel_max_queued | 2024-03-09 22:10:42 | 131072 |
| slave_parallel_threads | 2024-03-09 22:10:42 | 0 |
| slave_parallel_workers | 2024-03-09 22:10:42 | 0 |
| slave_skip_errors | 2024-03-09 22:10:42 | OFF |
| system_time_zone | 2024-03-09 22:10:42 | CET |

| read_only | 2024-03-27 09:42:50 | ON |
| slave_skip_errors | 2024-03-27 12:33:13 | 1032 |
| slave_skip_errors | 2024-03-27 12:35:13 | OFF |
| slave_skip_errors | 2024-03-27 12:42:13 | 1032 |
| slave_skip_errors | 2024-03-27 12:50:13 | OFF |

| slave_parallel_threads | 2024-04-02 10:17:32 | 8 |
| slave_parallel_workers | 2024-04-02 10:17:32 | 8 |
| slave_parallel_max_queued | 2024-04-02 10:22:32 | 1048576 |
| slave_parallel_max_queued | 2024-04-02 10:23:32 | 4194304 |
| slave_parallel_max_queued | 2024-04-02 10:25:32 | 16777216 |
| slave_parallel_threads | 2024-04-02 10:25:32 | 16 |
| slave_parallel_workers | 2024-04-02 10:25:32 | 16 |
| slave_parallel_threads | 2024-04-02 10:28:32 | 32 |
| slave_parallel_workers | 2024-04-02 10:28:32 | 32 |
| auto_increment_increment | 2024-04-02 10:39:32 | 2 |
| auto_increment_offset | 2024-04-02 10:39:32 | 2 |
| slave_parallel_max_queued | 2024-04-02 10:57:32 | 131072 |
| slave_parallel_threads | 2024-04-02 10:57:32 | 0 |
| slave_parallel_workers | 2024-04-02 10:57:32 | 0 |
| system_time_zone | 2024-04-02 10:57:32 | CEST |

| slave_parallel_max_queued | 2024-04-16 14:06:32 | 16777216 |
| slave_parallel_threads | 2024-04-16 14:06:32 | 8 |
| slave_parallel_workers | 2024-04-16 14:06:32 | 8 |
| slave_parallel_max_queued | 2024-04-16 14:26:32 | 131072 |
| slave_parallel_threads | 2024-04-16 14:26:32 | 0 |
| slave_parallel_workers | 2024-04-16 14:26:32 | 0 |

| slave_parallel_max_queued | 2024-04-17 09:03:32 | 16777216 |
| slave_parallel_threads | 2024-04-17 09:03:32 | 16 |
| slave_parallel_workers | 2024-04-17 09:03:32 | 16 |

| slave_parallel_max_queued | 2024-04-24 08:26:32 | 131072 |
| slave_parallel_threads | 2024-04-24 08:26:32 | 0 |
| slave_parallel_workers | 2024-04-24 08:26:32 | 0 |
| read_only | 2024-04-24 08:42:32 | OFF |
+---------------------------+---------------------+----------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hier sieht man sehr genau, wann und was an der Datenbank gemacht wurde:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Am 9. März wurde &lt;code&gt;dbstat&lt;/code&gt; zum ersten Mal installiert.&lt;/li&gt;
&lt;li&gt;Dann am 27. März (vor Ostern) scheint es dann Probleme mit der Replikation gegeben zu haben (hier wurde die neue Version von &lt;code&gt;dbstat&lt;/code&gt; installiert, die das gleichzeitige Sammeln auf Master und Slave erlaubt. Dies führte zu Replikationsfehlern, die teilweise behoben wurden).&lt;/li&gt;
&lt;li&gt;Am 2. April (nach Ostern) haben wir dann versucht, den Rückstand mit der parallelen Replikation aufzuholen. Man sieht auch, dass &lt;code&gt;AUTO_INCREMENT_OFFSET&lt;/code&gt; und &lt;code&gt;AUTO_INCREMENT_INCREMENT&lt;/code&gt; geändert wurden. Hier haben wir einen Fehler in der Datenbankkonfiguration korrigiert&amp;hellip;&lt;/li&gt;
&lt;li&gt;Ausserdem ist zu sehen, dass die Zeitzone von &lt;code&gt;CET&lt;/code&gt; auf &lt;code&gt;CEST&lt;/code&gt; geändert hat (Sommerzeit!) Warum erst am 2. April ist mir nicht ganz klar. (Vielleicht weil das über die Replikation gekommen ist?)&lt;/li&gt;
&lt;li&gt;Dann haben wir am 16. und 17. April haben wir versucht einen &amp;ldquo;Bug&amp;rdquo; in der parallelen Replikation zu reproduzieren. Anscheinend haben wir den Wert nicht zurückgesetzt. Denn erst nach dem Neustart am 24. April (übliches zweiwöchentliches Wartungsfenster) wurde der Wert wieder zurückgesetzt.&lt;/li&gt;
&lt;li&gt;Am 24. April sieht man auch, dass die Datenbank jetzt die Rolle des aktiven Masters übernommen hat (&lt;code&gt;read_only = off&lt;/code&gt;). Es hat also ein Gracefull-Switchover stattgefunden&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Fazit&lt;/strong&gt;: Ein sehr nützliches Feature um zu sehen, wann was geändert wurde. Obwohl ich alle diese Operationen genau verfolgt habe, bin ich doch erstaunt über die Aussagekraft dieses Features. Ich würde mir wünschen, dass es in allen Datenbanken installiert wird&amp;hellip;&lt;/p&gt;
&lt;h2 id="metadata-lock-und-innodb-transaction-lock"&gt;Metadata Lock und InnoDB Transaction Lock&lt;/h2&gt;
&lt;p&gt;Aufgrund des geringen Traffics auf unseren Datenbanken sehen wir hier leider nicht allzu viel Spannendes.&lt;/p&gt;
&lt;p&gt;Hier sind die Metadata Locks, die wir in den den letzten 24 Stunden auf dem Master &amp;ldquo;erwischt&amp;rdquo; haben:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+---------------+---------------------+--------+-----------------+--------------+---------------+-----------------+----------------------------------------------------------------------+
| connection_id | ts | user | host | table_schema | table_name | state | SUBSTR(REGEXP_REPLACE(REPLACE(query, &amp;quot;&amp;lt;br&amp;gt;n&amp;quot;, ' '), '&amp;lt;br&amp;gt; +', ' '), 1, 64) |
+---------------+---------------------+--------+-----------------+--------------+---------------+-----------------+----------------------------------------------------------------------+
| 18 | 2024-04-23 14:16:47 | zabbix | localhost:51252 | zabbix | triggers | Writing to net | select triggerid,description,expression,error,priority,type,valu |
| 1325025 | 2024-04-23 16:01:47 | zabbix | localhost:50150 | | | init for update | delete from history_text where itemid=85477 and clock&amp;lt;1678167661 |
| 1325025 | 2024-04-23 16:01:47 | zabbix | localhost:50150 | zabbix | history_text | init for update | delete from history_text where itemid=85477 and clock&amp;lt;1678167661 |
| 1365229 | 2024-04-24 02:13:47 | root | localhost:38096 | dbstat | global_status | Writing to net | SELECT /*!40001 SQL_NO_CACHE */ `machine_name`, `variable_name`, |
| 18 | 2024-04-24 03:10:47 | zabbix | localhost:51252 | zabbix | item_tag | Writing to net | select itemtagid,itemid,tag,value from item_tag |
| 1368524 | 2024-04-24 04:41:47 | zabbix | localhost:38112 | | | | NULL |
| 1368524 | 2024-04-24 04:41:47 | zabbix | localhost:38112 | zabbix | history_uint | | NULL |
| 18 | 2024-04-24 05:46:47 | zabbix | localhost:51252 | zabbix | item_tag | Sending data | select itemtagid,itemid,tag,value from item_tag |
+---------------+---------------------+--------+-----------------+--------------+---------------+-----------------+----------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;InnoDB-Locks haben wir in den letzten 24 Stunden repektive 7 Tage keine gefunden.&lt;/p&gt;
&lt;p&gt;Es wäre interessant, ein System zu sehen, auf dem mehr passiert&amp;hellip;&lt;/p&gt;
&lt;h2 id="globaler-status"&gt;Globaler Status&lt;/h2&gt;
&lt;p&gt;Wenn ein normales Datenbankmonitoring wie z.B. der &lt;a href="https://www.fromdual.com/fromdual-performance-monitor" target="_blank"&gt;FromDual Performance Monitor für MariaDB und MySQL&lt;/a&gt; (fpmmm) mit Zabbix verwendet wird, ist dieses Feature nicht unbedingt notwendig. Die meisten unserer Kunden haben jedoch kein brauchbares Monitoring im Einsatz. Daher wäre dieses Feature sehr nützlich für Post-Morten-Analysen&amp;hellip;&lt;/p&gt;
&lt;p&gt;Zum Beispiel InnoDB-Lock-Waits, minuten-granular über die letzten 30 Tage (analog zu &lt;code&gt;sar&lt;/code&gt; aus &lt;code&gt;sysstat&lt;/code&gt;):&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.fromdual.com/sites/default/files/innodb_row_lock_waits.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Hier sieht man, dass die Datenbank am 10. April zwischen 08:37 und 08:41 neu gestartet wurde. Das könnte man zwar auch anders herausfinden, ist aber leider oft aus verschiedenen Gründen nicht möglich (Error Log wegrotiert, etc.).&lt;/p&gt;
&lt;p&gt;Interessant ist auch der Trendbruch um den 2. April. Zu diesem Zeitpunkt haben wir mit der parallelen Replikation rumexperimentiert. Es sollte kein Failover gewesen sein (siehe &lt;code&gt;GLOBAL VARIABLES&lt;/code&gt;, weiter oben).&lt;/p&gt;
&lt;p&gt;Obwohl die parallele Replikation später wieder deaktiviert wurde, gab es mehr Locks. Eine ähnliche Situation um den 16./17. April auch hier haben wir mit der parallelen Replikation herumgespielt, was sich auf das Locking-Verhalten ausgewirkt zu haben scheint.&lt;/p&gt;
&lt;p&gt;Auch mit diesem Feature gibt es viele Möglichkeiten die Datenbank zu untersuchen. Leider ist unsere Datenbank relativ langweilig: Hauptsächlich monotoner Traffic (der durch das Monitoring reichlich vorhanden ist) und aussergwöhnlicher Traffic in sehr geringem Umfang.&lt;/p&gt;
&lt;p&gt;Anmerkung: Dieser Text wurde mit der Unterstützung von &lt;a href="https://www.deepl.com/" target="_blank"&gt;DeepL&lt;/a&gt; optimiert.&lt;/p&gt;</description></item><item><title>MariaDBs parallele Replikation zum Aufholen</title><link>https://www.fromdual.com/de/blog/mariadbs-parallele-replikation-zum-aufholen/</link><pubDate>Mon, 08 Apr 2024 11:01:45 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/mariadbs-parallele-replikation-zum-aufholen/</guid><description>&lt;p&gt;Aufgrund eines applikatorischen Fehlers ist unsere Replikation während 5 Tagen stehen geblieben (über Ostern). Nachdem das Problem gelöst war, sollte die Replikation aufholen, was sich als sehr zäh herausstellte. All die üblichen Tricks (&lt;code&gt;innodb_flush_log_at_trx_commit&lt;/code&gt;, &lt;code&gt;sync_binlog&lt;/code&gt;, etc.) wurden bereits ausgereizt. Also haben wir uns an der parallelen Replikation des MariaDB Servers versucht.&lt;/p&gt;
&lt;p&gt;Per default ist die parallel Replikation deaktiviert:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SHOW GLOBAL VARIABLES LIKE '%parallel%';
+-------------------------------+------------+
| Variable_name | Value |
+-------------------------------+------------+
| slave_domain_parallel_threads | 0 |
| slave_parallel_max_queued | 131072 |
| slave_parallel_mode | optimistic |
| slave_parallel_threads | 0 |
| slave_parallel_workers | 0 |
+-------------------------------+------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Durch setzen der Server Variablen &lt;code&gt;slave_parallel_threads&lt;/code&gt; wird die parallele Replikation aktiviert:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SET GLOBAL slave_parallel_threads = 8;
ERROR 1198 (HY000): This operation cannot be performed as you have a running slave ''; run STOP SLAVE '' first
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dies muss aber bei gestoppter Replikation erfolgen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; STOP SLAVE;
SQL&amp;gt; SET GLOBAL slave_parallel_threads = 8;
SQL&amp;gt; START SLAVE;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anschliessend hat die Replikation etwas schneller aufgeholt. Da wir aber ungeduldig waren, haben wir versucht, das ganze noch schneller hin zu kriegen. Mit dem Befehl:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SHOW SLAVE STATUS&amp;lt;br&amp;gt;G
...
Slave_SQL_Running_State: Waiting for room in worker thread event queue
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;haben wir die folgende Meldung gefunden. Man würde sie auch über den Befehl &lt;code&gt;SHOW PROCESSLIST&lt;/code&gt; sehen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SHOW PROCESSLIST;
+--------+-------------+- ... -+-----------+------+-----------------------------------------------+- ... -+
| Id | User | ... | Command | Time | State | ... |
+--------+-------------+- ... -+-----------+------+-----------------------------------------------+- ... -+
... ... ...
| 212496 | system user | ... | Slave_SQL | 16 | Waiting for room in worker thread event queue | ... |
+--------+-------------+- ... -+-----------+------+-----------------------------------------------+- ... -+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Gemäss Doku kann es in diesem Fall helfen, die Variable &lt;code&gt;slave_parallel_max_queued&lt;/code&gt; etwas zu vergrössern (Achtung: Oom!).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; STOP SLAVE;
SQL&amp;gt; SET GLOBAL slave_parallel_max_queued = 1*1024*1024;
SQL&amp;gt; SHOW GLOBAL VARIABLES LIKE '%parallel%';
+-------------------------------+------------+
| Variable_name | Value |
+-------------------------------+------------+
| slave_domain_parallel_threads | 0 |
| slave_parallel_max_queued | 1048576 |
| slave_parallel_mode | optimistic |
| slave_parallel_threads | 8 |
| slave_parallel_workers | 8 |
+-------------------------------+------------+
SQL&amp;gt; START SLAVE;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wir haben mit den Werten &lt;code&gt;slave_parallel_threads&lt;/code&gt; im Bereich von 4 bis 32 (bei 8 vCores) und bei &lt;code&gt;slave_parallel_max_queued&lt;/code&gt; im Bereich von 128 kbyte bis 32 Mbyte rumgespielt.
&lt;strong&gt;Achtung&lt;/strong&gt;: Nicht übertreiben: 32 Threads x 32 Mbyte = 1 Gbyte RAM (Oom)!&lt;/p&gt;
&lt;p&gt;Um herauszufinden, welche Werte das Optimum sind, müsste man ausführlicher testen und messen. Jedenfalls hat die Replikation nach circa einer Stunde die 5 Tage Rückstand aufgeholt, gegen Schluss etwas mehr als zu Beginn, was hoffentlich durch unsere Konfigurationsanpassungen verursacht wurde.&lt;/p&gt;
&lt;img src="https://www.fromdual.com/sites/default/files/slave_lag_parallel_replication.png" width="640" /&gt;
&lt;p&gt;Je nach dem, was gerade an DML Statements läuft sieht man, dass alle Threads genutzt werden können oder die einen Threads auf andere Threads warten müssen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SHOW PROCESSLIST;
+--------+-----------------+--------------+--------+---------------------------------------------------------------+------------+
| Id | User | Command | Time | State | Info |
+--------+-----------------+--------------+--------+---------------------------------------------------------------+------------+
| 2 | event_scheduler | Daemon | 506179 | Waiting for next activation | NULL |
| 191154 | root | Query | 0 | starting | show pr... |
| 208669 | replication | Binlog Dump | 297 | Master has sent all binlog to slave; waiting for more updates | NULL |
| 212495 | system user | Slave_IO | 20 | Waiting for master to send event | NULL |
| 212497 | system user | Slave_worker | 0 | Waiting for prior transaction to commit | NULL |
| 212498 | system user | Slave_worker | 0 | Waiting for prior transaction to commit | NULL |
| 212499 | system user | Slave_worker | 0 | Waiting for prior transaction to commit | NULL |
| 212500 | system user | Slave_worker | 0 | Waiting for prior transaction to commit | NULL |
| 212501 | system user | Slave_worker | 0 | Write_rows_log_event::write_row(-1) on table `history_uint` | insert ... |
| 212502 | system user | Slave_worker | 0 | Write_rows_log_event::write_row(-1) on table `history_uint` | insert ... |
| 212503 | system user | Slave_worker | 0 | Write_rows_log_event::write_row(-1) on table `history_str` | insert ... |
| 212504 | system user | Slave_worker | 0 | Waiting for prior transaction to commit | NULL |
| 212505 | system user | Slave_worker | 0 | Waiting for prior transaction to commit | NULL |
| 212506 | system user | Slave_worker | 0 | Waiting for prior transaction to commit | NULL |
| 212507 | system user | Slave_worker | 0 | Waiting for prior transaction to commit | NULL |
| 212510 | system user | Slave_worker | 0 | Waiting for prior transaction to commit | NULL |
| 212509 | system user | Slave_worker | 0 | Waiting for prior transaction to commit | NULL |
| 212508 | system user | Slave_worker | 0 | Waiting for prior transaction to commit | NULL |
| 212511 | system user | Slave_worker | 0 | Waiting for prior transaction to commit | NULL |
| 212512 | system user | Slave_worker | 0 | Waiting for prior transaction to commit | NULL |
| 212496 | system user | Slave_SQL | 16 | Waiting for room in worker thread event queue | NULL |
+--------+-----------------+--------------+--------+---------------------------------------------------------------+------------+

SQL&amp;gt; SHOW PROCESSLIST;
+--------+-----------------+--------------+--------+---------------------------------------------------------------+------------+
| Id | User | Command | Time | State | Info |
+--------+-----------------+--------------+--------+---------------------------------------------------------------+------------+
| 2 | event_scheduler | Daemon | 506197 | Waiting for next activation | NULL |
| 191154 | root | Query | 0 | starting | show pr... |
| 208669 | replication | Binlog Dump | 315 | Master has sent all binlog to slave; waiting for more updates | NULL |
| 212495 | system user | Slave_IO | 37 | Waiting for master to send event | NULL |
| 212497 | system user | Slave_worker | 0 | Delete_rows_log_event::ha_delete_row(-1) on table `history` | delete ... |
| 212498 | system user | Slave_worker | 0 | Delete_rows_log_event::find_row(-1) on table `history` | delete ... |
| 212499 | system user | Slave_worker | 0 | Delete_rows_log_event::ha_delete_row(-1) on table `history` | delete ... |
| 212500 | system user | Slave_worker | 0 | Delete_rows_log_event::find_row(-1) on table `history` | delete ... |
| 212501 | system user | Slave_worker | 0 | Delete_rows_log_event::find_row(-1) on table `history` | delete ... |
| 212502 | system user | Slave_worker | 0 | Delete_rows_log_event::find_row(-1) on table `history` | delete ... |
| 212503 | system user | Slave_worker | 0 | Delete_rows_log_event::find_row(-1) on table `history` | delete ... |
| 212504 | system user | Slave_worker | 0 | Delete_rows_log_event::ha_delete_row(-1) on table `history` | delete ... |
| 212505 | system user | Slave_worker | 0 | Delete_rows_log_event::find_row(-1) on table `history` | delete ... |
| 212506 | system user | Slave_worker | 0 | Delete_rows_log_event::ha_delete_row(-1) on table `history` | delete ... |
| 212507 | system user | Slave_worker | 0 | Delete_rows_log_event::find_row(-1) on table `history` | delete ... |
| 212510 | system user | Slave_worker | 0 | Delete_rows_log_event::find_row(-1) on table `history` | delete ... |
| 212509 | system user | Slave_worker | 0 | Delete_rows_log_event::find_row(-1) on table `history` | delete ... |
| 212508 | system user | Slave_worker | 0 | Delete_rows_log_event::find_row(-1) on table `history` | delete ... |
| 212511 | system user | Slave_worker | 0 | Delete_rows_log_event::find_row(-1) on table `history` | delete ... |
| 212512 | system user | Slave_worker | 0 | Delete_rows_log_event::find_row(-1) on table `history` | delete ... |
| 212496 | system user | Slave_SQL | 11 | Waiting for room in worker thread event queue | NULL |
+--------+-----------------+--------------+--------+---------------------------------------------------------------+------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unser Monitoring hat uns auch schön angezeigt, dass die CPU Load hoch ging, das I/O-System mehr zu tun kriegte und mehr Rows modifiziert wurden&amp;hellip;&lt;/p&gt;
&lt;img src="https://www.fromdual.com/sites/default/files/slave_lag_cpu.png" width="640" /&gt;
&lt;img src="https://www.fromdual.com/sites/default/files/slave_lag_io.png" width="640" /&gt;
&lt;img src="https://www.fromdual.com/sites/default/files/slave_lag_handler.png" width="640" /&gt;
&lt;p&gt;Was ebenfalls auffiel ist, dass mit parallel Replication plötzlich Foreing Key Fehler auftraten, ein Phänomen, dass wir so vorher noch nicht beobachtet hatten:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FromDual.maas2.prod2 - Warning: InnoDB Foreign Key error detected

Trigger: InnoDB Foreign Key error detected
Trigger status: PROBLEM
Trigger severity: Warning
Trigger URL: https://fromdual.com/innodb-foreign-key-error-detected

Item values: 1

1. InnoDB new Foreign Key error (FromDual.maas2.prod2:FromDual.MySQL.innodb.ForeignKey_new): 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mit dem Befehl &lt;code&gt;SHOW ENGINE INNODB STATUS&amp;lt;br&amp;gt;G&lt;/code&gt; kann man diese entsprechend inspizieren oder im Monitoring anschauen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;------------------------
LATEST FOREIGN KEY ERROR
------------------------
2024-04-02 10:36:39 0x7f36088ff640 Transaction:
TRANSACTION 7199599266, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
6 lock struct(s), heap size 1128, 3 row lock(s), undo log entries 1
MariaDB thread id 228555, OS thread handle 139870048613952, query id 28453893 Write_rows_log_event::write_row(-1) on table `alerts`
insert into alerts (alertid,actionid,eventid,userid,clock,mediatypeid,sendto,subject,message,status,error,esc_step,alerttype,acknowledgeid,parameters) values (203687,4,471733,3,1712044003,1,'xxx@fromdual.com','Zabbix server - High: Too many processes on Zabbix server','Trigger: Too many processes on Zabbix server
Trigger status: PROBLEM
Trigger severity: High
Trigger URL:

Item values: 309

1. Number of processes (Zabbix server:proc.num[]): 309',3,'',1,0,null,'{}')
Foreign key constraint fails for table `zabbix`.`alerts`:
,
 CONSTRAINT `c_alerts_2` FOREIGN KEY (`eventid`) REFERENCES `events` (`eventid`) ON DELETE CASCADE in parent table, in index alerts_3 tuple:
DATA TUPLE: 2 fields;
 ...

But in parent table `zabbix`.`events`, in index PRIMARY,
the closest match we can find is record:
PHYSICAL RECORD: n_fields 12; compact format; info bits 0
 ...
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="quellen"&gt;Quellen&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/kb/en/parallel-replication/" target="_blank"&gt;Parallel Replication&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>MariaDB Server aus den Quellen bauen</title><link>https://www.fromdual.com/de/blog/mariadb-server-aus-den-quellen-bauen/</link><pubDate>Thu, 04 Apr 2024 14:53:18 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/mariadb-server-aus-den-quellen-bauen/</guid><description>&lt;p&gt;Kürzlich musste ich ein neues MariaDB Feature testen, welches auf unseren Wunsch entwickelt wurde (&lt;a href="https://jira.mariadb.org/browse/MDEV-33782" target="_blank"&gt;MDEV-33782&lt;/a&gt;). Um dieses Feature zu testen musste ich aber den MariaDB Server selber aus den Quellen bauen, was ich schon seit längerem nicht mehr gemacht habe. Also eine neue Herausforderung, insbesondere mit &lt;code&gt;CMake&lt;/code&gt;&amp;hellip;&lt;/p&gt;
&lt;p&gt;I habe hierzu die MariaDB Dokumentation &lt;a href="https://mariadb.com/kb/en/get-build-and-test-latest-mariadb-the-lazy-way/" target="_blank"&gt;Get, Build and Test Latest MariaDB the Lazy Way&lt;/a&gt; befolgt um den Server zu bauen.&lt;/p&gt;
&lt;p&gt;Auf Ubuntu 22.04 hat es bei mir, aus mir nicht bekannten Gründen, nicht funktioniert. Also habe ich mir einen Ubuntu 23.04 (Lunar Lobster) LXC Container geclont und darin den MariaDB Server gebaut.&lt;/p&gt;
&lt;p&gt;Damit das ganze funktioniert musste aber vorher noch im Container &lt;code&gt;/etc/apt/sources.list&lt;/code&gt; durch die Paketquellen ergänzt werden:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;deb-src http://de.archive.ubuntu.com/ubuntu lunar main restricted universe multiverse
deb-src http://de.archive.ubuntu.com/ubuntu lunar-updates main restricted universe multiverse
deb-src http://de.archive.ubuntu.com/ubuntu lunar-security main restricted universe multiverse
deb-src http://de.archive.ubuntu.com/ubuntu lunar-backports main restricted universe multiverse
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dann wurde nach Vorgabe weiter verfahren:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; apt install build-essential bison
shell&amp;gt; apt build-dep mariadb-server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Der Entsprechende Branch wurde geclont:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; # git clone https://github.com/andremralves/server.git mariadb-MDEV-33782
shell&amp;gt; # git branch --all
shell&amp;gt; git clone --branch MDEV-33782 --single-branch https://github.com/andremralves/server.git mariadb-MDEV-33782
shell&amp;gt; cd mariadb-MDEV-33782
shell&amp;gt; # git checkout 11.5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;und dann der Server gebaut. Dies hat auf meiner alten Maschine etwa 20 Minuten gedauert. &lt;code&gt;CMake&lt;/code&gt; ist noch auf einen Fehler gelaufen, welcher mit dem Nachinstallieren des entsprechenden Pakets gelöst wurde (&lt;a href="https://jira.mariadb.org/browse/MDEV-33815" target="_blank"&gt;MDEV-33815&lt;/a&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; apt install libgnutls28-dev
shell&amp;gt; cmake . -DBUILD_CONFIG=mysql_release &amp;amp;&amp;amp; make -j8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Die Tests wurden ausgeführt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; cd mysql-test
shell&amp;gt; ./mtr rpl.rpl_create_drop_event
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Und dann ein &lt;a href="https://mariadb.com/kb/en/creating-the-mariadb-binary-tarball/" target="_blank"&gt;Binary-Tarball gebaut&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; make package
Run CPack packaging tool...
CPack: Create package using TGZ
CPack: Install projects
CPack: - Run preinstall target for: MariaDB
CPack: - Install project: MariaDB []
CPack: Create package
CPack: - package: /root/mariadb-MDEV-33782/mariadb-11.5.0-linux-x86_64.tar.gz generated.
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>MaxScale Konfigurations-Synchronisation</title><link>https://www.fromdual.com/de/blog/maxscale-konfigurations-synchronisation/</link><pubDate>Tue, 02 Apr 2024 17:43:29 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/maxscale-konfigurations-synchronisation/</guid><description>&lt;h2 id="inhaltsverzeichnis"&gt;Inhaltsverzeichnis&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/maxscale-konfigurations-synchronisation/#overview"&gt;Übersicht&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/maxscale-konfigurations-synchronisation/#preparations"&gt;Vorbereitungen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/maxscale-konfigurations-synchronisation/#activate-configuration-synchronization"&gt;MaxScale Konfigurations-Synchronisation aktivieren&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/maxscale-konfigurations-synchronisation/#change-parameter"&gt;MaxScale Parameter ändern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/maxscale-konfigurations-synchronisation/#add-new-slave"&gt;Neuer Slave hinzufügen und MaxScale bekannt machen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/maxscale-konfigurations-synchronisation/#remove-old-slave"&gt;Alter Slave entfernen und MaxScale bekannt machen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/maxscale-konfigurations-synchronisation/#how-is-configuration-synchronized"&gt;Wie wird die Konfiguration synchronisiert?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/maxscale-konfigurations-synchronisation/#what-happens-on-conflict"&gt;Was passiert im Konfliktfall?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/maxscale-konfigurations-synchronisation/#tests"&gt;Tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/maxscale-konfigurations-synchronisation/#deactivate-configuration-synchronization"&gt;MaxScale Konfigurations-Synchronisation wieder deaktivieren&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/maxscale-konfigurations-synchronisation/#literature"&gt;Literatur/Quellen&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="übersicht"&gt;Übersicht&lt;/h2&gt;
&lt;p&gt;Ein Feature, welches ich beim Stöbern kürzlich entdecke habe, ist die MaxScale Konfigurations-Synchronisation Funktionalität.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.fromdual.com/sites/default/files/maxscale_config_sync.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Pon Suresh Pandian hat bereits 2022 einen &lt;a href="https://mariadb.com/resources/blog/mariadb-maxscale-6-0-native-clustering/" target="_blank"&gt;Blog-Artikel&lt;/a&gt; über dieses Feature geschrieben, der noch etwas ausführlicher ist, als dieser Beitrag hier.&lt;/p&gt;
&lt;h2 id="vorbereitungen"&gt;Vorbereitungen&lt;/h2&gt;
&lt;p&gt;Es wurde eine &lt;a href="https://linuxcontainers.org/incus/" target="_blank"&gt;Incus Container&lt;/a&gt;-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 &lt;a href="https://mariadb.com/downloads/community/maxscale/" target="_blank"&gt;heruntergeladen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Die Datenbank-Konfiguration sieht für alle 3 Knoten analog aus:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#
# /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
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Die MaxScale Knoten wurden wie im Artikel &lt;a href="https://www.fromdual.com/sharding-mit-mariadb-maxscale"&gt;Sharding mit MariaDB MaxScale&lt;/a&gt; beschrieben gebaut.&lt;/p&gt;
&lt;p&gt;Der &lt;code&gt;maxscale_admin&lt;/code&gt; User hat genau die selben Rechte wie dort beschrieben, der &lt;code&gt;maxscale_monitor&lt;/code&gt; User hat folgende Rechte erhalten:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RELOAD, SUPER, REPLICATION SLAVE, READ_ONLY ADMIN
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Siehe auch hier: &lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-mariadb-monitor/#required-grants" target="_blank"&gt;Required Grants&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Die MaxScale Start-Konfiguration sieht wie folgt aus:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#
# /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
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Wichtig&lt;/strong&gt;: Die Konfiguration sollte auf allen MaxScale Knoten gleich aussehen!&lt;/p&gt;
&lt;p&gt;Und dann noch ein paar Checks um sicher zu sein, dass alles stimmt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; maxctrl list listeners
┌───────────────┬──────┬──────┬─────────┬──────────────┐
│ Name │ Port │ Host │ State │ Service │
├───────────────┼──────┼──────┼─────────┼──────────────┤
│ WriteListener │ 3306 │ :: │ Running │ WriteService │
└───────────────┴──────┴──────┴─────────┴──────────────┘

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

shell&amp;gt; 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&amp;gt; SELECT @@hostname, test.* FROM test.test;
+------------+----+-----------+---------------------+
| @@hostname | id | data | ts |
+------------+----+-----------+---------------------+
| deb12-n2 | 1 | Some data | 2024-03-26 09:40:21 |
+------------+----+-----------+---------------------+

SQL&amp;gt; SELECT @@hostname, test.* FROM test.test FOR UPDATE;
+------------+----+-----------+---------------------+
| @@hostname | id | data | ts |
+------------+----+-----------+---------------------+
| deb12-n1 | 1 | Some data | 2024-03-26 09:40:21 |
+------------+----+-----------+---------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Und noch ein weiterer Test ob MaxScale den Failover auch wirklich richtig ausführt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; 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&amp;gt; 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]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Welcher MaxScale Knoten gerade für das Monitoring und Failover zuständig ist (&lt;code&gt;cooperatve_monitoring&lt;/code&gt;) kann wir folgt eruiert werden:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; maxctrl show monitor Replication-Monitor | grep -e 'Diagnostics' -e '&amp;quot;primary&amp;quot;' -e 'lock_held' | uniq
│ Monitor Diagnostics │ { │
│ │ &amp;quot;primary&amp;quot;: true, │
│ │ &amp;quot;lock_held&amp;quot;: true, │
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Es sollte sichergestellt sein, das bis hier hin alles sauber funktioniert. Ansonsten haben die weiteren Schritte keinen wirklichen Sinn.&lt;/p&gt;
&lt;h2 id="maxscale-konfigurations-synchronisation-aktivieren"&gt;MaxScale Konfigurations-Synchronisation aktivieren&lt;/h2&gt;
&lt;p&gt;Für die Konfigurations-Synchronisation braucht es einen eigenen Datenbank-User mit folgenden Rechten:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; CREATE USER 'maxscale_confsync'@'%' IDENTIFIED BY 'secret';
SQL&amp;gt; GRANT SELECT, INSERT, UPDATE, CREATE ON `mysql`.`maxscale_config` TO maxscale_confsync@'%';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anschliessend muss MaxScale entsprechend konfiguriert werden (auf beiden MaxScale Knoten), damit die Konfigurations-Synchronisation aktiviert wird. Diese Konfiguration erfolgt im globalen MaxScale Abschnitt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#
# /etc/maxscale.cnf
#

[maxscale]
config_sync_cluster = Replication-Monitor
config_sync_user = maxscale_confsync
config_sync_password = secret
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dann erfolgt der Neustart der MaxScale Knoten:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; systemctl restart maxscale
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Die MaxScale Konfigurations-Synchronisation kann auch dynamisch aktiviert und deaktiviert werden:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; maxctrl show maxscale | grep config_sync
│ │ &amp;quot;config_sync_cluster&amp;quot;: null, │
│ │ &amp;quot;config_sync_db&amp;quot;: &amp;quot;mysql&amp;quot;, │
│ │ &amp;quot;config_sync_interval&amp;quot;: &amp;quot;5000ms&amp;quot;, │
│ │ &amp;quot;config_sync_password&amp;quot;: null, │
│ │ &amp;quot;config_sync_timeout&amp;quot;: &amp;quot;10000ms&amp;quot;, │
│ │ &amp;quot;config_sync_user&amp;quot;: null, │
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hier ist wichtig, die richtige Reihenfolge der 3 Befehle einzuhalten, da es sonst einen Fehler gibt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; MAXCTRL_WARNINGS=0 maxctrl alter maxscale config_sync_user='maxscale_confsync'
shell&amp;gt; MAXCTRL_WARNINGS=0 maxctrl alter maxscale config_sync_password='secret'
shell&amp;gt; MAXCTRL_WARNINGS=0 maxctrl alter maxscale config_sync_cluster='Replication-Monitor'
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="maxscale-parameter-ändern"&gt;MaxScale Parameter ändern&lt;/h2&gt;
&lt;p&gt;Als ersten Test haben wir die MaxScale Monitor Variable &lt;code&gt;monitor_interval&lt;/code&gt; ins Auge gefasst, die in diesem Fall auf beiden MaxScale-Knoten sogar unterschiedlich ist:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; maxctrl show monitor Replication-Monitor | grep monitor_interval
│ │ &amp;quot;monitor_interval&amp;quot;: &amp;quot;750ms&amp;quot;,

shell&amp;gt; maxctrl show monitor Replication-Monitor | grep monitor_interval
│ │ &amp;quot;monitor_interval&amp;quot;: &amp;quot;1000ms&amp;quot;,
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mit dem &lt;code&gt;alter monitor&lt;/code&gt; Befehl kann die Variable jetzt auf einem MaxScale Knoten gesetzt werden:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; MAXCTRL_WARNINGS=0 maxctrl alter monitor Replication-Monitor monitor_interval=500ms
OK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;was einerseits im MaxScale Error Log ersichtlich ist:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2024-03-26 14:09:16 notice : (ConfigManager); Updating to configuration version 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;anderseits sollte der Wert innerhalb von 5 Sekunden (&lt;code&gt;config_sync_interval&lt;/code&gt;) auf den zweiten MaxScale Knoten propagiert werden, was mit dem obigen Befehl überprüft werden kann.&lt;/p&gt;
&lt;h2 id="neuer-slave-hinzufügen-und-maxscale-bekannt-machen"&gt;Neuer Slave hinzufügen und MaxScale bekannt machen&lt;/h2&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; maxctrl create server deb12-n3 10.139.158.39

shell&amp;gt; MAXCTRL_WARNINGS=0 maxctrl link monitor Replication-Monitor deb12-n3
OK

shell&amp;gt; MAXCTRL_WARNINGS=0 maxctrl link service WriteService deb12-n3
OK

shell&amp;gt; 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 │
└──────────┴────────────────┴──────┴─────────────┴─────────────────┴────────────┴─────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="alter-slave-entfernen-und-maxscale-bekannt-machen"&gt;Alter Slave entfernen und MaxScale bekannt machen&lt;/h2&gt;
&lt;p&gt;Bevor ein Slave gelöscht werden kann, sollte er bei einem MaxScale Knoten aus dem Replikations-Cluster entfernt werden:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; maxctrl destroy server deb12-n1 --force
OK

shell&amp;gt; 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 │
└──────────┴────────────────┴──────┴─────────────┴─────────────────┴────────────┴─────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anschliessend kann der Slave abgebaut werden.&lt;/p&gt;
&lt;h2 id="wie-wird-die-konfiguration-synchronisiert"&gt;Wie wird die Konfiguration synchronisiert?&lt;/h2&gt;
&lt;p&gt;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&amp;hellip;&lt;/p&gt;
&lt;p&gt;Die Konfiguration wird in der Tabelle &lt;code&gt;mysql.maxscale_config&lt;/code&gt; abgespeichert, welche wie folgt aussieht:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Diese Tabelle hat in etwa folgenden Inhalt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; 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 | {&amp;quot;config&amp;quot;:[{&amp;quot;id&amp;quot;:&amp;quot;deb12-n1&amp;quot;,&amp;quot;typ ... ter_name&amp;quot;:&amp;quot;Replication-Monitor&amp;quot;} | deb12-mxs1 | {&amp;quot;deb12-mxs1&amp;quot;: &amp;quot;OK&amp;quot;, &amp;quot;deb12-mxs2&amp;quot;: &amp;quot;OK&amp;quot;} |
+---------------------+---------+-----------------------------------------------------------------------+------------+------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eine lokale Kopie liegt zur Sicherheit auf jedem Knoten vor:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; cut -b-32 /var/lib/maxscale/maxscale-config.json
{&amp;quot;config&amp;quot;:[{&amp;quot;id&amp;quot;:&amp;quot;deb12-n2&amp;quot;,&amp;quot;typ
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="was-passiert-im-konfliktfall"&gt;Was passiert im Konfliktfall?&lt;/h2&gt;
&lt;p&gt;Siehe hierzu auch: &lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-mariadb-maxscale-configuration-guide/#error-handling-in-configuration-synchronization" target="_blank"&gt;Error Handling in Configuration Synchronization&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Wenn zeitgleich (innerhalb von &lt;code&gt;config_sync_interval&lt;/code&gt;?) auf zwei unterschiedlichen MaxScale Knoten die Konfiguration geändert wird erhalten wir folgende Fehlermeldung:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Error: Server at http://127.0.0.1:8989 responded with 400 Bad Request to `PATCH monitors/Replication-Monitor`
{
 &amp;quot;errors&amp;quot;: [
 {
 &amp;quot;detail&amp;quot;: &amp;quot;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.&amp;quot;
 }
 ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Folgender Befehl hilft allenfalls bei grösseren Störungen das Problem zu erkennen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; maxctrl show maxscale | grep -A9 'Config Sync'
│ Config Sync │ { │
│ │ &amp;quot;checksum&amp;quot;: &amp;quot;0052fe6f775168bf00778abbe37775f6f642adc7&amp;quot;, │
│ │ &amp;quot;nodes&amp;quot;: { │
│ │ &amp;quot;deb12-mxs1&amp;quot;: &amp;quot;OK&amp;quot;, │
│ │ &amp;quot;deb12-mxs2&amp;quot;: &amp;quot;OK&amp;quot; │
│ │ }, │
│ │ &amp;quot;origin&amp;quot;: &amp;quot;deb12-mxs2&amp;quot;, │
│ │ &amp;quot;status&amp;quot;: &amp;quot;OK&amp;quot;, │
│ │ &amp;quot;version&amp;quot;: 3 │
│ │ } │
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="tests"&gt;Tests&lt;/h2&gt;
&lt;p&gt;Alle Tests wurden auch unter Last ausgeführt. Folgende Tests liefen parallel:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;insert_test.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;insert_test.sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mixed_test.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;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&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;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&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Alle Tests sind bei allen Manipulationen einwandfrei und ohne Probleme durchgelaufen.&lt;/p&gt;
&lt;h2 id="maxscale-konfigurations-synchronisation-wieder-deaktivieren"&gt;MaxScale Konfigurations-Synchronisation wieder deaktivieren&lt;/h2&gt;
&lt;p&gt;Auf beiden MaxScale Knoten ist folgender Befehl auszuführen und damit ist die Konfigurations-Synchronisation wieder beendet:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; MAXCTRL_WARNINGS=0 maxctrl alter maxscale config_sync_cluster=''
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="literaturquellen"&gt;Literatur/Quellen&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-mariadb-maxscale-configuration-guide/#config_sync_cluster" target="_blank"&gt;MaxScale globale Konfigurationsvariable: config_sync_cluster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-mariadb-maxscale-configuration-guide/#configuration-synchronization" target="_blank"&gt;MaxScale Configuration Synchronization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/resources/blog/mariadb-maxscale-6-0-native-clustering/" target="_blank"&gt;Pon Suresh Pandian, MariaDB, 24. August 2022: MariaDB MaxScale 6.0 Native Clustering&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jira.mariadb.org/browse/MXS-4144" target="_blank"&gt;Where to get maxscale 6 mysql.maxscale_config table source sql&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-setting-up-mariadb-maxscale/" target="_blank"&gt;Setting up MariaDB MaxScale&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-configuring-the-mariadb-monitor/" target="_blank"&gt;Configuring the MariaDB Monitor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/mariadb-maxscale-load-balancer-with-master-master-replication"&gt;MariaDB MaxScale Load Balancer with Master/Master Replication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-mariadb-monitor/#configuration" target="_blank"&gt;MariaDB Monitor - Configuration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Sharding mit MariaDB MaxScale</title><link>https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/</link><pubDate>Mon, 18 Mar 2024 16:08:04 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/</guid><description>&lt;h2 id="inhaltsverzeichnis"&gt;Inhaltsverzeichnis&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#overview"&gt;Übersicht&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#preparation"&gt;Vorbereitung der Shards (MariaDB Datenbank Instanzen)&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#creating-test-data"&gt;Testdaten erstellen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#creating-roles-and-users"&gt;Rollen und User erstellen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#maxscale-monitor-user"&gt;MaxScale Monitor User&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#mascale-admin-user"&gt;MaxScale Admin User&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#create-application-role-and-accounts"&gt;Applikationsrolle und Accounts erstellen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#proxy-protocol"&gt;Proxy Protokoll&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#maxscale-schema-router-configuration"&gt;MaxScale SchemaRouter Konfiguration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#starting-and-stopping-maxscale"&gt;Starten und Stoppen des MaxScale Load Balancers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#application-tests"&gt;Applikations-Tests&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#simple-applicationt-test"&gt;Einfache Applikations-Tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#new-command-show-shards"&gt;Neuer Befehl &lt;code&gt;show shards&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#general-tests"&gt;Allgemeinere Test&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#less-simple-tests"&gt;Weniger einfache Test&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#more-complex-appliation-tests"&gt;Komplexere Applikations-Tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#cross-shard-tests"&gt;Cross-Shard-Tests&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#operation-of-a-sharding-system"&gt;Betrieb eines MaxScale Sharding-Systems&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#do-on-all-shards"&gt;Do-on-all-shards&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#invalidate-database-map-cache"&gt;Invalidieren des Database Map Caches&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#changing-schema-router-variables-dynamically"&gt;Wie ändert man SchemaRouter Variablen dynamisch?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#adding-and-removing-a-tenant"&gt;Hinzufügen und Entfernen eines Mandanten&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#moving-a-tenant"&gt;Umziehen eines Mandanten&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#adding-or-removing-a-shard"&gt;Hinzufügen oder entfernen eines Shards&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#adapting-configuration-files"&gt;Anpassen der Konfigurationsdateien&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#maintenance-work-on-a-shard"&gt;Wartungsarbeiten am Shard&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#observation-of-a-sharding-system"&gt;Beobachtung / Observierung eines MariaDB MaxScale Sharding-Systems&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/sharding-mit-mariadb-maxscale/#literature"&gt;Literatur / Quellen&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="übersicht"&gt;Übersicht&lt;/h2&gt;
&lt;p&gt;Dieses Feature sollte mehr oder weniger mit MariaDB MaxScale 6.x.y, 22.08.x, 23.02.x, 23.08.x und 24.02.x funktionieren. Wir haben es mit der neusten MaxScale Version 23.08.05 getestet, da wir mit einer älteren Version auf Problem gestossen sind (&lt;a href="https://jira.mariadb.org/browse/MXS-5026" target="_blank"&gt;MXS-5026&lt;/a&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; maxscale --version
MaxScale 23.08.5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Als Datenbank Backend (Shards) haben wir MariaDB 10.11 verwendet.&lt;/p&gt;
&lt;p&gt;Weniger als ca 2% aller uns bekannten MariaDB Installationen sind das, was wir technische unter Multi-Mandanten-Systeme (multi-tenant systems) verstehen (jeder Mandant (Kunde) in einer eigenen Datenbank (auch Schema genannt)).&lt;/p&gt;
&lt;p&gt;Daher wird dieses MariaDB MaxScale Feature relativ selten genutzt und es besteht die erhöhte Gefahr auf Bugs zu stossen, auf welche vorher noch niemand gestossen ist!&lt;/p&gt;
&lt;p&gt;Dieses Feature wird bei MariadDB MaxScale: &lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-schemarouter/" target="_blank"&gt;SchemaRouter&lt;/a&gt; genannt und wird immer noch als Beta-Qualität deklariert (&lt;a href="https://jira.mariadb.org/browse/MXS-5025" target="_blank"&gt;MXS-5025&lt;/a&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;maxctrl&amp;gt; show module schemarouter
┌─────────────┬────────────────────────────────────────────────┐
│ Module │ schemarouter │
├─────────────┼────────────────────────────────────────────────┤
│ Type │ Router │
├─────────────┼────────────────────────────────────────────────┤
│ Version │ V1.0.0 │
├─────────────┼────────────────────────────────────────────────┤
│ Maturity │ Beta │
├─────────────┼────────────────────────────────────────────────┤
│ Description │ A database sharding router for simple sharding │
├─────────────┼────────────────────────────────────────────────┤
│ ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Die Ziel-Topologie soll wie folgt aussehen: Jeder Kunde (Mandant, Tenant) liegt in einer eigenen Datenbank (= Schema). Die Datenbanken sind über mehrere MariaDB Instanzen (Shards) verteilt. Damit die Applikation transparent auf die Datenbank zugreifen kann wird ein Pärchen MaxScale Load Balancer davor geschaltet, welches weiss, wo der Kunde liegt und den Traffic entsprechend an das Shard weiterleitet. Damit die MaxScale Load Balancer hochverfügbar ausgelegt sind, wird noch eine virtuelle IP (VIP) z.B. mittels Keepalived vorgeschaltet. Wem das noch zu einfach ist, der kann jedes einzelne Shard noch als Master/Slave- oder Galera Cluster-Konstrukt auslegen&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.fromdual.com/sites/default/files/sharding_0.png" alt=""&gt;&lt;/p&gt;
&lt;h2 id="vorbereitung-der-shards-mariadb-datenbank-instanzen"&gt;Vorbereitung der Shards (MariaDB Datenbank Instanzen)&lt;/h2&gt;
&lt;p&gt;Als erstes hatten wir bei diesem PoC Problem mit der &lt;code&gt;test&lt;/code&gt; Datenbank. Durch das Löschen der &lt;code&gt;test&lt;/code&gt; Datenbank auf allen Shards ist das Problem verschwunden. Alternativ dazu kann man &lt;code&gt;mariadb-secure-installation&lt;/code&gt; ausführen, was man auf Produktionsystemen sowieso machen sollte, oder man nutzt die MaxScale Konfigurationsparameter: &lt;code&gt;ignore_tables&lt;/code&gt; oder &lt;code&gt;ignore_tables_regex&lt;/code&gt; um gleiche Tabellen in unterschiedlichen Shards zu erlauben (&lt;a href="https://jira.mariadb.org/browse/MXS-5027" target="_blank"&gt;MXS-5027&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Siehe auch: &lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-schemarouter/#router-parameters" target="_blank"&gt;MaxScale Router Parameters&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="testdaten-erstellen"&gt;Testdaten erstellen&lt;/h3&gt;
&lt;p&gt;Damit wir was zum Spielen haben, haben wir uns Testdaten erstellt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- Auf Shard 1: 2 Kunden

SQL&amp;gt; CREATE DATABASE customer_0010;
SQL&amp;gt; CREATE TABLE customer_0010.address (id INT UNSIGNED, name VARCHAR(255));
SQL&amp;gt; CREATE TABLE customer_0010.sales (id INT UNSIGNED, product VARCHAR(255), sales TINYINT, amount DECIMAL(6, 2), total_amount DECIMAL(6, 2));
SQL&amp;gt; INSERT INTO customer_0010.address VALUES (1, 'Customer 10 GmbH');
SQL&amp;gt; INSERT INTO customer_0010.sales VALUES (1, 'Apples', 5, 1.2, 6), (2, 'Pears', 2, 0.9, 1.8), (3, 'Bread', 1, 2.5, 2.5);

SQL&amp;gt; CREATE DATABASE customer_0011;
SQL&amp;gt; CREATE TABLE customer_0011.address (id INT UNSIGNED, name VARCHAR(255));
SQL&amp;gt; CREATE TABLE customer_0011.sales (id INT UNSIGNED, product VARCHAR(255), sales TINYINT, amount DECIMAL(6, 2), total_amount DECIMAL(6, 2));
SQL&amp;gt; INSERT INTO customer_0011.address VALUES (1, 'Customer 11 SE');
SQL&amp;gt; INSERT INTO customer_0011.sales VALUES (1, 'Oranges', 2, 1.7, 3.4), (2, 'Salat', 5, 1.2, 6);

-- Auf Shard 2: 3 Kunden

SQL&amp;gt; CREATE DATABASE customer_0020;
SQL&amp;gt; CREATE TABLE customer_0020.address (id INT UNSIGNED, name VARCHAR(255));
SQL&amp;gt; CREATE TABLE customer_0020.sales (id INT UNSIGNED, product VARCHAR(255), sales TINYINT, amount DECIMAL(6, 2), total_amount DECIMAL(6, 2));
SQL&amp;gt; INSERT INTO customer_0020.address VALUES (1, 'Customer 20 AG');
SQL&amp;gt; INSERT INTO customer_0020.sales VALUES (1, 'Oranges', 2, 1.7, 3.4), (2, 'Salat', 5, 1.2, 6);

SQL&amp;gt; CREATE DATABASE customer_0021;
SQL&amp;gt; CREATE TABLE customer_0021.address (id INT UNSIGNED, name VARCHAR(255));
SQL&amp;gt; CREATE TABLE customer_0021.sales (id INT UNSIGNED, product VARCHAR(255), sales TINYINT, amount DECIMAL(6, 2), total_amount DECIMAL(6, 2));
SQL&amp;gt; INSERT INTO customer_0021.address VALUES (1, 'Customer 21 GmbH');
SQL&amp;gt; INSERT INTO customer_0021.sales VALUES (1, 'Oranges', 2, 1.7, 3.4), (2, 'Salat', 5, 1.2, 6);

SQL&amp;gt; CREATE DATABASE customer_0022;
SQL&amp;gt; CREATE TABLE customer_0022.address (id INT UNSIGNED, name VARCHAR(255));
SQL&amp;gt; CREATE TABLE customer_0022.sales (id INT UNSIGNED, product VARCHAR(255), sales TINYINT, amount DECIMAL(6, 2), total_amount DECIMAL(6, 2));
SQL&amp;gt; INSERT INTO customer_0022.address VALUES (1, 'Customer 22 Gebr.');
SQL&amp;gt; INSERT INTO customer_0022.sales VALUES (1, 'Oranges', 2, 1.7, 3.4), (2, 'Salat', 5, 1.2, 6);

-- Auf Shard 3: 1 Kunde

SQL&amp;gt; CREATE DATABASE customer_0030;
SQL&amp;gt; CREATE TABLE customer_0030.address (id INT UNSIGNED, name VARCHAR(255));
SQL&amp;gt; CREATE TABLE customer_0030.sales (id INT UNSIGNED, product VARCHAR(255), sales TINYINT, amount DECIMAL(6, 2), total_amount DECIMAL(6, 2));
SQL&amp;gt; INSERT INTO customer_0030.address VALUES (1, 'Customer 30 GmbH');
SQL&amp;gt; INSERT INTO customer_0030.sales VALUES (1, 'Pickles', 2, 2.2, 4.4), (2, 'Salat', 1, 3.1, 3.1), (3, 'Pudding', 5, 2.2, 11.0), (4, 'Asparagus', 12, .3, 3.6);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="rollen-und-user-erstellen"&gt;Rollen und User erstellen&lt;/h3&gt;
&lt;p&gt;Da in einem geshardeten System, im Unterschied zum Beispiel zu einem Galera Cluster, die einzelnen Datenbank-Instanzen nichts voneinander wissen und nicht miteinander kommunizieren, müssen wir die Rollen und User bzw. Accounts jeweils auf JEDEM Shard einzeln anlegen.&lt;/p&gt;
&lt;p&gt;MariaDB MaxScale braucht jeweils für den SchemaRouter Service und den Monitor einen User (auf jedem Shard).&lt;/p&gt;
&lt;p&gt;Der Monitor User ist, wie der Name sagt fürs Monitoring zuständig und der SchemaRouter Service User um die User Account Informationen aus den Sharding-Backends einzusammeln und die Queries an das richtige Shard weiterzuleiten.&lt;/p&gt;
&lt;p&gt;Da in einem redundanten System typischerweise mit mindestens zwei MaxScale Routern gearbeitet wird und wir dem auseinanderlaufen der Privilegien der Accounts vorbeugen wollten arbeiten wir mit Rollen sowohl für die MaxScale User als auch die applikatorischen User.&lt;/p&gt;
&lt;h3 id="maxscale-monitor-user"&gt;MaxScale Monitor User&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; CREATE ROLE maxscale_monitor_role;

SQL&amp;gt; GRANT SELECT ON mysql.user TO 'maxscale_monitor_role';
SQL&amp;gt; GRANT REPLICATION CLIENT ON *.* TO 'maxscale_monitor_role';
SQL&amp;gt; GRANT SLAVE MONITOR ON *.* TO 'maxscale_monitor_role';
SQL&amp;gt; GRANT FILE ON *.* TO 'maxscale_monitor_role';
SQL&amp;gt; GRANT CONNECTION ADMIN ON *.* TO 'maxscale_monitor_role';

SQL&amp;gt; SHOW GRANTS FOR maxscale_monitor_role;
+-----------------------------------------------------------------------------------------------+
| Grants for maxscale_monitor_role |
+-----------------------------------------------------------------------------------------------+
| GRANT FILE, BINLOG MONITOR, CONNECTION ADMIN, SLAVE MONITOR ON *.* TO `maxscale_monitor_role` |
| GRANT SELECT ON `mysql`.`user` TO `maxscale_monitor_role` |
+-----------------------------------------------------------------------------------------------+

SQL&amp;gt; CREATE USER maxscale_monitor@'10.139.158.210' IDENTIFIED BY 'secret';
SQL&amp;gt; CREATE USER maxscale_monitor@'10.139.158.211' IDENTIFIED BY 'secret';

SQL&amp;gt; GRANT maxscale_monitor_role TO maxscale_monitor@'10.139.158.210';
SQL&amp;gt; GRANT maxscale_monitor_role TO maxscale_monitor@'10.139.158.211';

SQL&amp;gt; SET DEFAULT ROLE maxscale_monitor_role FOR maxscale_monitor@'10.139.158.210';
SQL&amp;gt; SET DEFAULT ROLE maxscale_monitor_role FOR maxscale_monitor@'10.139.158.211';

SQL&amp;gt; SELECT user, host, is_role, default_role FROM mysql.user WHERE user LIKE 'maxscale_monitor%';
+-----------------------+----------------+---------+-----------------------+
| User | Host | is_role | default_role |
+-----------------------+----------------+---------+-----------------------+
| maxscale_monitor_role | | Y | |
| maxscale_monitor | 10.139.158.210 | N | maxscale_monitor_role |
| maxscale_monitor | 10.139.158.211 | N | maxscale_monitor_role |
+-----------------------+----------------+---------+-----------------------+

SQL&amp;gt; SHOW GRANTS FOR maxscale_monitor@'10.139.158.211';
+------------------------------------------------------------------------------------------------------------------------------+
| Grants for maxscale_monitor@10.139.158.211 |
+------------------------------------------------------------------------------------------------------------------------------+
| GRANT `maxscale_monitor_role` TO `maxscale_monitor`@`10.139.158.211` |
| GRANT USAGE ON *.* TO `maxscale_monitor`@`10.139.158.211` IDENTIFIED BY PASSWORD '*14E65567ABDB5135D0CFD9A70B3032C179A49EE7' |
| SET DEFAULT ROLE `maxscale_monitor_role` FOR `maxscale_monitor`@`10.139.158.211` |
+------------------------------------------------------------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="maxscale-admin-user"&gt;MaxScale Admin User&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; CREATE ROLE maxscale_admin_role;

SQL&amp;gt; GRANT SHOW DATABASES ON *.* TO 'maxscale_admin_role';
SQL&amp;gt; GRANT SELECT ON mysql.user TO 'maxscale_admin_role';
SQL&amp;gt; GRANT SELECT ON mysql.db TO 'maxscale_admin_role';
SQL&amp;gt; GRANT SELECT ON mysql.tables_priv TO 'maxscale_admin_role';
SQL&amp;gt; GRANT SELECT ON mysql.columns_priv TO 'maxscale_admin_role';
SQL&amp;gt; GRANT SELECT ON mysql.proxies_priv TO 'maxscale_admin_role';
SQL&amp;gt; GRANT SELECT ON mysql.roles_mapping TO 'maxscale_admin_role';
SQL&amp;gt; GRANT SELECT ON mysql.procs_priv TO 'maxscale_admin_role';

SQL&amp;gt; SHOW GRANTS FOR maxscale_admin_role;
+------------------------------------------------------------------+
| Grants for maxscale_admin_role |
+------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `maxscale_admin_role` |
| GRANT SELECT ON `mysql`.`user` TO `maxscale_admin_role` |
| GRANT SELECT ON `mysql`.`roles_mapping` TO `maxscale_admin_role` |
| GRANT SELECT ON `mysql`.`tables_priv` TO `maxscale_admin_role` |
| GRANT SELECT ON `mysql`.`procs_priv` TO `maxscale_admin_role` |
| GRANT SELECT ON `mysql`.`db` TO `maxscale_admin_role` |
| GRANT SELECT ON `mysql`.`columns_priv` TO `maxscale_admin_role` |
| GRANT SELECT ON `mysql`.`proxies_priv` TO `maxscale_admin_role` |
+------------------------------------------------------------------+

SQL&amp;gt; CREATE USER maxscale_admin@'10.139.158.210' IDENTIFIED BY 'secret';
SQL&amp;gt; CREATE USER maxscale_admin@'10.139.158.211' IDENTIFIED BY 'secret';

SQL&amp;gt; GRANT maxscale_admin_role TO maxscale_admin@'10.139.158.210';
SQL&amp;gt; GRANT maxscale_admin_role TO maxscale_admin@'10.139.158.211';

SQL&amp;gt; SET DEFAULT ROLE maxscale_admin_role FOR maxscale_admin@'10.139.158.210';
SQL&amp;gt; SET DEFAULT ROLE maxscale_admin_role FOR maxscale_admin@'10.139.158.211';

SQL&amp;gt; SELECT user, host, is_role, default_role FROM mysql.user WHERE user LIKE 'maxscale_admin%';
+---------------------+----------------+---------+---------------------+
| User | Host | is_role | default_role |
+---------------------+----------------+---------+---------------------+
| maxscale_admin_role | | Y | |
| maxscale_admin | 10.139.158.210 | N | maxscale_admin_role |
| maxscale_admin | 10.139.158.211 | N | maxscale_admin_role |
+---------------------+----------------+---------+---------------------+

SQL&amp;gt; SHOW GRANTS FOR maxscale_admin@'10.139.158.211';
+----------------------------------------------------------------------------------------------------------------------------+
| Grants for maxscale_admin@10.139.158.211 |
+----------------------------------------------------------------------------------------------------------------------------+
| GRANT `maxscale_admin_role` TO `maxscale_admin`@`10.139.158.211` |
| GRANT USAGE ON *.* TO `maxscale_admin`@`10.139.158.211` IDENTIFIED BY PASSWORD '*14E65567ABDB5135D0CFD9A70B3032C179A49EE7' |
| SET DEFAULT ROLE `maxscale_admin_role` FOR `maxscale_admin`@`10.139.158.211` |
+----------------------------------------------------------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Siehe dazu auch: &lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-schemarouter/#configuration" target="_blank"&gt;SchemaRouter Configuration&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="applikationsrolle-und-accounts-erstellen"&gt;Applikationsrolle und Accounts erstellen&lt;/h3&gt;
&lt;p&gt;Für die Applikation braucht es ebenfalls einen User, den wir hier wie auf jedem Shard wie folgt erstellen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; CREATE ROLE app_role;
SQL&amp;gt; GRANT SELECT, INSERT, UPDATE, DELETE ON `customer_%`.* TO 'app_role';
SQL&amp;gt; GRANT SHOW DATABASES ON *.* TO 'app_role';
SQL&amp;gt; GRANT CREATE, DROP, ALTER ON *.* TO 'app_role'; -- For creating new tenant databases

SQL&amp;gt; SHOW GRANTS FOR app_role;
+----------------------------------------------------------------------+
| Grants for app_role |
+----------------------------------------------------------------------+
| GRANT SHOW DATABASES ON *.* TO `app_role` |
| GRANT SELECT, INSERT, UPDATE, DELETE ON `customer_%`.* TO `app_role` |
+----------------------------------------------------------------------+

SQL&amp;gt; CREATE USER app@'10.139.158.%' IDENTIFIED BY 'secret';
SQL&amp;gt; GRANT app_role TO app@'10.139.158.%';
SQL&amp;gt; SET DEFAULT ROLE app_role FOR app@'10.139.158.%';

SQL&amp;gt; SELECT user, host, is_role, default_role FROM mysql.user WHERE user LIKE 'app%';
+----------+--------------+---------+--------------+
| User | Host | is_role | default_role |
+----------+--------------+---------+--------------+
| app_role | | Y | |
| app | 10.139.158.% | N | app_role |
+----------+--------------+---------+--------------+

SQL&amp;gt; SHOW GRANTS FOR app@'10.139.158.%';
+---------------------------------------------------------------------------------------------------------------+
| Grants for app@10.139.158.% |
+---------------------------------------------------------------------------------------------------------------+
| GRANT `app_role` TO `app`@`10.139.158.%` |
| GRANT USAGE ON *.* TO `app`@`10.139.158.%` IDENTIFIED BY PASSWORD '*14E65567ABDB5135D0CFD9A70B3032C179A49EE7' |
| SET DEFAULT ROLE `app_role` FOR `app`@`10.139.158.%` |
+---------------------------------------------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="proxy-protokoll"&gt;Proxy Protokoll&lt;/h3&gt;
&lt;p&gt;Load Balancer und Proxies haben die Eigenschaft, dass sie die IP Adressen der Clients durch ihre eigenen IP Adressen austauschen. Dies führt einerseits dazu, dass man auf der Datenbank nicht mehr sieht, woher der Client ursprünglich kommt, andererseits kann man die Zugriffsberechtigungen nicht mehr auf User und IP vergeben, da immer gegen die IP der Load Balancer geprüft wird.&lt;/p&gt;
&lt;p&gt;Diese beiden Probleme können mittels des Proxy Protokolls gelöst werden.&lt;/p&gt;
&lt;p&gt;Hierzu müssen einerseits die Datenbank und andererseits die Load Balancer, in diesem Fall der MaxScale, das Proxy Protokoll aktiviert haben.&lt;/p&gt;
&lt;p&gt;Auf der Datenbank-Seite aktiviert man das Proxy Protokoll wie folgt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#
# my.cnf
#

[mariadbd]

proxy_protocol_networks = ::1, 10.139.158.0/24, localhost
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;und auf MaxScale-Seite mit:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#
# /etc/maxscale.cnf
#

[shard&amp;lt;n&amp;gt;]
type = server
proxy_protocol = true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Überprüfen kann man die beiden Einstellungen mit:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SHOW GLOBAL VARIABLES LIKE 'proxy%';
+-------------------------+---------------------------------+
| Variable_name | Value |
+-------------------------+---------------------------------+
| proxy_protocol_networks | ::1, 10.139.158.0/24, localhost |
+-------------------------+---------------------------------+

shell&amp;gt; maxctrl show server shard1 | grep proxy
│ │ &amp;quot;proxy_protocol&amp;quot;: true, │
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Quellen&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt" target="_blank"&gt;The PROXY protocol, Versions 1 &amp;amp; 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-mariadb-maxscale-configuration-guide/#proxy_protocol" target="_blank"&gt;MariaDB MaxScale Configuration Guide - Proxy Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/kb/en/proxy-protocol-support/" target="_blank"&gt;MariaDB Knowledge Base: Proxy Protocol Support&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="maxscale-schemarouter-konfiguration"&gt;MaxScale SchemaRouter Konfiguration&lt;/h2&gt;
&lt;p&gt;Als nächstes bereiten wir die MaxScale Konfiguration fürs Sharding vor. Die von MariaDB empfohlene Datei ist &lt;code&gt;/etc/maxscale.cnf&lt;/code&gt;. Ob es sinnvoller ist eine eigene Konfigurations-Datei unter &lt;code&gt;/etc/maxscale.cnf.d/&lt;/code&gt; zu erstellen oder gar den ganze MaxScale dynamisch zu konfigurieren (&lt;code&gt;/var/lib/maxscale/maxscale.cnf.d/*.cnf&lt;/code&gt;), wird sich langfristig zeigen. Siehe auch Warnungen weiter unten. Die Konfigurationsdatei für dieses Sharding PoC sieht wie folgt aus:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#
# /etc/maxscale.cnf
#

[maxscale]
threads = auto
admin_gui = false

[shard1]
type = server
address = 10.139.158.1
port = 3363
proxy_protocol = true

[shard2]
type=server
address=10.139.158.1
port=3364
proxy_protocol = true

[shard3]
type = server
address = 10.139.158.1
port = 3365
proxy_protocol = true

[Sharding-Monitor]
type = monitor
module = galeramon
servers = shard1,shard2,shard3
user = maxscale_monitor
password = secret
monitor_interval = 1s

[Sharded-Service-Listener]
type = listener
service = Sharded-Service
protocol = MariaDBClient
port = 3306

[Sharded-Service]
type = service
router = schemarouter
servers = shard1,shard2,shard3
user = maxscale_admin
password = secret
auth_all_servers = true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Hinweis&lt;/strong&gt;: Empfehlung des MaxScale Entwicklers: &amp;ldquo;One workaround might be to actually use &lt;code&gt;galeramon&lt;/code&gt; to monitor the nodes instead of &lt;code&gt;mariadbmon&lt;/code&gt;.&amp;rdquo;&lt;/p&gt;
&lt;h2 id="starten-und-stoppen-des-maxscale-load-balancers"&gt;Starten und Stoppen des MaxScale Load Balancers&lt;/h2&gt;
&lt;p&gt;Starten und Stoppen von MaxScale erfolgt wie üblich über SystemD:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; systemctl restart maxscale

shell&amp;gt; systemctl status maxscale
● maxscale.service - MariaDB MaxScale Database Proxy
 Loaded: loaded (/lib/systemd/system/maxscale.service; enabled; vendor preset: enabled)
 Drop-In: /run/systemd/system/service.d
 └─zzz-lxc-service.conf
 Active: active (running) since Tue 2024-02-27 09:52:57 UTC; 39s ago
 Process: 187 ExecStart=/usr/bin/maxscale (code=exited, status=0/SUCCESS)
 Main PID: 188 (maxscale)
 Tasks: 10 (limit: 18663)
 Memory: 4.6M
 CPU: 150ms
 CGroup: /system.slice/maxscale.service
 └─188 /usr/bin/maxscale

systemd[1]: Starting MariaDB MaxScale Database Proxy...
maxscale[188]: Module 'galeramon' loaded from '/usr/lib/x86_64-linux-gnu/maxscale/libgaleramon.so'.
maxscale[188]: Module 'schemarouter' loaded from '/usr/lib/x86_64-linux-gnu/maxscale/libschemarouter.so'.
maxscale[188]: Using up to 2.3GiB of memory for query classifier cache
systemd[1]: Started MariaDB MaxScale Database Proxy.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Falls es Fehler oder Warnungen gegeben hat, kann man diese im MaxScale Error Log sehen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; grep -v notice /var/log/maxscale/maxscale.log
2024-02-13 16:47:22 MariaDB MaxScale is shut down.
----------------------------------------------------


MariaDB MaxScale /var/log/maxscale/maxscale.log Tue Feb 13 16:47:22 2024
----------------------------------------------------------------------------
2024-02-27 09:52:56 warning: Discarding journal file '/var/lib/maxscale/Sharding-Monitor_journal.json'. File is for module 'mariadbmon'. Current module is 'galeramon'.
2024-02-27 09:52:56 warning: [galeramon] Invalid 'wsrep_local_index' on server 'shard1': 18446744073709551615
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="applikations-tests"&gt;Applikations-Tests&lt;/h2&gt;
&lt;h3 id="einfache-applikations-tests"&gt;Einfache Applikations-Tests&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; mariadb --user=app --password=secret --host=10.139.158.211 --port=3306 --execute='show databases'
+--------------------+
| Database |
+--------------------+
| customer_0010 |
| customer_0011 |
| customer_0020 |
| customer_0021 |
| customer_0022 |
| customer_0030 |
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="neuer-befehl-show-shards"&gt;Neuer Befehl &lt;code&gt;show shards&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; mariadb --user=app --password=secret --host=10.139.158.211 --port=3306 customer_0030 --execute='show shards' | grep customer_00.* | sort | column -t
customer_0010.address shard1
customer_0010.sales shard1
customer_0010. shard1
customer_0011.address shard1
customer_0011.sales shard1
customer_0011. shard1
customer_0020.address shard2
customer_0020.sales shard2
customer_0020. shard2
customer_0021.address shard2
customer_0021.sales shard2
customer_0021. shard2
customer_0022.address shard2
customer_0022.sales shard2
customer_0022. shard2
customer_0030.address shard3
customer_0030.sales shard3
customer_0030. shard3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Neue Datenbanken werden nicht sofort angezeigt, sondern erst wenn die gecachten Daten upgedatet wurden (&lt;code&gt;refresh_interval&lt;/code&gt; (300s / 5 min)).&lt;/p&gt;
&lt;p&gt;Siehe hierzu auch: &lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-schemarouter/#custom-sql-commands" target="_blank"&gt;Custom SQL commands&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="allgemeinere-test"&gt;Allgemeinere Test&lt;/h3&gt;
&lt;p&gt;Zur Erinnerung:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Shard&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Port&lt;/th&gt;
&lt;th style="text-align: left;"&gt;Customer&lt;/th&gt;
&lt;th style="text-align: left;"&gt;State&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style="text-align: right"&gt;
&lt;td&gt;#1&lt;/td&gt;
&lt;td&gt;3363&lt;/td&gt;
&lt;td&gt;customer_001&amp;lt;n&amp;gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Running&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style="text-align: right"&gt;
&lt;td&gt;#2&lt;/td&gt;
&lt;td&gt;3364&lt;/td&gt;
&lt;td&gt;customer_002&amp;lt;n&amp;gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Running&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style="text-align: right"&gt;
&lt;td&gt;#3&lt;/td&gt;
&lt;td&gt;3365&lt;/td&gt;
&lt;td&gt;customer_003&amp;lt;n&amp;gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Running&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style="text-align: right"&gt;
&lt;td&gt;#4&lt;/td&gt;
&lt;td&gt;3366&lt;/td&gt;
&lt;td&gt;customer_004&amp;lt;n&amp;gt;&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Running&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; mariadb --user=app --password=secret --host=10.139.158.211 --port=3306 --execute='SELECT @@port'
+--------+
| @@port |
+--------+
| 3363 |
+--------+
shell&amp;gt; mariadb --user=app --password=secret --host=10.139.158.211 --port=3306 --database=customer_0010 --execute='SELECT @@port'
+--------+
| @@port |
+--------+
| 3363 |
+--------+
shell&amp;gt; mariadb --user=app --password=secret --host=10.139.158.211 --port=3306 --database=customer_0020 --execute='SELECT @@port'
+--------+
| @@port |
+--------+
| 3364 |
+--------+
shell&amp;gt; mariadb --user=app --password=secret --host=10.139.158.211 --port=3306 --execute='use customer_0020; SELECT @@port'
+--------+
| @@port |
+--------+
| 3364 |
+--------+
shell&amp;gt; mariadb --user=app --password=secret --host=10.139.158.211 --port=3306 customer_0010 --execute='SELECT @@port'
+--------+
| @@port |
+--------+
| 3363 |
+--------+
shell&amp;gt; mariadb --user=app --password=secret --host=10.139.158.211 --port=3306 customer_0020 --execute='SELECT @@port'
+--------+
| @@port |
+--------+
| 3364 |
+--------+
shell&amp;gt; mariadb --user=app --password=secret --host=10.139.158.211 --port=3306 customer_0030 --execute='SELECT @@port'
+--------+
| @@port |
+--------+
| 3365 |
+--------+
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="weniger-einfache-backup--test"&gt;Weniger einfache (Backup-) Test&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; mariadb-dump --user=app --password=secret --host=10.139.158.211 --port=3306 --single-transaction customer_0010 &amp;gt; /tmp/customer_0010.sql
shell&amp;gt; echo $?
0
shell&amp;gt; mariadb-dump --user=app --password=secret --host=10.139.158.211 --port=3306 --single-transaction customer_0020 &amp;gt; /tmp/customer_0020.sql
shell&amp;gt; echo $?
0
shell&amp;gt; mariadb-dump --user=app --password=secret --host=10.139.158.211 --port=3306 --single-transaction customer_0030 &amp;gt; /tmp/customer_0030.sql
shell&amp;gt; echo $?
0
shell&amp;gt; mariadb-dump --user=app --password=secret --host=10.139.158.211 --port=3306 --single-transaction --databases customer_0011 &amp;gt; /tmp/customer_0011.sql
shell&amp;gt; echo $?
0
shell&amp;gt; mariadb-dump --user=app --password=secret --host=10.139.158.211 --port=3306 --single-transaction --databases customer_0021 &amp;gt; /tmp/customer_0021.sql
shell&amp;gt; echo $?
0
shell&amp;gt; mariadb-dump --user=app --password=secret --host=10.139.158.211 --port=3306 --single-transaction --databases customer_0030 &amp;gt; /tmp/customer_0030.sql
shell&amp;gt; echo $?
0

shell&amp;gt; ll /tmp/customer_00*sql
-rw-rw-r-- 1 oli oli 2738 Mar 18 12:07 /tmp/customer_0010.sql
-rw-rw-r-- 1 oli oli 2904 Mar 18 12:08 /tmp/customer_0011.sql
-rw-rw-r-- 1 oli oli 2712 Mar 18 12:08 /tmp/customer_0020.sql
-rw-rw-r-- 1 oli oli 2906 Mar 18 12:08 /tmp/customer_0021.sql
-rw-rw-r-- 1 oli oli 2964 Mar 18 12:08 /tmp/customer_0030.sql

shell&amp;gt; tail -n 1 /tmp/customer_*.sql
==&amp;gt; /tmp/customer_0010.sql &amp;lt;==
-- Dump completed on 2024-02-13 14:39:21

==&amp;gt; /tmp/customer_0011.sql &amp;lt;==
-- Dump completed on 2024-02-13 14:39:35

==&amp;gt; /tmp/customer_0020.sql &amp;lt;==
-- Dump completed on 2024-02-13 14:40:15

==&amp;gt; /tmp/customer_0021.sql &amp;lt;==
-- Dump completed on 2024-02-13 14:40:42

==&amp;gt; /tmp/customer_0030.sql &amp;lt;==
-- Dump completed on 2024-02-13 14:40:52

shell&amp;gt; cat /tmp/customer_00*sql | grep -A1 -i insert
INSERT INTO `address` VALUES
(1,'Customer 10 GmbH');
--
INSERT INTO `sales` VALUES
(1,'Apples',5,1.20,6.00),
--
INSERT INTO `address` VALUES
(1,'Customer 11 SE');
--
INSERT INTO `sales` VALUES
(1,'Oranges',2,1.70,3.40),
--
INSERT INTO `address` VALUES
(1,'Customer 20 AG');
--
INSERT INTO `sales` VALUES
(1,'Oranges',2,1.70,3.40),
--
INSERT INTO `address` VALUES
(1,'Customer 21 GmbH');
--
INSERT INTO `sales` VALUES
(1,'Oranges',2,1.70,3.40),
--
INSERT INTO `address` VALUES
(1,'Customer 30 GmbH');
--
INSERT INTO `sales` VALUES
(1,'Pickles',2,2.20,4.40),
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In MaxScale 23.08.4 war ein ziemlich übler Bug drin: Ein Rückgabewert von 0 aber keine Daten im Backup!!! Siehe dazu auch die Tickets: &lt;a href="https://jira.mariadb.org/browse/MXS-4966" target="_blank"&gt;MXS-4966: mariadb-dump gets an error dumping schemas&lt;/a&gt; und &lt;a href="https://jira.mariadb.org/browse/MXS-4947" target="_blank"&gt;MXS-4947: Tables in information_schema are treated as a normal tables&lt;/a&gt;. Symptome des Bugs sehen wie folgt aus:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Error: Couldn't read status information for table address ()
Error: Couldn't read status information for table sales ()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Daher empfiehlt sich dringend ein Upgrade auf MaxScale 23.08.5.&lt;/p&gt;
&lt;h3 id="komplexere-applikations-tests"&gt;Komplexere Applikations-Tests&lt;/h3&gt;
&lt;p&gt;Wir haben noch einen etwas komplexeren Test erstellt (&lt;code&gt;./sharding_test.php&lt;/code&gt;), der folgen Queries abarbeitet:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SET NAMES utf8mb4
SHOW DATABASES
use customer_&amp;lt;nnnn&amp;gt;
START TRANSACTION;
SELECT MIN(id) AS first, MAX(id) AS last FROM `sales`
INSERT INTO sales (id, product, sales, amount, total_amount) VALUES (%d, '%s', %f, %f, %f)
INSERT INTO sales (id, product, sales, amount, total_amount) VALUES (%d, '%s', %f, %f, %f)
UPDATE sales SET product = 'Prepare to delete' WHERE id = %d
DELETE FROM sales WHERE id = %d
COMMIT
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dieser Test lief einwandfrei durch. Die dazu gehörende Kontroll-Abfrage:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SELECT * FROM customer_0021.sales WHERE id &amp;gt;= (SELECT MAX(id) - 10 FROM customer_0021.sales);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Verschiedene Last-Szenarien können zusätzlich mit &lt;code&gt;db_bench&lt;/code&gt; oder dem Acronis &lt;code&gt;perfkit&lt;/code&gt; getestet werden. Weitere Informationen dazu siehe &lt;a href="https://www.fromdual.com/mariadb-and-mysql-benchmarking#database-benchmark"&gt;hier&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="cross-shard-tests"&gt;Cross-Shard-Tests&lt;/h3&gt;
&lt;p&gt;Allensfalls könnte man auf die Idee kommen, Cross-Shard-Queries auszuführen. Dies wird NICHT funktionieren, was ja nicht wirklich erstaunen sollte, da erstens nicht ganz einfach zu implementieren und zweites hier beschrieben:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Note: As the sharding solution in MaxScale is relatively simple, cross-database queries between two or more shards are not supported.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quelle&lt;/strong&gt;: &lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-simple-sharding-with-two-servers/" target="_blank"&gt;Simple Sharding with Two Servers&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;und&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;USE db1 is routed to the server with db1. If the database is divided to multiple servers, only one server will get the command.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quelle&lt;/strong&gt;: &lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-schemarouter/" target="_blank"&gt;SchemaRouter&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Hier ein Test mit &lt;code&gt;UNION&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; use customer_0030
Database changed
SQL&amp;gt; SELECT * FROM customer_0020.sales UNION SELECT * FROM customer_0030.sales;
ERROR 1146 (42S02): Table 'customer_0020.sales' doesn't exist
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Und hier noch der Gegenbeweis:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; use customer_0020
Database changed
SQL&amp;gt; SELECT * FROM customer_0020.sales UNION SELECT * FROM customer_0030.sales;
ERROR 1146 (42S02): Table 'customer_0030.sales' doesn't exist
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Und hier noch den Test mit &lt;code&gt;JOIN&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; use customer_0020
SQL&amp;gt; SELECT *
 FROM customer_0020.sales a
 JOIN customer_0030.sales b ON a.id = b.id
WHERE a.sales &amp;gt; 1
;
ERROR 1146 (42S02): Table 'customer_0030.sales' doesn't exist

SQL&amp;gt; use customer_0030
SQL&amp;gt; SELECT *
 FROM customer_0020.sales a
 JOIN customer_0030.sales b ON a.id = b.id
WHERE a.sales &amp;gt; 1
;
ERROR 1146 (42S02): Table 'customer_0020.sales' doesn't exist
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="betrieb-eines-maxscale-sharding-systems"&gt;Betrieb eines MaxScale Sharding-Systems&lt;/h2&gt;
&lt;p&gt;In diesem Kapitel besprechen wir einige Punkte die für den Betrieb eines MariaDB MaxScale Sharding-Systems nützlich sein können.&lt;/p&gt;
&lt;h3 id="do-on-all-shards"&gt;Do-on-all-shards&lt;/h3&gt;
&lt;p&gt;Da es immer wieder vorkommen kann, dass O/S oder Datenbank Operationen auf allen Shards ausgeführt werden müssen, wäre es sicher sinnvoll ein Skript zu erstellen, welches reihum, auf allen Shards, den selben Befehl ausführt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; ./do-on-all-shards.sh --sql='SHOW DATABASES'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ein so geartetes Skript dürfte die Fehlerrate im Betrieb stark reduzieren. Auch Operationen wie das Re-sharding eines Mandanten, wie weiter unten beschrieben, werden sinnvollerweise geskriptet und zentral ausgeführt (&lt;a href="https://jira.mariadb.org/browse/MXS-5029" target="_blank"&gt;MXS-5029&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id="invalidieren-des-database-map-caches"&gt;Invalidieren des Database Map Caches&lt;/h3&gt;
&lt;p&gt;Mit dem Befehlt &lt;code&gt;invalidate&lt;/code&gt; kann man den Database Map Cache des MariaDB MaxScale SchemaRouters invalidieren. Dies gibt uns die Möglichkeit, nach dem Hinzufügen oder dem Entfernen von Mandanten den Cache schnell wieder auf einen aktuellen Stand zu bringen.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; maxctrl call command schemarouter invalidate Sharded-Service
OK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Im unterschied zum Befehl &lt;code&gt;invalidate&lt;/code&gt;, bei welchem die Einträge nach dem nächsten &lt;code&gt;refresh_intervall&lt;/code&gt; auf den neusten Stand gebracht werden, löscht der Befehl &lt;code&gt;clear&lt;/code&gt; die Einträge und ein Remap wird sofort ausgeführt.&lt;/p&gt;
&lt;p&gt;Wenn man den Database Map Cache von remote mit einem REST API Call invalidieren möchte, geht das wie folgt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; curl -i -X POST -u api_admin:secret http://10.139.158.211:8989/v1/maxscale/modules/schemarouter/clear?Sharded-Service
HTTP/1.1 204 No Content
Connection: close
Date: Mon, 18 Mar 24 11:49:58 GMT
X-Frame-Options: Deny
X-XSS-Protection: 1
Referrer-Policy: same-origin
Cache-Control: no-cache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Quellen&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-schemarouter/#module-commands" target="_blank"&gt;Schema Router - Module commands&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-maxscale-resource/#call-a-module-command" target="_blank"&gt;Call a module command&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-rest-api/" target="_blank"&gt;MaxScale REST API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="wie-ändert-man-schemarouter-variablen-dynamisch"&gt;Wie ändert man SchemaRouter Variablen dynamisch?&lt;/h3&gt;
&lt;p&gt;Das &lt;code&gt;refresh_interval&lt;/code&gt; spezifiziert die Lebensdauer der Einträge im SchemaRouter Database Map Cache. Der default Wert ist 300 s (5 min). Refresh Interval ist daher, meiner Meinung nach, ein unglücklicher Begriff da es nicht das Intervall zwischen zwei Mappings definiert sondern die Lebzeit der Cache-Einträge (livetime?, timeout?). Sobald der Eintrag gelöscht wurde wir ein neuer Refresh der &amp;ldquo;Database Map&amp;rdquo; auf jedem Shard getriggert. Der Befehl sieht aktuell wie folgt aus:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT LOWER(t.table_schema), LOWER(t.table_name) FROM information_schema.tables t
 UNION ALL
SELECT LOWER(s.schema_name), '' FROM information_schema.schemata s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So wie es aussieht reicht ein simpler Connect um den Refresh der Database Map zu triggern.&lt;/p&gt;
&lt;p&gt;Der aktuelle Wert für &lt;code&gt;refresh_intervall&lt;/code&gt; kann wie folgt abgefragt werden.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; maxctrl show service Sharded-Service | grep refresh_interval | awk -F'│' '{ print $3 }'
 &amp;quot;refresh_interval&amp;quot;: &amp;quot;300000ms&amp;quot;,
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Um den Wert dynamisch zu ändern hilft folgender Befehl:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; MAXCTRL_WARNINGS=0 maxctrl alter service Sharded-Service refresh_interval=10s
OK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Der Wert sollte nicht zu klein eingestellt werden, da während des Mapping Vorgangs alle anderen Connections angehalten werden.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quellen&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-schemarouter/#refresh_interval" target="_blank"&gt;MariaDB MaxScale SchemaRouter - refresh_interval&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-schemarouter/#refresh_databases" target="_blank"&gt;MariaDB MaxScale SchemaRouter - refresh_database&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="hinzufügen-und-entfernen-eines-mandanten"&gt;Hinzufügen und Entfernen eines Mandanten&lt;/h3&gt;
&lt;p&gt;Das Hinzufügen eines neuen Mandanten zu einem Shard stellt kein grosses Problem dar:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; CREATE DATABASE customer_0029;
SQL&amp;gt; use customer_0029
SQL&amp;gt; CREATE TABLE address LIKE customer_template.address;
SQL&amp;gt; CREATE TABLE sales LIKE customer_template.sales;

shell&amp;gt; maxctrl call command schemarouter invalidate Sharded-Service
OK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Das Entfernen eines Mandanten von einem Shard ist hingegen etwas komplizierter und muss in Absprache mit der Applikation erfolgen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; DROP DATABASE customer_0011;

shell&amp;gt; ./sharding_test.php
.....ERROR: Table 'customer_0011.sales' doesn't exist...ERROR: Unknown database 'customer_0011'.ERROR: Unknown database 'customer_0011'......ERROR: Unknown database 'customer_0011'...

shell&amp;gt; maxctrl call command schemarouter clear Sharded-Service
OK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Zumindest ist mir bisher noch keine schlauere Variante eingefallen. Siehe auch Umziehen eines Mandanten weiter unten.&lt;/p&gt;
&lt;h3 id="umziehen-eines-mandanten"&gt;Umziehen eines Mandanten&lt;/h3&gt;
&lt;p&gt;Die Kombination von Hinzufügen und Entfernen wäre dann das Umziehen eines Mandanten von einem Shard auf ein anderes Shard, auch re-sharding genannt. Hierzu muss ebenfalls wieder mit der Applikation zusammen eine konzertierte Aktion geplant werden.&lt;/p&gt;
&lt;p&gt;Falls dies nicht möglich ist, kann zumindest die Zeit, welche die Applikation Fehler erhält reduziert werden&amp;hellip; Folgendes Vorgehen kann verwendet werden um einen Mandanten von Shard 2 auf Shard 3 umzuziehen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; use customer_0020; LOCK TABLES address READ, sales READ; -- Auf Shard 2, Applikation wir allenfalls blockiert!

shell&amp;gt; mariadb-dump --user=app --password=secret --host=10.139.158.1 --port=3364 --single-transaction --skip-add-locks --databases customer_0020 | mariadb --user=app --password=secret --host=10.139.158.1 --port=3365 # Kopieren des Mandanten 20 von Shard 2 auf Shard 3

SQL&amp;gt; DROP DATABASE customer_0020; -- Löschen von Mandant 20 geht nicht!
ERROR 1192 (HY000): Can't execute the given command because you have active locked tables or an active transaction

SQL&amp;gt; UNLOCK TABLES; DROP DATABASE customer_0020; # So geht das Löschen von Mandant 20.

shell&amp;gt; maxctrl call command schemarouter clear Sharded-Service # MaxScale Database Map aktualisieren. Schnell machen!!!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bis zum Refresh der Database Map kann es zu folgenden Fehlern kommen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;error : (47621) [schemarouter] (Sharded-Service); 'customer_0020.' found on servers 'shard2','shard3' for user 'app'@'10.139.158.1'.
error : (47621) [schemarouter] (Sharded-Service); 'customer_0020.address' found on servers 'shard2','shard3' for user 'app'@'10.139.158.1'.
error : (47621) [schemarouter] (Sharded-Service); 'customer_0020.sales' found on servers 'shard2','shard3' for user 'app'@'10.139.158.1'.
error : (47621) [schemarouter] (Sharded-Service); Duplicate tables found, closing session.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Und applikationsseitig zu:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ERROR: Error: duplicate tables found on two different shards
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="hinzufügen-oder-entfernen-eines-shards"&gt;Hinzufügen oder entfernen eines Shards&lt;/h3&gt;
&lt;p&gt;Das Umziehen eines Mandanten von einem Shard auf ein anderes Shard ist das kleine re-sharding. Etwas komplexer wird es, wenn man neue Shards hinzufügen oder alte Shards entfernen möchte. Im Anschluss daran (nach dem Hinzufügen bzw. vor dem Entfernen) würde dann ein grosses re-sharding erfolgen. Zuerst das Erweitern des Clusters um ein Shard:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; maxctrl list servers
┌────────┬──────────────┬──────┬─────────────┬─────────┬───────────────┬──────────────────┐
│ Server │ Address │ Port │ Connections │ State │ GTID │ Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼───────────────┼──────────────────┤
│ shard1 │ 10.139.158.1 │ 3363 │ 0 │ Running │ 0-3363-26014 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼───────────────┼──────────────────┤
│ shard2 │ 10.139.158.1 │ 3364 │ 0 │ Running │ 0-3364-240612 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼───────────────┼──────────────────┤
│ shard3 │ 10.139.158.1 │ 3365 │ 0 │ Running │ 0-3365-289873 │ Sharding-Monitor │
└────────┴──────────────┴──────┴─────────────┴─────────┴───────────────┴──────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Das vorbereitete Shard wird MaxScale bekannt gemacht:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; maxctrl create server shard4 10.139.158.1 3366
OK

shell&amp;gt; maxctrl list servers
┌────────┬──────────────┬──────┬─────────────┬─────────┬──────────────┬──────────────────┐
│ Server │ Address │ Port │ Connections │ State │ GTID │ Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard1 │ 10.139.158.1 │ 3363 │ 1 │ Running │ 0-3363-23676 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard2 │ 10.139.158.1 │ 3364 │ 1 │ Running │ 0-3364-52321 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard3 │ 10.139.158.1 │ 3365 │ 1 │ Running │ 0-3365-39751 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard4 │ 10.139.158.1 │ 3366 │ 0 │ Down │ │ │
└────────┴──────────────┴──────┴─────────────┴─────────┴──────────────┴──────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dann wird des neue Shard mit dem MaxScale Monitor und dem Service verknüpft:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; MAXCTRL_WARNINGS=0 maxctrl link monitor Sharding-Monitor shard4
OK

shell&amp;gt; MAXCTRL_WARNINGS=0 maxctrl link service Sharded-Service shard4
OK

shell&amp;gt; maxctrl list servers
┌────────┬──────────────┬──────┬─────────────┬─────────┬──────────────┬──────────────────┐
│ Server │ Address │ Port │ Connections │ State │ GTID │ Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard1 │ 10.139.158.1 │ 3363 │ 1 │ Running │ 0-3363-24961 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard2 │ 10.139.158.1 │ 3364 │ 1 │ Running │ 0-3364-56215 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard3 │ 10.139.158.1 │ 3365 │ 1 │ Running │ 0-3365-45177 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard4 │ 10.139.158.1 │ 3366 │ 1 │ Running │ 0-3366-32 │ Sharding-Monitor │
└────────┴──────────────┴──────┴─────────────┴─────────┴──────────────┴──────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ob dieser zweite Schritt ebenfalls zwingend notwendig ist, wurde nicht untersucht.&lt;/p&gt;
&lt;p&gt;Im MariaDB MaxScale Error Log kann man den ganzen Ablauf mitverfolgen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;warning: Discarding journal file '/var/lib/maxscale/Sharding-Monitor_journal.json'. Servers described in the journal are different from the ones configured on the current monitor.
warning: Saving runtime modifications to 'Sharding-Monitor' in '/var/lib/maxscale/maxscale.cnf.d/Sharding-Monitor.cnf'. The modified values will override the values found in the static configuration files.
notice : shard4 sent version string '10.11.7-MariaDB-log'. Detected type: MariaDB, version: 10.11.7.
notice : Server 'shard4' charset: latin1_swedish_ci
notice : Server changed state: shard4[10.139.158.1:3366]: server_up. [Down]→[Running]
warning: Saving runtime modifications to 'Sharded-Service' in '/var/lib/maxscale/maxscale.cnf.d/Sharded-Service.cnf'. The modified values will override the values found in the static configuration files.
notice : Added 'shard4' to 'Sharded-Service'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Was wir hier nicht vergessen dürfen, ist das neue Shard ebenfalls mit dem Proxy Protokoll auszustatten:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; maxctrl show server shard4 | grep proxy
│ │ &amp;quot;proxy_protocol&amp;quot;: false, │

MAXCTRL_WARNINGS=0 maxctrl alter server shard4 proxy_protocol=true
OK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Und jetzt können neue Mandanten auf dem neuen Shard hinzugefügt oder alte Mandanten auf das neue Shard umgezogen werden&amp;hellip; In unserem Set-up wollen wir alle Mandanten vom Shard 1 auf Shard 4 umziehen und zudem einen neuen Mandanten &lt;code&gt;customer_0040&lt;/code&gt; auf Shard 4 erstellen. Die dazu benötigten Einzelschritte sind oben aufgeführt.&lt;/p&gt;
&lt;p&gt;Nachdem das Shard 1 leergeräumt wurde, kann es abgebaut werden:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; maxctrl list servers
┌────────┬──────────────┬──────┬─────────────┬─────────┬──────────────┬──────────────────┐
│ Server │ Address │ Port │ Connections │ State │ GTID │ Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard1 │ 10.139.158.1 │ 3363 │ 1 │ Running │ 0-3363-25916 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard2 │ 10.139.158.1 │ 3364 │ 1 │ Running │ 0-3364-62887 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard3 │ 10.139.158.1 │ 3365 │ 1 │ Running │ 0-3365-54035 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard4 │ 10.139.158.1 │ 3366 │ 1 │ Running │ 0-3366-2247 │ Sharding-Monitor │
└────────┴──────────────┴──────┴─────────────┴─────────┴──────────────┴──────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mit dem Befehl &lt;code&gt;destroy server&lt;/code&gt; wir ein Shard gelöscht. Bevor das aber funktioniert muss ein Shard aus dem Monitor und dem Service entfernt werden:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; MAXCTRL_WARNINGS=0 maxctrl unlink service Sharded-Service shard1
OK

shell&amp;gt; MAXCTRL_WARNINGS=0 maxctrl unlink monitor Sharding-Monitor shard1
OK

shell&amp;gt; maxctrl list servers
┌────────┬──────────────┬──────┬─────────────┬─────────┬──────────────┬──────────────────┐
│ Server │ Address │ Port │ Connections │ State │ GTID │ Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard1 │ 10.139.158.1 │ 3363 │ 0 │ Running │ │ │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard2 │ 10.139.158.1 │ 3364 │ 1 │ Running │ 0-3364-64394 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard3 │ 10.139.158.1 │ 3365 │ 1 │ Running │ 0-3365-56072 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard4 │ 10.139.158.1 │ 3366 │ 1 │ Running │ 0-3366-3267 │ Sharding-Monitor │
└────────┴──────────────┴──────┴─────────────┴─────────┴──────────────┴──────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wenn das Shard aus dem Monitor und dem Service herausgelöst ist, kann es anschliessend gelöscht werden:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; maxctrl destroy server shard1
Warning: Object 'shard1' is defined in a static configuration file and cannot be permanently deleted. If MaxScale is restarted, the object will appear again.
To hide these warnings, run:

 export MAXCTRL_WARNINGS=0

OK
shell&amp;gt; maxctrl list servers
┌────────┬──────────────┬──────┬─────────────┬─────────┬──────────────┬──────────────────┐
│ Server │ Address │ Port │ Connections │ State │ GTID │ Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard2 │ 10.139.158.1 │ 3364 │ 1 │ Running │ 0-3364-65018 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard3 │ 10.139.158.1 │ 3365 │ 1 │ Running │ 0-3365-56886 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard4 │ 10.139.158.1 │ 3366 │ 1 │ Running │ 0-3366-3648 │ Sharding-Monitor │
└────────┴──────────────┴──────┴─────────────┴─────────┴──────────────┴──────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Und im MaxScale Error Log kann man die Änderungen schön mitverfolgen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;notice : Removed 'shard1' from 'Sharded-Service'
warning: Discarding journal file '/var/lib/maxscale/Sharding-Monitor_journal.json'. Servers described in the journal are different from the ones configured on the current monitor.
notice : Destroyed server 'shard1' at 10.139.158.1:3363
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Wichtig:&lt;/strong&gt; Mir wurde mitgeteilt, dass mit &lt;code&gt;destroy server --force&lt;/code&gt; die &lt;code&gt;unlink service&lt;/code&gt; und &lt;code&gt;unlink monitor&lt;/code&gt; Befehle durch MaxScale automatisch ausgeführt werden.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quelle:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-mariadb-maxscale-administration-tutorial/#managing-servers" target="_blank"&gt;MariaDB MaxScale Administration Tutorial - Managing servers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="anpassen-der-konfigurationsdateien"&gt;Anpassen der Konfigurationsdateien&lt;/h3&gt;
&lt;p&gt;Bei den oben beschriebenen Shard Operationen habe wir einige Warnungen erhalten:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Warning: Object 'shard1' is defined in a static configuration file and cannot be permanently deleted. If MaxScale is restarted, the object will appear again.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;und&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Warning: Saving runtime modifications to 'Sharding-Monitor' in '/var/lib/maxscale/maxscale.cnf.d/Sharding-Monitor.cnf'. The modified values will override the values found in the static configuration files.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Die entsprechenden Konfigurationsdateien werden von MaxScale automatisch bei dynamischen System-Änderungen erstellt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; ll /var/lib/maxscale/maxscale.cnf.d/ /etc/maxscale.cnf
-rw-r--r-- 1 root root 612 Feb 13 14:23 /etc/maxscale.cnf

/var/lib/maxscale/maxscale.cnf.d/:
total 12
-rw------- 1 maxscale maxscale 187 Feb 13 16:08 Sharding-Monitor.cnf
-rw------- 1 maxscale maxscale 150 Feb 13 16:07 Sharded-Service.cnf
-rw------- 1 maxscale maxscale 52 Feb 13 15:46 shard4.cnf

cat /var/lib/maxscale/maxscale.cnf.d/*
[Sharded-Service]
debug=true
refresh_interval=10000ms
auth_all_servers=true
log_debug=true
password=secret
router=schemarouter
type=service
user=maxscale_admin
targets=shard2,shard3,shard4

[Sharding-Monitor]
module=galeramon
monitor_interval=1000ms
password=secret
servers=shard2,shard3,shard4
type=monitor
user=maxscale_monitor

[shard4]
address=10.139.158.1
port=3366
type=server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Es müssen also noch entsprechende Nachbesserungen an den Konfigurationsdateien vorgenommen werden. Man müsste sich allgemein überlegen ob man bei einem hochdynamischen System nicht alles dynamisch über Befehle konfigurieren sollte&amp;hellip;&lt;/p&gt;
&lt;h3 id="wartungsarbeiten-am-shard"&gt;Wartungsarbeiten am Shard&lt;/h3&gt;
&lt;p&gt;Wenn für Wartungsarbeiten ein Shard offline genommen werden soll, hier im Beispiel &lt;code&gt;shard2&lt;/code&gt;, kann dies wie folgt bewerkstelligt werden:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; maxctrl list servers
┌────────┬──────────────┬──────┬─────────────┬─────────┬──────────────┬──────────────────┐
│ Server │ Address │ Port │ Connections │ State │ GTID │ Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard2 │ 10.139.158.1 │ 3364 │ 1 │ Running │ 0-3364-69817 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard3 │ 10.139.158.1 │ 3365 │ 1 │ Running │ 0-3365-63166 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼──────────────┼──────────────────┤
│ shard4 │ 10.139.158.1 │ 3366 │ 1 │ Running │ 0-3366-6902 │ Sharding-Monitor │
└────────┴──────────────┴──────┴─────────────┴─────────┴──────────────┴──────────────────┘

shell&amp;gt; maxctrl set server shard2 drain
OK

shell&amp;gt; maxctrl set server shard2 maintenance
OK

shell&amp;gt; maxctrl list servers
┌────────┬──────────────┬──────┬─────────────┬──────────────────────┬───────────────┬──────────────────┐
│ Server │ Address │ Port │ Connections │ State │ GTID │ Monitor │
├────────┼──────────────┼──────┼─────────────┼──────────────────────┼───────────────┼──────────────────┤
│ shard2 │ 10.139.158.1 │ 3364 │ 0 │ Maintenance, Running │ 0-3364-240612 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼──────────────────────┼───────────────┼──────────────────┤
│ shard3 │ 10.139.158.1 │ 3365 │ 0 │ Running │ 0-3365-289873 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼──────────────────────┼───────────────┼──────────────────┤
│ shard4 │ 10.139.158.1 │ 3366 │ 0 │ Running │ 0-3366-119848 │ Sharding-Monitor │
└────────┴──────────────┴──────┴─────────────┴──────────────────────┴───────────────┴──────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Zu diesem Zeitpunkt können die Wartungsarbeiten an der Maschine oder der Datenbank vorgenommen werden&amp;hellip;&lt;/p&gt;
&lt;p&gt;Anschliessend müssen BEIDE Stati wieder gecleared werden, sofern beide gesetzt wurden (&lt;a href="https://jira.mariadb.org/browse/MXS-5028" target="_blank"&gt;MXS-5028&lt;/a&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; maxctrl clear server shard2 maintenance
OK

shell&amp;gt; maxctrl clear server shard2 drain
OK

maxctrl list servers
┌────────┬──────────────┬──────┬─────────────┬─────────┬───────────────┬──────────────────┐
│ Server │ Address │ Port │ Connections │ State │ GTID │ Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼───────────────┼──────────────────┤
│ shard2 │ 10.139.158.1 │ 3364 │ 0 │ Running │ 0-3364-240612 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼───────────────┼──────────────────┤
│ shard3 │ 10.139.158.1 │ 3365 │ 0 │ Running │ 0-3365-289873 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼───────────────┼──────────────────┤
│ shard4 │ 10.139.158.1 │ 3366 │ 0 │ Running │ 0-3366-119848 │ Sharding-Monitor │
└────────┴──────────────┴──────┴─────────────┴─────────┴───────────────┴──────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Der Unterschied zwischen &lt;code&gt;drain&lt;/code&gt; und &lt;code&gt;maintenance&lt;/code&gt; besteht darin, dass bei &lt;code&gt;drain&lt;/code&gt; keinen neuen Verbindungen mehr auf das Shard zugelassen werden aber bei bestehenden Verbindungen wird gewartet, bis sie geschlossen werden. Bei &lt;code&gt;maintenance&lt;/code&gt; werden die Verbindungen sofort zwangsweise terminiert.&lt;/p&gt;
&lt;h2 id="beobachtung--observierung-eines-mariadb-maxscale-sharding-systems"&gt;Beobachtung / Observierung eines MariaDB MaxScale Sharding-Systems&lt;/h2&gt;
&lt;p&gt;Mit dem MaxScale CLI Client &lt;code&gt;maxtrl&lt;/code&gt; kann man den Zustand des MariaDB MaxScale Load Balancers abfragen hierzu gibt es zahlreiche Befehle, hauptsächlich &lt;code&gt;list&lt;/code&gt; und &lt;code&gt;show&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; maxctrl show module schemarouter | head -n 12
┌─────────────┬────────────────────────────────────────────────┐
│ Module │ schemarouter │
├─────────────┼────────────────────────────────────────────────┤
│ Type │ Router │
├─────────────┼────────────────────────────────────────────────┤
│ Version │ V1.0.0 │
├─────────────┼────────────────────────────────────────────────┤
│ Maturity │ Beta │
├─────────────┼────────────────────────────────────────────────┤
│ Description │ A database sharding router for simple sharding │
├─────────────┼────────────────────────────────────────────────┤
│ Parameters │ ... │

shell&amp;gt; maxctrl list servers
┌────────┬──────────────┬──────┬─────────────┬─────────┬───────────────┬──────────────────┐
│ Server │ Address │ Port │ Connections │ State │ GTID │ Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼───────────────┼──────────────────┤
│ shard2 │ 10.139.158.1 │ 3364 │ 4 │ Running │ 0-3364-290859 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼───────────────┼──────────────────┤
│ shard3 │ 10.139.158.1 │ 3365 │ 4 │ Running │ 0-3365-322671 │ Sharding-Monitor │
├────────┼──────────────┼──────┼─────────────┼─────────┼───────────────┼──────────────────┤
│ shard4 │ 10.139.158.1 │ 3366 │ 4 │ Running │ 0-3366-140018 │ Sharding-Monitor │
└────────┴──────────────┴──────┴─────────────┴─────────┴───────────────┴──────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Die Information für die Spalte Connections ist verwirrend, da wir bei diesem Sharding-System in diesem Fall auf jedem Shard nur 1, 1 und 2 Verbindungen offen haben.&lt;/p&gt;
&lt;p&gt;Wenn man sich mit &lt;code&gt;SHOW PROCESSLIST&lt;/code&gt; die Situation aber auf dem jeweiligen Shard anschaut, sieht man, dass MaxScale auf JEDEM Shard für jede eingehende Verbindung auch eine Verbindung auf jedes Shard aufbaut. Somit ist die Anzeige oben eigentlich technisch korrekt, nur nicht, was man erwarten würde:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SHOW PROCESSLIST;
+--------+------------------+----------------------+---------------+---------+------+----------+-----------------------------------------------------------------+----------+
| Id | User | Host | db | Command | Time | State | Info | Progress |
+--------+------------------+----------------------+---------------+---------+------+----------+-----------------------------------------------------------------+----------+
| 123 | root | localhost | customer_0021 | Query | 0 | starting | show processlist | 0.000 |
| 68107 | maxscale_monitor | 10.139.158.211:35418 | NULL | Sleep | 0 | | NULL | 0.000 |
| 113372 | app | 10.139.158.1:47548 | NULL | Sleep | 47 | | NULL | 0.000 |
| 113538 | app | 10.139.158.1:49058 | NULL | Sleep | 41 | | NULL | 0.000 |
| 113662 | app | 10.139.158.1:47072 | NULL | Sleep | 37 | | NULL | 0.000 |
| 114789 | app | 10.139.158.1:39574 | customer_0022 | Query | 0 | Updating | UPDATE sales SET product = 'Prepare to delete' WHERE id = 15622 | 0.000 |
+--------+------------------+----------------------+---------------+---------+------+----------+-----------------------------------------------------------------+----------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Das skaliert so nicht bei grossen Systemen mit hunderten oder tausenden von Mandanten! Ev. kommt in diesem Fall das &lt;a href="https://mariadb.com/kb/en/thread-pool-in-mariadb/" target="_blank"&gt;MariaDB Thread Pool&lt;/a&gt; Feature zum Einsatz.&lt;/p&gt;
&lt;p&gt;Laut dem MaxScale Entwickler ist dies erwartetes Verhalten&amp;hellip; (&lt;a href="https://jira.mariadb.org/browse/MXS-4977" target="_blank"&gt;MXS-4977&lt;/a&gt;)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; maxctrl list services
┌─────────────────┬──────────────┬─────────────┬───────────────────┬────────────────────────┐
│ Service │ Router │ Connections │ Total Connections │ Targets │
├─────────────────┼──────────────┼─────────────┼───────────────────┼────────────────────────┤
│ Sharded-Service │ schemarouter │ 4 │ 82776 │ shard2, shard3, shard4 │
└─────────────────┴──────────────┴─────────────┴───────────────────┴────────────────────────┘

shell&amp;gt; maxctrl list listeners
┌──────────────────────────┬──────┬──────┬─────────┬─────────────────┐
│ Name │ Port │ Host │ State │ Service │
├──────────────────────────┼──────┼──────┼─────────┼─────────────────┤
│ Sharded-Service-Listener │ 3306 │ :: │ Running │ Sharded-Service │
└──────────────────────────┴──────┴──────┴─────────┴─────────────────┘

shell&amp;gt; maxctrl list monitors
┌──────────────────┬─────────┬────────────────────────┐
│ Monitor │ State │ Servers │
├──────────────────┼─────────┼────────────────────────┤
│ Sharding-Monitor │ Running │ shard2, shard3, shard4 │
└──────────────────┴─────────┴────────────────────────┘

shell&amp;gt; maxctrl show server shard2 | head -n 20
┌─────────────────────┬──────────────────────────────────────────────┐
│ Server │ shard2 │
├─────────────────────┼──────────────────────────────────────────────┤
│ Source │ /etc/maxscale.cnf │
├─────────────────────┼──────────────────────────────────────────────┤
│ Address │ 10.139.158.1 │
├─────────────────────┼──────────────────────────────────────────────┤
│ Port │ 3364 │
├─────────────────────┼──────────────────────────────────────────────┤
│ State │ Running │
├─────────────────────┼──────────────────────────────────────────────┤
│ Version │ 10.11.7-MariaDB-log │
├─────────────────────┼──────────────────────────────────────────────┤
│ Uptime │ 178960 │
├─────────────────────┼──────────────────────────────────────────────┤
│ Last Event │ server_down │
├─────────────────────┼──────────────────────────────────────────────┤
│ Triggered At │ Sun, 04 Feb 2024 07:37:17 GMT │
├─────────────────────┼──────────────────────────────────────────────┤
│ Services │ Sharded-Service │
├─────────────────────┼──────────────────────────────────────────────┤
│ Monitors │ Sharding-Monitor │
├─────────────────────┼──────────────────────────────────────────────┤
...
├─────────────────────┼──────────────────────────────────────────────┤
│ Current Connections │ 5 │
├─────────────────────┼──────────────────────────────────────────────┤
│ Total Connections │ 27 │
├─────────────────────┼──────────────────────────────────────────────┤
│ Max Connections │ 5 │

shell&amp;gt; maxctrl show service Sharded-Service
┌─────────────────────┬──────────────────────────────────────────────────────┐
│ Service │ Sharded-Service │
├─────────────────────┼──────────────────────────────────────────────────────┤
│ Source │ /var/lib/maxscale/maxscale.cnf.d/Sharded-Service.cnf │
├─────────────────────┼──────────────────────────────────────────────────────┤
│ Router │ schemarouter │
├─────────────────────┼──────────────────────────────────────────────────────┤
│ State │ Started │
├─────────────────────┼──────────────────────────────────────────────────────┤
│ Started At │ 3/18/2024, 1:52:30 PM │
├─────────────────────┼──────────────────────────────────────────────────────┤
│ Users Loaded At │ 3/18/2024, 1:52:30 PM │
├─────────────────────┼──────────────────────────────────────────────────────┤
│ Current Connections │ 4 │
├─────────────────────┼──────────────────────────────────────────────────────┤
│ Total Connections │ 84590 │
├─────────────────────┼──────────────────────────────────────────────────────┤
│ Max Connections │ 5 │
├─────────────────────┼──────────────────────────────────────────────────────┤
│ Cluster │ │
├─────────────────────┼──────────────────────────────────────────────────────┤
│ Servers │ shard2 │
│ │ shard3 │
│ │ shard4 │
├─────────────────────┼──────────────────────────────────────────────────────┤
│ Services │ │
├─────────────────────┼──────────────────────────────────────────────────────┤
│ Filters │ │
├─────────────────────┼──────────────────────────────────────────────────────┤
│ Parameters │ { │
│ │ &amp;quot;auth_all_servers&amp;quot;: true, │
│ │ &amp;quot;connection_keepalive&amp;quot;: &amp;quot;300000ms&amp;quot;, │
│ │ &amp;quot;debug&amp;quot;: true, │
│ │ &amp;quot;disable_sescmd_history&amp;quot;: false, │
│ │ &amp;quot;enable_root_user&amp;quot;: false, │
│ │ &amp;quot;force_connection_keepalive&amp;quot;: false, │
│ │ &amp;quot;idle_session_pool_time&amp;quot;: &amp;quot;-1ms&amp;quot;, │
│ │ &amp;quot;ignore_tables&amp;quot;: [], │
│ │ &amp;quot;ignore_tables_regex&amp;quot;: null, │
│ │ &amp;quot;localhost_match_wildcard_host&amp;quot;: true, │
│ │ &amp;quot;log_auth_warnings&amp;quot;: true, │
│ │ &amp;quot;log_debug&amp;quot;: true, │
│ │ &amp;quot;log_info&amp;quot;: false, │
│ │ &amp;quot;log_notice&amp;quot;: false, │
│ │ &amp;quot;log_warning&amp;quot;: false, │
│ │ &amp;quot;max_connections&amp;quot;: 0, │
│ │ &amp;quot;max_sescmd_history&amp;quot;: 50, │
│ │ &amp;quot;max_staleness&amp;quot;: &amp;quot;150000ms&amp;quot;, │
│ │ &amp;quot;multiplex_timeout&amp;quot;: &amp;quot;60000ms&amp;quot;, │
│ │ &amp;quot;net_write_timeout&amp;quot;: &amp;quot;0ms&amp;quot;, │
│ │ &amp;quot;password&amp;quot;: &amp;quot;*****&amp;quot;, │
│ │ &amp;quot;prune_sescmd_history&amp;quot;: true, │
│ │ &amp;quot;rank&amp;quot;: &amp;quot;primary&amp;quot;, │
│ │ &amp;quot;refresh_databases&amp;quot;: false, │
│ │ &amp;quot;refresh_interval&amp;quot;: &amp;quot;10000ms&amp;quot;, │
│ │ &amp;quot;retain_last_statements&amp;quot;: -1, │
│ │ &amp;quot;router&amp;quot;: &amp;quot;schemarouter&amp;quot;, │
│ │ &amp;quot;session_trace&amp;quot;: false, │
│ │ &amp;quot;strip_db_esc&amp;quot;: true, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;service&amp;quot;, │
│ │ &amp;quot;user&amp;quot;: &amp;quot;maxscale_admin&amp;quot;, │
│ │ &amp;quot;user_accounts_file&amp;quot;: null, │
│ │ &amp;quot;user_accounts_file_usage&amp;quot;: &amp;quot;add_when_load_ok&amp;quot;, │
│ │ &amp;quot;version_string&amp;quot;: null, │
│ │ &amp;quot;wait_timeout&amp;quot;: &amp;quot;0ms&amp;quot; │
│ │ } │
├─────────────────────┼──────────────────────────────────────────────────────┤
│ Router Diagnostics │ { │
│ │ &amp;quot;average_session&amp;quot;: 0.028822357131634554, │
│ │ &amp;quot;longest_sescmd_chain&amp;quot;: 4, │
│ │ &amp;quot;longest_session&amp;quot;: 50, │
│ │ &amp;quot;queries&amp;quot;: 761134, │
│ │ &amp;quot;sescmd_percentage&amp;quot;: 44.44342257736483, │
│ │ &amp;quot;shard_map_hits&amp;quot;: 84356, │
│ │ &amp;quot;shard_map_misses&amp;quot;: 5, │
│ │ &amp;quot;shard_map_stale&amp;quot;: 229, │
│ │ &amp;quot;shard_map_updates&amp;quot;: 216, │
│ │ &amp;quot;shortest_session&amp;quot;: 0, │
│ │ &amp;quot;times_sescmd_limit_exceeded&amp;quot;: 0 │
│ │ } │
└─────────────────────┴──────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Siehe hierzu auch &lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-schemarouter/#router-diagnostics" target="_blank"&gt;MaxScale SchemaRouter Router diagnostics&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell&amp;gt; maxctrl show monitor Sharding-Monitor
┌─────────────────────┬──────────────────────────────────────────────────────────┐
│ Monitor │ Sharding-Monitor │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ Source │ /etc/maxscale.cnf │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ Module │ galeramon │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ State │ Running │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ Servers │ shard1 │
│ │ shard2 │
│ │ shard3 │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ Parameters │ { │
│ │ &amp;quot;available_when_donor&amp;quot;: false, │
│ │ &amp;quot;backend_connect_attempts&amp;quot;: 1, │
│ │ &amp;quot;backend_connect_timeout&amp;quot;: &amp;quot;3000ms&amp;quot;, │
│ │ &amp;quot;backend_read_timeout&amp;quot;: &amp;quot;3000ms&amp;quot;, │
│ │ &amp;quot;backend_write_timeout&amp;quot;: &amp;quot;3000ms&amp;quot;, │
│ │ &amp;quot;disable_master_failback&amp;quot;: false, │
│ │ &amp;quot;disable_master_role_setting&amp;quot;: false, │
│ │ &amp;quot;disk_space_check_interval&amp;quot;: &amp;quot;0ms&amp;quot;, │
│ │ &amp;quot;disk_space_threshold&amp;quot;: null, │
│ │ &amp;quot;events&amp;quot;: &amp;quot;all,master_down,master_up,...,new_donor&amp;quot;, │
│ │ &amp;quot;journal_max_age&amp;quot;: &amp;quot;28800000ms&amp;quot;, │
│ │ &amp;quot;module&amp;quot;: &amp;quot;galeramon&amp;quot;, │
│ │ &amp;quot;monitor_interval&amp;quot;: &amp;quot;1000ms&amp;quot;, │
│ │ &amp;quot;password&amp;quot;: &amp;quot;*****&amp;quot;, │
│ │ &amp;quot;root_node_as_master&amp;quot;: false, │
│ │ &amp;quot;script&amp;quot;: null, │
│ │ &amp;quot;script_timeout&amp;quot;: &amp;quot;90000ms&amp;quot;, │
│ │ &amp;quot;set_donor_nodes&amp;quot;: false, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;monitor&amp;quot;, │
│ │ &amp;quot;use_priority&amp;quot;: false, │
│ │ &amp;quot;user&amp;quot;: &amp;quot;maxscale_monitor&amp;quot; │
│ │ } │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ Monitor Diagnostics │ { │
│ │ &amp;quot;disable_master_failback&amp;quot;: false, │
│ │ &amp;quot;disable_master_role_setting&amp;quot;: false, │
│ │ &amp;quot;root_node_as_master&amp;quot;: false, │
│ │ &amp;quot;server_info&amp;quot;: [ │
│ │ { │
│ │ &amp;quot;gtid_binlog_pos&amp;quot;: &amp;quot;0-3363-26014&amp;quot;, │
│ │ &amp;quot;gtid_current_pos&amp;quot;: &amp;quot;0-3363-26014&amp;quot;, │
│ │ &amp;quot;master_id&amp;quot;: 0, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;shard1&amp;quot;, │
│ │ &amp;quot;read_only&amp;quot;: false, │
│ │ &amp;quot;server_id&amp;quot;: 3363 │
│ │ }, │
│ │ { │
│ │ &amp;quot;gtid_binlog_pos&amp;quot;: &amp;quot;0-3364-240612&amp;quot;, │
│ │ &amp;quot;gtid_current_pos&amp;quot;: &amp;quot;0-3364-240612&amp;quot;, │
│ │ &amp;quot;master_id&amp;quot;: 0, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;shard2&amp;quot;, │
│ │ &amp;quot;read_only&amp;quot;: false, │
│ │ &amp;quot;server_id&amp;quot;: 3364 │
│ │ }, │
│ │ { │
│ │ &amp;quot;gtid_binlog_pos&amp;quot;: &amp;quot;0-3365-289873&amp;quot;, │
│ │ &amp;quot;gtid_current_pos&amp;quot;: &amp;quot;0-3365-289873&amp;quot;, │
│ │ &amp;quot;master_id&amp;quot;: 0, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;shard3&amp;quot;, │
│ │ &amp;quot;read_only&amp;quot;: false, │
│ │ &amp;quot;server_id&amp;quot;: 3365 │
│ │ } │
│ │ ], │
│ │ &amp;quot;set_donor_nodes&amp;quot;: false, │
│ │ &amp;quot;use_priority&amp;quot;: false │
│ │ } │
└─────────────────────┴──────────────────────────────────────────────────────────┘

shell&amp;gt; maxctrl list sessions;
┌───────┬──────┬──────────────┬───────────────────────┬───────┬─────────────────┬────────┬──────────────┐
│ Id │ User │ Host │ Connected │ Idle │ Service │ Memory │ I/O-Activity │
├───────┼──────┼──────────────┼───────────────────────┼───────┼─────────────────┼────────┼──────────────┤
│ 87240 │ app │ 10.139.158.1 │ 3/18/2024, 2:33:54 PM │ 0 │ Sharded-Service │ 68644 │ 33 │
├───────┼──────┼──────────────┼───────────────────────┼───────┼─────────────────┼────────┼──────────────┤
│ 72654 │ app │ 10.139.158.1 │ 3/18/2024, 2:25:27 PM │ 506.3 │ Sharded-Service │ 199328 │ 0 │
├───────┼──────┼──────────────┼───────────────────────┼───────┼─────────────────┼────────┼──────────────┤
│ 72364 │ app │ 10.139.158.1 │ 3/18/2024, 2:25:18 PM │ 516 │ Sharded-Service │ 199328 │ 0 │
├───────┼──────┼──────────────┼───────────────────────┼───────┼─────────────────┼────────┼──────────────┤
│ 72530 │ app │ 10.139.158.1 │ 3/18/2024, 2:25:23 PM │ 510.5 │ Sharded-Service │ 199328 │ 0 │
└───────┴──────┴──────────────┴───────────────────────┴───────┴─────────────────┴────────┴──────────────┘

shell&amp;gt; maxctrl show session 26
┌───────────────────────┬───────────────────────────────────────┐
│ Id │ 26 │
├───────────────────────┼───────────────────────────────────────┤
│ Service │ Sharded-Service │
├───────────────────────┼───────────────────────────────────────┤
│ State │ Session started │
├───────────────────────┼───────────────────────────────────────┤
│ User │ app │
├───────────────────────┼───────────────────────────────────────┤
│ Host │ 10.139.158.1 │
├───────────────────────┼───────────────────────────────────────┤
│ Port │ 42854 │
├───────────────────────┼───────────────────────────────────────┤
│ Database │ │
├───────────────────────┼───────────────────────────────────────┤
│ Connected │ 2/4/2024, 9:31:12 AM │
├───────────────────────┼───────────────────────────────────────┤
│ Idle │ 610.4 │
├───────────────────────┼───────────────────────────────────────┤
│ Parameters │ { │
│ │ &amp;quot;log_error&amp;quot;: false, │
│ │ &amp;quot;log_info&amp;quot;: false, │
│ │ &amp;quot;log_notice&amp;quot;: false, │
│ │ &amp;quot;log_warning&amp;quot;: false │
│ │ } │
├───────────────────────┼───────────────────────────────────────┤
│ Client TLS Cipher │ │
├───────────────────────┼───────────────────────────────────────┤
│ Connection attributes │ { │
│ │ &amp;quot;_client_name&amp;quot;: &amp;quot;libmariadb&amp;quot;, │
│ │ &amp;quot;_client_version&amp;quot;: &amp;quot;3.3.8&amp;quot;, │
│ │ &amp;quot;_os&amp;quot;: &amp;quot;Linux&amp;quot;, │
│ │ &amp;quot;_pid&amp;quot;: &amp;quot;251037&amp;quot;, │
│ │ &amp;quot;_platform&amp;quot;: &amp;quot;x86_64&amp;quot;, │
│ │ &amp;quot;_server_host&amp;quot;: &amp;quot;10.139.158.211&amp;quot;, │
│ │ &amp;quot;program_name&amp;quot;: &amp;quot;mysql&amp;quot; │
│ │ } │
├───────────────────────┼───────────────────────────────────────┤
│ Connections │ shard1 │
│ │ shard2 │
│ │ shard3 │
├───────────────────────┼───────────────────────────────────────┤
│ Connection IDs │ 666 │
│ │ 139 │
│ │ 138 │
├───────────────────────┼───────────────────────────────────────┤
│ Queries │ │
├───────────────────────┼───────────────────────────────────────┤
│ Log │ │
├───────────────────────┼───────────────────────────────────────┤
│ Memory │ { │
│ │ &amp;quot;connection_buffers&amp;quot;: { │
│ │ &amp;quot;backends&amp;quot;: { │
│ │ &amp;quot;shard1&amp;quot;: { │
│ │ &amp;quot;misc&amp;quot;: 678, │
│ │ &amp;quot;readq&amp;quot;: 65536, │
│ │ &amp;quot;total&amp;quot;: 66214, │
│ │ &amp;quot;writeq&amp;quot;: 0 │
│ │ }, │
│ │ &amp;quot;shard2&amp;quot;: { │
│ │ &amp;quot;misc&amp;quot;: 662, │
│ │ &amp;quot;readq&amp;quot;: 0, │
│ │ &amp;quot;total&amp;quot;: 662, │
│ │ &amp;quot;writeq&amp;quot;: 0 │
│ │ }, │
│ │ &amp;quot;shard3&amp;quot;: { │
│ │ &amp;quot;misc&amp;quot;: 678, │
│ │ &amp;quot;readq&amp;quot;: 65536, │
│ │ &amp;quot;total&amp;quot;: 66214, │
│ │ &amp;quot;writeq&amp;quot;: 0 │
│ │ } │
│ │ }, │
│ │ &amp;quot;client&amp;quot;: { │
│ │ &amp;quot;misc&amp;quot;: 654, │
│ │ &amp;quot;readq&amp;quot;: 65536, │
│ │ &amp;quot;total&amp;quot;: 66190, │
│ │ &amp;quot;writeq&amp;quot;: 0 │
│ │ }, │
│ │ &amp;quot;total&amp;quot;: 199280 │
│ │ }, │
│ │ &amp;quot;exec_metadata&amp;quot;: 0, │
│ │ &amp;quot;last_queries&amp;quot;: 0, │
│ │ &amp;quot;sescmd_history&amp;quot;: 48, │
│ │ &amp;quot;total&amp;quot;: 199328, │
│ │ &amp;quot;variables&amp;quot;: 0 │
│ │ } │
├───────────────────────┼───────────────────────────────────────┤
│ I/O Activity │ 0 │
└───────────────────────┴───────────────────────────────────────┘

shell&amp;gt; maxctrl show listener Sharded-Service-Listener
┌────────────┬───────────────────────────────────────────┐
│ Name │ Sharded-Service-Listener │
├────────────┼───────────────────────────────────────────┤
│ Source │ /etc/maxscale.cnf │
├────────────┼───────────────────────────────────────────┤
│ Service │ Sharded-Service │
├────────────┼───────────────────────────────────────────┤
│ Parameters │ { │
│ │ &amp;quot;MariaDBProtocol&amp;quot;: { │
│ │ &amp;quot;allow_replication&amp;quot;: true │
│ │ }, │
│ │ &amp;quot;address&amp;quot;: &amp;quot;::&amp;quot;, │
│ │ &amp;quot;authenticator&amp;quot;: null, │
│ │ &amp;quot;authenticator_options&amp;quot;: null, │
│ │ &amp;quot;connection_init_sql_file&amp;quot;: null, │
│ │ &amp;quot;connection_metadata&amp;quot;: [ │
│ │ &amp;quot;character_set_client=auto&amp;quot;, │
│ │ &amp;quot;character_set_connection=auto&amp;quot;, │
│ │ &amp;quot;character_set_results=auto&amp;quot;, │
│ │ &amp;quot;max_allowed_packet=auto&amp;quot;, │
│ │ &amp;quot;system_time_zone=auto&amp;quot;, │
│ │ &amp;quot;time_zone=auto&amp;quot;, │
│ │ &amp;quot;tx_isolation=auto&amp;quot; │
│ │ ], │
│ │ &amp;quot;port&amp;quot;: 3306, │
│ │ &amp;quot;protocol&amp;quot;: &amp;quot;MariaDBProtocol&amp;quot;, │
│ │ &amp;quot;proxy_protocol_networks&amp;quot;: null, │
│ │ &amp;quot;service&amp;quot;: &amp;quot;Sharded-Service&amp;quot;, │
│ │ &amp;quot;socket&amp;quot;: null, │
│ │ &amp;quot;sql_mode&amp;quot;: &amp;quot;default&amp;quot;, │
│ │ &amp;quot;ssl&amp;quot;: false, │
│ │ &amp;quot;ssl_ca&amp;quot;: null, │
│ │ &amp;quot;ssl_cert&amp;quot;: null, │
│ │ &amp;quot;ssl_cert_verify_depth&amp;quot;: 9, │
│ │ &amp;quot;ssl_cipher&amp;quot;: null, │
│ │ &amp;quot;ssl_crl&amp;quot;: null, │
│ │ &amp;quot;ssl_key&amp;quot;: null, │
│ │ &amp;quot;ssl_verify_peer_certificate&amp;quot;: false, │
│ │ &amp;quot;ssl_verify_peer_host&amp;quot;: false, │
│ │ &amp;quot;ssl_version&amp;quot;: &amp;quot;MAX&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;listener&amp;quot;, │
│ │ &amp;quot;user_mapping_file&amp;quot;: null │
│ │ } │
└────────────┴───────────────────────────────────────────┘

shell&amp;gt; maxctrl show module schemarouter
┌─────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Module │ schemarouter │
├─────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Type │ Router │
├─────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Version │ V1.0.0 │
├─────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Maturity │ Beta │
├─────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Description │ A database sharding router for simple sharding │
├─────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Parameters │ [ │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: false, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Enable debug mode&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;debug&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;bool&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: [], │
│ │ &amp;quot;description&amp;quot;: &amp;quot;List of tables to ignore when checking for duplicates&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;ignore_tables&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;stringlist&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Regex of tables to ignore when checking for duplicates&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;ignore_tables_regex&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;regex&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: &amp;quot;150000ms&amp;quot;, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Maximum allowed staleness of a database map entry before clients block and wait for an update&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;max_staleness&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;duration&amp;quot;, │
│ │ &amp;quot;unit&amp;quot;: &amp;quot;ms&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: false, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Refresh database mapping information&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;refresh_databases&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;bool&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: &amp;quot;300000ms&amp;quot;, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;How often to refresh the database mapping information&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;refresh_interval&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;duration&amp;quot;, │
│ │ &amp;quot;unit&amp;quot;: &amp;quot;ms&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: false, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Retrieve users from all backend servers instead of only one&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;auth_all_servers&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;bool&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: &amp;quot;300000ms&amp;quot;, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;How ofted idle connections are pinged&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;connection_keepalive&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;duration&amp;quot;, │
│ │ &amp;quot;unit&amp;quot;: &amp;quot;ms&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;deprecated&amp;quot;: true, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Alias for 'wait_timeout'&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;connection_timeout&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;duration&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: false, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Disable session command history&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;disable_sescmd_history&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;bool&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: false, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Allow the root user to connect to this service&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;enable_root_user&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;bool&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: false, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Ping connections unconditionally&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;force_connection_keepalive&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;bool&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: &amp;quot;-1ms&amp;quot;, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Put connections into pool after session has been idle for this long&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;idle_session_pool_time&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;duration&amp;quot;, │
│ │ &amp;quot;unit&amp;quot;: &amp;quot;ms&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: true, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Match localhost to wildcard host&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;localhost_match_wildcard_host&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;bool&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: true, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Log a warning when client authentication fails&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;log_auth_warnings&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;bool&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: false, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Log debug messages for this service (debug builds only)&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;log_debug&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;bool&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: false, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Log info messages for this service&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;log_info&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;bool&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: false, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Log notice messages for this service&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;log_notice&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;bool&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: false, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Log warning messages for this service&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;log_warning&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;bool&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: 0, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Maximum number of connections&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;max_connections&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;count&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: 50, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Session command history size&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;max_sescmd_history&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;count&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: &amp;quot;60000ms&amp;quot;, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;How long a session can wait for a connection to become available&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;multiplex_timeout&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;duration&amp;quot;, │
│ │ &amp;quot;unit&amp;quot;: &amp;quot;ms&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: &amp;quot;0ms&amp;quot;, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Network write timeout&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;net_write_timeout&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;duration&amp;quot;, │
│ │ &amp;quot;unit&amp;quot;: &amp;quot;ms&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Password for the user used to retrieve database users&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: true, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;password&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;password&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: true, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Prune old session command history if the limit is exceeded&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;prune_sescmd_history&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;bool&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: &amp;quot;primary&amp;quot;, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Service rank&amp;quot;, │
│ │ &amp;quot;enum_values&amp;quot;: [ │
│ │ &amp;quot;primary&amp;quot;, │
│ │ &amp;quot;secondary&amp;quot; │
│ │ ], │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;rank&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;enum&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: -1, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Number of statements kept in memory&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;retain_last_statements&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;int&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: false, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Enable session tracing for this service&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;session_trace&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;bool&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: false, │
│ │ &amp;quot;deprecated&amp;quot;: true, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Track session state using server responses&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;session_track_trx_state&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;bool&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: true, │
│ │ &amp;quot;deprecated&amp;quot;: true, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Strip escape characters from database names&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;strip_db_esc&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;bool&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Username used to retrieve database users&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: true, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;user&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Load additional users from a file&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: false, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;user_accounts_file&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;path&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: &amp;quot;add_when_load_ok&amp;quot;, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;When and how the user accounts file is used&amp;quot;, │
│ │ &amp;quot;enum_values&amp;quot;: [ │
│ │ &amp;quot;add_when_load_ok&amp;quot;, │
│ │ &amp;quot;file_only_always&amp;quot; │
│ │ ], │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: false, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;user_accounts_file_usage&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;enum&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Custom version string to use&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;version_string&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;default_value&amp;quot;: &amp;quot;0ms&amp;quot;, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Connection idle timeout&amp;quot;, │
│ │ &amp;quot;mandatory&amp;quot;: false, │
│ │ &amp;quot;modifiable&amp;quot;: true, │
│ │ &amp;quot;name&amp;quot;: &amp;quot;wait_timeout&amp;quot;, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;duration&amp;quot;, │
│ │ &amp;quot;unit&amp;quot;: &amp;quot;ms&amp;quot; │
│ │ } │
│ │ ] │
├─────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Commands │ [ │
│ │ { │
│ │ &amp;quot;attributes&amp;quot;: { │
│ │ &amp;quot;arg_max&amp;quot;: 1, │
│ │ &amp;quot;arg_min&amp;quot;: 1, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Clear schemarouter shard map cache&amp;quot;, │
│ │ &amp;quot;method&amp;quot;: &amp;quot;POST&amp;quot;, │
│ │ &amp;quot;parameters&amp;quot;: [ │
│ │ { │
│ │ &amp;quot;description&amp;quot;: &amp;quot;The schemarouter service&amp;quot;, │
│ │ &amp;quot;required&amp;quot;: true, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;SERVICE&amp;quot; │
│ │ } │
│ │ ] │
│ │ }, │
│ │ &amp;quot;id&amp;quot;: &amp;quot;clear&amp;quot;, │
│ │ &amp;quot;links&amp;quot;: { │
│ │ &amp;quot;self&amp;quot;: &amp;quot;http://127.0.0.1:8989/v1/modules/schemarouter/clear/&amp;quot; │
│ │ }, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;module_command&amp;quot; │
│ │ }, │
│ │ { │
│ │ &amp;quot;attributes&amp;quot;: { │
│ │ &amp;quot;arg_max&amp;quot;: 1, │
│ │ &amp;quot;arg_min&amp;quot;: 1, │
│ │ &amp;quot;description&amp;quot;: &amp;quot;Invalidate schemarouter shard map cache&amp;quot;, │
│ │ &amp;quot;method&amp;quot;: &amp;quot;POST&amp;quot;, │
│ │ &amp;quot;parameters&amp;quot;: [ │
│ │ { │
│ │ &amp;quot;description&amp;quot;: &amp;quot;The schemarouter service&amp;quot;, │
│ │ &amp;quot;required&amp;quot;: true, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;SERVICE&amp;quot; │
│ │ } │
│ │ ] │
│ │ }, │
│ │ &amp;quot;id&amp;quot;: &amp;quot;invalidate&amp;quot;, │
│ │ &amp;quot;links&amp;quot;: { │
│ │ &amp;quot;self&amp;quot;: &amp;quot;http://127.0.0.1:8989/v1/modules/schemarouter/invalidate/&amp;quot; │
│ │ }, │
│ │ &amp;quot;type&amp;quot;: &amp;quot;module_command&amp;quot; │
│ │ } │
│ │ ] │
└─────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

shell&amp;gt; maxctrl show commands schemarouter
┌────────────┬────────────┬──────────────────────────┐
│ Command │ Parameters │ Descriptions │
├────────────┼────────────┼──────────────────────────┤
│ clear │ SERVICE │ The schemarouter service │
├────────────┼────────────┼──────────────────────────┤
│ invalidate │ SERVICE │ The schemarouter service │
└────────────┴────────────┴──────────────────────────┘

shell&amp;gt; maxctrl show dbusers Sharded-Service
┌───────────────────────┬────────────────┬───────────────────────┬───────┬───────┬────────┬───────┬──────┐
│ User │ Host │ Plugin │ TLS │ Super │ Global │ Proxy │ Role │
├───────────────────────┼────────────────┼───────────────────────┼───────┼───────┼────────┼───────┼──────┤
│ PUBLIC │ │ │ false │ false │ false │ false │ │
├───────────────────────┼────────────────┼───────────────────────┼───────┼───────┼────────┼───────┼──────┤
│ app │ 10.139.158.% │ mysql_native_password │ false │ false │ false │ false │ │
├───────────────────────┼────────────────┼───────────────────────┼───────┼───────┼────────┼───────┼──────┤
│ app_role │ │ │ false │ false │ false │ false │ │
├───────────────────────┼────────────────┼───────────────────────┼───────┼───────┼────────┼───────┼──────┤
│ mariadb.sys │ localhost │ mysql_native_password │ false │ false │ false │ false │ │
├───────────────────────┼────────────────┼───────────────────────┼───────┼───────┼────────┼───────┼──────┤
│ maxscale_admin │ 10.139.158.210 │ mysql_native_password │ false │ false │ false │ false │ │
├───────────────────────┼────────────────┼───────────────────────┼───────┼───────┼────────┼───────┼──────┤
│ maxscale_admin │ 10.139.158.211 │ mysql_native_password │ false │ false │ false │ false │ │
├───────────────────────┼────────────────┼───────────────────────┼───────┼───────┼────────┼───────┼──────┤
│ maxscale_admin_role │ │ │ false │ false │ false │ false │ │
├───────────────────────┼────────────────┼───────────────────────┼───────┼───────┼────────┼───────┼──────┤
│ maxscale_monitor │ 10.139.158.210 │ mysql_native_password │ false │ false │ false │ false │ │
├───────────────────────┼────────────────┼───────────────────────┼───────┼───────┼────────┼───────┼──────┤
│ maxscale_monitor │ 10.139.158.211 │ mysql_native_password │ false │ false │ false │ false │ │
├───────────────────────┼────────────────┼───────────────────────┼───────┼───────┼────────┼───────┼──────┤
│ maxscale_monitor_role │ │ │ false │ false │ false │ false │ │
├───────────────────────┼────────────────┼───────────────────────┼───────┼───────┼────────┼───────┼──────┤
│ mysql │ localhost │ mysql_native_password │ false │ true │ true │ false │ │
├───────────────────────┼────────────────┼───────────────────────┼───────┼───────┼────────┼───────┼──────┤
│ root │ localhost │ mysql_native_password │ false │ true │ true │ false │ │
└───────────────────────┴────────────────┴───────────────────────┴───────┴───────┴────────┴───────┴──────┘

shell&amp;gt; maxctrl show commands mariadbmon
┌───────────────────────────┬─────────────────────────────┬───────────────────────────────────────────────────────────────────────────────┐
│ Command │ Parameters │ Descriptions │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ switchover │ MONITOR, [SERVER], [SERVER] │ Monitor name, New primary (optional), Current primary (optional) │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ switchover-force │ MONITOR, [SERVER], [SERVER] │ Monitor name, New primary (optional), Current primary (optional) │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ async-switchover │ MONITOR, [SERVER], [SERVER] │ Monitor name, New primary (optional), Current primary (optional) │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ failover │ MONITOR │ Monitor name │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ async-failover │ MONITOR │ Monitor name │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ rejoin │ MONITOR, SERVER │ Monitor name, Joining server │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ async-rejoin │ MONITOR, SERVER │ Monitor name, Joining server │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ reset-replication │ MONITOR, [SERVER] │ Monitor name, Primary server (optional) │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ async-reset-replication │ MONITOR, [SERVER] │ Monitor name, Primary server (optional) │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ release-locks │ MONITOR │ Monitor name │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ async-release-locks │ MONITOR │ Monitor name │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ fetch-cmd-result │ MONITOR │ Monitor name │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ cancel-cmd │ MONITOR │ Monitor name │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ async-cs-add-node │ MONITOR, STRING, STRING │ Monitor name, Hostname/IP of node to add to ColumnStore cluster, Timeout │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ async-cs-remove-node │ MONITOR, STRING, STRING │ Monitor name, Hostname/IP of node to remove from ColumnStore cluster, Timeout │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ cs-get-status │ MONITOR │ Monitor name │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ async-cs-get-status │ MONITOR │ Monitor name │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ async-cs-start-cluster │ MONITOR, STRING │ Monitor name, Timeout │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ async-cs-stop-cluster │ MONITOR, STRING │ Monitor name, Timeout │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ async-cs-set-readonly │ MONITOR, STRING │ Monitor name, Timeout │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ async-cs-set-readwrite │ MONITOR, STRING │ Monitor name, Timeout │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ async-rebuild-server │ MONITOR, SERVER, [SERVER] │ Monitor name, Target server, Source server (optional) │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ async-create-backup │ MONITOR, SERVER, STRING │ Monitor name, Source server, Backup name │
├───────────────────────────┼─────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
│ async-restore-from-backup │ MONITOR, SERVER, STRING │ Monitor name, Target server, Backup name │
└───────────────────────────┴─────────────────────────────┴───────────────────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="literatur--quellen"&gt;Literatur / Quellen&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;MaxScale: &lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-simple-sharding-with-two-servers/" target="_blank"&gt;Simple Sharding with Two Servers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;MaxScale: &lt;a href="https://mariadb.com/kb/en/mariadb-maxscale-2308-schemarouter/" target="_blank"&gt;SchemaRouter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>dbstat für MariaDB (und MySQL)</title><link>https://www.fromdual.com/de/blog/dbstat-fuer-mariadb-und-mysql/</link><pubDate>Thu, 14 Mar 2024 11:33:42 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/dbstat-fuer-mariadb-und-mysql/</guid><description>&lt;h2 id="inhaltsverzeichnis"&gt;Inhaltsverzeichnis&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/dbstat-fuer-mariadb-und-mysql/#features"&gt;Funktionalität&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/dbstat-fuer-mariadb-und-mysql/#how-does-dbstat-work"&gt;Wie funktioniert &lt;code&gt;dbstat&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/dbstat-fuer-mariadb-und-mysql/#installation"&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/dbstat-fuer-mariadb-und-mysql/#queries"&gt;Abfrage&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/dbstat-fuer-mariadb-und-mysql/#table_size"&gt;&lt;code&gt;table_size&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/dbstat-fuer-mariadb-und-mysql/#processlist"&gt;&lt;code&gt;processlist&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/dbstat-fuer-mariadb-und-mysql/#trx_and_lck"&gt;&lt;code&gt;trx_and_lck&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/dbstat-fuer-mariadb-und-mysql/#metadata_lock"&gt;&lt;code&gt;metadata_lock&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/dbstat-fuer-mariadb-und-mysql/#global_variables"&gt;&lt;code&gt;global_variables&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/dbstat-fuer-mariadb-und-mysql/#global_status"&gt;&lt;code&gt;global_status&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/dbstat-fuer-mariadb-und-mysql/#testing"&gt;Testen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/de/blog/dbstat-fuer-mariadb-und-mysql/#sources"&gt;Quellen&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Eine Idee, die ich schon lange ins Auge gefasst und jetzt endlich, dank eines Kunden, in Angriff genommen habe, ist &lt;code&gt;dbstat&lt;/code&gt; für MariaDB/MySQL. Die Idee ist angelehnt an &lt;code&gt;sar/sysstat&lt;/code&gt; von Sebastien Godard:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;sar - Collect, report, or save system activity information.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;und Oracle Statspack:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Statspack is a performance tuning tool &amp;hellip; to quickly gather detailed analysis of the performance of that database instance.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="funktionalität"&gt;Funktionalität&lt;/h2&gt;
&lt;p&gt;Zwar haben wir seit längerem das Performance Schema, aber dieses deckt einige Punkte nicht ab, die wir in der Praxis als Problem sehen und von Kunden gewünscht werden:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Das Modul &lt;code&gt;table_size&lt;/code&gt; sammelt Daten über das Wachstum von Tabellen. Somit können Aussagen über das Wachstum einzelner Tabellen, Datenbanken, die zukünftigen &lt;a href="https://mariadb.com/kb/en/catalogs/" data-targe="_blank"&gt;MariaDB Kataloge&lt;/a&gt; oder die ganze Instanz gemacht werden. Dies ist interessant für Nutzer welche Multi-Mandanten-Systeme (multi-tenant) im Einsatz oder sonst wie mit unkontrolliertem Wachstum zu kämpfen haben.&lt;/li&gt;
&lt;li&gt;Das Modul &lt;code&gt;processlist&lt;/code&gt; macht in regelmässigen Abständen einen Snapshot der Prozessliste und speichert diese ab. Diese Informationen sind nützlich bei post-mortem Analysen wenn der Nutzer zu langsam war, seine Prozessliste wegzuspeichern oder um zu verstehen, wie sich ein Problem aufgebaut hat.&lt;/li&gt;
&lt;li&gt;Oft baut sich das Problem durch langlaufende Transaktion, Row Locks oder Metadata Locks auf. Diese werden durch die Module &lt;code&gt;trx_and_lck&lt;/code&gt; sowie &lt;code&gt;metadata_lock&lt;/code&gt; festgehalten und abgespeichert. Somit können wir Probleme sehen, die wir vorher gar nicht gespürt haben oder wird sehen nach dem Unglück, was zum Problem geführt hat (analog zu einem Fahrtenschreiber im Fahrzeug).&lt;/li&gt;
&lt;li&gt;Eine weitere Fragestellung, die wir in der Praxis manchmal antreffen, ist: Wann wurde welche Datenbankvariable geändert und wie sah sie vorher aus. Dies wird durch das Modul &lt;code&gt;global_variables&lt;/code&gt; abgedeckt. Wer oder warum die Variable geändert hat, kann leider datenbankseitig nicht eruiert werden. Dazu sind betriebliche Prozesse notwendig.&lt;/li&gt;
&lt;li&gt;Das letzte Modul, &lt;code&gt;global_status&lt;/code&gt; deckt eigentlich das ab, was &lt;code&gt;sar/sysstat&lt;/code&gt; tut. Es sammelt die Werte von &lt;code&gt;SHOW GLOBAL STATUS;&lt;/code&gt; ein und speichert sie ab für spätere Analysezwecke oder um damit einfach Graphen erstellen zu können.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="wie-funktioniert-dbstat"&gt;Wie funktioniert &lt;code&gt;dbstat&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;dbstat&lt;/code&gt; nutzt als Scheduler den Datenbank &lt;a href="https://mariadb.com/kb/en/event-scheduler/" target="_blank"&gt;Event Scheduler&lt;/a&gt;. Dieser muss bei MariaDB zuerst eingeschaltet werden (&lt;code&gt;event_scheduler = ON&lt;/code&gt;). Bei MySQL ist er bereits per default eingeschaltet. Der Event Scheduler hat den Vorteil, dass wir die Jobs feingranularer aktivieren können, zum Beispiel 10 s, was mit der Crontab nicht möglich wäre.&lt;/p&gt;
&lt;p&gt;Der Event Scheduler führt dann &lt;a href="https://mariadb.com/kb/en/create-procedure/" target="_blank"&gt;SQL/PSM&lt;/a&gt; Code aus um einerseits die Daten zu sammeln und andererseits die Daten auch wieder zu löschen, damit die &lt;code&gt;dbstat&lt;/code&gt; Datenbank nicht ins unermessliche wächst.&lt;/p&gt;
&lt;p&gt;Aktuell sind folgende Jobs vorgesehen:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Modul&lt;/th&gt;
&lt;th style="text-align: left;"&gt;Sammeln&lt;/th&gt;
&lt;th style="text-align: left;"&gt;Löschen&lt;/th&gt;
&lt;th style="text-align: left;"&gt;Mengengerüst&lt;/th&gt;
&lt;th style="text-align: left;"&gt;Bemerkungen&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;table_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1/d um 02:04&lt;/td&gt;
&lt;td&gt;12/h, 1000 rows, &amp;gt; 31 d&lt;/td&gt;
&lt;td&gt;1000 tab x 31 d = 31k rows&lt;/td&gt;
&lt;td&gt;Sollte bis 288k Tabellen funktionieren.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;processlist&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1/min&lt;/td&gt;
&lt;td&gt;1/min, 1000 rows, &amp;gt; 7 d&lt;/td&gt;
&lt;td&gt;1000 con x 1440 min x 7 d = 10M rows&lt;/td&gt;
&lt;td&gt;Sollte bis 1000 Concurrent Connections funktionieren.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;trx_and_lck&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1/min&lt;/td&gt;
&lt;td&gt;1/min, 1000 rows, &amp;gt; 7 d&lt;/td&gt;
&lt;td&gt;100 lck x 1440 min x 7 d = 1M rows&lt;/td&gt;
&lt;td&gt;Hängt sehr stark von der Anwendung ab.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;metadata_lock&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1/min&lt;/td&gt;
&lt;td&gt;12/h, 1000 rows, &amp;gt; 30 d&lt;/td&gt;
&lt;td&gt;100 mdl x 1440 x 30 d = 4M rows&lt;/td&gt;
&lt;td&gt;Hängt sehr stark von der Anwendung ab.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;global_variables&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1/min&lt;/td&gt;
&lt;td&gt;nie&lt;/td&gt;
&lt;td&gt;1000 rows&lt;/td&gt;
&lt;td&gt;Im Normalfall sollte diese Tabelle nicht anwachsen.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;global_status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1/min&lt;/td&gt;
&lt;td&gt;1/min, 1000 rows, &amp;gt; 30 d&lt;/td&gt;
&lt;td&gt;1000 rows x 1440 x 30 d = 40M rows&lt;/td&gt;
&lt;td&gt;Kann gross werden?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="installation"&gt;Installation&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;dbstat&lt;/code&gt; kann von &lt;a href="https://github.com/FromDual/dbstat" target="_blank"&gt;Github&lt;/a&gt; heruntergeladen werden und steht unter der GPLv2.&lt;/p&gt;
&lt;p&gt;Die Installation ist einfach: Als erstes die SQL Datei &lt;code&gt;create_user_and_db.sql&lt;/code&gt; ausführen. Dann in der Datenbank &lt;code&gt;dbstat&lt;/code&gt; die entsprechenden &lt;code&gt;create_*.sql&lt;/code&gt; Dateien für die jeweiligen Module ausführen. Es gibt zur Zeit keine direkten Abhängigkeiten zwischen den Modulen. Wenn ein anderer User oder eine andere Datenbank als &lt;code&gt;dbstat&lt;/code&gt; verwendet werden soll, muss man sich selber drum kümmern.&lt;/p&gt;
&lt;h2 id="abfrage"&gt;Abfrage&lt;/h2&gt;
&lt;p&gt;Einige mögliche Abfragen auf die Daten wurden bereits vorbereitet. Sie sind zu finden in den Dateien &lt;code&gt;query_*.sql&lt;/code&gt;. Hier ein paar Beispiele:&lt;/p&gt;
&lt;h3 id="table_size"&gt;&lt;code&gt;table_size&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;SELECT `table_schema`, `table_name`, `ts`, `table_rows`, `data_length`, `index_length`
 FROM `table_size`
 WHERE `table_catalog` = 'def'
 AND `table_schema` = 'dbstat'
 AND `table_name` = 'table_size'
ORDER BY `ts` ASC
;
+--------------+------------+---------------------+------------+-------------+--------------+
| table_schema | table_name | ts | table_rows | data_length | index_length |
+--------------+------------+---------------------+------------+-------------+--------------+
| dbstat | table_size | 2024-03-09 20:01:00 | 0 | 16384 | 16384 |
| dbstat | table_size | 2024-03-10 17:26:33 | 310 | 65536 | 16384 |
| dbstat | table_size | 2024-03-11 08:28:12 | 622 | 114688 | 49152 |
| dbstat | table_size | 2024-03-12 08:02:38 | 934 | 114688 | 49152 |
| dbstat | table_size | 2024-03-13 08:08:55 | 1247 | 278528 | 81920 |
+--------------+------------+---------------------+------------+-------------+--------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="processlist"&gt;&lt;code&gt;processlist&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;SELECT connection_id, ts, time, state, SUBSTR(REGEXP_REPLACE(REPLACE(query, &amp;quot;&amp;lt;br&amp;gt;n&amp;quot;, ' '), '&amp;lt;br&amp;gt; +', ' '), 1, 64) AS query
 FROM processlist
 WHERE command != 'Sleep'
 AND connection_id = @connection_id
 ORDER BY ts ASC
 LIMIT 5
;
+---------------+---------------------+---------+---------------------------------+---------------------------------------------+
| connection_id | ts | time | state | query |
+---------------+---------------------+---------+---------------------------------+---------------------------------------------+
| 14956 | 2024-03-09 20:21:12 | 13.042 | Waiting for table metadata lock | update test set data = 'bla' where id = 100 |
| 14956 | 2024-03-09 20:22:12 | 73.045 | Waiting for table metadata lock | update test set data = 'bla' where id = 100 |
| 14956 | 2024-03-09 20:23:12 | 133.044 | Waiting for table metadata lock | update test set data = 'bla' where id = 100 |
| 14956 | 2024-03-09 20:24:12 | 193.044 | Waiting for table metadata lock | update test set data = 'bla' where id = 100 |
| 14956 | 2024-03-09 20:25:12 | 253.041 | Waiting for table metadata lock | update test set data = 'bla' where id = 100 |
+---------------+---------------------+---------+---------------------------------+---------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="trx_and_lck"&gt;&lt;code&gt;trx_and_lck&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;SELECT * FROM trx_and_lck&amp;lt;br&amp;gt;G
*************************** 1. row ***************************
 machine_name:
 connection_id: 14815
 trx_id: 269766
 ts: 2024-03-09 20:05:57
 user: root
 host: localhost
 db: test
 command: Query
 time: 41.000
 running_since: 2024-03-09 20:05:16
 state: Statistics
 info: select * from test where id = 6 for update
 trx_state: LOCK WAIT
 trx_started: 2024-03-09 20:05:15
trx_requested_lock_id: 269766:821:5:7
 trx_tables_in_use: 1
 trx_tables_locked: 1
 trx_lock_structs: 2
 trx_rows_locked: 1
 trx_rows_modified: 0
 lock_mode: X
 lock_type: RECORD
 lock_table_schema: test
 lock_table_name: test
 lock_index: PRIMARY
 lock_space: 821
 lock_page: 5
 lock_rec: 7
 lock_data: 6
*************************** 2. row ***************************
 machine_name:
 connection_id: 14817
 trx_id: 269760
 ts: 2024-03-09 20:05:57
 user: root
 host: localhost
 db: test
 command: Sleep
 time: 60.000
 running_since: 2024-03-09 20:04:57
 state:
 info:
 trx_state: RUNNING
 trx_started: 2024-03-09 20:04:56
trx_requested_lock_id: NULL
 trx_tables_in_use: 0
 trx_tables_locked: 1
 trx_lock_structs: 2
 trx_rows_locked: 1
 trx_rows_modified: 1
 lock_mode: X
 lock_type: RECORD
 lock_table_schema: test
 lock_table_name: test
 lock_index: PRIMARY
 lock_space: 821
 lock_page: 5
 lock_rec: 7
 lock_data: 6
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="metadata_lock"&gt;&lt;code&gt;metadata_lock&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;SELECT lock_mode, ts, user, host, lock_type, table_schema, table_name, time, started, state, query
 FROM metadata_lock
 WHERE connection_id = 14347
 ORDER BY started DESC
 LIMIT 5
;
+-------------------------+---------------------+------+-----------+----------------------+--------------+------------+-------+---------------------+----------------+------------------------------------------------------+
| lock_mode | ts | user | host | lock_type | table_schema | table_name | time | started | state | query |
+-------------------------+---------------------+------+-----------+----------------------+--------------+------------+-------+---------------------+----------------+------------------------------------------------------+
| MDL_SHARED_WRITE | 2024-03-13 10:27:33 | root | localhost | Table metadata lock | test | test | 1.000 | 2024-03-13 10:27:32 | Updating | UPDATE test set data3 = MD5(id) |
| MDL_BACKUP_TRANS_DML | 2024-03-13 10:27:33 | root | localhost | Backup lock | | | 1.000 | 2024-03-13 10:27:32 | Updating | UPDATE test set data3 = MD5(id) |
| MDL_BACKUP_ALTER_COPY | 2024-03-13 10:22:33 | root | localhost | Backup lock | | | 0.000 | 2024-03-13 10:22:33 | altering table | ALTER TABLE test DROP INDEX ts, ADD INDEX (ts, data) |
| MDL_SHARED_UPGRADABLE | 2024-03-13 10:22:33 | root | localhost | Table metadata lock | test | test | 0.000 | 2024-03-13 10:22:33 | altering table | ALTER TABLE test DROP INDEX ts, ADD INDEX (ts, data) |
| MDL_INTENTION_EXCLUSIVE | 2024-03-13 10:22:33 | root | localhost | Schema metadata lock | test | | 0.000 | 2024-03-13 10:22:33 | altering table | ALTER TABLE test DROP INDEX ts, ADD INDEX (ts, data) |
+-------------------------+---------------------+------+-----------+----------------------+--------------+------------+-------+---------------------+----------------+------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="global_variables"&gt;&lt;code&gt;global_variables&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;SELECT variable_name, COUNT(*) AS cnt
 FROM global_variables
 GROUP BY variable_name
 HAVING COUNT(*) &amp;gt; 1
;
+-------------------------+-----+
| variable_name | cnt |
+-------------------------+-----+
| innodb_buffer_pool_size | 7 |
+-------------------------+-----+

SELECT variable_name, ts, variable_value
 FROM global_variables
 WHERE variable_name = 'innodb_buffer_pool_size'
;
+-------------------------+---------------------+----------------+
| variable_name | ts | variable_value |
+-------------------------+---------------------+----------------+
| innodb_buffer_pool_size | 2024-03-09 21:36:28 | 134217728 |
| innodb_buffer_pool_size | 2024-03-09 21:40:25 | 268435456 |
| innodb_buffer_pool_size | 2024-03-09 21:41:25 | 268435456 |
| innodb_buffer_pool_size | 2024-03-09 21:42:25 | 268435456 |
| innodb_buffer_pool_size | 2024-03-09 21:43:25 | 268435456 |
| innodb_buffer_pool_size | 2024-03-09 21:44:25 | 268435456 |
| innodb_buffer_pool_size | 2024-03-09 21:48:14 | 134217728 |
+-------------------------+---------------------+----------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="global_status"&gt;&lt;code&gt;global_status&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;SELECT s1.ts
 , s1.variable_value AS 'table_open_cache_misses'
 , s2.variable_value AS 'table_open_cache_hits'
 FROM global_status AS s1
 JOIN global_status AS s2 ON s1.ts = s2.ts
 WHERE s1.variable_name = 'table_open_cache_misses'
 AND s2.variable_name = 'table_open_cache_hits'
 AND s1.ts BETWEEN '2024-03-13 11:55:00' AND '2024-03-13 12:05:00'
 ORDER BY ts ASC
;
+---------------------+-------------------------+-----------------------+
| ts | table_open_cache_misses | table_open_cache_hits |
+---------------------+-------------------------+-----------------------+
| 2024-03-13 11:55:47 | 1001 | 60711 |
| 2024-03-13 11:56:47 | 1008 | 61418 |
| 2024-03-13 11:57:47 | 1015 | 62125 |
| 2024-03-13 11:58:47 | 1022 | 62829 |
| 2024-03-13 11:59:47 | 1029 | 63533 |
| 2024-03-13 12:00:47 | 1036 | 64237 |
| 2024-03-13 12:01:47 | 1043 | 64944 |
| 2024-03-13 12:02:47 | 1050 | 65651 |
| 2024-03-13 12:03:47 | 1057 | 66355 |
| 2024-03-13 12:04:47 | 1064 | 67059 |
+---------------------+-------------------------+-----------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="testen"&gt;Testen&lt;/h2&gt;
&lt;p&gt;Zur Zeit haben wir &lt;code&gt;dbstat&lt;/code&gt; auf unseren Test- und Produktionssystemen ausgerollt um es zu testen und zu sehen ob unsere Annahmen bezüglich Stabilität und Berechnungen des Mengengerüsts zutreffen. Zudem stellen wir beim selber nutzen am besten fest, wenn es noch was fehlt oder die Handhabung unpraktisch ist (&lt;a href="https://en.wikipedia.org/wiki/Eating_your_own_dog_food" target="_blank"&gt;Eat your own dog food&lt;/a&gt;).&lt;/p&gt;
&lt;h2 id="quellen"&gt;Quellen&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://linux.die.net/man/1/sar" target="_blank"&gt;sar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oracle.com/cd/A97385_01/server.920/a96533/statspac.htm" target="_blank"&gt;Using Oracle Statspack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/FromDual/dbstat" target="_blank"&gt;dbstat auf Github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/SQL/PSM" target="_blank"&gt;SQL/PSM&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Wir bauen uns ein Data Warehouse aus dem General Query Log</title><link>https://www.fromdual.com/de/blog/wir-bauen-uns-ein-data-warehouse-aus-dem-general-query-log/</link><pubDate>Tue, 30 Jan 2024 16:17:31 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/wir-bauen-uns-ein-data-warehouse-aus-dem-general-query-log/</guid><description>&lt;p&gt;Das Design eines Data Warehouses unterscheidet sich vom relationalen Design. Data Warehouses designt man oft nach dem Konzept des &lt;a href="https://de.wikipedia.org/wiki/Sternschema" target="_blank" title="Star Schema"&gt;Star Schemas&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Üblicherweise zäumt man beim Bau eines Data Warehouses das Pferd von hinten auf:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Welche Fragen soll mein Data Warehouse beantworten können?&lt;/li&gt;
&lt;li&gt;Wie muss ich mein Modell designen damit sich meine Fragen einfach beantworten lassen?&lt;/li&gt;
&lt;li&gt;Woher kriege ich die Daten um das Modell zu befüllen?&lt;/li&gt;
&lt;li&gt;Wie befülle ich mein Model mit den Daten?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Zu Übungszwecken sind wir hier einer Fragestellung nachgegangen, welche ab und zu bei unserem Support auftaucht: Das System fängt plötzlich und unerwartet an sich ungewöhnlich zu verhalten, niemand hat was gemacht und niemand weiss warum. Beispiel bei einem Kunden letzte Woche: Um 15 Uhr fängt das System an instabil zu werden, wird anschliessend hart neu gestartet und stabilisiert sich dann ab 16 Uhr wieder&amp;hellip;&lt;/p&gt;
&lt;img src="https://www.fromdual.com/sites/default/files/gql_sudden_stop.png"&gt;
&lt;p&gt;Das einfachste wäre es, in einem solchen Fall, schnell mit dem Befehl &lt;code&gt;SHOW PROCESSLIST&lt;/code&gt; auf die Datenbank zu schauen und dann wird oft sofort klar, wo das Problem liegt. Aber oft vergessen das die Kunden oder sie sind nicht schnell genug. Bei diesem Kunden war das General Query Log bereits eingeschaltet, das wäre also ein prima Fall für unser General Query Log Data Warehouse!&lt;/p&gt;
&lt;h2 id="welche-fragen-soll-mein-data-warehouse-beantworten-können"&gt;Welche Fragen soll mein Data Warehouse beantworten können?&lt;/h2&gt;
&lt;p&gt;Die generische Fragestellung für dieses Problem müsste in etwa lauten: &amp;ldquo;Wer oder was hat mein System dazu veranlasst, sich ungewöhnlich zu verhalten.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Technisch ausgedrückt würde die Frage in etwa lauten:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Wer: Welcher User oder Account war zur fraglichen Zeit mit wie vielen Connections auf der Datenbank drauf? Was war daran ungewöhnlich?&lt;/li&gt;
&lt;li&gt;Was: Welche Abfragen liefen zur fraglichen Zeit in welchem Schema auf dem System? Welche dieser Abfragen waren ungewöhnlich?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="wie-soll-mein-modell-aussehen"&gt;Wie soll mein Modell aussehen?&lt;/h2&gt;
&lt;p&gt;Aus der Fragestellung können wir bereits einige Fakten und Dimensionen ableiten:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User oder Account (User + Host)&lt;/li&gt;
&lt;li&gt;Zeit&lt;/li&gt;
&lt;li&gt;Connections&lt;/li&gt;
&lt;li&gt;Schema&lt;/li&gt;
&lt;li&gt;Abfragen&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Und daraus ergeben sich auch bereit 4 Dimensionen und die Fact-Tabelle:&lt;/p&gt;
&lt;img src="https://www.fromdual.com/sites/default/files/gql_model.png"&gt;
&lt;h2 id="datenquelle"&gt;Datenquelle&lt;/h2&gt;
&lt;p&gt;Woher die Daten kommen ist in diesem Fall relativ einfach zu beantworten: Der Kunde stellt seine General Query Logs zur Verfügung oder zu Testzwecken kann man auch die General Query Logs unserer eigenen Systeme verwenden.&lt;/p&gt;
&lt;h2 id="wie-wird-das-modell-befüllt"&gt;Wie wird das Modell befüllt?&lt;/h2&gt;
&lt;p&gt;Technisch geht das unter dem Begriff &lt;a href="https://de.wikipedia.org/wiki/ETL-Prozess" target="_blank" title="ETL-Prozess"&gt;ETL-Prozess&lt;/a&gt; (Extract-Transform-Load). In unserem Fall haben wir einen General Query Log Parser gebaut, der das General Query Log einliest, die Daten entsprechend aufbereitet und im Modell abspeichert.&lt;/p&gt;
&lt;h2 id="überprüfung-des-modells"&gt;Überprüfung des Modells&lt;/h2&gt;
&lt;p&gt;Und dann kommen wir auch schon zur Überprüfung des Modells. Wir haben dazu Testdaten eines unserer Systeme verwendet:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Welcher User war zur fraglichen Zeit auf dem System drauf?&lt;/li&gt;
&lt;li&gt;Welcher User hatte zur fraglichen Zeit wie viel Connections offen?&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- --&gt;
&lt;pre&gt;&lt;code&gt;SELECT td.time, cd.user, COUNT(*) AS count
 FROM connection_dim cd
 JOIN query_fact AS qf ON qf.connection_id = cd.connection_id
 JOIN time_dim AS td ON td.time_id = qf.time_id
 JOIN date_dim AS dd ON dd.date_id = qf.date_id
 WHERE td.time BETWEEN '17:00' AND '18:30'
 AND dd.date = '2019-08-02'
 GROUP BY td.time, cd.user
 ORDER BY td.time ASC, cd.user
;
+----------+---------------+-------+
| time | user | count |
+----------+---------------+-------+
| 17:58:00 | UNKNOWN USER | 1 |
| 17:59:00 | brman | 58 |
| 17:59:00 | brman_catalog | 18 |
| 17:59:00 | root | 5 |
| 18:00:00 | brman | 296 |
| 18:00:00 | brman_catalog | 7 |
| 18:00:00 | root | 3 |
| 18:01:00 | brman_catalog | 18 |
| 18:01:00 | root | 3 |
| 18:06:00 | brman | 266 |
| 18:06:00 | brman_catalog | 6 |
| 18:07:00 | brman | 88 |
| 18:07:00 | brman_catalog | 7 |
| 18:10:00 | brman | 211 |
| 18:10:00 | brman_catalog | 18 |
| 18:10:00 | root | 4 |
| 18:11:00 | brman | 141 |
| 18:11:00 | root | 3 |
| 18:13:00 | brman | 4 |
| 18:14:00 | brman | 348 |
| 18:17:00 | brman | 354 |
| 18:17:00 | brman_catalog | 12 |
| 18:17:00 | root | 1 |
+----------+---------------+-------+
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Welcher Account war zur fraglichen Zeit auf dem System drauf?&lt;/li&gt;
&lt;li&gt;Welcher Account hatte zur fraglichen Zeit wie viel Connections offen?&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- --&gt;
&lt;pre&gt;&lt;code&gt;SELECT td.time, cd.user, cd.hostname, COUNT(*) AS count
 FROM connection_dim cd
 JOIN query_fact AS qf ON qf.connection_id = cd.connection_id
 JOIN time_dim AS td ON td.time_id = qf.time_id
 JOIN date_dim AS dd ON dd.date_id = qf.date_id
 WHERE td.time BETWEEN '17:00' AND '18:30'
 AND dd.date = '2019-08-02'
 GROUP BY td.time, cd.user, cd.hostname
 ORDER BY td.time ASC, cd.user
;
+----------+---------------+--------------+-------+
| time | user | hostname | count |
+----------+---------------+--------------+-------+
| 17:58:00 | UNKNOWN USER | UNKNOWN HOST | 1 |
| 17:59:00 | brman | localhost | 58 |
| 17:59:00 | brman_catalog | localhost | 18 |
| 17:59:00 | root | localhost | 5 |
| 18:00:00 | brman | localhost | 296 |
| 18:00:00 | brman_catalog | localhost | 7 |
| 18:00:00 | root | localhost | 3 |
| 18:01:00 | brman_catalog | localhost | 18 |
| 18:01:00 | root | localhost | 3 |
| 18:06:00 | brman | localhost | 266 |
| 18:06:00 | brman_catalog | localhost | 6 |
| 18:07:00 | brman | localhost | 88 |
| 18:07:00 | brman_catalog | localhost | 7 |
| 18:10:00 | brman | localhost | 211 |
| 18:10:00 | brman_catalog | localhost | 18 |
| 18:10:00 | root | localhost | 4 |
| 18:11:00 | brman | localhost | 141 |
| 18:11:00 | root | localhost | 3 |
| 18:13:00 | brman | localhost | 4 |
| 18:14:00 | brman | localhost | 348 |
| 18:17:00 | brman | localhost | 354 |
| 18:17:00 | brman_catalog | localhost | 12 |
| 18:17:00 | root | localhost | 1 |
+----------+---------------+--------------+-------+
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Was war daran ungewöhnlich?&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- --&gt;
&lt;pre&gt;&lt;code&gt;SELECT cd.user, td.time, COUNT(*) AS count
 FROM connection_dim cd
 JOIN query_fact AS qf ON qf.connection_id = cd.connection_id
 JOIN time_dim AS td ON td.time_id = qf.time_id
 JOIN date_dim AS dd ON dd.date_id = qf.date_id
 WHERE td.time BETWEEN '17:00' AND '18:30'
 AND dd.date = '2019-08-02'
 GROUP BY td.time, cd.user
 ORDER BY cd.user ASC, td.time ASC
;
+---------------+----------+-------+
| user | time | count |
+---------------+----------+-------+
| brman | 17:59:00 | 58 |
| brman | 18:00:00 | 296 |
| brman | 18:06:00 | 266 |
| brman | 18:07:00 | 88 |
| brman | 18:10:00 | 211 |
| brman | 18:11:00 | 141 |
| brman | 18:13:00 | 4 |
| brman | 18:14:00 | 348 |
| brman | 18:17:00 | 354 |
| brman_catalog | 17:59:00 | 18 |
| brman_catalog | 18:00:00 | 7 |
| brman_catalog | 18:01:00 | 18 |
| brman_catalog | 18:06:00 | 6 |
| brman_catalog | 18:07:00 | 7 |
| brman_catalog | 18:10:00 | 18 |
| brman_catalog | 18:17:00 | 12 |
| root | 17:59:00 | 5 |
| root | 18:00:00 | 3 |
| root | 18:01:00 | 3 |
| root | 18:10:00 | 4 |
| root | 18:11:00 | 3 |
| root | 18:17:00 | 1 |
| UNKNOWN USER | 17:58:00 | 1 |
+---------------+----------+-------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Man könnte hier z.B. ableiten, dass der User &lt;code&gt;brman&lt;/code&gt; relativ viele Verbindung in der fraglichen Zeit offen hatte. Ob das ungewöhnlich ist, dazu haben wir zu wenige Daten bzw. dazu ist der Zeitraum zu klein.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Welche Abfragen liefen zur fraglichen Zeit in welchem Schema auf dem System?&lt;/li&gt;
&lt;li&gt;Welche dieser Abfragen waren ungewöhnlich?&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- --&gt;
&lt;pre&gt;&lt;code&gt;SELECT sd.schema_name, td.time, SUBSTR(std.statement_text, 1, 128) AS query
 FROM query_fact AS qf
 JOIN time_dim AS td ON td.time_id = qf.time_id
 JOIN schema_dim AS sd ON sd.schema_id = qf.schema_id
 JOIN statement_dim AS std ON std.statement_id = qf.statement_id
 WHERE td.time BETWEEN '17:00' AND '18:30'
 AND sd.schema_name = 'brman_catalog'
 AND std.command = 'Query'
 ORDER BY td.time, qf.statement_id
 LIMIT 10
;
+---------------+----------+----------------------------------------------------------------------------------------------------------------------------------+
| schema_name | time | query |
+---------------+----------+----------------------------------------------------------------------------------------------------------------------------------+
| brman_catalog | 17:59:00 | SET NAMES `utf8` |
| brman_catalog | 17:59:00 | SELECT COUNT ( * ) AS `cnt` FROM `information_schema` . `tables` WHERE `table_schema` = ? AND TABLE_NAME = ? |
| brman_catalog | 17:59:00 | SELECT COUNT ( * ) AS `cnt` FROM `information_schema` . `tables` WHERE `table_schema` = ? AND TABLE_NAME = ? |
| brman_catalog | 17:59:00 | CREATE TABLE `metadata` ( `id` TINYINT UNSIGNED NOT NULL AUTO_INCREMENT , `key` VARCHARACTER (?) NOT NULL , `value` VARCHARACTER |
| brman_catalog | 17:59:00 | INSERT INTO `metadata` ( `key` , `value` ) VALUES (...) |
| brman_catalog | 17:59:00 | INSERT INTO `metadata` ( `key` , `value` ) VALUES (...) |
| brman_catalog | 17:59:00 | CREATE TABLE `backups` ( `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT , `instance_name` VARCHARACTER (?) NOT NULL , `start_ts` |
| brman_catalog | 17:59:00 | CREATE TABLE `backup_details` ( `backup_id` INTEGER UNSIGNED NOT NULL , `hostname` VARCHARACTER (?) NULL , `binlog_file` VARCHAR |
| brman_catalog | 17:59:00 | CREATE TABLE `files` ( `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT , `schema_name` VARCHARACTER (?) NULL , `original_name` VAR |
| brman_catalog | 17:59:00 | CREATE TABLE `binary_logs` ( `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT , `filename` VARCHARACTER (?) NOT NULL , `begin_ts` I |
+---------------+----------+----------------------------------------------------------------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="verbesserungsvorschläge"&gt;Verbesserungsvorschläge&lt;/h2&gt;
&lt;p&gt;Anhand dieser ersten Iteration des Modells sieht man auch schon, welche Fragen das Modell noch nicht beantworten kann oder wo das Modell zu ungenau ist. Dies kann dann in einer zweiten Rund nachgebessert werden&amp;hellip;.&lt;/p&gt;
&lt;p&gt;Beispiele hierzu sind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Die Granulariät der Dimension time ist mit Minutengenauigkeit möglicherweise zu grob. Sekundengenauigkeit wäre sinnvoller?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Die Frage, wie lange eine Connection offen war lässt sich nich so einfach beantworten. Ev. wäre hier eine weiter Fact Tabelle angebracht?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT cd.connection_number, cd.user, cd.hostname, tdf.time AS time_from, tdt.time AS time_to, (UNIX_TIMESTAMP(tdt.time) - UNIX_TIMESTAMP(tdf.time)) AS duration
 FROM connection_dim AS cd
 JOIN query_fact AS qf1 ON cd.connection_id = qf1.connection_id
 JOIN time_dim AS tdf ON tdf.time_id = qf1.time_id
 JOIN statement_dim AS sdf ON sdf.statement_id = qf1.statement_id
 JOIN query_fact AS qf2 ON cd.connection_id = qf2.connection_id
 JOIN time_dim AS tdt ON tdt.time_id = qf2.time_id
 JOIN statement_dim AS sdt ON sdt.statement_id = qf2.statement_id
 WHERE tdf.time BETWEEN '17:00' AND '18:30'
 AND sdf.command = 'Connect'
 AND sdt.command = 'Quit'
 AND (UNIX_TIMESTAMP(tdt.time) - UNIX_TIMESTAMP(tdf.time)) &amp;gt; 0
 ORDER BY tdf.time
;
+-------------------+-------+-----------+-----------+----------+----------+
| connection_number | user | hostname | time_from | time_to | duration |
+-------------------+-------+-----------+-----------+----------+----------+
| 211 | brman | localhost | 17:59:00 | 18:00:00 | 60 |
| 215 | root | localhost | 18:00:00 | 18:17:00 | 1020 |
| 219 | brman | localhost | 18:06:00 | 18:07:00 | 60 |
| 225 | brman | localhost | 18:10:00 | 18:11:00 | 60 |
| 226 | brman | localhost | 18:13:00 | 18:14:00 | 60 |
+-------------------+-------+-----------+-----------+----------+----------+
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Spannend wäre natürlich jetzt noch, wenn man eine KI auf das Problem ansetzt. Wie traniert man sie richtig und findet sie das Problem, wenn sie trainiert wurde?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Soweit die kleine Spielerei zum Bau eines Data Warehouses&amp;hellip;&lt;/p&gt;</description></item><item><title>InnoDB Deadlock bei SELECT? Nicht möglich! Oder doch?</title><link>https://www.fromdual.com/de/blog/innodb-deadlock-bei-select-nicht-moeglich-oder-doch/</link><pubDate>Sun, 19 Nov 2023 16:18:11 +0100</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/innodb-deadlock-bei-select-nicht-moeglich-oder-doch/</guid><description>&lt;h2 id="einleitung"&gt;Einleitung&lt;/h2&gt;
&lt;p&gt;Kurz vorab zwei Punkte:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Ein Deadlock ist eine Zustand, in welchem 2 unterschiedliche Transaktionen nicht mehr in der Lage sind weiter zu arbeiten, weil jede Transaktion jeweils einen Lock hält, welche die andere Transaktion gerade bräuchte. Weil jetzt beide Transaktionen jeweils darauf warten, bis die andere Transaktion ihren Lock wieder frei gibt, wird keine von beiden Transaktionen ihre jeweiligen Locks wieder frei geben. Und das würde bis zum Sankt-Nimmerleins-Tag andauern. Um das zu vermeiden schreitet die MariaDB Instanz ein und killt kurzerhand diejenige Transaktion, die weniger Arbeit geleistet hat. Die Applikation kriegt darauf hin eine Deadlock Fehlermeldung vom Typ:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Im MariaDB Ökosystem gilt allgemein das Mantra, dass ein &lt;code&gt;SELECT&lt;/code&gt; keine Locks verursacht (Ausnahme: &lt;code&gt;FOR UPDATE&lt;/code&gt; oder &lt;code&gt;LOCK IN SHARE MODE&lt;/code&gt;) und somit auch nicht Teil eines Deadlocks sein kann.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="das-problem"&gt;Das Problem&lt;/h2&gt;
&lt;p&gt;Ein langjähriger Kunde kommt zum FromDual remote-DBA Team mit der Bitte, eine Deadlock-Situation zu erklären:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hallo FromDual Team,
ich brauche mal wieder euer Fachwissen zum Thema Deadlocks.
Wann würde es Euch passen?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Die Situation ist folgende: Transaktion 1 besteht aus einem simplen &lt;code&gt;INSERT&lt;/code&gt;. Transaktion 2 besteht aus einem &lt;code&gt;SELECT&lt;/code&gt;. Das dürfte eigentlich KEINEN Deadlock verursachen!&lt;/p&gt;
&lt;p&gt;Zuerst prüfen wir folgende Punkte ab:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sind alle Tabellen, die durch diese Abfragen betroffen sind, sauber indexiert? Jawohl sind sie. Die Queries laufen alle perfekt!&lt;/li&gt;
&lt;li&gt;Ist die &lt;code&gt;SELECT&lt;/code&gt; Abfragen eventuell Teil einer grösseren Transaktion (NICHT Auto-Commit Transaktion) und daher nicht die eigentlich Ursache für den Deadlock? Nein, ist sie nicht. Es handelt sich um Auto-Commit Transaktionen.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Was nun? Was man zur Erläuterung noch sagen muss: Das &lt;code&gt;SELECT&lt;/code&gt; wird mit einer sehr hohen Kadenz, also so ca. alle 5 ms abgesetzt!&lt;/p&gt;
&lt;p&gt;Dass der &lt;code&gt;INSERT&lt;/code&gt; Locks erzeugt ist klar. Wird ja auch angezeigt. Aber warum erzeugt der &lt;code&gt;SELECT&lt;/code&gt; Befehl Locks? Diese werden ebenfalls angezeigt!&lt;/p&gt;
&lt;p&gt;Also versuchen wir das Problem in Einzelschritte runter zu brechen.&lt;/p&gt;
&lt;h2 id="der-lösungsweg"&gt;Der Lösungsweg&lt;/h2&gt;
&lt;p&gt;Das Query sieht wie folgt aus:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SET @id = (SELECT id FROM test WHERE id = 3);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wenn wir dieses Query in eine explizite Transaktion packen, können wir die Locks sogar sehen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; START TRANSACTION;
SQL&amp;gt; SET @id = (SELECT id FROM test WHERE id = 3);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;und in einer zweiten Session:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SELECT * FROM information_schema.INNODB_TRX&amp;lt;br&amp;gt;G
*************************** 1. row ***************************
 trx_id: 0
 trx_state: RUNNING
 trx_started: 2023-11-19 15:27:09
 trx_requested_lock_id: NULL
 trx_wait_started: NULL
 trx_weight: 2
 trx_mysql_thread_id: 3765
 trx_query: NULL
 trx_operation_state:
 trx_tables_in_use: 0
 trx_tables_locked: 1
 trx_lock_structs: 2
 trx_lock_memory_bytes: 1128
 trx_rows_locked: 1
 trx_rows_modified: 0
 trx_concurrency_tickets: 0
 trx_isolation_level: REPEATABLE READ
 trx_unique_checks: 1
 trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
 trx_is_read_only: 0
trx_autocommit_non_locking: 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Leider sehen wir nicht welche Art von Lock (IS) es ist, da die View &lt;code&gt;INNODB_LOCKS&lt;/code&gt; leer ist.&lt;/p&gt;
&lt;h2 id="die-lösung"&gt;Die Lösung&lt;/h2&gt;
&lt;p&gt;Wenn wir den selben Versuch mit &amp;ldquo;normalen&amp;rdquo; &lt;code&gt;SELECT&lt;/code&gt;s machen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; START TRANSACTION; SELECT id FROM test WHERE id = 3;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;oder&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; START TRANSACTION; SELECT id INTO @id FROM test WHERE id = 3;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;sehen wir KEINE Locks:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SELECT * FROM information_schema.INNODB_TRX&amp;lt;br&amp;gt;G
*************************** 1. row ***************************
 trx_id: 0
 trx_state: RUNNING
 trx_started: 2023-11-19 15:31:35
 trx_requested_lock_id: NULL
 trx_wait_started: NULL
 trx_weight: 0
 trx_mysql_thread_id: 3765
 trx_query: NULL
 trx_operation_state:
 trx_tables_in_use: 0
 trx_tables_locked: 0
 trx_lock_structs: 0
 trx_lock_memory_bytes: 1128
 trx_rows_locked: 0
 trx_rows_modified: 0
 trx_concurrency_tickets: 0
 trx_isolation_level: REPEATABLE READ
 trx_unique_checks: 1
 trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
 trx_is_read_only: 0
trx_autocommit_non_locking: 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Es scheint also so zu sein, dass das Konstrukt &lt;code&gt;SET @id = (...)&lt;/code&gt; diesen IS Lock verursacht. Der Kunde schreibt seine Applikation um und kurz darauf erhalten wir folgende Meldung:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hallo FromDual Team,
Euer Tipp war goldrichtig.
Seit Freitag Mittag keine Deadlocks mehr.
Danke und schönes Wochenende.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;img src="https://www.fromdual.com/sites/default/files/innodb_deadlocks.png" title="InnoDB Deadlocks" width="640"&gt;
&lt;h2 id="weitere-geklärte-fragen"&gt;Weitere geklärte Fragen&lt;/h2&gt;
&lt;p&gt;MySQL 8.0 verhält sich gleich? Ja, genau gleich.&lt;/p&gt;
&lt;h2 id="nachtrag"&gt;Nachtrag&lt;/h2&gt;
&lt;p&gt;Mein lieber Kollege Matthias hat mich noch auf eine Folgeidee gebracht: Wie sieht das Ganze aus mit MariaDB Stored Procedures und Stored Functions?&lt;/p&gt;
&lt;p&gt;Die beiden Tests hier:&lt;/p&gt;
&lt;pre&gt;
DELIMITER //

CREATE OR REPLACE PROCEDURE locktestsp (INOUT id INT)
BEGIN
 SELECT id INTO id FROM test WHERE id = id LIMIT 1;
END;
//

DELIMITER ;

SET @id = 3;
START TRANSACTION;
CALL locktestsp(@id);
SELECT @id;

SELECT trx_tables_locked, trx_lock_structs, trx_rows_locked FROM information_schema.INNODB_TRX;
+-------------------+------------------+-----------------+
| trx_tables_locked | trx_lock_structs | trx_rows_locked |
+-------------------+------------------+-----------------+
| 0 | 0 | 0 |
+-------------------+------------------+-----------------+
&lt;/pre&gt;&lt;br&gt;
&lt;p&gt;und hier:&lt;/p&gt;
&lt;pre&gt;
DELIMITER //

CREATE OR REPLACE FUNCTION locktestsf (IN id INT)
RETURNS CHAR(50) DETERMINISTIC
BEGIN
 SELECT id INTO id FROM test WHERE id = id LIMIT 1;
 RETURN id;
END;
//

DELIMITER ;

START TRANSACTION;
SELECT locktestsf(3);

 SELECT trx_tables_locked, trx_lock_structs, trx_rows_locked FROM information_schema.INNODB_TRX;
+-------------------+------------------+-----------------+
| trx_tables_locked | trx_lock_structs | trx_rows_locked |
+-------------------+------------------+-----------------+
| 0 | 0 | 0 |
+-------------------+------------------+-----------------+
&lt;/pre&gt;&lt;br&gt;</description></item><item><title>Webinar: Upgrade Ihres MySQL 5.7 Galera Clusters auf MySQL 8.0 ohne Ausfallzeiten</title><link>https://www.fromdual.com/de/blog/webinar-galera-upgrade-57-auf-80/</link><pubDate>Thu, 16 Nov 2023 11:34:22 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/webinar-galera-upgrade-57-auf-80/</guid><description>&lt;p&gt;Sie haben sicher schon davon gehört, dass MySQL 5.7 im Oktober 2023 das End of Life (EOL) erreicht hat. In diesem Webinar zeigen wir Ihnen, dass die Migration von MySQL 5.7 Galera Cluster nicht schwierig ist. MySQL 8.0 ist seit 5 Jahren allgemein verfügbar, und das Galera Cluster für MySQL 8.0 hat sich seit über 3 Jahren im Markt bewährt. Es ist also wirklich an der Zeit, sich auf die Migration vorzubereiten.&lt;/p&gt;
&lt;p&gt;Im ersten Webinar dieser Reihe werden wir uns mit den neuen Funktionen von Galera Cluster mit MySQL 8 befassen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Die neuen Funktionen von Galera Cluster für MySQL 8, von denen Sie profitieren können, einschliesslich der in der Galera Cluster Enterprise Edition (EE) verfügbaren Funktionen&lt;/li&gt;
&lt;li&gt;Wie man eine Migration von MySQL 5.7 auf MySQL 8.0 plant&lt;/li&gt;
&lt;li&gt;Dinge, die vor der Migration getestet werden sollten&lt;/li&gt;
&lt;li&gt;Häufige Fallstricke bei einer solchen Migration&lt;/li&gt;
&lt;li&gt;Wie Sie sicherstellen, dass Ihr Galera Cluster während der Migration ohne Ausfallzeiten weiterläuft&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Das Webinar findet am Freitag, 17. November 2023, 9:00 - 10:00 MEZ statt.&lt;/p&gt;
&lt;p&gt;Für das Webinar können Sie sich &lt;a href="https://www2.galeracluster.com/l/38852/2023-11-01/9ssk5y" target="_blank" title="Für Webinar registrieren"&gt;hier registrieren&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Präsentatoren:&lt;/strong&gt;&lt;br&gt;
Oli Sennhauser, Fromdual&lt;br&gt;
Sakari Keskitalo, Codership, the developers of Galera Cluster&lt;/p&gt;
&lt;p&gt;Über Galera Cluster Support Subskriptionen und Migrations-Support informieren wir sie gerne persönlich. Bitte wenden Sie sich an uns, wir helfen Ihnen gerne weiter.&lt;/p&gt;
&lt;p&gt;Bitte senden Sie Ihre Fragen, Anmerkungen und Ihr Feedback an: &lt;a href="mailto:contact@fromdual.com"&gt;contact@fromdual.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Mit freundlichen Grüssen,&lt;br&gt;
Ihr FromDual Team&lt;/p&gt;</description></item><item><title>MariaDB und MySQL Schulungsprogramm 2023</title><link>https://www.fromdual.com/de/blog/mariadb-und-mysql-schulungsprogramm-2023/</link><pubDate>Tue, 17 Jan 2023 16:24:10 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/mariadb-und-mysql-schulungsprogramm-2023/</guid><description>&lt;img src="https://www.fromdual.com/sites/default/files/seminar-g8bc40a1dd_1920.jpg" width="640" /&gt;
&lt;p&gt;Auch im Jahr 2023 haben Sie wieder die Möglichkeit, sich bei unseren 3 Schulungspartnern in Essen, Köln und Berlin MariaDB und MySQL seitig fit zu machen.&lt;/p&gt;
&lt;p&gt;Folgende Termine können wir Ihnen für das Jahr 2023 schon jetzt anbieten:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;30&lt;br&gt;. Januar bis 3. Februar 2023: MariaDB/MySQL für Fortgeschrittene, GFU Schulungszentrum, Köln&lt;/li&gt;
&lt;li&gt;27&lt;br&gt;. Februar bis 3. März 2023: MariaDB/MySQL für Fortgeschrittene, Heinlein Akademie, Berlin&lt;/li&gt;
&lt;li&gt;20&lt;br&gt;. bis 24. März 2023: MariaDB/MySQL für Fortgeschrittene, Linuxhotel, Essen&lt;/li&gt;
&lt;li&gt;27&lt;br&gt;. bis 29. März 2023: Galera Cluster für MariaDB/MySQL, GFU Schulungszentrum, Köln&lt;/li&gt;
&lt;li&gt;24&lt;br&gt;. bis 28. April 2023: MariaDB/MySQL für Fortgeschrittene, GFU Schulungszentrum, Köln&lt;/li&gt;
&lt;li&gt;3&lt;br&gt;. bis 5. Mai 2023: Galera Cluster für MariaDB/MySQL, Linuxhotel, Essen&lt;/li&gt;
&lt;li&gt;15&lt;br&gt;. bis 17. Mai 2023: Galera Cluster für MariaDB/MySQL, Heinlein Akademie, Berlin&lt;/li&gt;
&lt;li&gt;5&lt;br&gt;. bis 7. Juni 2023: Galera Cluster für MariaDB/MySQL, GFU Schulungszentrum, Köln&lt;/li&gt;
&lt;li&gt;24&lt;br&gt;. bis 28. Juli 2023: MariaDB/MySQL für Fortgeschrittene, GFU Schulungszentrum, Köln&lt;/li&gt;
&lt;li&gt;11&lt;br&gt;. bis 15. September 2023: MariaDB/MySQL für Fortgeschrittene, Heinlein Akademie, Berlin&lt;/li&gt;
&lt;li&gt;25&lt;br&gt;. bis 27. September 2023: Galera Cluster für MariaDB/MySQL, GFU Schulungszentrum, Köln&lt;/li&gt;
&lt;li&gt;23&lt;br&gt;. bis 27. Oktober 2023: MariaDB/MySQL für Fortgeschrittene, GFU Schulungszentrum, Köln&lt;/li&gt;
&lt;li&gt;6&lt;br&gt;. bis 8. November 2023: Galera Cluster für MariaDB/MySQL, Linuxhotel, Essen&lt;/li&gt;
&lt;li&gt;13&lt;br&gt;. bis 15. November 2023: Galera Cluster für MariaDB/MySQL, Heinlein Akademie, Berlin&lt;/li&gt;
&lt;li&gt;27&lt;br&gt;. November bis 1. Dezember 2023: MariaDB/MySQL für Fortgeschrittene, Linuxhotel, Essen&lt;/li&gt;
&lt;li&gt;4&lt;br&gt;. bis 6. Dezember 2023: Galera Cluster für MariaDB/MySQL, GFU Schulungszentrum, Köln&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Diese Schulungen können &lt;a href="https://www.fromdual.com/de/mysql-mariadb-schulungstermine"&gt;online gebucht&lt;/a&gt; werden.&lt;/p&gt;
&lt;p&gt;Weitere Termine können während des Jahres noch hinzukommen.&lt;/p&gt;
&lt;p&gt;Bei Fragen, oder wenn Sie Spezialwünsche haben, stehen wir Ihnen gerne mit Rat und Tat zur Seite! Zögern Sie nicht, uns zu kontaktieren, wir helfen Ihnen gerne per &lt;a href="mailto:contact@fromdual.com"&gt;eMail&lt;/a&gt; weiter.&lt;/p&gt;
&lt;p&gt;Bei allen weiteren Galera, MariaDB und MySQL Problemen oder Fragen unterstützen wir Sie natürlich ebenfalls gerne!&lt;/p&gt;
&lt;p&gt;Mit freundlichen Grüssen,&lt;br&gt;
Ihr FromDual Team&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quellen&lt;/strong&gt;: Bild von &lt;a href="https://pixabay.com/de/users/startupstockphotos-690514/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=594125"&gt;StartupStockPhotos&lt;/a&gt; auf &lt;a href="https://pixabay.com/de//?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=594125"&gt;Pixabay&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Zusätzliche Galera Cluster Schulung im Dezember 2022</title><link>https://www.fromdual.com/de/blog/zusaetzliche-galera-cluster-schulung-im-dezember-2022/</link><pubDate>Mon, 10 Oct 2022 16:40:32 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/zusaetzliche-galera-cluster-schulung-im-dezember-2022/</guid><description>&lt;p&gt;Aufgrund der grossen Nachfrage bieten wir vom 19. bis 22. Dezember 2022 ein zusätzliches Galera Cluster Seminar an. Dies in Zusammenarbeit mit unserem Schulungspartner, der &lt;a href="https://www.gfu.net/" target="_blank" title="GfU Cyrus AG"&gt;GfU Cyrus AG&lt;/a&gt; in Köln.&lt;/p&gt;
&lt;p&gt;Dieses Seminar wird remote durchgeführt. Sie können an diesem Seminar also bequem von zuhause aus teilnehmen&amp;hellip;&lt;/p&gt;
&lt;p&gt;Eine Seminarübersicht finden Sie &lt;a href="https://www.fromdual.com/de/galera-cluster-fuer-mysql-mariadb-schulung" title="Galera Cluster für MariaDB und MySQL Schulung"&gt;hier&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Sie können das Seminar einfach online &lt;a href="https://www.gfu.net/seminare-schulungen-kurse/datenbanken__db-sprachen__design_sk81/galera-cluster-fuer-mariadb-und-mysql_s2307.html" target="_blank" title="Schulung Galera Cluster für MySQL/MariaDB
"&gt;buchen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Falls Sie an diesen Terminen verhindert sind, haben wir bereits einige &lt;a href="https://www.fromdual.com/de/mysql-mariadb-schulungstermine" title="MariaDB und MySQL Schulungstermine"&gt;Termine für das Jahr 2023&lt;/a&gt; für Sie in Planung.&lt;/p&gt;
&lt;p&gt;Ihr FromDual Schulungsteam&lt;/p&gt;</description></item><item><title>FromDual Seminarprogramm 2022 für MariaDB und MySQL ist online</title><link>https://www.fromdual.com/de/blog/fromdual-seminarprogramm-2022-mariadb-mysql/</link><pubDate>Fri, 15 Oct 2021 10:27:48 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/fromdual-seminarprogramm-2022-mariadb-mysql/</guid><description>&lt;p&gt;Das FromDual &lt;a href="https://www.fromdual.com/mysql-mariadb-training-class-schedule"&gt;Seminarprogramm für 2022&lt;/a&gt; steht. Mit unseren Schulungspartnern Linuxhotel in Essen, GfU Cyrus in Köln sowie der Heinlein Akademie in Berlin bieten wir auch 2022 wieder zahlreiche MariaDB und MySQL Schulungen an:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/advanced-mysql-mariadb-training"&gt;MariaDB und MySQL für Fortgeschrittene&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fromdual.com/galera-cluster-for-mysql-mariadb-training"&gt;Galera Cluster für MariaDB und MySQL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Im Seminar für Fortgeschrittene nehmen wir uns Themen wie Backup/Restore, Master/Slave Replikation, Galera Cluster sowie Datenbank-Konfigurations-Tuning und SQL Query Tuning vor und üben die einzelnen Punkte praktisch.&lt;/p&gt;
&lt;p&gt;Im Galera Cluster Seminar nehmen wir alle Aspekte des Aufbaus und des Betriebs eines Galera Clusters durch und praktizieren das Ganz anschliessend ausführlich.&lt;/p&gt;
&lt;p&gt;Den Umständen entsprechend werden die Seminare entweder vor Ort, remote oder hybrid durchgeführt.&lt;/p&gt;
&lt;p&gt;Bei allfälligen Fragen bitten wir Sie, mit uns oder unseren Schulungspartnern Kontakt aufzunehmen und diese zu klären.&lt;/p&gt;</description></item><item><title>MariaDB und MySQL Schulungsprogramm 2021</title><link>https://www.fromdual.com/de/blog/mariadb-und-mysql-schulungsprogramm-2021/</link><pubDate>Wed, 03 Feb 2021 09:40:24 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/mariadb-und-mysql-schulungsprogramm-2021/</guid><description>&lt;p&gt;Das FromDual MariaDB und MySQL Schulungsprogramm 2021 steht jetzt zur Verfügung.&lt;/p&gt;
&lt;p&gt;Die Schulungen werden remote oder auch vor Ort bei unseren drei Schulungspartnern in Essen, Köln und Berlin angeboten. Oder ein Seminar, ganz persönlich, remote oder vor Ort, bei Ihnen in der Firma.&lt;/p&gt;
&lt;p&gt;Wer neu in der Thematik MariaDB oder MySQL ist, für den ist das Seminar &lt;strong&gt;MariaDB/MySQL für Einsteiger&lt;/strong&gt; vorgesehen. Falls Sie sich mit MariaDB oder MySQL schon auskennen empfehlen wir Ihnen das Seminar &lt;strong&gt;MariaDB/MySQL für Fortgeschrittene&lt;/strong&gt;. Wenn Sie Entwickler und nicht Administrator sind, dann ist das Seminar &lt;strong&gt;MariaDB/MySQL für Entwickler&lt;/strong&gt; genau das richtige für Sie. Und sollten Sie mit dem Gedanken spielen, sich einen Galera Cluster anzulachen, dann zeigen wir Ihnen im Seminar &lt;strong&gt;Galera Cluster für MariaDB/MySQL&lt;/strong&gt; wie man das richtig macht und wo Sie dabei aufpassen sollten.&lt;/p&gt;
&lt;p&gt;Die geplanten Schulungstermine für das Jahr 2021 finden Sie, immer auf dem neusten Stand, auf unserer Website: &lt;a href="https://www.fromdual.com/de/mysql-mariadb-schulungstermine" title="MariaDB und MySQL Schulungstermine"&gt;MariaDB und MySQL Schulungstermine&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>MariaDB/MySQL Datenbank-Administrator/in gesucht</title><link>https://www.fromdual.com/de/blog/mariadb-mysql-datenbank-administrator-in-gesucht/</link><pubDate>Thu, 19 Nov 2020 15:48:16 +0100</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/mariadb-mysql-datenbank-administrator-in-gesucht/</guid><description>&lt;p&gt;Ausschreibungszeitraum: Q4 2020 bis Q2 2021. Später bitte nicht mehr melden.&lt;/p&gt;
&lt;p&gt;Einer unserer Kunden sucht eine/n erfahrene/n MariaDB/MySQL Datenbank-Administrator/in. Arbeitspensum: 80 bis 100% in Festanstellung. Arbeitsort: Hauptstadt Bern (Schweiz).
Erfahrung im Betrieb von MariaDB/MySQL Datenbanken im Enterprise-Umfeld sind erforderlich sowie gute MariaDB/MySQL sowie Galera Cluster Kenntnisse notwendig. Einsatzfeld hochkritische, produktive MariaDB Galera Cluster.&lt;/p&gt;
&lt;p&gt;Auszug aus der Original-Stellenausschreibung:&lt;/p&gt;
&lt;h2 id="aufgaben"&gt;Aufgaben:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Aufsetzen, Testen, Betreiben, Warten und Dokumentieren von Datenbanken und Datenbankservern im Entwicklungs-, Abnahme- und Produktivumfeld inkl. Log und Backup&lt;/li&gt;
&lt;li&gt;Überwachen und Optimieren der Datenbanken und Datenbankservern hinsichtlich Sicherheit und Performance&lt;/li&gt;
&lt;li&gt;Unterhalten der Dokumentationen im DB-Umfeld&lt;/li&gt;
&lt;li&gt;Unterstützung des 3rd-Level-Supports für die von uns betriebenen Webanwendungen, Services und Datenbanken&lt;/li&gt;
&lt;li&gt;Unterstützen der Softwareentwicklerinnen/ Softwareentwickler bei datenbankbezogenen Fragen und Problemen&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="kompetenzen"&gt;Kompetenzen:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Hochschulausbildung im Bereich IT oder grosse Praxiserfahrung in der Datenbankadministration&lt;/li&gt;
&lt;li&gt;Fundierte Erfahrungen im Konzipieren, Optimieren, Administrieren und Betreiben (überwachen, absichern, testen) von SQL und NoSQL-Datenbanken und Datenbankclustern im Webumfeld&lt;/li&gt;
&lt;li&gt;Freude an neuen Technologien und Bereitschaft, sich in neue Themen einzuarbeiten und nach agilen Vorgehensweisen zu arbeiten&lt;/li&gt;
&lt;li&gt;Sehr gute Ausdrucksfähigkeit in Wort und Schrift sowie gute Englischkenntnisse&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Wer Interesse hat, soll sich bei mir (Oli Sennhauser) melden, damit ich ihn/sie mit unserem Kunden kurzschliessen kann. FromDual hat kein direktes finanzielles Interesse an dieser Stellenausschreibung!&lt;/p&gt;</description></item><item><title>Programm des Vorarlberger LinuxDay 2020 wurde veröffentlicht</title><link>https://www.fromdual.com/de/blog/programm-des-vorarlberger-linuxday-2020-wurde-veroeffentlicht/</link><pubDate>Fri, 18 Sep 2020 10:00:27 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/programm-des-vorarlberger-linuxday-2020-wurde-veroeffentlicht/</guid><description>&lt;p&gt;Das diesjährige Programm des &lt;a href="https://www.linuxday.at/" target="_blank" title="GNU/LinuxDay in Vorarlberg - am 10. Okt. 2020"&gt;Vorarlberger LinuxDay 2020&lt;/a&gt; wurde veröffentlicht!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.linuxday.at/" target="_blank" title="LinuxDay 2020"&gt;&lt;img src="https://www.fromdual.com/sites/default/files/linuxday-2020-virtuell-k.png" width="625" height="403" alt="LinuxDay 2020 am 10. Oktober" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Die &lt;a href="https://www.linuxday.at/vortraege/2020" target="_blank" title="LinuxDay Vorträge 2020"&gt;Vorträge&lt;/a&gt; decken Themen querbeet aus dem Opensource Umfeld ab: Linux Mint, Smart Home, Bootloader, Regolith Linux, LibreOffice, MariaDB/MySQL, Forensik, PostgreSQL, Patch-Management, Softwareverteilung, Backup und Videokonferenzdienste.&lt;/p&gt;
&lt;p&gt;Im Unterschied zu anderen Jahren findet dieses Jahr die Konferenz am 10. Oktober 2020 rein virtuell statt. Die Teilnahme lohnt sich!&lt;/p&gt;
&lt;p&gt;Für MariaDB/MySQL Enthusiasten können wir den Vortrag &lt;a href="https://www.linuxday.at/mariadbmysql-stolperfallen-und-wie-komme-ich-da-wieder-raus" target="_blank" title="MariaDB/MySQL Stolperfallen und wie komme ich da wieder raus"&gt;MariaDB/MySQL Stolperfallen und wie komme ich da wieder raus&lt;/a&gt; empfehlen.&lt;/p&gt;</description></item><item><title>Programm der DOAG 2020 Konferenz veröffentlicht</title><link>https://www.fromdual.com/de/blog/programm-der-doag-2020-konferenz-veroeffentlicht/</link><pubDate>Fri, 11 Sep 2020 10:56:19 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/programm-der-doag-2020-konferenz-veroeffentlicht/</guid><description>&lt;p&gt;Das diesjährige &lt;a href="https://programm.doag.org/doag/2020/#/schedule/2020-11-18" target="_blank"&gt;Programm der DOAG 2020 Konferenz + Ausstellung&lt;/a&gt; ist veröffentlicht!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://2020.doag.org/de/home/" target="_blank"&gt;&lt;img src="https://www.fromdual.com/sites/default/files/doag-2020-konferenz-ausstellung-banner-600x100.jpg" title="DOAG 2020 Konferenz + Ausstellung" alt="doag2020" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Das Programm ist abwechslungsreich und interessant, wenn auch etwas kleiner als andere Jahre. Die Teilnahme lohnt sich!&lt;/p&gt;
&lt;p&gt;Aus MariaDB/MySQL Sicht ist vor allem der Mittwoch, im Raum Kiew (55 Plätze), mit den üblichen Verdächtigen, interessant:&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;09:00 - 09:45&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Sessionkeynote: &lt;a href="https://programm.doag.org/doag/2020/#/scheduledEvent/604531" target="_blank"&gt;The new MySQL Database and MySQL Analytics Service - Extreme Performance, Cloud Scale, Significant Cost Savings&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Nipun Agarwal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10:00 - 10:45&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://programm.doag.org/doag/2020/#/scheduledEvent/603455" target="_blank"&gt;MariaDB/MySQL Stolperfallen und wie komme ich da wieder raus&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Oliver Sennhauser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11:00 - 11:45&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://programm.doag.org/doag/2020/#/scheduledEvent/603469" target="_blank"&gt;Wie ein Ei dem anderen... MySQL ReplicaSets&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Matthias Jung&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12:00 - 12:45&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://programm.doag.org/doag/2020/#/scheduledEvent/603481" target="_blank"&gt;MySQL 8: Ein Statusbericht 3 Jahre nach dem ersten Release&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Carsten Thalheimer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Die Konferenz findet dieses Jahr von Dienstag, dem 17. November 2020 bis Donnerstag, dem 19. November 2020 in Nürnberg und in Teilen auch als online Veranstaltung statt. Mehr dazu auf der &lt;a href="https://2020.doag.org/de/home/" target="_blank" title="DOAG 2020 KONFERENZ + AUSSTELLUNG"&gt;Website der Konferenz&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>FromDual Schulungen trotz Corona - einfach online und remote</title><link>https://www.fromdual.com/de/blog/fromdual-schulungen-trotz-corona-einfach-online-und-remote/</link><pubDate>Mon, 23 Mar 2020 15:43:41 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/fromdual-schulungen-trotz-corona-einfach-online-und-remote/</guid><description>&lt;p&gt;Aufgrund der aktuellen Corona-Pandemie sind sämtliche FromDual vor Ort Schulungen, Schulungen bei unseren Schulungspartnern sowie FromDual Beratungseinsätze bis auf weiteres sistiert.&lt;/p&gt;
&lt;p&gt;Damit Sie diesen Frühling aber nicht ganz ohne Weiterbildung auskommen müssen, sind wir bereits seit letzter Woche daran, alternative Möglichkeiten mittels online Schulungen zu testen. Erste Testschulungen wurden bereits Ende letzter Woche und anfangs dieser Woche durchgeführt.&lt;/p&gt;
&lt;p&gt;Möglicherweise bietet sich Ihnen in den nächsten Wochen die Möglichkeit, aus dem Homeoffice die eine oder andere unserer online-Schulungen zu besuchen. Voraussichtlich betroffen sind die &lt;a href="https://www.fromdual.com/de/mysql-mariadb-schulungstermine" title="MariaDB und MySQL Schulungstermine"&gt;folgenden Schulungstermine&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2&lt;br&gt;. bis 4. April: Galera Cluster für MariaDB/MySQL im Linuxhotel in Essen&lt;/li&gt;
&lt;li&gt;23&lt;br&gt;. bis 24. April: Galera Cluster für MariaDB/MySQL in der Heinlein Academy in Berlin&lt;/li&gt;
&lt;li&gt;4&lt;br&gt;. bis 8. Mai: MariaDB/MySQL für Fortgeschrittene bei der GfU Cyrus in Köln&lt;/li&gt;
&lt;li&gt;11&lt;br&gt;. bis 15. Mai: MariaDB/MySQL für Fortgeschrittene in der Heinlein Academy in Berlin&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ob es auch noch spätere Schulungstermine betrifft werden wir sehen&amp;hellip;&lt;/p&gt;
&lt;h2 id="remote-geht-es-besser---fromdual-remote-dba-dienstleistungen"&gt;Remote geht es besser - FromDual remote-DBA Dienstleistungen&lt;/h2&gt;
&lt;p&gt;Die Corona-Pandemie verursacht möglicherweise auch ein anderes oder neues Lastmuster auf Ihrer Datenbank.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Buchungen werden storniert (Reiseportale),&lt;/li&gt;
&lt;li&gt;Rabatt-Aktionen gestartet (Outdoor-Aktivitäten),&lt;/li&gt;
&lt;li&gt;es wird vermehrt online eingekauft (Webshops),&lt;/li&gt;
&lt;li&gt;es wird vermehrt kommuniziert (VoIP, Video-Conferencing, Screen Sharing) oder auch&lt;/li&gt;
&lt;li&gt;die Plattformen des Gesundheitswesen werden heftiger als sonst in Anspruch genommen.&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Falls diese Situation bei Ihnen zu betrieblichen Problemen oder Performance-Engpässen führt, helfen wir Ihnen auch gerne mit einem &lt;a href="https://www.fromdual.com/de/mysql-remote-dba" title="FromDual Remote-DBA Dienstleistungen für MariaDB und MySQL"&gt;remote-DBA Einsatz&lt;/a&gt; anstelle eines vor Ort Beratungseinsatzes weiter. Wir haben diese Technologien bereits seit Jahren im Einsatz und zeigen Ihnen auch gerne remote, wie Sie Ihre Probleme in den Griff kriegen.&lt;/p&gt;</description></item><item><title>Eher Finger weg: innodb_deadlock_detect</title><link>https://www.fromdual.com/de/blog/eher-finger-weg-innodb-deadlock-detect/</link><pubDate>Fri, 06 Mar 2020 15:27:58 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/eher-finger-weg-innodb-deadlock-detect/</guid><description>&lt;p&gt;Kürzlich haben wir bei einem unserer Kunden, der gelegentlich massive Datenbankprobleme hat, bei der Durchsicht der MySQL Konfigurationsdatei (&lt;code&gt;my.cnf&lt;/code&gt;) festgestellt, dass er die InnoDB Deadlock-Erkennung (&lt;code&gt;innodb_deadlock_detect&lt;/code&gt;) deaktiviert hatte.&lt;/p&gt;
&lt;p&gt;Da wir davon bisher immer abgeraten haben, ich aber noch nie konkret über dieses Problem gestolpert bin, bin ich der Sache noch etwas nachgegangen und habe zur Variable &lt;code&gt;innodb_deadlock_detect&lt;/code&gt; recherchiert.&lt;/p&gt;
&lt;p&gt;Die MySQL Dokumentation sagt dazu folgendes &lt;br&gt;[&lt;a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-deadlock-detection.html" target="_blank"&gt;1&lt;/a&gt;&lt;br&gt;]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disabling Deadlock Detection&lt;/strong&gt;
On high concurrency systems, deadlock detection can cause a slowdown when numerous threads wait for the same lock. At times, it may be more efficient to disable deadlock detection and rely on the &lt;code&gt;innodb_lock_wait_timeout&lt;/code&gt; setting for transaction rollback when a deadlock occurs. Deadlock detection can be disabled using the &lt;code&gt;innodb_deadlock_detect&lt;/code&gt; configuration option.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Und zum Parameter &lt;code&gt;innodb_deadlock_detect&lt;/code&gt; selbst &lt;br&gt;[&lt;a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_deadlock_detect" target="_blank"&gt;2&lt;/a&gt;&lt;br&gt;]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This option is used to disable deadlock detection. On high concurrency systems, deadlock detection can cause a slowdown when numerous threads wait for the same lock. At times, it may be more efficient to disable deadlock detection and rely on the &lt;code&gt;innodb_lock_wait_timeout&lt;/code&gt; setting for transaction rollback when a deadlock occurs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Das Problem ist, dass jedes mal, wenn MySQL einen (Row) Lock oder Table Lock macht, überprüft wird, ob dieser Lock einen Deadlock verursacht. Was entsprechend teuer ist. Diese Feature wurde übrigens von Facebook entwickelt &lt;br&gt;[&lt;a href="https://github.com/facebookarchive/webscalesql-5.6/commit/d9e347dc3db6acb247517896128fec664ab19202" target="_blank"&gt;3&lt;/a&gt;&lt;br&gt;].&lt;/p&gt;
&lt;p&gt;Die entsprechenden Funktionen sind in &lt;br&gt;[4&lt;br&gt;] zu finden:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class DeadlockChecker, method check_and_resolve (DeadlockChecker::check_and_resolve)

Every InnoDB (row) Lock (for mode LOCK_S or LOCK_X) and type ORed with LOCK_GAP or
LOCK_REC_NOT_GAP, ORed with LOCK_INSERT_INTENTION

Enqueue a waiting request for a lock which cannot be granted immediately.

lock_rec_enqueue_waiting()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;und&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Every (InnoDB) Table Lock

Enqueues a waiting request for a table lock which cannot be granted immediately. Checks for deadlocks.

lock_table_enqueue_waiting()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Das heisst jetzt, wenn die Variable &lt;code&gt;innodb_deadlock_detect&lt;/code&gt; eingeschaltet ist (= default) wird bei jedem Lock (Row oder Table) überprüft, ob dadurch ein Deadlock entsteht. Wenn die Variable ausgeschaltet ist, wird diese Überprüfung NICHT ausgeführt (was schneller ist) und die Transaktion bleibt im (Dead-)Lock hängen bis der Lock frei gegeben wird oder die Zeit &lt;code&gt;innodb_lock_wait_timeout&lt;/code&gt; (default 50 Sekunden) überschritten ist. Dann schlägt der InnoDB Lock Wait Timeout (Detektor?) zu.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL&amp;gt; SHOW GLOBAL VARIABLES LIKE 'innodb_lock_wait%';
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 50 |
+--------------------------+-------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Das bedeutet, das Deaktivieren der InnoDB Deadlock Detektion ist interessant, wenn Sie sehr viele (wie Facebook!?!) kurze/kleine Transaktionen haben, bei welchen wenig bis keine Konflikte auftreten.
Im Weiteren wird empfohlen, gleichzeitig die &lt;code&gt;innodb_lock_wait_timeout&lt;/code&gt; auf einen sehr geringen Wert (wenige Sekunden) einzustellen.&lt;/p&gt;
&lt;p&gt;Da die meisten unserer Kunden aber nicht in die Kragenweite Facebook passen und tendenziell eher nicht viele gleichzeitige kurze/kleine Transaktionen haben sondern wenig lange Transaktionen (mit wahrscheinlich vielen Locks und daher einer grossen Deadlock-Wahrscheinlichkeit), kann ich mir durchaus vorstellen, dass das deaktivieren diese Parameters für das Verschlucken (Locks stauen auf) des Kundensystems verantwortlich ist, was anschliessen dazu führt, dass &lt;code&gt;max_connections&lt;/code&gt; erreicht wird und schliesslich gar nichts mehr geht.&lt;/p&gt;
&lt;p&gt;Daher würde ich dringend empfehlen, die InnoDB Deadlock Detektierung aktiviert zu lassen. Ausser man weiss genau, was man tut (nach ca. 2 Wochen testen und messen).&lt;/p&gt;
&lt;h2 id="literatur"&gt;Literatur&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;br&gt;[1&lt;br&gt;] &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-deadlock-detection.html" target="_blank" title="Deadlock Detection and Rollback"&gt;Deadlock Detection and Rollback&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;br&gt;[2&lt;br&gt;] InnoDB Startup Options and System Variables: &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_deadlock_detect" target="_blank" title="innodb_deadlock_detect"&gt;innodb_deadlock_detect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;br&gt;[3&lt;br&gt;] Einführung der Variable &lt;code&gt;innodb_deadlock_detect&lt;/code&gt; in WebScaleSQL von Facebook auf &lt;a href="https://github.com/facebookarchive/webscalesql-5.6/commit/d9e347dc3db6acb247517896128fec664ab19202" target="_blank"&gt;Github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;br&gt;[4&lt;br&gt;] MariaDB/MySQL Quellcode: &lt;code&gt;storage/innobase/lock/lock0lock.cc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;br&gt;[5&lt;br&gt;] MariaDB InnoDB System Variables: &lt;a href="https://mariadb.com/kb/en/innodb-system-variables/#innodb_deadlock_detect" target="_blank" title="innodb_deadlock_detect"&gt;innodb_deadlock_detect&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>FromDual ist 10 Jahre alt</title><link>https://www.fromdual.com/de/blog/fromdual-ist-10-jahre-alt/</link><pubDate>Mon, 02 Mar 2020 10:01:36 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/fromdual-ist-10-jahre-alt/</guid><description>&lt;p&gt;Am 1. März 2020 wurde die FromDual GmbH 10 Jahre alt! Wir möchten allen Kunden, Partnern und Interessenten herzlich für die Unterstützung und gute Zusammenarbeit in den vergangenen 10 Jahren danken. Es würde uns freuen, Euch auch in den kommenden 10 Jahren kompetent beraten und betreuen zu dürfen.&lt;/p&gt;
&lt;p&gt;Euer FromDual Team&lt;/p&gt;
&lt;img src="https://www.fromdual.com/sites/default/files/anniversary-2673504_1280.jpg" width="640" alt="anniversary" /&gt;
&lt;p&gt;Bild von &lt;a href="https://pixabay.com/de/users/kalhh-86169/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=2673504" target="_blank"&gt;kalhh&lt;/a&gt; auf &lt;a href="https://pixabay.com/de/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=2673504" target="_blank"&gt;Pixabay&lt;/a&gt;&lt;/p&gt;</description></item><item><title>FromDual mit Galera Cluster und SQL Query Tuning an den Chemnitzer Linux-Tagen 2020</title><link>https://www.fromdual.com/de/blog/fromdual-mit-galera-cluster-und-sql-query-tuning-an-den-chemnitzer-linux-tagen-2020/</link><pubDate>Tue, 11 Feb 2020 09:55:49 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/fromdual-mit-galera-cluster-und-sql-query-tuning-an-den-chemnitzer-linux-tagen-2020/</guid><description>&lt;p&gt;Zum 22. mal finden am 14. und 15. März 2020 die &lt;a href="https://chemnitzer.linux-tage.de/2020/de/" target="_blank" title="Chemnitzer Linux-Tage"&gt;Chemnitzer Linux-Tage&lt;/a&gt; an der Technische Universität Chemnitz statt.&lt;/p&gt;
&lt;p&gt;FromDual ist am Samstag, 14. März das erste mal mit einem Workshop &amp;ldquo;MariaDB Galera Cluster ganz einfach&amp;rdquo; und am Sonntag, 15. März wieder einmal mit einem Vortrag, diesmal zum Thema MariaDB Performance Tuning: &amp;ldquo;MariaDB SQL Query Tuning für Applikations-Entwickler&amp;rdquo;, präsent.&lt;/p&gt;
&lt;p&gt;Falls Sie also einmal unter Anleitung einen MariaDB Galera Cluster aufsetzen oder sich mit SQL Query Tuning unter MariaDB vertraut machen wollen, besuchen Sie doch einfach die Chemnitzer Linux-Tage 2020 und sitzen Sie in unseren Vortrag rein oder melden Sie sich für unseren Workshop an.&lt;/p&gt;
&lt;img src="https://www.fromdual.com/sites/default/files/hacker-1569744_1280.jpg" width="640" alt="CLT 2020" /&gt;
&lt;p&gt;Bild von Génesis Gabriella auf Pixabay&lt;/p&gt;</description></item><item><title>FromDual wird offizieller MariaDB Partner</title><link>https://www.fromdual.com/de/blog/fromdual-wird-offizieller-mariadb-partner/</link><pubDate>Tue, 11 Feb 2020 08:51:05 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/fromdual-wird-offizieller-mariadb-partner/</guid><description>&lt;p&gt;Nach Jahren der losen Zusammenarbeit wird FromDual jetzt, im Frühjahr 2020, &lt;a href="https://mariadb.com/about-us/partners/fromdual-gmbh/" target="_blank" title="FromDual: MariaDB partners"&gt;offizieller MariaDB Partner&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Schon seit Beginn der MariaDB Erfolgsgeschichte befasst sich FromDual mit der Open Source Datenbank MariaDB und hat ihre Kunden mit Rat und Tat beim Betrieb der Datenbank unterstützt.&lt;/p&gt;
&lt;p&gt;Da die Nachfrage nach MariaDB in den letzten Jahren laufend zu nahm, bietet FromDual schon seit längerem Schulungen, vor Ort Beratungen (Consulting), und remote-DBA Dienstleistungen für die MariaDB Datenbank an.&lt;/p&gt;
&lt;p&gt;Der nächste Schritt, die offizielle Zusammenarbeit, ist somit schon längst fällig: Daher ist FromDual nun offizieller MariaDB Partner geworden.&lt;/p&gt;
&lt;p&gt;Dieser Schritt ermöglicht es FromDual ihren Kunden auch beim Erwerb, der Nutzung und der Implementierung der MariaDB Enterprise Edition Subskription tatkräftig zu unterstützen um den grössten Nutzen aus dieser Datenbank zu ziehen.&lt;/p&gt;
&lt;p&gt;Zudem wurden sämtliche FromDual Tools und die Monitoring-as-a-Service Dienstleistung für die MariaDB Datenbank funktionsfähig gemacht und werden schon seit einiger Zeit vollständig unterstützt.&lt;/p&gt;</description></item><item><title>FromDual Performance Monitor 1.1.0 für MariaDB und MySQL freigegeben</title><link>https://www.fromdual.com/de/blog/fromdual-performance-monitor-1.1.0-fuer-mariadb-und-mysql-freigegeben/</link><pubDate>Thu, 06 Feb 2020 14:19:01 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/fromdual-performance-monitor-1.1.0-fuer-mariadb-und-mysql-freigegeben/</guid><description>&lt;p&gt;FromDual hat die Version 1.1.0 des Performance Monitors für MariaDB, MySQL und Galera Cluster (fpmmm) freigegeben.&lt;/p&gt;
&lt;p&gt;Dieser Performance Monitor ermöglicht es DBAs und Systemadministratoren einen tiefen Einblick in das Innenleben der Datenbank und des Servers, auf dem sich die Datenbank befindet, zu erhalten.&lt;/p&gt;
&lt;p&gt;Die neue Version steht hier zum &lt;a href="https://support.fromdual.com/admin/public/download.php" target="_blank" title="FromDual Software Download"&gt;Download&lt;/a&gt; bereit.&lt;/p&gt;
&lt;p&gt;Falls Sie sich die Mühe sparen wollen, eine eigene Monitoring-Infrastruktur aufzubauen, nutzen Sie doch einfach unsere &lt;a href="https://www.fromdual.com/monitoring-as-a-service-maas-for-mysql" title="Monitoring-as-a-Service"&gt;kostengünstige Monitoring-as-a-Service Lösung&lt;/a&gt;. Gerne schicken wir Ihnen ein Angebot.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Wichtige Neuerungen&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fpmmm steht jetzt paketiert für CentOS mit RPM und Ubuntu mit DEB Paketen zur Verfügung.&lt;/li&gt;
&lt;li&gt;MariaDB 10.4 wird ab sofort offiziell durch fpmmm unterstützt.&lt;/li&gt;
&lt;li&gt;fpmmm unterstützt ab sofort ausschliesslich PHP Version 7+.&lt;/li&gt;
&lt;li&gt;Sämtliche Zabbix Templates wurden auf die Version 4.0 upgegraded.&lt;/li&gt;
&lt;li&gt;Ein Backup-Hook wurde eingeführt. Sie können somit Ihr eigenes Backup-System oder den FromDual Backup Manager in fpmmm einbinden.&lt;/li&gt;
&lt;li&gt;Graphen für InnoDB Buffer Pool flushing und InnoDB Files wurden hinzugefügt.&lt;/li&gt;
&lt;li&gt;MariaDB Thread Pool Monitoring, für Anwendungen welche extrem viele Verbindungen zu Datenbank aufbauen, wurde hinzugefügt.&lt;/li&gt;
&lt;li&gt;Die Graphen für den Aria Pagecache wurde verbessert.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Eine vollständige Liste der Verbesserungen finden Sie &lt;a href="https://www.fromdual.com/fromdual-performance-monitor-for-mariadb-and-mysql-1.1.0-has-been-released" title="fpmmm v1.1.0 Release Notes"&gt;hier&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Eine detaillierte Anleitung zum fpmmm finden Sie im &lt;a href="https://www.fromdual.com/fpmmm-installation-guide" title="fpmmm Installation Guide"&gt;fpmmm Installation Guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Viel Spass beim Überwachen Ihrer MariaDB/MySQL Datenbank,&lt;br&gt;
Ihr FromDual Team&lt;/p&gt;</description></item><item><title>MariaDB und MySQL Schulungsprogramm 2020</title><link>https://www.fromdual.com/de/blog/mariadb-und-mysql-schulungsprogramm-2020/</link><pubDate>Fri, 13 Dec 2019 16:02:24 +0000</pubDate><author>oli.sennhauser@fromdual.com (Oli Sennhauser)</author><guid>https://www.fromdual.com/de/blog/mariadb-und-mysql-schulungsprogramm-2020/</guid><description>&lt;p&gt;Hiermit präsentieren wir Ihnen das FromDual Schulungsprogramm 2020 für MariaDB und MySQL. Auch nächstes Jahr führen wir wieder zahlreiche MariaDB und MySQL Schulungen für Anfänger und Fortgeschrittene bei unseren 3 Partnern in Essen, Köln und Berlin durch.&lt;/p&gt;
&lt;p&gt;Die Liste mit Schulungsterminen 2020 finden Sie &lt;a href="https://www.fromdual.com/de/mysql-mariadb-schulungstermine" title="MariaDB und MySQL Schulungstermine 2020"&gt;hier&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Welches für Sie das richtige Schulungsmodul ist, erläutern wir unter der Seite &lt;a href="https://www.fromdual.com/de/mysql-mariadb-schulungsmodule" title="MariaDB und MySQL Schulungsmodule"&gt;Schulungsmodule&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Gleich richtig los geht es anfangs Januar mit MariaDB und MySQL für Einsteiger. Dieser Kurs ist schon fast ausgebucht und findet daher sicher statt. Sichern Sie sich also noch die letzten Plätze!&lt;/p&gt;
&lt;h2 id="fromdual-technik-blog"&gt;FromDual Technik-Blog&lt;/h2&gt;
&lt;p&gt;Im FromDual Blog berichten wir regelmässig darüber, mit welchen Themen wir unser Wissen erweitert haben. Diese Artikel sind frei verfügbar und somit auch Ihnen zugänglich:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mit MySQL 8.0 wird utf8mb4 das neue Default Character Set. Was Sie hierzu beachten sollten, haben wir im Artikel: &lt;a href="https://www.fromdual.com/mariadb-and-mysql-character-set-conversion" title="MariaDB and MySQL Character Set Conversion"&gt;MariaDB and MySQL Character Set Conversion&lt;/a&gt; beschrieben.&lt;/li&gt;
&lt;li&gt;Wie Sie den Progress Indicator des Backup und Recovery Managers nutzen, finden Sie im Artikel: &lt;a href="https://www.fromdual.com/fromdual-recovery-manager-rman-with-progress-indicator" target="FromDual Recovery Manager (rman) with progress indicator"&gt;FromDual Recovery Manager (rman) with progress indicator&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Falls Sie sich entscheiden sollten, die Seite zu wechseln, gibt es im Artikel &lt;a href="https://www.fromdual.com/migration-from-mysql-5-7-to-mariadb-10-4" title="Migration from MySQL 5.7 to MariaDB 10.4"&gt;Migration from MySQL 5.7 to MariaDB 10.4&lt;/a&gt; einige nützliche Tipps hierzu.&lt;/li&gt;
&lt;li&gt;Wenn Sie genauer wissen wollen, wo Ihr RAM geblieben ist gibt Ihnen der Artikel: &lt;a href="https://www.fromdual.com/who-else-is-using-my-memory-file-system-cache-analysis" title="Who else is using my memory - File System Cache analysis"&gt;Who else is using my memory - File System Cache analysis&lt;/a&gt; möglicherweise neue Denkanstösse.&lt;/li&gt;
&lt;li&gt;Bei der Untersuchung von Problemen nutzen wir immer wieder das General Query Log. Wie man es aber nur für eine bestimmte Verbindung aktiviert, finden Sie hier beschrieben: &lt;a href="https://www.fromdual.com/enable-gerneral-quey-log-per-connection-in-mariadb" title="Enable General Query Log per Connection in MariaDB"&gt;Enable General Query Log per Connection in MariaDB&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Auch wir sind (leider) nicht ganz fehlerfrei. Wie wir unser CRM kaputt und anschliessend wieder heile gemacht haben beschreiben wir in: &lt;a href="https://www.fromdual.com/oops-that-sql-query-was-not-intended-flashback"&gt;Oops! - That SQL Query was not intended&amp;hellip; Flashback&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Falls Sie sich das Swappen auf Ihrem System nicht erklären können, empfehlen wir Ihnen folgenden Artikel: &lt;a href="https://www.fromdual.com/do-not-underestimate-performance-impacts-of-swapping-on-numa-database-systems"&gt;Do not underestimate performance impacts of swapping on NUMA database systems&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="werkzeuge-für-den-täglichen-datenbankbetrieb"&gt;Werkzeuge für den täglichen Datenbankbetrieb&lt;/h2&gt;
&lt;p&gt;FromDual bietet Ihnen auch eine breite Palette von nützlichen Werkzeuge für den täglichen Betrieb und die Pflege Ihrer MariaDB und MySQL Datenbanken an:&lt;/p&gt;
&lt;p&gt;Das überaus nützliche sys Schema haben wir von MySQL nach MariaDB portiert. Das sys Schema können Sie jetzt von Github beziehen. Wie sie dazu vorgehen müssen beschreiben wir im Artikel &lt;a href="https://www.fromdual.com/mariadb-sys-schema" title="MariaDB sys Schema"&gt;MariaDB sys Schema&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Im Sommer haben wir einen weiteren Release des &lt;a href="https://www.fromdual.com/fromdual-ops-center-for-mariadb-and-mysql-0.9.2-has-been-released" title="FromDual Ops Centers für MariaDB und MySQL"&gt;FromDual Ops Centers für MariaDB und MySQL freigegeben&lt;/a&gt;. Point-in-Time Recovery ist jetzt ebenfalls über das Ops Center möglich.&lt;/p&gt;
&lt;p&gt;Ebenfalls im August ist die neuste Version des &lt;a href="https://www.fromdual.com/fromdual-backup-manager-2.2.1-has-been-released" title="FromDual Backup und Recovery Managers (brman)"&gt;FromDual Backup und Recovery Managers (brman)&lt;/a&gt; für MariaDB und MySQL erschienen.&lt;/p&gt;
&lt;p&gt;Hoffentlich noch dieses Jahr releasen wir die nächste Version des &lt;a href="https://www.fromdual.com/fpmmm-installation-guide" title="FromDual Performance Monitors für MariaDB und MySQL (fpmmm)"&gt;FromDual Performance Monitors für MariaDB und MySQL (fpmmm)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Sämtliche FromDual Software kann von FromDual Servern unter &lt;a href="https://www.fromdual.com/download" title="FromDual Download"&gt;Download&lt;/a&gt; heruntergeladen werden.&lt;/p&gt;
&lt;h2 id="monitoring-as-a-service"&gt;Monitoring as a Service&lt;/h2&gt;
&lt;p&gt;Falls Sie sich keine eigene Monitoring Lösung antun wollen, bieten wir Ihnen auch gerne unsere preiswerte &lt;a href="https://www.fromdual.com/monitoring-as-a-service-maas-for-mysql" title="Monitoring-as-a-Service (MaaS)"&gt;Monitoring-as-a-Service (MaaS)&lt;/a&gt; Lösung an. Nehmen Sie hierzu einfach mit uns Kontakt auf. Wir unterbreiten Ihnen gerne ein Angebot.&lt;/p&gt;
&lt;p&gt;Ich denke, hiermit haben Sie genügend zu tun, damit Ihnen über die Feiertag nicht langweilig wird&amp;hellip; :-)&lt;/p&gt;
&lt;p&gt;Schöne Weihnachten und einen gute Rutsch ins neue Jahr wünscht Ihnen&lt;br&gt;
Ihr FromDual Schulungsteam&lt;/p&gt;</description></item></channel></rss>