Hoe Analytics op MySQL draaien?
MySQL is een geweldige database voor online transactieverwerking (OLTP)-workloads. Voor sommige bedrijven was het lange tijd meer dan genoeg. De tijden zijn veranderd en de zakelijke vereisten samen met hen. Omdat bedrijven ernaar streven meer gegevensgestuurd te zijn, worden steeds meer gegevens opgeslagen voor verdere analyse; klantgedrag, prestatiepatronen, netwerkverkeer, logboeken, enz. Het maakt niet uit in welke branche u zich bevindt, het is zeer waarschijnlijk dat er gegevens zijn die u wilt bewaren en analyseren om beter te begrijpen wat er gaande is en hoe u uw bedrijf kunt verbeteren. Helaas is MySQL niet de beste optie voor het opslaan en opvragen van de grote hoeveelheid gegevens. Natuurlijk, het kan het en het heeft tools om grote hoeveelheden gegevens te verwerken (bijv. InnoDB-compressie), maar het gebruik van een speciale oplossing voor Online Analytics Processing (OLAP) zal hoogstwaarschijnlijk uw vermogen om een grote hoeveelheid op te slaan en op te vragen aanzienlijk verbeteren van gegevens.
Een manier om dit probleem aan te pakken, is door een speciale database te gebruiken voor het uitvoeren van analyses. Meestal wilt u voor dergelijke taken een kolomvormige gegevensopslag gebruiken - ze zijn meer geschikt voor het verwerken van grote hoeveelheden gegevens:gegevens die in kolommen zijn opgeslagen, zijn doorgaans gemakkelijker te comprimeren, ze zijn ook gemakkelijker toegankelijk per kolom - meestal vraagt u om wat gegevens opgeslagen in een aantal kolommen - de mogelijkheid om alleen die kolommen op te halen in plaats van alle rijen te lezen en onnodige gegevens eruit te filteren, maakt de gegevens sneller toegankelijk.
Hoe gegevens van MySQL naar ClickHouse te repliceren?
Een voorbeeld van de columnar datastore die geschikt is voor analytics is ClickHouse, een open source column store. Een uitdaging is om ervoor te zorgen dat de gegevens in ClickHouse synchroon lopen met de gegevens in MySQL. Natuurlijk is het altijd mogelijk om een of andere datapijplijn op te zetten en automatisch batchgewijs in ClickHouse te laden. Maar zolang u met enkele beperkingen kunt leven, is er een betere manier om bijna realtime replicatie van MySQL naar ClickHouse in te stellen. In deze blogpost bekijken we hoe het kan.
ClickHouse-installatie
Allereerst moeten we ClickHouse installeren. We gebruiken de quickstart van de 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
Zodra dit is gebeurd, moeten we een manier vinden om de gegevens van MySQL naar ClickHouse over te zetten. Een van de mogelijke oplossingen is om de clickhouse-mysql-data-reader van Altinity te gebruiken. Allereerst moeten we pip3 (python3-pip in Ubuntu) installeren omdat Python in versie 3.4 minimaal vereist is. Dan kunnen we pip3 gebruiken om enkele van de vereiste Python-modules te installeren:
pip3 install mysqlclient
pip3 install mysql-replication
pip3 install clickhouse-driver
Zodra dit is gebeurd, moeten we de repository klonen. Voor Centos 7 zijn ook RPM's beschikbaar, het is ook mogelijk om het te installeren met pip3 (clickhouse-mysql-pakket) maar we ontdekten dat de versie die beschikbaar is via pip niet de laatste updates bevat en we willen master branch van git repository gebruiken:
git clone https://github.com/Altinity/clickhouse-mysql-data-reader
Dan kunnen we het installeren met pip:
pip3 install -e /path/to/clickhouse-mysql-data-reader/
De volgende stap is het aanmaken van MySQL-gebruikers die door clickhouse-mysql-data-reader nodig zijn om toegang te krijgen tot MySQL-gegevens:
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)
U moet ook uw MySQL-configuratie controleren om er zeker van te zijn dat binaire logboeken zijn ingeschakeld, max_binlog_size is ingesteld op 768M, binlogs in 'rij'-indeling zijn en dat de tool verbinding kan maken met de MySQL. Hieronder is een fragment uit de documentatie:
[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
De gegevens importeren
Als alles klaar is kun je de gegevens importeren in ClickHouse. Idealiter zou u de import uitvoeren op een host met vergrendelde tabellen, zodat er tijdens het proces geen verandering plaatsvindt. U kunt een slave gebruiken als de bron van de gegevens. Het uit te voeren commando is:
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
Het zal verbinding maken met MySQL op host 10.0.0.142 met behulp van gegeven referenties, het zal de tabel 'paginaweergaven' in het schema 'wiki' kopiëren naar een ClickHouse die op de lokale host wordt uitgevoerd (127.0.0.1). Tabel wordt automatisch gemaakt en gegevens worden gemigreerd.
Voor deze blog hebben we ongeveer 50 miljoen rijen geïmporteerd uit de dataset "pageviews" die beschikbaar is gesteld door Wikimedia Foundation. Het tabelschema in MySQL is:
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)
De tool vertaalde dit in het volgende 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.
Zodra het importeren is voltooid, kunnen we de inhoud van MySQL vergelijken:
mysql> SELECT COUNT(*) FROM wiki.pageviews\G
*************************** 1. row ***************************
COUNT(*): 50986914
1 row in set (24.56 sec)
en 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.)
Zelfs in zo'n kleine tabel kun je duidelijk zien dat MySQL meer tijd nodig had om er doorheen te bladeren dan ClickHouse.
Bij het starten van het proces om het binaire logboek voor gebeurtenissen te bekijken, zou u idealiter de informatie over het binaire logboekbestand en de positie van waar de tool zou moeten beginnen met luisteren, doorgeven. U kunt dat eenvoudig controleren op de slave nadat de eerste import is voltooid.
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
Als je er niet langs komt, begint hij gewoon te luisteren naar alles wat binnenkomt:
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
Laten we wat meer gegevens laden en kijken hoe het voor ons zal werken. We kunnen zien dat alles in orde lijkt door naar de logs van clickhouse-mysql-data-reader te kijken:
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
Waar we rekening mee moeten houden zijn de beperkingen van de tool. De grootste is dat het alleen INSERT's ondersteunt. Er is geen ondersteuning voor DELETE of UPDATE. Er is ook geen ondersteuning voor DDL's, daarom zullen incompatibele schemawijzigingen die op MySQL worden uitgevoerd, de MySQL naar ClickHouse-replicatie breken.
Ook vermeldenswaard is het feit dat de ontwikkelaars van het script aanbevelen om pypy te gebruiken om de prestaties van de tool te verbeteren. Laten we enkele stappen doorlopen die nodig zijn om dit in te stellen.
Eerst moet je pypy downloaden en decomprimeren:
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
Vervolgens moeten we pip en alle vereisten voor de clickhouse-mysql-data-reader installeren - precies dezelfde dingen die we eerder hebben behandeld, terwijl we de normale installatie beschreven:
./bin/pypy -m ensurepip
./bin/pip3 install mysql-replication
./bin/pip3 install clickhouse-driver
./bin/pip3 install mysqlclient
De laatste stap is het installeren van clickhouse-mysql-data-reader vanuit de github-repository (we nemen aan dat het al is gekloond):
./bin/pip3 install -e /path/to/clickhouse-mysql-data-reader/
Dat is alles. Vanaf nu zou je alle commando's moeten uitvoeren met behulp van de omgeving die voor pypy is gemaakt:
./bin/pypy ./bin/clickhouse-mysql
Testen
De gegevens zijn geladen, we kunnen controleren of alles soepel verliep door de grootte van de tabel te vergelijken:
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 ziet er correct uit. Laten we enkele zoekopdrachten uitvoeren om te zien hoe ClickHouse zich gedraagt. Houd er rekening mee dat al deze instellingen verre van productiekwaliteit zijn. We gebruikten twee kleine VM's, 4 GB geheugen, elk één vCPU. Dus hoewel de dataset niet groot was, was het genoeg om het verschil te zien. Vanwege de kleine steekproef is het vrij moeilijk om "echte" analyses uit te voeren, maar we kunnen nog steeds enkele willekeurige zoekopdrachten uitvoeren.
Laten we eens kijken van welke dagen van de week we gegevens hebben en hoeveel pagina's er per dag zijn bekeken in onze voorbeeldgegevens:
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.)
In het geval van MySQL ziet deze query er als volgt uit:
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)
Zoals je kunt zien, had MySQL 3,5 minuut nodig om een volledige tafelscan uit te voeren.
Laten we nu eens kijken hoeveel pagina's een maandelijkse waarde hebben die groter is dan 100:
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.)
In het geval van MySQL is het weer 3,5 minuut:
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)
Nog een zoekopdracht, alleen een zoekopdracht op basis van enkele tekenreekswaarden:
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.)
Nog een zoekopdracht, zoekacties in de string en een voorwaarde op basis van de kolom 'maandelijks':
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.)
In het geval van MySQL ziet het er als volgt uit:
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)
Dus bijna 3 minuten. De tweede vraag is hetzelfde:
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)
Natuurlijk kun je stellen dat je meer indexen kunt toevoegen om de queryprestaties te verbeteren, maar het feit is dat voor het toevoegen van indexen extra gegevens op schijf moeten worden opgeslagen. Indexen hebben schijfruimte nodig en ze brengen ook operationele uitdagingen met zich mee - als we het hebben over echte OLAP-gegevenssets, hebben we het over terabytes aan gegevens. Het kost veel tijd en vereist een goed gedefinieerd en getest proces om schemawijzigingen in een dergelijke omgeving uit te voeren. Daarom kunnen speciale zuilvormige datastores erg handig zijn en enorm helpen om een beter inzicht te krijgen in alle analytische data die iedereen opslaat.