はじめに
ClickHouse に、MySQL に直接接続する機能が追加されました。
MySQL のテーブルを、直接ClickHouse から利用することが可能です。
ClickHouse は、集計が高速なデータベースで、GROUP BY を使うようなクエリは MySQL 上で普通に集計するよりも、ClickHouse 側からMySQLのデータを使って集計する方が高速です。
以下は手元の仮想環境でテストした記録です。
仮想環境なので厳密なテストではなく、参考程度に見てください。
サンプルデータの準備
適当に1000万件程度のデータを用意してテストします。
#!/usr/bin/perl
use strict;
use warnings;
srand(0);
my $RECORD_COUNT = 10000000;
for(my $i = 1; $i <= $RECORD_COUNT; $i++) {
my @field;
push(@field, $i);
push(@field, sprintf("%08d\@example.com", $i));
push(@field, int(rand(5))+1);
push(@field, int(rand(10))+1);
push(@field, int(rand(100000))+1);
push(@field, int(rand(1000000))+1);
push(@field, int(rand(10000000))+1);
print join("\t", @field), "\n";
}
実行してサンプルデータを作ります。500MBくらいになりました。
$ ./makedata.pl > data.tsv
$ ls -lh data.tsv
-rw-rw-r-- 1 mikage mikage 512M Jan 18 07:44 data.tsv
$ head -2 data.tsv
1 00000001@example.com 1 8 9638 870466 5773036
2 00000002@example.com 4 7 36877 873905 7450951
MySQL での測定
MySQL にテーブルを作り、インポートします。
数値より文字列データを集計することの方が多そうなので、中身は数値ですが型を変えています。
その後、適当に集計してみます。
mysql> CREATE TABLE testdata (
id INT NOT NULL PRIMARY KEY,
email BLOB NOT NULL,
data1 INT NOT NULL,
data2 VARCHAR(100) NOT NULL,
data3 BLOB NOT NULL,
data4 BLOB NOT NULL,
data5 BLOB NOT NULL
) ENGINE=InnoDB;
mysql> LOAD DATA LOCAL INFILE 'data.tsv' INTO TABLE testdata;
Query OK, 10000000 rows affected (44.98 sec)
Records: 10000000 Deleted: 0 Skipped: 0 Warnings: 0
mysql> SELECT data1, COUNT(*) FROM testdata GROUP BY data1;
+-------+----------+
| data1 | COUNT(*) |
+-------+----------+
| 1 | 2000453 |
| 2 | 1999993 |
| 3 | 1998988 |
| 4 | 1999013 |
| 5 | 2001553 |
+-------+----------+
5 rows in set (3.22 sec)
mysql> SELECT data2, COUNT(*) FROM testdata GROUP BY data2;
+-------+----------+
| data2 | COUNT(*) |
+-------+----------+
| 1 | 1000530 |
| 10 | 999424 |
| 2 | 1000648 |
| 3 | 1000357 |
| 4 | 998349 |
| 5 | 998889 |
| 6 | 999786 |
| 7 | 998774 |
| 8 | 1001805 |
| 9 | 1001438 |
+-------+----------+
10 rows in set (4.01 sec)
mysql> SELECT data3, COUNT(*) FROM testdata GROUP BY data3;
-- 結果省略
100000 rows in set (3 min 32.82 sec)
mysql> SELECT data1, COUNT(DISTINCT data5) FROM testdata GROUP BY data1;
+-------+-----------------------+
| data1 | COUNT(DISTINCT data5) |
+-------+-----------------------+
| 1 | 1813005 |
| 2 | 1812503 |
| 3 | 1812072 |
| 4 | 1811674 |
| 5 | 1814106 |
+-------+-----------------------+
5 rows in set (3 min 3.56 sec)
BLOB型でGROUPBYをするとかなり遅いようです。
ClickHouse を使って測定
ClickHouse 上で、テーブル名の代わりに、以下のような形で指定すれば、MySQL のデータを利用できます。
mysql('host:port', 'database', 'table', 'user', 'password)
ClickHouse のクライアントを起動してテストします。
起動時に --multiline オプションをつけないと、複数行のクエリをかけないのでつけておきます。
$ clickhouse-client --multiline
SELECT
data1,
COUNT(*)
FROM mysql('localhost', 'mikage', 'testdata', 'mikage', '')
GROUP BY data1
┌─data1─┬─COUNT()─┐
│ 4 │ 1999013 │
│ 3 │ 1998988 │
│ 2 │ 1999993 │
│ 5 │ 2001553 │
│ 1 │ 2000453 │
└───────┴─────────┘
5 rows in set. Elapsed: 2.685 sec. Processed 10.00 million rows, 40.00 MB (3.72 million rows/s., 14.90 MB/s.)
SELECT
data2,
COUNT(*)
FROM mysql('localhost', 'mikage', 'testdata', 'mikage', '')
GROUP BY data2
┌─data2─┬─COUNT()─┐
│ 6 │ 999786 │
│ 8 │ 1001805 │
│ 9 │ 1001438 │
│ 3 │ 1000357 │
│ 2 │ 1000648 │
│ 4 │ 998349 │
│ 5 │ 998889 │
│ 10 │ 999424 │
│ 1 │ 1000530 │
│ 7 │ 998774 │
└───────┴─────────┘
10 rows in set. Elapsed: 2.692 sec. Processed 10.00 million rows, 101.00 MB (3.71 million rows/s., 37.52 MB/s.)
SELECT
data3,
COUNT(*)
FROM mysql('localhost', 'mikage', 'testdata', 'mikage', '')
GROUP BY data3
-- 結果省略
100000 rows in set. Elapsed: 5.236 sec. Processed 10.00 million rows, 138.89 MB (1.91 million rows/s., 26.52 MB/s.)
SELECT
data1,
uniqExact(data5)
FROM mysql('localhost', 'mikage', 'testdata', 'mikage', '')
GROUP BY data1
┌─data1─┬─uniqExact(data5)─┐
│ 4 │ 1811674 │
│ 3 │ 1812072 │
│ 2 │ 1812503 │
│ 5 │ 1814106 │
│ 1 │ 1813005 │
└───────┴──────────────────┘
5 rows in set. Elapsed: 12.944 sec. Processed 10.00 million rows, 198.89 MB (772.55 thousand rows/s., 15.37 MB/s.)
-- ClickHouse には、概算でユニーク数を求める機能もあるので、そちらで計算してみます。
SELECT
data1,
uniq(data5)
FROM mysql('localhost', 'mikage', 'testdata', 'mikage', '')
GROUP BY data1
┌─data1─┬─uniq(data5)─┐
│ 4 │ 1812684 │
│ 3 │ 1811607 │
│ 2 │ 1817432 │
│ 5 │ 1815763 │
│ 1 │ 1815300 │
└───────┴─────────────┘
5 rows in set. Elapsed: 6.026 sec. Processed 10.00 million rows, 198.89 MB (1.66 million rows/s., 33.00 MB/s.)
MySQL では非常に時間がかかったクエリも、ごく短時間で処理できます。
繰り返し集計するときは、データを一度ClickHouseにコピーしてしまう方がよいかもしれません。
以下のようにコピーしてしまえば、その後のクエリはさらに速くなります。
とりあえずStripeLogエンジンを使うのがおすすめです。
CREATE TABLE testdata
ENGINE = StripeLog AS
SELECT *
FROM mysql('localhost', 'mikage', 'testdata', 'mikage', '')
Ok.
0 rows in set. Elapsed: 9.823 sec. Processed 10.00 million rows, 917.66 MB (1.02 million rows/s., 93.42 MB/s.)
主キーがある場合、MergeTree テーブルを使うのもよいかもしれません。
こちらがClickHouseで一番主に使われるエンジンです。
ORDER BY でデータのソート順を指定する必要があります(重複があっても問題ありません)
CREATE TABLE testdata
ENGINE = MergeTree
ORDER BY id AS
SELECT *
FROM mysql('localhost', 'mikage', 'testdata', 'mikage', '')
Ok.
0 rows in set. Elapsed: 20.466 sec. Processed 10.00 million rows, 917.66 MB (488.61 thousand rows/s., 44.84 MB/s.)
先ほどのクエリを実行してみます。
以下は StripeLog エンジンでの測定結果です。
SELECT
data1,
COUNT(*)
FROM testdata
GROUP BY data1
┌─data1─┬─COUNT()─┐
│ 4 │ 1999013 │
│ 3 │ 1998988 │
│ 2 │ 1999993 │
│ 5 │ 2001553 │
│ 1 │ 2000453 │
└───────┴─────────┘
5 rows in set. Elapsed: 0.071 sec. Processed 10.00 million rows, 40.00 MB (141.13 million rows/s., 564.53 MB/s.)
SELECT
data2,
COUNT(*)
FROM testdata
GROUP BY data2
┌─data2─┬─COUNT()─┐
│ 6 │ 999786 │
│ 8 │ 1001805 │
│ 9 │ 1001438 │
│ 3 │ 1000357 │
│ 2 │ 1000648 │
│ 4 │ 998349 │
│ 5 │ 998889 │
│ 10 │ 999424 │
│ 1 │ 1000530 │
│ 7 │ 998774 │
└───────┴─────────┘
10 rows in set. Elapsed: 0.177 sec. Processed 10.00 million rows, 101.00 MB (56.34 million rows/s., 569.05 MB/s.)
SELECT
data3,
COUNT(*)
FROM testdata
GROUP BY data3
-- 結果省略
100000 rows in set. Elapsed: 0.779 sec. Processed 10.00 million rows, 138.89 MB (12.84 million rows/s., 178.29 MB/s.)
SELECT
data1,
uniqExact(data5)
FROM testdata
GROUP BY data1
┌─data1─┬─uniqExact(data5)─┐
│ 5 │ 1814106 │
└───────┴──────────────────┘
┌─data1─┬─uniqExact(data5)─┐
│ 1 │ 1813005 │
└───────┴──────────────────┘
┌─data1─┬─uniqExact(data5)─┐
│ 4 │ 1811674 │
└───────┴──────────────────┘
┌─data1─┬─uniqExact(data5)─┐
│ 3 │ 1812072 │
└───────┴──────────────────┘
┌─data1─┬─uniqExact(data5)─┐
│ 2 │ 1812503 │
└───────┴──────────────────┘
5 rows in set. Elapsed: 1.725 sec. Processed 10.00 million rows, 198.89 MB (5.80 million rows/s., 115.32 MB/s.)
SELECT
data1,
uniq(data5)
FROM testdata
GROUP BY data1
┌─data1─┬─uniq(data5)─┐
│ 4 │ 1812684 │
│ 3 │ 1811607 │
│ 2 │ 1817432 │
│ 5 │ 1815763 │
│ 1 │ 1815300 │
└───────┴─────────────┘
5 rows in set. Elapsed: 0.285 sec. Processed 10.00 million rows, 198.89 MB (35.03 million rows/s., 696.62 MB/s.)
まとめ
時間をまとめると以下のようになります。
(1回ずつ、VM環境での測定なので誤差は大きいと思います)
左から、MySQLでの時間、ClickHouseがMySQLからデータを読み込んで処理する場合の時間、ClickHouse上にコピー済みのデータを処理する時間です。
クエリ | MySQL処理時間(秒) | MySQL->ClickHouse処理時間(秒) | ClickHouse処理時間(秒) |
---|---|---|---|
groupby(data1) | 3.22 | 2.685 | 0.071 |
groupby(data2) | 4.01 | 2.692 | 0.177 |
groupby(data3) | 212.82 | 5.236 | 0.779 |
groupby(data1)+uniq(data5) | 183.56 | 12.944 | 1.725 |
groupby(data1)+uniq(data5)概算 | (機能なし) | 6.026 | 0.285 |
(追記)型の対応
MySQLのデータを参照した時、ClickHouse側に対応する型がない場合は、String型になるようです。
Decimal型などは対応したものがないため、String型になってしまいます。
数値として扱いたい場合は、精度は変わってしまいますが、MySQL側でDouble型にしておくなどが必要そうです。
また、Date型、DateTime型も、MySQLとClickHouseでは範囲が異なります。
ClickHouseでは、どちらも1970~20382105年までになります。
こちらも範囲外のデータがある場合、String型にする、年月日でカラムを分割して数値としてコピーする、などが必要かもしれません。
MySQL の型 | ClickHouse の型 | 備考 |
---|---|---|
tinyint | UInt8 / Int8 | |
smallint | UInt16 / Int16 | |
int / mediumint | UInt32 / Int32 | |
bigint | UInt64 / Int64 | |
float | Float32 | |
double | Float64 | |
date | Date | 表現できる範囲差あり |
datetime | DateTime | 表現できる範囲差あり |
timestamp | DateTime | |
binary | FixedString | |
上記以外 | String |