ビッグデータ向け?DBであるClickHouseをscalaから触ってみたので備忘録を
導入方法などを書くだけでベンチマークとかのパフォーマンス検証はありません(でも次に書きたいかも…)
全体の流れ
- EC2インスタンスにClickHouseを入れる
- テーブルの用意
- 設定ファイル変更
- scalaプロジェクトの用意
という感じで説明していきます
EC2インスタンスにClickHouseを入れる
AmazonLinuxを想定
https://github.com/Altinity/clickhouse-rpm-install
の手順に沿ってyumで入れる
installができたらclientで接続出来ることの確認
$ clickhouse-client
ClickHouse client version 1.1.54390.
Connecting to localhost:9000 as user default.
Connected to ClickHouse server version 1.1.54390.
:)
:) show databases
SHOW DATABASES
┌─name────┐
│ default │
│ system │
└─────────┘
2 rows in set. Elapsed: 0.026 sec.
テーブルの用意
- DATABASEの用意
他のRDB同様DDLで作成
:) CREATE DATABASE my_test
CREATE DATABASE my_test
Ok.
0 rows in set. Elapsed: 0.003 sec.
:) show databases
SHOW DATABASES
┌─name────┐
│ default │
│ my_test │
│ system │
└─────────┘
3 rows in set. Elapsed: 0.001 sec.
:)
:) use my_test
USE my_test
Ok.
0 rows in set. Elapsed: 0.009 sec.
:)
(デフォルトだと使用中のDB名は表示されないっぽい…)
- MergeTree engineでテーブルを用意
MergeTreeはlogなどを集計/検索するのに有効なエンジンらしい?(よくわかってない)
:) CREATE TABLE log_20180729 (
:-] write_date Date,
:-] data1 String,
:-] data2 String,
:-] created_at DateTime
:-] ) ENGINE = MergeTree(write_date, (write_date, data1, data2, created_at), 8192)
CREATE TABLE log_20180729
(
write_date Date,
data1 String,
data2 String,
created_at DateTime
)
ENGINE = MergeTree(write_date, (write_date, data1, data2, created_at), 8192)
Ok.
0 rows in set. Elapsed: 0.004 sec.
:) CREATE TABLE log_20180729 (
:-] write_date Date,
:-] data1 String,
:-] data2 String,
:-] created_at DateTime
:-] ) ENGINE = MergeTree(write_date, (write_date, data1, data2, created_at), 8192)
CREATE TABLE log_20180729
(
write_date Date,
data1 String,
data2 String,
created_at DateTime
)
ENGINE = MergeTree(write_date, (write_date, data1, data2, created_at), 8192)
Received exception from server (version 1.1.54390):
Code: 57. DB::Exception: Received from localhost:9000, 127.0.0.1. DB::Exception: Table my_test.log_20180729 already exists..
0 rows in set. Elapsed: 0.039 sec.
:)
MergeTreeエンジンのテーブルは日別ごとに作成するといいらしい(よくわかってない)
MergeTreeの引数は MergeTree(<日付カラム>, <主キー用のタプル>, <索引粒度>)
となっている(索引粒度ってなんなんだ…)
適当にデータを挿入しておく
:) INSERT INTO log_20180728 VALUES('2018-07-28', 'foo', 'bar', NOW());
INSERT INTO log_20180728 VALUES
Ok.
1 rows in set. Elapsed: 0.005 sec.
:) INSERT INTO log_20180729 VALUES('2018-07-29', 'foo', 'fuga', NOW());
INSERT INTO log_20180729 VALUES
Ok.
1 rows in set. Elapsed: 0.001 sec.
:) INSERT INTO log_20180729 VALUES('2018-07-29', 'bar', 'foo', NOW());
INSERT INTO log_20180729 VALUES
Ok.
1 rows in set. Elapsed: 0.003 sec.
:)
- Mergeテーブルの作成
Mergeは直接的な実データを持たず複数のテーブルを一つのテーブルのように扱うことができる
:) CREATE TABLE log (
:-] write_date Date,
:-] data1 String,
:-] data2 String,
:-] created_at DateTime
:-] ) ENGINE = Merge('my_test', '^log_')
CREATE TABLE log
(
write_date Date,
data1 String,
data2 String,
created_at DateTime
)
ENGINE = Merge('my_test', '^log_')
Ok.
0 rows in set. Elapsed: 0.003 sec.
:)
- SELECTしてみる
:) SELECT * FROM log
SELECT *
FROM log
┌─write_date─┬─data1─┬─data2─┬──────────created_at─┐
│ 2018-07-28 │ foo │ bar │ 2018-07-28 10:42:03 │
└────────────┴───────┴───────┴─────────────────────┘
┌─write_date─┬─data1─┬─data2─┬──────────created_at─┐
│ 2018-07-29 │ foo │ fuga │ 2018-07-28 10:42:03 │
└────────────┴───────┴───────┴─────────────────────┘
┌─write_date─┬─data1─┬─data2─┬──────────created_at─┐
│ 2018-07-29 │ bar │ foo │ 2018-07-28 10:42:04 │
└────────────┴───────┴───────┴─────────────────────┘
3 rows in set. Elapsed: 0.004 sec.
:) SELECT data1, COUNT(1) FROM log GROUP BY data1
SELECT
data1,
COUNT(1)
FROM log
GROUP BY data1
┌─data1─┬─COUNT(1)─┐
│ bar │ 1 │
│ foo │ 2 │
└───────┴──────────┘
2 rows in set. Elapsed: 0.008 sec.
:)
設定ファイル変更
-
/etc/clickhouse-server/users.xml
の編集
ClickHouseのユーザを定義しているファイル
初期ではdefault
とreadonly
の二種類のユーザがいるのでそれぞれ
以下を変更しておく(ipv6表記)
default
<networks>
<ip>::1</ip> <!-- ローカルからのみアクセス可能にしておく -->
</networks>
readonly
<password>任意のパスワード</password>
<networks>
<ip>::/0</ip> <!-- すべてのIPからアクセス可能 -->
</networks>
(実運用に使う場合は <hoost_regexp>
などで設定する)
https://clickhouse.yandex/docs/en/operations/access_rights/
- config.xmlを変更 以下を追記
<!-- 接続可能なhost、ワイルドカード(::)は全てのipv4/ipv6アドレスを受け付ける -->
<listen_host>::</listen_host>
- EC2のセキュリティグループにClickServer用のインバウンドを追加する
9000番はclickhouse-client用の口
8123番はhttp-interface用の口
(EC2エアプなのでお粗末な設定かもしれない…)
scalaプロジェクトの用意
sbt new
で新しいプロジェクトを用意
このへんの説明は割愛、好きにやってくれいjdbc依存性を追加
clickhouse用jdbcは二種類あって
https://github.com/housepower/ClickHouse-Native-JDBC
https://github.com/yandex/clickhouse-jdbc
今回は2つ目のjdbcを使った(こちらはnaitiveと違ってhttp-interfaceを利用するjdbc)
build.sbt
libraryDependencies ++= Seq(
"org.scalikejdbc" %% "scalikejdbc" % "2.5.2", // ついでにscalike-jdbcも追加
"ru.yandex.clickhouse" % "clickhouse-jdbc" % "0.1.40",
"ch.qos.logback" % "logback-classic" % "1.2.3"
)
- ClickHouse接続用のコードを書く
適当に
import scalikejdbc._
Class.forName("ru.yandex.clickhouse.ClickHouseDriver")
ConnectionPool.singleton("jdbc:clickhouse://<ec2のホスト>/my_test", "readonly", "<設定したパスワード>")
implicit val session = AutoSession
val result =
sql"""
|SELECT * FROM log
""".stripMargin.map(rs => rs.string("data1")).list().apply()
println(result)
さぁ実行だ!
$ sbt run
...
13:48:16.050 [run-main-0] DEBUG ru.yandex.clickhouse.ClickHouseStatementImpl - Request url: http://***:8123/?extremes=0&user=readonly&database=my_test&password=****&compress=1
13:48:16.105 [run-main-0] DEBUG scalikejdbc.StatementExecutor$$anon$1 - SQL execution completed
[SQL Execution]
SELECT * FROM log; (32 ms)
[Stack Trace]
...
example.Hello$.delayedEndpoint$example$Hello$1(Hello.scala:24)
example.Hello$delayedInit$body.apply(Hello.scala:5)
scala.Function0.apply$mcV$sp(Function0.scala:34)
scala.Function0.apply$mcV$sp$(Function0.scala:34)
scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
scala.App.$anonfun$main$1$adapted(App.scala:76)
scala.collection.immutable.List.foreach(List.scala:389)
scala.App.main(App.scala:76)
scala.App.main$(App.scala:74)
example.Hello$.main(Hello.scala:5)
example.Hello.main(Hello.scala)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:498)
...
List(foo, foo, bar)
[success] Total time: 8 s, completed 2018/07/29 13:48:16
データが取得できましたね
ログから裏でhttp-interfaceが叩かれているのがわかります
見ての通り(*でぼかしてますが)パスワードが平文(という以前にGETパラメータ)で送られているので十分に気をつけて運用しましょう