sql >> Base de Datos >  >> RDS >> Mysql

Cargas de trabajo híbridas de bases de datos OLTP/Analytics:replicación de datos MySQL en ClickHouse

¿Cómo ejecutar Analytics en MySQL?

MySQL es una excelente base de datos para cargas de trabajo de procesamiento de transacciones en línea (OLTP). Para algunas empresas, solía ser más que suficiente durante mucho tiempo. Los tiempos han cambiado y los requisitos comerciales junto con ellos. A medida que las empresas aspiran a basarse más en los datos, se almacenan cada vez más datos para su posterior análisis; comportamiento del cliente, patrones de rendimiento, tráfico de red, registros, etc. No importa en qué industria se encuentre, es muy probable que haya datos que desee conservar y analizar para comprender mejor lo que está sucediendo y cómo mejorar su negocio. Desafortunadamente, para almacenar y consultar la gran cantidad de datos, MySQL no es la mejor opción. Claro, puede hacerlo y tiene herramientas para ayudar a acomodar grandes cantidades de datos (por ejemplo, compresión InnoDB), pero el uso de una solución dedicada para el Procesamiento de análisis en línea (OLAP) probablemente mejorará en gran medida su capacidad para almacenar y consultar una gran cantidad. de datos.

Una forma de abordar este problema será utilizar una base de datos dedicada para ejecutar análisis. Por lo general, desea usar un almacén de datos en columnas para tales tareas; son más adecuados para manejar grandes cantidades de datos:los datos almacenados en columnas generalmente son más fáciles de comprimir, también es más fácil acceder a ellos por columna; por lo general, solicita algunos datos almacenados en un par de columnas:la capacidad de recuperar solo esas columnas en lugar de leer todas las filas y filtrar los datos innecesarios hace que el acceso a los datos sea más rápido.

¿Cómo replicar datos de MySQL a ClickHouse?

Un ejemplo de almacén de datos en columnas adecuado para análisis es ClickHouse, un almacén de columnas de código abierto. Un desafío es garantizar que los datos de ClickHouse estén sincronizados con los datos de MySQL. Claro, siempre es posible configurar una canalización de datos de algún tipo y realizar una carga por lotes automatizada en ClickHouse. Pero siempre que pueda vivir con algunas limitaciones, hay una mejor manera de configurar la replicación casi en tiempo real desde MySQL en ClickHouse. En esta publicación de blog, veremos cómo se puede hacer.

Instalación de ClickHouse

En primer lugar, debemos instalar ClickHouse. Usaremos el inicio rápido del sitio 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

Una vez hecho esto, necesitamos encontrar un medio para transferir los datos de MySQL a ClickHouse. Una de las posibles soluciones es usar clickhouse-mysql-data-reader de Altinity. En primer lugar, tenemos que instalar pip3 (python3-pip en Ubuntu) ya que se requiere Python en la versión al menos 3.4. Luego podemos usar pip3 para instalar algunos de los módulos de Python necesarios:

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

Una vez hecho esto, tenemos que clonar el repositorio. Para Centos 7, los RPM también están disponibles, también es posible instalarlo usando pip3 (paquete clickhouse-mysql) pero encontramos que la versión disponible a través de pip no contiene las últimas actualizaciones y queremos usar la rama maestra del repositorio git:

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

Luego, podemos instalarlo usando pip:

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

El siguiente paso será crear los usuarios de MySQL requeridos por clickhouse-mysql-data-reader para acceder a los datos de 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)

También debe revisar su configuración de MySQL para asegurarse de que tiene habilitados los registros binarios, max_binlog_size está establecido en 768M, los binlogs están en formato de "fila" y que la herramienta puede conectarse a MySQL. A continuación se muestra un extracto de la documentación:

[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

Importar los datos

Cuando todo esté listo, puede importar los datos a ClickHouse. Idealmente, ejecutaría la importación en un host con tablas bloqueadas para que no ocurra ningún cambio durante el proceso. Puede utilizar un esclavo como fuente de datos. El comando a ejecutar será:

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

Se conectará a MySQL en el host 10.0.0.142 usando las credenciales proporcionadas, copiará la tabla "páginas vistas" en el esquema "wiki" a un ClickHouse que se ejecuta en el host local (127.0.0.1). La tabla se creará automáticamente y los datos se migrarán.

A los fines de este blog, importamos aproximadamente 50 millones de filas del conjunto de datos de "páginas vistas" que puso a disposición la Fundación Wikimedia. El esquema de la tabla en MySQL es:

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)

