Wie führt man Analytics auf MySQL aus?
MySQL ist eine großartige Datenbank für Workloads zur Online-Transaktionsverarbeitung (OLTP). Für manche Unternehmen war das lange Zeit mehr als genug. Die Zeiten haben sich geändert und mit ihnen die geschäftlichen Anforderungen. Da Unternehmen danach streben, stärker datengesteuert zu sein, werden immer mehr Daten zur weiteren Analyse gespeichert; Kundenverhalten, Leistungsmuster, Netzwerkverkehr, Protokolle usw. Ganz gleich, in welcher Branche Sie tätig sind, es ist sehr wahrscheinlich, dass es Daten gibt, die Sie aufbewahren und analysieren möchten, um besser zu verstehen, was vor sich geht und wie Sie Ihr Geschäft verbessern können. Leider ist MySQL zum Speichern und Abfragen der großen Datenmenge nicht die beste Option. Sicher, es kann es und es verfügt über Tools, die dabei helfen, große Datenmengen zu bewältigen (z. B. InnoDB-Komprimierung), aber die Verwendung einer dedizierten Lösung für Online Analytics Processing (OLAP) wird Ihre Fähigkeit zum Speichern und Abfragen einer großen Menge höchstwahrscheinlich erheblich verbessern von Daten.
Eine Möglichkeit, dieses Problem anzugehen, besteht darin, eine dedizierte Datenbank für die Ausführung von Analysen zu verwenden. Typischerweise möchten Sie für solche Aufgaben einen spaltenorientierten Datenspeicher verwenden – sie eignen sich besser für die Verarbeitung großer Datenmengen:In Spalten gespeicherte Daten sind normalerweise einfacher zu komprimieren, es ist auch einfacher, auf Spaltenbasis zuzugreifen – normalerweise fragen Sie danach Daten, die in einigen Spalten gespeichert sind - die Möglichkeit, nur diese Spalten abzurufen, anstatt alle Zeilen zu lesen, und nicht benötigte Daten herauszufiltern, beschleunigt den Zugriff auf die Daten.
Wie werden Daten von MySQL zu ClickHouse repliziert?
Ein Beispiel für einen spaltenorientierten Datenspeicher, der sich für Analysen eignet, ist ClickHouse, ein Open-Source-Spaltenspeicher. Eine Herausforderung besteht darin sicherzustellen, dass die Daten in ClickHouse mit den Daten in MySQL synchronisiert sind. Sicher, es ist immer möglich, eine Art Datenpipeline einzurichten und automatisierte Batch-Ladevorgänge in ClickHouse durchzuführen. Aber solange Sie mit einigen Einschränkungen leben können, gibt es eine bessere Möglichkeit, eine fast in Echtzeit erfolgende Replikation von MySQL in ClickHouse einzurichten. In diesem Blog-Beitrag werden wir uns ansehen, wie es gemacht werden kann.
ClickHouse-Installation
Zuerst müssen wir ClickHouse installieren. Wir verwenden den Schnellstart von der ClickHouse-Website.
sudo apt-get install dirmngr # optional
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv E0C56BD4 # optional
echo "deb http://repo.yandex.ru/clickhouse/deb/stable/ main/" | sudo tee /etc/apt/sources.list.d/clickhouse.list
sudo apt-get update
sudo apt-get install -y clickhouse-server clickhouse-client
sudo service clickhouse-server start
Sobald dies erledigt ist, müssen wir einen Weg finden, die Daten von MySQL in ClickHouse zu übertragen. Eine der möglichen Lösungen ist die Verwendung des Clickhouse-mysql-data-reader von Altinity. Zunächst müssen wir pip3 (python3-pip in Ubuntu) installieren, da Python in Version mindestens 3.4 benötigt wird. Dann können wir pip3 verwenden, um einige der erforderlichen Python-Module zu installieren:
pip3 install mysqlclient
pip3 install mysql-replication
pip3 install clickhouse-driver
Sobald dies erledigt ist, müssen wir das Repository klonen. Für Centos 7 sind auch RPMs verfügbar, es ist auch möglich, es mit pip3 (Clickhouse-Mysql-Paket) zu installieren, aber wir haben festgestellt, dass die über pip verfügbare Version nicht die neuesten Updates enthält und wir den Master-Zweig aus dem Git-Repository verwenden möchten:
git clone https://github.com/Altinity/clickhouse-mysql-data-reader
Dann können wir es mit pip installieren:
pip3 install -e /path/to/clickhouse-mysql-data-reader/
Der nächste Schritt besteht darin, MySQL-Benutzer zu erstellen, die von clickhouse-mysql-data-reader für den Zugriff auf MySQL-Daten benötigt werden:
mysql> CREATE USER 'chreader'@'%' IDENTIFIED BY 'pass';
Query OK, 0 rows affected (0.02 sec)
mysql> CREATE USER 'chreader'@'127.0.0.1' IDENTIFIED BY 'pass';
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE USER 'chreader'@'localhost' IDENTIFIED BY 'pass';
Query OK, 0 rows affected (0.02 sec)
mysql> GRANT SELECT, REPLICATION CLIENT, REPLICATION SLAVE, SUPER ON *.* TO 'chreader'@'%';
Query OK, 0 rows affected (0.01 sec)
mysql> GRANT SELECT, REPLICATION CLIENT, REPLICATION SLAVE, SUPER ON *.* TO 'chreader'@'127.0.0.1';
Query OK, 0 rows affected (0.00 sec)
mysql> GRANT SELECT, REPLICATION CLIENT, REPLICATION SLAVE, SUPER ON *.* TO 'chreader'@'localhost';
Query OK, 0 rows affected, 1 warning (0.01 sec)
Sie sollten auch Ihre MySQL-Konfiguration überprüfen, um sicherzustellen, dass Sie Binärprotokolle aktiviert haben, max_binlog_size auf 768 MB eingestellt ist, Binlogs im Zeilenformat vorliegen und das Tool eine Verbindung zu MySQL herstellen kann. Nachfolgend ein Auszug aus der Dokumentation:
[mysqld]
# mandatory
server-id = 1
log_bin = /var/lib/mysql/bin.log
binlog-format = row # very important if you want to receive write, update and delete row events
# optional
expire_logs_days = 30
max_binlog_size = 768M
# setup listen address
bind-address = 0.0.0.0
Importieren der Daten
Wenn alles fertig ist, können Sie die Daten in ClickHouse importieren. Idealerweise führen Sie den Import auf einem Host mit gesperrten Tabellen aus, damit während des Vorgangs keine Änderungen vorgenommen werden. Sie können einen Slave als Quelle der Daten verwenden. Der auszuführende Befehl lautet:
clickhouse-mysql --src-server-id=1 --src-wait --nice-pause=1 --src-host=10.0.0.142 --src-user=chreader --src-password=pass --src-tables=wiki.pageviews --dst-host=127.0.0.1 --dst-create-table --migrate-table
Es stellt eine Verbindung zu MySQL auf Host 10.0.0.142 unter Verwendung der angegebenen Anmeldeinformationen her, es kopiert die Tabelle „Seitenaufrufe“ im Schema „Wiki“ in ein ClickHouse, das auf dem lokalen Host (127.0.0.1) ausgeführt wird. Die Tabelle wird automatisch erstellt und die Daten werden migriert.
Für die Zwecke dieses Blogs haben wir etwa 50 Millionen Zeilen aus dem von der Wikimedia Foundation zur Verfügung gestellten Datensatz „Seitenaufrufe“ importiert. Das Tabellenschema in MySQL ist:
mysql> SHOW CREATE TABLE wiki.pageviews\G
*************************** 1. row ***************************
Table: pageviews
Create Table: CREATE TABLE `pageviews` (
`date` date NOT NULL,
`hour` tinyint(4) NOT NULL,
`code` varbinary(255) NOT NULL,
`title` varbinary(1000) NOT NULL,
`monthly` bigint(20) DEFAULT NULL,
`hourly` bigint(20) DEFAULT NULL,
PRIMARY KEY (`date`,`hour`,`code`,`title`)
) ENGINE=InnoDB DEFAULT CHARSET=binary
1 row in set (0.00 sec)
Das Tool übersetzte dies in das folgende ClickHouse-Schema:
vagrant.vm :) SHOW CREATE TABLE wiki.pageviews\G
SHOW CREATE TABLE wiki.pageviews
Row 1:
──────
statement: CREATE TABLE wiki.pageviews ( date Date, hour Int8, code String, title String, monthly Nullable(Int64), hourly Nullable(Int64)) ENGINE = MergeTree(date, (date, hour, code, title), 8192)
1 rows in set. Elapsed: 0.060 sec.
Sobald der Import abgeschlossen ist, können wir den Inhalt von MySQL vergleichen:
mysql> SELECT COUNT(*) FROM wiki.pageviews\G
*************************** 1. row ***************************
COUNT(*): 50986914
1 row in set (24.56 sec)
und in ClickHouse:
vagrant.vm :) SELECT COUNT(*) FROM wiki.pageviews\G
SELECT COUNT(*)
FROM wiki.pageviews
Row 1:
──────
COUNT(): 50986914
1 rows in set. Elapsed: 0.014 sec. Processed 50.99 million rows, 50.99 MB (3.60 billion rows/s., 3.60 GB/s.)
Selbst in einer so kleinen Tabelle können Sie deutlich erkennen, dass MySQL mehr Zeit zum Durchsuchen benötigte als ClickHouse.
Wenn Sie den Prozess zum Überwachen des Binärprotokolls auf Ereignisse starten, übergeben Sie idealerweise die Informationen über die Binärprotokolldatei und die Position, von der aus das Tool mit der Überwachung beginnen soll. Sie können dies auf dem Slave leicht überprüfen, nachdem der anfängliche Import abgeschlossen ist.
clickhouse-mysql --src-server-id=1 --src-resume --src-binlog-file='binlog.000016' --src-binlog-position=194 --src-wait --nice-pause=1 --src-host=10.0.0.142 --src-user=chreader --src-password=pass --src-tables=wiki.pageviews --dst-host=127.0.0.1 --pump-data --csvpool
Wenn Sie es nicht bestehen, lauscht es einfach auf alles, was hereinkommt:
clickhouse-mysql --src-server-id=1 --src-resume --src-wait --nice-pause=1 --src-host=10.0.0.142 --src-user=chreader --src-password=pass --src-tables=wiki.pageviews --dst-host=127.0.0.1 --pump-data --csvpool
Lassen Sie uns weitere Daten laden und sehen, wie es für uns funktionieren wird. Wir können sehen, dass alles in Ordnung zu sein scheint, indem wir uns die Protokolle von clickhouse-mysql-data-reader ansehen:
2019-02-11 15:21:29,705/1549898489.705732:INFO:['wiki.pageviews']
2019-02-11 15:21:29,706/1549898489.706199:DEBUG:class:<class 'clickhouse_mysql.writer.poolwriter.PoolWriter'> insert
2019-02-11 15:21:29,706/1549898489.706682:DEBUG:Next event binlog pos: binlog.000016.42066434
2019-02-11 15:21:29,707/1549898489.707067:DEBUG:WriteRowsEvent #224892 rows: 1
2019-02-11 15:21:29,707/1549898489.707483:INFO:['wiki.pageviews']
2019-02-11 15:21:29,707/1549898489.707899:DEBUG:class:<class 'clickhouse_mysql.writer.poolwriter.PoolWriter'> insert
2019-02-11 15:21:29,708/1549898489.708083:DEBUG:Next event binlog pos: binlog.000016.42066595
2019-02-11 15:21:29,708/1549898489.708659:DEBUG:WriteRowsEvent #224893 rows: 1
Was wir im Auge behalten müssen, sind die Einschränkungen des Tools. Der größte ist, dass es nur INSERTs unterstützt. Es gibt keine Unterstützung für DELETE oder UPDATE. Es gibt auch keine Unterstützung für DDLs, daher werden alle inkompatiblen Schemaänderungen, die auf MySQL ausgeführt werden, die Replikation von MySQL zu ClickHouse unterbrechen.
Erwähnenswert ist auch die Tatsache, dass die Entwickler des Skripts empfehlen, pypy zu verwenden, um die Leistung des Tools zu verbessern. Lassen Sie uns einige Schritte durchgehen, die erforderlich sind, um dies einzurichten.
Zuerst müssen Sie pypy herunterladen und entpacken:
wget https://bitbucket.org/squeaky/portable-pypy/downloads/pypy3.5-7.0.0-linux_x86_64-portable.tar.bz2
tar jxf pypy3.5-7.0.0-linux_x86_64-portable.tar.bz2
cd pypy3.5-7.0.0-linux_x86_64-portable
Als nächstes müssen wir pip und alle Anforderungen für den clickhouse-mysql-data-reader installieren – genau die gleichen Dinge, die wir zuvor behandelt haben, während wir die reguläre Einrichtung beschrieben haben:
./bin/pypy -m ensurepip
./bin/pip3 install mysql-replication
./bin/pip3 install clickhouse-driver
./bin/pip3 install mysqlclient
Der letzte Schritt besteht darin, clickhouse-mysql-data-reader aus dem Github-Repository zu installieren (wir gehen davon aus, dass es bereits geklont wurde):
./bin/pip3 install -e /path/to/clickhouse-mysql-data-reader/
Das ist alles. Ab jetzt sollten Sie alle Befehle mit der für pypy erstellten Umgebung ausführen:
./bin/pypy ./bin/clickhouse-mysql
Tests
Die Daten wurden geladen, wir können überprüfen, ob alles reibungslos gelaufen ist, indem wir die Größe der Tabelle vergleichen:
MySQL:
mysql> SELECT COUNT(*) FROM wiki.pageviews\G
*************************** 1. row ***************************
COUNT(*): 204899465
1 row in set (1 min 40.12 sec)
ClickHouse:
vagrant.vm :) SELECT COUNT(*) FROM wiki.pageviews\G
SELECT COUNT(*)
FROM wiki.pageviews
Row 1:
──────
COUNT(): 204899465
1 rows in set. Elapsed: 0.100 sec. Processed 204.90 million rows, 204.90 MB (2.04 billion rows/s., 2.04 GB/s.)
Alles sieht richtig aus. Lassen Sie uns einige Abfragen ausführen, um zu sehen, wie sich ClickHouse verhält. Bitte bedenken Sie, dass dieses Setup alles andere als produktionsreif ist. Wir haben zwei kleine VMs, 4 GB Speicher und jeweils eine vCPU verwendet. Obwohl der Datensatz nicht groß war, reichte er aus, um den Unterschied zu sehen. Aufgrund der kleinen Stichprobe ist es ziemlich schwierig, „echte“ Analysen durchzuführen, aber wir können immer noch einige zufällige Abfragen auslösen.
Sehen wir uns an, von welchen Wochentagen wir Daten haben und wie viele Seiten pro Tag in unseren Beispieldaten aufgerufen wurden:
vagrant.vm :) SELECT count(*), toDayOfWeek(date) AS day FROM wiki.pageviews GROUP BY day ORDER BY day ASC;
SELECT
count(*),
toDayOfWeek(date) AS day
FROM wiki.pageviews
GROUP BY day
ORDER BY day ASC
┌───count()─┬─day─┐
│ 50986896 │ 2 │
│ 153912569 │ 3 │
└───────────┴─────┘
2 rows in set. Elapsed: 2.457 sec. Processed 204.90 million rows, 409.80 MB (83.41 million rows/s., 166.82 MB/s.)
Im Falle von MySQL sieht diese Abfrage wie folgt aus:
mysql> SELECT COUNT(*), DAYOFWEEK(date) AS day FROM wiki.pageviews GROUP BY day ORDER BY day;
+-----------+------+
| COUNT(*) | day |
+-----------+------+
| 50986896 | 3 |
| 153912569 | 4 |
+-----------+------+
2 rows in set (3 min 35.88 sec)
Wie Sie sehen, benötigte MySQL 3,5 Minuten für einen vollständigen Tabellenscan.
Sehen wir uns nun an, wie viele Seiten einen monatlichen Wert von mehr als 100 haben:
vagrant.vm :) SELECT count(*), toDayOfWeek(date) AS day FROM wiki.pageviews WHERE monthly > 100 GROUP BY day;
SELECT
count(*),
toDayOfWeek(date) AS day
FROM wiki.pageviews
WHERE monthly > 100
GROUP BY day
┌─count()─┬─day─┐
│ 83574 │ 2 │
│ 246237 │ 3 │
└─────────┴─────┘
2 rows in set. Elapsed: 1.362 sec. Processed 204.90 million rows, 1.84 GB (150.41 million rows/s., 1.35 GB/s.)
Bei MySQL sind es wieder 3,5 Minuten:
mysql> SELECT COUNT(*), DAYOFWEEK(date) AS day FROM wiki.pageviews WHERE YEAR(date) = 2018 AND monthly > 100 GROUP BY day;
^@^@+----------+------+
| COUNT(*) | day |
+----------+------+
| 83574 | 3 |
| 246237 | 4 |
+----------+------+
2 rows in set (3 min 3.48 sec)
Eine weitere Abfrage, nur eine Suche basierend auf einigen Zeichenfolgenwerten:
vagrant.vm :) select * from wiki.pageviews where title LIKE 'Main_Page' AND code LIKE 'de.m' AND hour=6;
SELECT *
FROM wiki.pageviews
WHERE (title LIKE 'Main_Page') AND (code LIKE 'de.m') AND (hour = 6)
┌───────date─┬─hour─┬─code─┬─title─────┬─monthly─┬─hourly─┐
│ 2018-05-01 │ 6 │ de.m │ Main_Page │ 8 │ 0 │
└────────────┴──────┴──────┴───────────┴─────────┴────────┘
┌───────date─┬─hour─┬─code─┬─title─────┬─monthly─┬─hourly─┐
│ 2018-05-02 │ 6 │ de.m │ Main_Page │ 17 │ 0 │
└────────────┴──────┴──────┴───────────┴─────────┴────────┘
2 rows in set. Elapsed: 0.015 sec. Processed 66.70 thousand rows, 4.20 MB (4.48 million rows/s., 281.53 MB/s.)
Eine weitere Abfrage, die einige Suchen in der Zeichenfolge und einer Bedingung basierend auf der Spalte „Monat“ durchführt:
vagrant.vm :) select title from wiki.pageviews where title LIKE 'United%Nations%' AND code LIKE 'en.m' AND monthly>100 group by title;
SELECT title
FROM wiki.pageviews
WHERE (title LIKE 'United%Nations%') AND (code LIKE 'en.m') AND (monthly > 100)
GROUP BY title
┌─title───────────────────────────┐
│ United_Nations │
│ United_Nations_Security_Council │
└─────────────────────────────────┘
2 rows in set. Elapsed: 0.083 sec. Processed 1.61 million rows, 14.62 MB (19.37 million rows/s., 175.34 MB/s.)
Im Falle von MySQL sieht es wie folgt aus:
mysql> SELECT * FROM wiki.pageviews WHERE title LIKE 'Main_Page' AND code LIKE 'de.m' AND hour=6;
+------------+------+------+-----------+---------+--------+
| date | hour | code | title | monthly | hourly |
+------------+------+------+-----------+---------+--------+
| 2018-05-01 | 6 | de.m | Main_Page | 8 | 0 |
| 2018-05-02 | 6 | de.m | Main_Page | 17 | 0 |
+------------+------+------+-----------+---------+--------+
2 rows in set (2 min 45.83 sec)
Also fast 3 Minuten. Die zweite Abfrage ist die gleiche:
mysql> select title from wiki.pageviews where title LIKE 'United%Nations%' AND code LIKE 'en.m' AND monthly>100 group by title;
+---------------------------------+
| title |
+---------------------------------+
| United_Nations |
| United_Nations_Security_Council |
+---------------------------------+
2 rows in set (2 min 40.91 sec)
Natürlich kann man argumentieren, dass Sie weitere Indizes hinzufügen können, um die Abfrageleistung zu verbessern, aber Tatsache ist, dass das Hinzufügen von Indizes erfordert, dass zusätzliche Daten auf der Festplatte gespeichert werden. Indizes benötigen Speicherplatz und stellen auch betriebliche Herausforderungen dar – wenn wir über reale OLAP-Datensätze sprechen, sprechen wir über Terabytes an Daten. Es ist sehr zeitaufwändig und erfordert einen gut definierten und getesteten Prozess, um Schemaänderungen in einer solchen Umgebung auszuführen. Aus diesem Grund können dedizierte spaltenförmige Datenspeicher sehr praktisch sein und enorm dazu beitragen, einen besseren Einblick in alle Analysedaten zu erhalten, die jeder speichert.