GitLab内のClickHouse
このドキュメントでは、GitLab RailsアプリケーションでClickHouseを使って機能を開発する方法のハイレベルな概要を説明します。
GDKのセットアップ
ClickHouseサーバーをローカルにセットアップする方法については、ClickHouseのインストールドキュメントを参照してください。
Railsアプリケーションの設定
-
サンプルファイルをコピーして、認証情報を設定します:
cp config/click_house.yml.example config/click_house.yml
-
clickhouse-client
CLI ツールを使用してデータベースを作成します:clickhouse-client --password
create database gitlab_clickhouse_development;
セットアップの検証
Railsコンソールを起動し、簡単なクエリを実行します:
ClickHouse::Client.select('SELECT 1', :main)
# => [{"1"=>1}]
データベーススキーマとマイグレーション
ClickHouse データベースについては、確立されたスキーママイグレーション手順はまだありません。私たちは、タイムスタンプ接頭辞付きの SQL ファイルを使用して、テスト環境のデータベーススキーマをスクラッチから構築するための非常に基本的なツールを用意しています。
db/click_house/main
フォルダに新しい SQL ファイルを置くことで、テーブルを作成することができます:
// 20230811124511_create_issues.sql
CREATE TABLE issues
(
id UInt64 DEFAULT 0,
title String DEFAULT ''
)
ENGINE = MergeTree
PRIMARY KEY (id)
開発環境の内部で作業している場合は、CREATE TABLE
ステートメントを実行することで、テーブルスキーマを作成または再作成できます。あるいは、Railsコンソールで次のスニペットを使うこともできます:
require_relative 'spec/support/database/click_house/hooks.rb'
# Drops and re-creates all tables
ClickHouseTestRunner.new.ensure_schema
データベースクエリの作成
ClickHouse データベースでは ORM (Object Relational Mapping) を使いません。主な理由は、GitLab アプリケーションにはActiveRecord
PostgresSQL アダプタ用に多くのカスタマイズがあり、アプリケーションは一般的にすべてのデータベースがPostgreSQL
を使っていると想定しているからです。ClickHouse関連の機能はまだ開発の初期段階にあるため、複数のActiveRecord
アダプタを扱う際にバグを発見するのが難しく、デバッグに時間がかかるのを避けるため、シンプルなHTTPクライアントを実装することにしました。
さらに、ClickHouse はActiveRecord
の他のアダプタと同じようには使えないかもしれません。アクセスパターンは従来のトランザクション・データベースとは異なり、ClickHouse:
- ネストされた集約
SELECT
GROUP BY
節を持つクエリを使用します。 - 単一の
INSERT
ステートメントを使用しません。データはバックグラウンド・ジョブで一括挿入されます。 - 異なる一貫性特性があり、トランザクションはありません。
- データベースレベルの検証はほとんどありません。
データベースクエリはClickHouse::Client
gem を使って記述・実行します。
events
テーブルからのシンプルなクエリ:
rows = ClickHouse::Client.select('SELECT * FROM events', :main)
プレースホルダを含むクエリを使用する場合、ClickHouse::Query
オブジェクトを使用することができます。実際の変数の置換、クォート、エスケープはClickHouseサーバーが行います。
raw_query = 'SELECT * FROM events WHERE id > {min_id:UInt64}'
placeholders = { min_id: Integer(100) }
query = ClickHouse::Client::Query.new(raw_query: raw_query, placeholders: placeholders)
rows = ClickHouse::Client.select(query, :main)
プレースホルダを使用する場合、クライアントはクエリに冗長化されたプレースホルダの値を指定することができます。to_redacted_sql
メソッドを呼び出すことで、クエリの冗長化されたバージョンを見ることができます:
puts query.to_redacted_sql
ClickHouseでは、1つのリクエストにつき1つのステートメントしか許可しません。これは、ステートメントが;
文字で閉じられ、その後別のクエリが “注入” されるという、一般的な SQL インジェクション脆弱性を悪用できないことを意味します:
ClickHouse::Client.select('SELECT 1; SELECT 2', :main)
# ClickHouse::Client::DatabaseError: Code: 62. DB::Exception: Syntax error (Multi-statements are not allowed): failed at position 9 (end of query): ; SELECT 2. . (SYNTAX_ERROR) (version 23.4.2.11 (official build))
サブクエリ
クエリ・プレースホルダを特別なSubquery
型で指定することで、ClickHouse::Client::Query
クラスで複雑なクエリを構成することができます。ライブラリはクエリとプレースホルダを正しくマージします:
subquery = ClickHouse::Client::Query.new(raw_query: 'SELECT id FROM events WHERE id = {id:UInt64}', placeholders: { id: Integer(10) })
raw_query = 'SELECT * FROM events WHERE id > {id:UInt64} AND id IN ({q:Subquery})'
placeholders = { id: Integer(10), q: subquery }
query = ClickHouse::Client::Query.new(raw_query: raw_query, placeholders: placeholders)
rows = ClickHouse::Client.select(query, :main)
# ClickHouse will replace the placeholders
puts query.to_sql # SELECT * FROM events WHERE id > {id:UInt64} AND id IN (SELECT id FROM events WHERE id = {id:UInt64})
puts query.to_redacted_sql # SELECT * FROM events WHERE id > $1 AND id IN (SELECT id FROM events WHERE id = $2)
puts query.placeholders # { id: 10 }
同じ名前で異なる値を持つプレースホルダが存在する場合、クエリはエラーを発生させます。
クエリ条件の記述
複数のフィルタ条件が存在するような複雑なフォームを扱う場合、 クエリの断片を文字列として連結してクエリを作成すると、 すぐに手が付けられなくなります。複数の条件を含むクエリには、ClickHouse::QueryBuilder
クラスを使うことができます。このクラスはArel
gem を使ってクエリを生成し、ActiveRecord
のようなクエリインターフェイスを提供します。
builder = ClickHouse::QueryBuilder.new('events')
query = builder
.where(builder.table[:created_at].lteq(Date.today))
.where(id: [1,2,3])
rows = ClickHouse::Client.select(query, :main)
テスト
ClickHouseはCI/CDで有効ですが、パイプラインのランタイムに大きな影響を与えないよう、:click_house
タグが付けられたテストケースのみClickHouseサーバーを実行することにしました。
:click_house
タグは、すべてのテストケースの前にデータベーススキーマが適切に設定されていることを保証します。
RSpec.describe MyClickHouseFeature, :click_house do
it 'returns rows' do
rows = ClickHouse::Client.select('SELECT 1', :main)
expect(rows.size).to eq(1)
end
end
複数のデータベース
設計上、ClickHouse::Client
ライブラリは複数のデータベースの設定をサポートしています。私たちはまだ開発の初期段階にあるため、main
というデータベースしか持っていません。
複数データベースの設定例:
development:
main:
database: gitlab_clickhouse_main_development
url: 'http://localhost:8123'
username: clickhouse
password: clickhouse
user_analytics: # made up database
database: gitlab_clickhouse_user_analytics_development
url: 'http://localhost:8123'
username: clickhouse
password: clickhouse
観測可能性
ClickHouse::Client
ライブラリを介して実行されるすべてのクエリは、ActiveSupport::Notifications
を介してクエリのパフォーマンスメトリクス(タイミング、リードバイト)を公開します。
ActiveSupport::Notifications.subscribe('sql.click_house') do |_, _, _, _, data|
puts data.inspect
end
さらに、Webインタラクションで実行されたClickHouseクエリを表示するには、パフォーマンスバーのch
ラベルの横にあるカウントを選択します。