La herramienta tradujo esto al siguiente esquema de 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 vez realizada la importación, podemos comparar el contenido de MySQL:

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

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

Incluso en una tabla tan pequeña, puede ver claramente que MySQL requirió más tiempo para escanearla que ClickHouse.

Al iniciar el proceso para ver el registro binario en busca de eventos, idealmente pasaría la información sobre el archivo de registro binario y la posición desde donde la herramienta debería comenzar a escuchar. Puede verificarlo fácilmente en el esclavo después de que se complete la importación inicial.

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 no lo aprueba, simplemente comenzará a escuchar todo lo que entre:

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

Carguemos algunos datos más y veamos cómo funcionará para nosotros. Podemos ver que todo parece estar bien mirando los registros 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

Lo que tenemos que tener en cuenta son las limitaciones de la herramienta. El más grande es que solo admite INSERT. No hay soporte para ELIMINAR o ACTUALIZAR. Tampoco hay soporte para DDL, por lo tanto, cualquier cambio de esquema incompatible ejecutado en MySQL interrumpirá la replicación de MySQL a ClickHouse.

También vale la pena señalar el hecho de que los desarrolladores del script recomiendan usar pypy para mejorar el rendimiento de la herramienta. Veamos algunos pasos necesarios para configurar esto.

Primero tienes que descargar y descomprimir 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

A continuación, tenemos que instalar pip y todos los requisitos para clickhouse-mysql-data-reader, exactamente lo mismo que cubrimos anteriormente, mientras describíamos la configuración normal:

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

El último paso será instalar clickhouse-mysql-data-reader desde el repositorio de github (asumimos que ya ha sido clonado):

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

Eso es todo. A partir de ahora, debe ejecutar todos los comandos utilizando el entorno creado para pypy:

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

Pruebas

Se han cargado los datos, podemos verificar que todo salió bien comparando el tamaño de la tabla:

MySQL:

mysql> SELECT COUNT(*) FROM wiki.pageviews\G
*************************** 1. row ***************************
COUNT(*): 204899465
1 row in set (1 min 40.12 sec)

Haga clic en 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.)

Todo parece correcto. Ejecutemos algunas consultas para ver cómo se comporta ClickHouse. Tenga en cuenta que toda esta configuración está lejos del nivel de producción. Utilizamos dos máquinas virtuales pequeñas, 4 GB de memoria, una vCPU cada una. Por lo tanto, aunque el conjunto de datos no era grande, fue suficiente para ver la diferencia. Debido a la muestra pequeña, es bastante difícil hacer análisis "reales", pero aún podemos generar algunas consultas aleatorias.

Veamos de qué días de la semana tenemos datos y cuántas páginas se han visto por día en nuestros datos de muestra:

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

En el caso de MySQL, esta consulta se ve a continuación:

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)

Como puede ver, MySQL necesitó 3,5 minutos para realizar un análisis completo de la tabla.

Ahora, veamos cuántas páginas tienen un valor mensual superior a 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.)

En el caso de MySQL, nuevamente son 3,5 minutos:

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)

Otra consulta, solo una búsqueda basada en algunos valores de cadena:

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

Otra consulta, haciendo algunas búsquedas en la cadena y una condición basada en la columna 'mensual':

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

En el caso de MySQL, se ve a continuación:

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)

Entonces, casi 3 minutos. La segunda consulta es la misma:

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)

Por supuesto, se puede argumentar que puede agregar más índices para mejorar el rendimiento de las consultas, pero el hecho es que agregar índices requerirá que se almacenen datos adicionales en el disco. Los índices requieren espacio en disco y también plantean desafíos operativos:si hablamos de conjuntos de datos OLAP del mundo real, estamos hablando de terabytes de datos. Lleva mucho tiempo y requiere un proceso bien definido y probado para ejecutar cambios de esquema en dicho entorno. Esta es la razón por la cual los almacenes de datos en columnas dedicados pueden ser muy útiles y ayudar enormemente a obtener una mejor perspectiva de todos los datos analíticos que todos almacenan.