Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Cargas de trabalho híbridas de banco de dados OLTP/Analytics:replicando dados MySQL para ClickHouse

Como executar o Analytics no MySQL?


MySQL é um ótimo banco de dados para cargas de trabalho de processamento de transações online (OLTP). Para algumas empresas, costumava ser mais do que suficiente por muito tempo. Os tempos mudaram e os requisitos de negócios junto com eles. À medida que as empresas aspiram a ser mais orientadas por dados, mais e mais dados são armazenados para análise posterior; comportamento do cliente, padrões de desempenho, tráfego de rede, logs, etc. Não importa em que setor você esteja, é muito provável que haja dados que você deseja manter e analisar para entender melhor o que está acontecendo e como melhorar seus negócios. Infelizmente, para armazenar e consultar uma grande quantidade de dados, o MySQL não é a melhor opção. Claro, ele pode fazer isso e tem ferramentas para ajudar a acomodar grandes quantidades de dados (por exemplo, compactação InnoDB), mas usar uma solução dedicada para processamento de análise online (OLAP) provavelmente melhorará muito sua capacidade de armazenar e consultar uma grande quantidade De dados.

Uma maneira de lidar com esse problema será usar um banco de dados dedicado para executar análises. Normalmente, você deseja usar um armazenamento de dados colunar para essas tarefas - eles são mais adequados para lidar com grandes quantidades de dados:os dados armazenados em colunas geralmente são mais fáceis de compactar, também é mais fácil acessar por coluna - normalmente você solicita alguns dados armazenados em algumas colunas - a capacidade de recuperar apenas essas colunas em vez de ler todas as linhas e filtrar dados desnecessários torna os dados acessados ​​mais rapidamente.

Como replicar dados do MySQL para o ClickHouse?


Um exemplo de armazenamento de dados colunar adequado para análise é o ClickHouse, um armazenamento de colunas de código aberto. Um desafio é garantir que os dados no ClickHouse estejam sincronizados com os dados no MySQL. Claro, sempre é possível configurar um pipeline de dados de algum tipo e realizar carregamento automatizado em lote no ClickHouse. Mas contanto que você possa viver com algumas limitações, há uma maneira melhor de configurar a replicação quase em tempo real do MySQL para o ClickHouse. Neste post vamos dar uma olhada em como isso pode ser feito.

Instalação do ClickHouse


Primeiro de tudo, precisamos instalar o ClickHouse. Usaremos o início rápido do site da 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

Feito isso, precisamos encontrar um meio de transferir os dados do MySQL para o ClickHouse. Uma das soluções possíveis é usar o clickhouse-mysql-data-reader da Altinity. Antes de tudo, temos que instalar o pip3 (python3-pip no Ubuntu), pois é necessário o Python na versão 3.4. Então podemos usar pip3 para instalar alguns dos módulos Python necessários:
pip3 install mysqlclient
pip3 install mysql-replication
pip3 install clickhouse-driver

Feito isso, temos que clonar o repositório. Para Centos 7, RPMs também estão disponíveis, também é possível instalá-lo usando pip3 (pacote clickhouse-mysql) mas descobrimos que a versão disponível através do pip não contém as atualizações mais recentes e queremos usar o branch master do repositório git:
git clone https://github.com/Altinity/clickhouse-mysql-data-reader

Então, podemos instalá-lo usando pip:
pip3 install -e /path/to/clickhouse-mysql-data-reader/

O próximo passo será criar usuários MySQL requeridos pelo clickhouse-mysql-data-reader para acessar os dados 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)

Você também deve revisar sua configuração do MySQL para garantir que os logs binários estejam ativados, max_binlog_size esteja definido como 768M, os logs binários estejam no formato 'linha' e que a ferramenta possa se conectar ao MySQL. Segue abaixo um trecho da documentação:
[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

Importando os dados


Quando tudo estiver pronto, você poderá importar os dados para o ClickHouse. Idealmente, você executaria a importação em um host com tabelas bloqueadas para que nenhuma alteração ocorra durante o processo. Você pode usar um escravo como a fonte dos dados. O comando a ser executado 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

Ele se conectará ao MySQL no host 10.0.0.142 usando as credenciais fornecidas, copiará a tabela 'pageviews' no esquema 'wiki' para um ClickHouse em execução no host local (127.0.0.1). A tabela será criada automaticamente e os dados serão migrados.

Para este blog, importamos cerca de 50 milhões de linhas do conjunto de dados “pageviews” disponibilizado pela Wikimedia Foundation. O esquema da tabela no 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)

