Come eseguire Analytics su MySQL?
MySQL è un ottimo database per carichi di lavoro OLTP (Online Transaction Processing). Per alcune aziende era più che sufficiente per molto tempo. I tempi sono cambiati e le esigenze aziendali insieme a loro. Poiché le aziende aspirano a essere più basate sui dati, sempre più dati vengono archiviati per ulteriori analisi; comportamento dei clienti, modelli di prestazioni, traffico di rete, registri, ecc. Indipendentemente dal settore in cui ti trovi, è molto probabile che ci siano dati che desideri conservare e analizzare per capire meglio cosa sta succedendo e come migliorare la tua attività. Sfortunatamente, per archiviare e interrogare la grande quantità di dati, MySQL non è l'opzione migliore. Certo, può farlo e ha strumenti per aiutare a ospitare grandi quantità di dati (ad es. Compressione InnoDB), ma l'utilizzo di una soluzione dedicata per l'elaborazione di analisi online (OLAP) molto probabilmente migliorerà notevolmente la tua capacità di archiviare e interrogare una grande quantità di dati.
Un modo per affrontare questo problema sarà utilizzare un database dedicato per l'esecuzione di analisi. In genere, si desidera utilizzare un datastore a colonne per tali attività:sono più adatti per la gestione di grandi quantità di dati:i dati archiviati nelle colonne in genere sono più facili da comprimere, è anche più facile accedervi in base alla colonna - in genere ne vengono richiesti alcuni dati archiviati in un paio di colonne:la possibilità di recuperare solo quelle colonne invece di leggere tutte le righe e filtrare i dati non necessari rende l'accesso ai dati più veloce.
Come replicare i dati da MySQL a ClickHouse?
Un esempio di datastore colonnare adatto per l'analisi è ClickHouse, un archivio di colonne open source. Una sfida è garantire che i dati in ClickHouse siano sincronizzati con i dati in MySQL. Certo, è sempre possibile configurare una pipeline di dati di qualche tipo ed eseguire il caricamento batch automatizzato in ClickHouse. Ma finché puoi convivere con alcune limitazioni, c'è un modo migliore per impostare la replica quasi in tempo reale da MySQL a ClickHouse. In questo post del blog daremo un'occhiata a come si può fare.
Installazione ClickHouse
Prima di tutto dobbiamo installare ClickHouse. Utilizzeremo la guida introduttiva dal sito Web di 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
Fatto ciò, dobbiamo trovare un mezzo per trasferire i dati da MySQL a ClickHouse. Una delle possibili soluzioni è utilizzare clickhouse-mysql-data-reader di Altinity. Prima di tutto, dobbiamo installare pip3 (python3-pip in Ubuntu) poiché Python nella versione almeno 3.4 è richiesto. Quindi possiamo usare pip3 per installare alcuni dei moduli Python richiesti:
pip3 install mysqlclient
pip3 install mysql-replication
pip3 install clickhouse-driver
Fatto ciò, dobbiamo clonare il repository. Per Centos 7 sono disponibili anche RPM, è anche possibile installarlo utilizzando pip3 (pacchetto clickhouse-mysql) ma abbiamo riscontrato che la versione disponibile tramite pip non contiene gli ultimi aggiornamenti e vogliamo utilizzare il ramo master dal repository git:
git clone https://github.com/Altinity/clickhouse-mysql-data-reader
Quindi, possiamo installarlo usando pip:
pip3 install -e /path/to/clickhouse-mysql-data-reader/
Il prossimo passo sarà creare gli utenti MySQL richiesti da clickhouse-mysql-data-reader per accedere ai dati 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)
Dovresti anche rivedere la tua configurazione MySQL per assicurarti di avere i log binari abilitati, max_binlog_size è impostato su 768M, i binlog sono in formato "riga" e che lo strumento può connettersi a MySQL. Di seguito un estratto dalla documentazione:
[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
Importazione dei dati
Quando tutto è pronto puoi importare i dati in ClickHouse. Idealmente, eseguiresti l'importazione su un host con tabelle bloccate in modo che non si verifichino modifiche durante il processo. È possibile utilizzare uno slave come origine dei dati. Il comando da eseguire sarà:
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
Si collegherà a MySQL sull'host 10.0.0.142 utilizzando le credenziali fornite, copierà la tabella "visualizzazioni di pagina" nello schema "wiki" su un ClickHouse in esecuzione sull'host locale (127.0.0.1). La tabella verrà creata automaticamente e i dati verranno migrati.
Ai fini di questo blog abbiamo importato circa 50 milioni di righe dal set di dati "visualizzazioni di pagina" messo a disposizione da Wikimedia Foundation. Lo schema della tabella in MySQL è:
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)
Lo strumento lo ha tradotto nel seguente schema ClickHouse:
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.
Una volta completata l'importazione, possiamo confrontare i contenuti di MySQL:
mysql> SELECT COUNT(*) FROM wiki.pageviews\G
*************************** 1. row ***************************
COUNT(*): 50986914
1 row in set (24.56 sec)
e 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.)
Anche in una tabella così piccola puoi vedere chiaramente che MySQL ha richiesto più tempo per scansionarlo rispetto a ClickHouse.
Quando si avvia il processo per osservare gli eventi nel registro binario, idealmente si dovrebbero passare le informazioni sul file di registro binario e sulla posizione da cui lo strumento dovrebbe iniziare l'ascolto. Puoi facilmente verificarlo sullo slave dopo che l'importazione iniziale è stata completata.
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
Se non lo superi, inizierà semplicemente ad ascoltare tutto ciò che arriva:
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
Carichiamo altri dati e vediamo come funzionerà per noi. Possiamo vedere che tutto sembra a posto guardando i log di 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
Quello che dobbiamo tenere a mente sono i limiti dello strumento. Il più grande è che supporta solo INSERT. Non c'è supporto per DELETE o UPDATE. Inoltre, non esiste supporto per DDL, pertanto eventuali modifiche allo schema incompatibili eseguite su MySQL interromperanno la replica da MySQL a ClickHouse.
Degno di nota è anche il fatto che gli sviluppatori dello script consigliano di utilizzare pypy per migliorare le prestazioni dello strumento. Esaminiamo alcuni passaggi necessari per configurarlo.
Per prima cosa devi scaricare e decomprimere 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
Successivamente, dobbiamo installare pip e tutti i requisiti per clickhouse-mysql-data-reader, esattamente le stesse cose che abbiamo trattato in precedenza, descrivendo la configurazione regolare:
./bin/pypy -m ensurepip
./bin/pip3 install mysql-replication
./bin/pip3 install clickhouse-driver
./bin/pip3 install mysqlclient
L'ultimo passaggio sarà installare clickhouse-mysql-data-reader dal repository github (supponiamo che sia già stato clonato):
./bin/pip3 install -e /path/to/clickhouse-mysql-data-reader/
È tutto. A partire da ora dovresti eseguire tutti i comandi usando l'ambiente creato per pypy:
./bin/pypy ./bin/clickhouse-mysql
Prove
I dati sono stati caricati, possiamo verificare che tutto sia andato per il meglio confrontando le dimensioni della tabella:
MySQL:
mysql> SELECT COUNT(*) FROM wiki.pageviews\G
*************************** 1. row ***************************
COUNT(*): 204899465
1 row in set (1 min 40.12 sec)
Fare clic su Casa:
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.)
Tutto sembra corretto. Eseguiamo alcune query per vedere come si comporta ClickHouse. Tieni presente che tutta questa configurazione è tutt'altro che di livello produttivo. Abbiamo utilizzato due piccole VM, 4 GB di memoria, una vCPU ciascuna. Pertanto, anche se il set di dati non era grande, è stato sufficiente per vedere la differenza. A causa del piccolo campione, è piuttosto difficile eseguire analisi "reali", ma possiamo comunque lanciare alcune query casuali.
Controlliamo da quali giorni della settimana abbiamo i dati e quante pagine sono state visualizzate al giorno nei nostri dati di esempio:
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.)
Nel caso di MySQL, questa query è simile alla seguente:
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)
Come puoi vedere, MySQL ha impiegato 3,5 minuti per eseguire una scansione completa della tabella.
Ora vediamo quante pagine hanno un valore mensile maggiore di 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.)
Nel caso di MySQL sono di nuovo 3,5 minuti:
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)
Un'altra query, solo una ricerca basata su alcuni valori di stringa:
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.)
Un'altra query, facendo alcune ricerche nella stringa e una condizione basata sulla colonna "mensile":
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 caso di MySQL appare come di seguito:
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)
Quindi, quasi 3 minuti. La seconda query è la stessa:
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)
Naturalmente, si può sostenere che è possibile aggiungere più indici per migliorare le prestazioni delle query, ma il fatto è che l'aggiunta di indici richiederà l'archiviazione di dati aggiuntivi su disco. Gli indici richiedono spazio su disco e pongono anche sfide operative:se parliamo di set di dati OLAP del mondo reale, stiamo parlando di terabyte di dati. Richiede molto tempo e richiede un processo ben definito e testato per eseguire modifiche allo schema su tale ambiente. Questo è il motivo per cui i datastore a colonna dedicati possono essere molto utili e aiutare enormemente a ottenere informazioni migliori su tutti i dati di analisi che tutti archiviano.