MySQLでAnalyticsを実行する方法は?
MySQLは、オンライントランザクション処理(OLTP)ワークロードに最適なデータベースです。一部の企業にとっては、長い間それで十分でした。時代は変わり、それに伴うビジネス要件も変わりました。企業がより多くのデータ駆動型を目指すにつれて、さらなる分析のためにますます多くのデータが保存されます。顧客の行動、パフォーマンスパターン、ネットワークトラフィック、ログなど。どの業界にいても、何が起こっているのか、ビジネスを改善する方法をよりよく理解するために保持および分析したいデータがある可能性が非常に高くなります。残念ながら、大量のデータを保存およびクエリする場合、MySQLは最適なオプションではありません。確かに、それは可能であり、大量のデータに対応するのに役立つツール(InnoDB圧縮など)がありますが、オンライン分析処理(OLAP)専用のソリューションを使用すると、大量のデータを保存およびクエリする能力が大幅に向上する可能性がありますデータの。
この問題に取り組む1つの方法は、分析を実行するための専用データベースを使用することです。通常、このようなタスクには列型データストアを使用する必要があります。これらは大量のデータを処理するのに適しています。列に格納されたデータは通常、圧縮が簡単で、列ごとにアクセスするのも簡単です。通常、いくつかのデータストアを要求します。いくつかの列に保存されたデータ-すべての行を読み取る代わりにそれらの列だけを取得し、不要なデータを除外する機能により、データへのアクセスが高速化されます。
MySQLからClickHouseにデータを複製する方法
分析に適した列型データストアの例は、オープンソースの列ストアであるClickHouseです。 1つの課題は、ClickHouseのデータがMySQLのデータと同期していることを確認することです。もちろん、ある種のデータパイプラインをセットアップし、ClickHouseへの自動バッチロードを実行することは常に可能です。ただし、いくつかの制限がある限り、MySQLからClickHouseへのほぼリアルタイムのレプリケーションを設定するためのより良い方法があります。このブログ投稿では、それがどのように行われるかを見ていきます。
ClickHouseのインストール
まず、ClickHouseをインストールする必要があります。 ClickHouseWebサイトからのクイックスタートを使用します。
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
これが完了したら、MySQLからClickHouseにデータを転送する手段を見つける必要があります。考えられる解決策の1つは、Altinityのclickhouse-mysql-data-readerを使用することです。まず、バージョン3.4以上のPythonが必要なため、pip3(Ubuntuではpython3-pip)をインストールする必要があります。次に、pip3を使用して、必要なPythonモジュールのいくつかをインストールできます。
pip3 install mysqlclient
pip3 install mysql-replication
pip3 install clickhouse-driver
これが完了したら、リポジトリのクローンを作成する必要があります。 Centos 7の場合、RPMも利用できます。pip3(clickhouse-mysqlパッケージ)を使用してインストールすることもできますが、pipで利用できるバージョンには最新の更新が含まれていないため、gitリポジトリのマスターブランチを使用します。
git clone https://github.com/Altinity/clickhouse-mysql-data-reader
次に、pipを使用してインストールできます:
pip3 install -e /path/to/clickhouse-mysql-data-reader/
次のステップは、clickhouse-mysql-data-readerがMySQLデータにアクセスするために必要な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)
また、MySQL構成を確認して、バイナリログが有効になっていること、max_binlog_sizeが768Mに設定されていること、binlogが「行」形式であり、ツールがMySQLに接続できることを確認する必要があります。以下はドキュメントからの抜粋です:
[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
データのインポート
すべての準備ができたら、データをClickHouseにインポートできます。理想的には、処理中に変更が発生しないように、テーブルがロックされたホストでインポートを実行します。データのソースとしてスレーブを使用できます。実行するコマンドは次のとおりです。
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
指定された資格情報を使用してホスト10.0.0.142上のMySQLに接続し、スキーマ「wiki」のテーブル「pageviews」をローカルホスト(127.0.0.1)で実行されているClickHouseにコピーします。テーブルが自動的に作成され、データが移行されます。
このブログの目的のために、ウィキメディア財団が提供する「ページビュー」データセットから約5,000万行をインポートしました。 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)
ツールはこれを次の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.
インポートが完了すると、MySQLの内容を比較できます:
mysql> SELECT COUNT(*) FROM wiki.pageviews\G
*************************** 1. row ***************************
COUNT(*): 50986914
1 row in set (24.56 sec)
と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.)
このような小さなテーブルでも、MySQLがClickHouseよりもスキャンに時間がかかっていることがはっきりとわかります。
バイナリログでイベントを監視するプロセスを開始するときは、理想的には、ツールがリッスンを開始する場所からバイナリログファイルと位置に関する情報を渡します。最初のインポートが完了した後、スレーブでそれを簡単に確認できます。
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
合格しなかった場合は、入ってくるものをすべてリッスンし始めます:
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
さらにいくつかのデータをロードして、それがどのように機能するかを見てみましょう。 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
私たちが心に留めておかなければならないのは、ツールの制限です。最大の問題は、INSERTのみをサポートしていることです。 DELETEまたはUPDATEはサポートされていません。 DDLもサポートされていないため、MySQLで実行された互換性のないスキーマ変更は、MySQLからClickHouseへのレプリケーションを破壊します。
また、スクリプトの開発者がツールのパフォーマンスを向上させるためにpypyの使用を推奨しているという事実も注目に値します。これを設定するために必要ないくつかの手順を実行してみましょう。
まず、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
次に、pipとclickhouse-mysql-data-readerのすべての要件をインストールする必要があります。これは、通常のセットアップについて説明しているときに、前に説明したものとまったく同じです。
./bin/pypy -m ensurepip
./bin/pip3 install mysql-replication
./bin/pip3 install clickhouse-driver
./bin/pip3 install mysqlclient
最後のステップは、githubリポジトリからclickhouse-mysql-data-readerをインストールすることです(すでにクローンが作成されていると想定しています):
./bin/pip3 install -e /path/to/clickhouse-mysql-data-reader/
それで全部です。これからは、pypy用に作成された環境を使用してすべてのコマンドを実行する必要があります:
./bin/pypy ./bin/clickhouse-mysql
テスト
データが読み込まれました。テーブルのサイズを比較することで、すべてがスムーズに進んだことを確認できます。
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.)
すべてが正しく見えます。 ClickHouseがどのように動作するかを確認するために、いくつかのクエリを実行してみましょう。このセットアップはすべて、実稼働グレードとはほど遠いことを覚えておいてください。 2つの小さなVM、4 GBのメモリ、それぞれ1つのvCPUを使用しました。したがって、データセットは大きくはありませんが、違いを確認するのに十分でした。サンプルが少ないため、「実際の」分析を行うことは非常に困難ですが、それでもいくつかのランダムなクエリをスローすることができます。
サンプルデータで、データが含まれている曜日と1日に表示されたページ数を確認しましょう。
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.)
MySQLの場合、このクエリは次のようになります。
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)
ご覧のとおり、MySQLは全表スキャンを実行するのに3.5分かかりました。
それでは、月間価値が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.)
MySQLの場合も3.5分です:
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)
別のクエリ、いくつかの文字列値に基づくルックアップ:
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.)
文字列と「月次」列に基づく条件でいくつかのルックアップを実行する別のクエリ:
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.)
MySQLの場合、次のようになります。
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)
だから、ほぼ3分。 2番目のクエリも同じです:
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)
もちろん、クエリのパフォーマンスを向上させるためにインデックスを追加できると主張することもできますが、実際には、インデックスを追加するには、ディスクに保存するデータを追加する必要があります。インデックスにはディスクスペースが必要であり、運用上の課題もあります。実際のOLAPデータセットについて話している場合は、テラバイトのデータについて話していることになります。このような環境でスキーマの変更を実行するには、多くの時間がかかり、明確に定義され、テストされたプロセスが必要です。これが、専用の柱状データストアが非常に便利であり、誰もが保存するすべての分析データについてより良い洞察を得るのに非常に役立つ理由です。