Mysql
 sql >> Base de données >  >> RDS >> Mysql

Charges de travail de base de données hybrides OLTP/Analytics :répliquer les données MySQL sur ClickHouse

Comment exécuter Analytics sur MySQL ?

MySQL est une excellente base de données pour les charges de travail de traitement des transactions en ligne (OLTP). Pour certaines entreprises, cela a été plus que suffisant pendant longtemps. Les temps ont changé et les besoins des entreprises avec eux. Alors que les entreprises aspirent à être davantage axées sur les données, de plus en plus de données sont stockées pour une analyse plus approfondie; comportement des clients, modèles de performances, trafic réseau, journaux, etc. Quel que soit votre secteur d'activité, il est très probable qu'il existe des données que vous souhaitez conserver et analyser pour mieux comprendre ce qui se passe et comment améliorer votre entreprise. Malheureusement, pour stocker et interroger une grande quantité de données, MySQL n'est pas la meilleure option. Bien sûr, il peut le faire et il dispose d'outils pour aider à gérer de grandes quantités de données (par exemple, la compression InnoDB), mais l'utilisation d'une solution dédiée pour le traitement analytique en ligne (OLAP) améliorera probablement considérablement votre capacité à stocker et à interroger une grande quantité. de données.

Une façon de résoudre ce problème consiste à utiliser une base de données dédiée pour exécuter des analyses. En règle générale, vous souhaitez utiliser un magasin de données en colonnes pour de telles tâches - ils sont plus adaptés au traitement de grandes quantités de données :les données stockées dans des colonnes sont généralement plus faciles à compresser, il est également plus facile d'y accéder par colonne - généralement, vous en demandez les données stockées dans quelques colonnes - la possibilité de récupérer uniquement ces colonnes au lieu de lire toutes les lignes et de filtrer les données inutiles accélère l'accès aux données.

Comment répliquer les données de MySQL vers ClickHouse ?

Un exemple de magasin de données en colonnes adapté à l'analyse est ClickHouse, un magasin de colonnes open source. L'un des défis consiste à s'assurer que les données de ClickHouse sont synchronisées avec les données de MySQL. Bien sûr, il est toujours possible de configurer un pipeline de données quelconque et d'effectuer un chargement par lots automatisé dans ClickHouse. Mais tant que vous pouvez vivre avec certaines limitations, il existe un meilleur moyen de configurer une réplication presque en temps réel de MySQL vers ClickHouse. Dans cet article de blog, nous verrons comment cela peut être fait.

Installation de ClickHouse

Tout d'abord, nous devons installer ClickHouse. Nous utiliserons le démarrage rapide du site Web de ClickHouse.

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

Une fois cela fait, nous devons trouver un moyen de transférer les données de MySQL vers ClickHouse. L'une des solutions possibles consiste à utiliser le lecteur clickhouse-mysql-data-reader d'Altinity. Tout d'abord, nous devons installer pip3 (python3-pip dans Ubuntu) car Python en version au moins 3.4 est requis. Ensuite, nous pouvons utiliser pip3 pour installer certains des modules Python requis :

pip3 install mysqlclient
pip3 install mysql-replication
pip3 install clickhouse-driver

Une fois cela fait, nous devons cloner le référentiel. Pour Centos 7, des RPM sont également disponibles, il est également possible de l'installer à l'aide de pip3 (package clickhouse-mysql) mais nous avons constaté que la version disponible via pip ne contient pas les dernières mises à jour et nous souhaitons utiliser la branche master du référentiel git :

git clone https://github.com/Altinity/clickhouse-mysql-data-reader

Ensuite, nous pouvons l'installer en utilisant pip :

pip3 install -e /path/to/clickhouse-mysql-data-reader/

La prochaine étape consistera à créer les utilisateurs MySQL requis par clickhouse-mysql-data-reader pour accéder aux données MySQL :

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)

Vous devez également revoir votre configuration MySQL pour vous assurer que les journaux binaires sont activés, que max_binlog_size est défini sur 768 M, que les journaux binaires sont au format "ligne" et que l'outil peut se connecter à MySQL. Ci-dessous un extrait de la documentation :

[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

Importer les données

Lorsque tout est prêt, vous pouvez importer les données dans ClickHouse. Idéalement, vous exécuteriez l'importation sur un hôte avec des tables verrouillées afin qu'aucun changement ne se produise pendant le processus. Vous pouvez utiliser un esclave comme source des données. La commande à exécuter sera :

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

Il se connectera à MySQL sur l'hôte 10.0.0.142 en utilisant les informations d'identification fournies, il copiera la table "pages vues" dans le schéma "wiki" vers un ClickHouse fonctionnant sur l'hôte local (127.0.0.1). Le tableau sera créé automatiquement et les données seront migrées.

Pour les besoins de ce blog, nous avons importé environ 50 millions de lignes à partir de l'ensemble de données "pages vues" mis à disposition par la Wikimedia Foundation. Le schéma de table dans MySQL est :

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)