A ferramenta traduziu isso no seguinte esquema 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.

Feita a importação, podemos comparar o conteúdo do MySQL:
mysql> SELECT COUNT(*) FROM wiki.pageviews\G
*************************** 1. row ***************************
COUNT(*): 50986914
1 row in set (24.56 sec)

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

Mesmo em uma tabela tão pequena, você pode ver claramente que o MySQL exigiu mais tempo para escaneá-la do que o ClickHouse.

Ao iniciar o processo para observar o log binário em busca de eventos, o ideal é passar as informações sobre o arquivo de log binário e a posição de onde a ferramenta deve começar a escutar. Você pode verificar isso facilmente no escravo após a conclusão da importação 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

Se você não passar, ele começará a ouvir qualquer coisa que vier:
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

Vamos carregar mais alguns dados e ver como isso funcionará para nós. Podemos ver que tudo parece bem olhando os logs do 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

O que devemos ter em mente são as limitações da ferramenta. O maior deles é que ele suporta apenas INSERTs. Não há suporte para DELETE ou UPDATE. Também não há suporte para DDLs, portanto, qualquer alteração de esquema incompatível executada no MySQL interromperá a replicação do MySQL para o ClickHouse.

Também digno de nota é o fato de que os desenvolvedores do script recomendam o uso do pypy para melhorar o desempenho da ferramenta. Vamos passar por algumas etapas necessárias para configurar isso.

Primeiro você tem que baixar e descompactar o 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

Em seguida, temos que instalar o pip e todos os requisitos para o clickhouse-mysql-data-reader - exatamente as mesmas coisas que abordamos anteriormente, enquanto descrevemos a configuração regular:
./bin/pypy -m ensurepip
./bin/pip3 install mysql-replication
./bin/pip3 install clickhouse-driver
./bin/pip3 install mysqlclient

O último passo será instalar o clickhouse-mysql-data-reader do repositório github (assumimos que já foi clonado):
./bin/pip3 install -e /path/to/clickhouse-mysql-data-reader/

Isso é tudo. A partir de agora você deve executar todos os comandos usando o ambiente criado para pypy:
./bin/pypy ./bin/clickhouse-mysql

Testes


Os dados foram carregados, podemos verificar que tudo correu bem comparando o tamanho da tabela:

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

Tudo parece correto. Vamos executar algumas consultas para ver como o ClickHouse se comporta. Por favor, tenha em mente que toda esta configuração está longe do nível de produção. Usamos duas VMs pequenas, 4 GB de memória, uma vCPU cada. Portanto, mesmo que o conjunto de dados não fosse grande, foi o suficiente para ver a diferença. Devido à pequena amostra, é muito difícil fazer análises “reais”, mas ainda podemos lançar algumas consultas aleatórias.

Vamos verificar de quais dias da semana temos dados e quantas páginas foram visualizadas por dia em nossos dados de exemplo:
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.)

No caso do MySQL esta consulta se parece com abaixo:
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 você pode ver, o MySQL precisou de 3,5 minutos para fazer uma varredura completa da tabela.

Agora, vamos ver quantas páginas têm valor mensal maior que 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.)

No caso do MySQL, são novamente 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)

Outra consulta, apenas uma pesquisa baseada em alguns valores de string:
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.)

Outra consulta, fazendo algumas pesquisas na string e uma condição baseada na coluna 'mensal':
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.)

No caso do MySQL, fica assim:
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)

Então, quase 3 minutos. A segunda consulta é a mesma:
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)

Claro, pode-se argumentar que você pode adicionar mais índices para melhorar o desempenho da consulta, mas o fato é que adicionar índices exigirá que dados adicionais sejam armazenados em disco. Os índices exigem espaço em disco e também apresentam desafios operacionais - se estamos falando de conjuntos de dados OLAP do mundo real, estamos falando de terabytes de dados. Leva muito tempo e requer um processo bem definido e testado para executar as alterações de esquema nesse ambiente. É por isso que os datastores colunares dedicados podem ser muito úteis e ajudar tremendamente a obter uma melhor visão de todos os dados analíticos que todos armazenam.