LoginSignup
0

More than 5 years have passed since last update.

EC2インスタンスにClickHouseを入れてローカルからscalike-jdbcで接続した

Posted at

ビッグデータ向け?DBであるClickHouseをscalaから触ってみたので備忘録を
導入方法などを書くだけでベンチマークとかのパフォーマンス検証はありません(でも次に書きたいかも…)

全体の流れ

  1. EC2インスタンスにClickHouseを入れる
  2. テーブルの用意
  3. 設定ファイル変更
  4. 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のユーザを定義しているファイル
    初期では defaultreadonly の二種類のユーザがいるのでそれぞれ
    以下を変更しておく(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エアプなのでお粗末な設定かもしれない…) スクリーンショット 2018-07-29 13.38.16.png

scalaプロジェクトの用意

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パラメータ)で送られているので十分に気をつけて運用しましょう :relaxed:

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0