L'outil a traduit cela dans le schéma ClickHouse suivant :

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.

Une fois l'import effectué, nous pouvons comparer le contenu de MySQL :

mysql> SELECT COUNT(*) FROM wiki.pageviews\G
*************************** 1. row ***************************
COUNT(*): 50986914
1 row in set (24.56 sec)

et dans 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.)

Même dans un si petit tableau, vous pouvez clairement voir que MySQL a mis plus de temps à le parcourir que ClickHouse.

Lors du démarrage du processus de surveillance des événements du journal binaire, idéalement, vous devez transmettre les informations sur le fichier journal binaire et la position à partir de laquelle l'outil doit commencer à écouter. Vous pouvez facilement vérifier cela sur l'esclave une fois l'importation initiale terminée.

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

Si vous ne le réussissez pas, il commencera simplement à écouter tout ce qui arrivera :

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

Chargeons quelques données supplémentaires et voyons comment cela fonctionnera pour nous. On peut voir que tout semble ok en regardant les logs de clickhouse-mysql-data-reader :

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

Ce que nous devons garder à l'esprit, ce sont les limites de l'outil. Le plus important est qu'il ne prend en charge que les INSERT. Il n'y a pas de support pour DELETE ou UPDATE. Il n'y a pas non plus de support pour les DDL, donc toute modification de schéma incompatible exécutée sur MySQL interrompra la réplication MySQL vers ClickHouse.

Il convient également de noter le fait que les développeurs du script recommandent d'utiliser pypy pour améliorer les performances de l'outil. Passons en revue quelques étapes nécessaires pour configurer cela.

Dans un premier temps, vous devez télécharger et décompresser pypy :

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

Ensuite, nous devons installer pip et toutes les exigences pour le clickhouse-mysql-data-reader - exactement les mêmes choses que nous avons couvertes plus tôt, tout en décrivant l'installation normale :

./bin/pypy -m ensurepip
./bin/pip3 install mysql-replication
./bin/pip3 install clickhouse-driver
./bin/pip3 install mysqlclient

La dernière étape consistera à installer clickhouse-mysql-data-reader à partir du référentiel github (nous supposons qu'il a déjà été cloné) :

./bin/pip3 install -e /path/to/clickhouse-mysql-data-reader/

C'est tout. À partir de maintenant, vous devez exécuter toutes les commandes en utilisant l'environnement créé pour pypy :

./bin/pypy ./bin/clickhouse-mysql

Tests

Les données ont été chargées, nous pouvons vérifier que tout s'est bien passé en comparant la taille du tableau :

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.)

Tout semble correct. Exécutons quelques requêtes pour voir comment se comporte ClickHouse. Veuillez garder à l'esprit que toute cette configuration est loin d'être de qualité production. Nous avons utilisé deux petites machines virtuelles, 4 Go de mémoire, un vCPU chacune. Par conséquent, même si l'ensemble de données n'était pas volumineux, il suffisait de voir la différence. En raison du petit échantillon, il est assez difficile de faire de "vraies" analyses, mais nous pouvons toujours lancer des requêtes aléatoires.

Vérifions de quels jours de la semaine nous disposons de données et combien de pages ont été consultées par jour dans nos exemples de données :

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.)

Dans le cas de MySQL, cette requête ressemble à ceci :

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)

Comme vous pouvez le voir, MySQL a eu besoin de 3,5 minutes pour effectuer une analyse complète de la table.

Voyons maintenant combien de pages ont une valeur mensuelle supérieure à 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.)

Dans le cas de MySQL, c'est encore 3,5 minutes :

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)

Une autre requête, juste une recherche basée sur certaines valeurs de chaîne :

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.)

Une autre requête, faisant quelques recherches dans la chaîne et une condition basée sur la colonne "mensuel" :

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.)

Dans le cas de MySQL, cela ressemble à ceci :

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)

Donc, presque 3 minutes. La deuxième requête est la même :

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)

Bien sûr, on peut affirmer que vous pouvez ajouter plus d'index pour améliorer les performances des requêtes, mais le fait est que l'ajout d'index nécessitera le stockage de données supplémentaires sur le disque. Les index nécessitent de l'espace disque et posent également des problèmes opérationnels - si nous parlons d'ensembles de données OLAP du monde réel, nous parlons de téraoctets de données. Cela prend beaucoup de temps et nécessite un processus bien défini et testé pour exécuter des modifications de schéma sur un tel environnement. C'est pourquoi les magasins de données en colonnes dédiés peuvent être très pratiques et aider énormément à mieux comprendre toutes les données d'analyse que tout le monde stocke.