ClickHouse は、いくつかの種類のユーザー定義関数 (UDFs) をサポートしています。
- 実行可能 UDF は、外部プログラムやスクリプト (Python、Bash など) を起動し、STDIN / STDOUT を介してデータのブロックをストリーミングします。ClickHouse を再コンパイルすることなく、既存のコードやツールを統合するために使用できます。インプロセスの選択肢と比べると呼び出しごとのオーバーヘッドは大きく、より重いロジックや別のランタイムが必要な場合に適しています。
- SQL UDFs は、
CREATE FUNCTIONを使って SQL のみで定義されます。これらはクエリプランにインライン展開されるため (プロセス境界はありません) 、軽量で、式ロジックの再利用や複雑な計算カラムの簡略化に適しています。 - Experimental WebAssembly UDFs は、WebAssembly にコンパイルされたコードを、サーバープロセス内のサンドボックスで実行します。外部の実行可能ファイルよりも呼び出しごとのオーバーヘッドが低く、ネイティブ拡張機能よりも優れた分離性を備えているため、WASM をターゲットにできる言語 (例: C/C++/Rust) で記述されたカスタムアルゴリズムに適しています。
- Experimental ドライバーベースの実行可能 UDF では、オペレーターが提供する "ドライバー" により、
CREATE FUNCTION ... ENGINE = DriverName(...) AS '...'で指定されたコードスニペットを、関数作成時に実行可能 UDF に変換できます (たとえばコンパイルして)。これは実行可能 UDF をベースとしており、サーバー側のドライバー設定が必要です。
実行可能ユーザー定義関数
ClickHouse は、データ処理のために任意の外部実行可能プログラムまたはスクリプトを呼び出すことができます。
実行可能ユーザー定義関数の設定は、1 つ以上の XML ファイルに配置できます。
設定へのパスは、user_defined_executable_functions_config パラメータで指定します。
関数の設定には、次の項目が含まれます:
| Parameter | Description | Required | Default Value |
|---|---|---|---|
name |
関数名 | はい | - |
command |
実行するスクリプト名、または execute_direct が false の場合は実行コマンド |
はい | - |
argument |
引数の type と、必要に応じて引数の name を指定します。各引数は個別の設定として記述します。Native や JSONEachRow など、ユーザー定義関数のフォーマットで引数名がシリアライゼーションの一部となる場合は、name の指定が必要です |
はい | c + argument_number |
format |
引数をコマンドに渡す際の フォーマット。コマンドの出力でも同じフォーマットを使用する必要があります | はい | - |
return_type |
戻り値の型 | はい | - |
return_name |
戻り値の名前。Native や JSONEachRow など、ユーザー定義関数のフォーマットで戻り値の名前がシリアライゼーションの一部となる場合は、return_name の指定が必要です |
任意 | result |
type |
実行可能の種類。type が executable に設定されている場合は単一のコマンドが起動され、executable_pool に設定されている場合はコマンドのプールが作成されます |
はい | - |
max_command_execution_time |
データの block を処理する際の最大実行時間 (秒) 。この設定は executable_pool コマンドでのみ有効です |
任意 | 10 |
command_termination_timeout |
パイプが閉じられたあと、コマンドの終了を待機する時間 (秒) 。この時間を過ぎると、コマンドを実行しているプロセスに SIGTERM が送信されます |
任意 | 10 |
command_read_timeout |
コマンドの stdout からデータを読み取る際のタイムアウト (ミリ秒) | 任意 | 10000 |
command_write_timeout |
コマンドの stdin にデータを書き込む際のタイムアウト (ミリ秒) | 任意 | 10000 |
pool_size |
コマンドプールのサイズ | 任意 | 16 |
send_chunk_header |
process にデータの chunk を送信する前に、行数を送信するかどうかを制御します | 任意 | false |
execute_direct |
execute_direct = 1 の場合、command は user_scripts_path で指定された user_scripts フォルダー内から検索されます。追加のスクリプト引数は空白区切りで指定できます。例: script_name arg1 arg2。execute_direct = 0 の場合、command は bin/sh -c の引数として渡されます |
任意 | 1 |
lifetime |
関数の再読み込み間隔 (秒) 。0 に設定すると、関数は再読み込みされません |
任意 | 0 |
deterministic |
関数が決定論的かどうか (同じ入力に対して常に同じ結果を返すか) | 任意 | false |
stderr_reaction |
コマンドの stderr 出力の扱い方。値: none (無視) 、log (stderr をすべて即座に記録) 、log_first (終了後に先頭 4 KiB を記録) 、log_last (終了後に末尾 4 KiB を記録) 、throw (stderr に出力があった時点で直ちに例外を送出) 。log_first または log_last をゼロ以外の終了コードとともに使用した場合、stderr の内容が例外メッセージに含まれます |
任意 | log_last |
check_exit_code |
true の場合、ClickHouse はコマンドの終了コードを確認します。終了コードがゼロ以外の場合は例外が発生します | 任意 | true |
コマンドは STDIN から引数を読み取り、結果を STDOUT に出力する必要があります。また、引数は反復的に処理する必要があります。つまり、1 つの chunk の引数を処理したら、次の chunk を待機しなければなりません。
実行可能ユーザー定義関数
例
インラインスクリプトからの UDF
XML または YAML の設定で execute_direct を 0 に明示的に指定し、test_function_sum を手動で作成します。
test_function.xml ファイル (デフォルトのパス設定では /etc/clickhouse-server/test_function.xml) 。
<functions>
<function>
<type>executable</type>
<name>test_function_sum</name>
<return_type>UInt64</return_type>
<argument>
<type>UInt64</type>
<name>lhs</name>
</argument>
<argument>
<type>UInt64</type>
<name>rhs</name>
</argument>
<format>TabSeparated</format>
<command>cd /; clickhouse-local --input-format TabSeparated --output-format TabSeparated --structure 'x UInt64, y UInt64' --query "SELECT x + y FROM table"</command>
<execute_direct>0</execute_direct>
<deterministic>true</deterministic>
</function>
</functions>test_function.yaml ファイル (デフォルトのパス設定では /etc/clickhouse-server/test_function.yaml) 。
functions:
type: executable
name: test_function_sum
return_type: UInt64
argument:
- type: UInt64
name: lhs
- type: UInt64
name: rhs
format: TabSeparated
command: 'cd /; clickhouse-local --input-format TabSeparated --output-format TabSeparated --structure ''x UInt64, y UInt64'' --query "SELECT x + y FROM table"'
execute_direct: 0
deterministic: trueSELECT test_function_sum(2, 2);┌─test_function_sum(2, 2)─┐
│ 4 │
└─────────────────────────┘Python スクリプトからの UDF
この例では、STDIN から値を読み取り、それを文字列として返す UDF を作成します。
XML または YAML の設定を使用して test_function を作成します。
ファイル test_function.xml (デフォルトのパス設定では /etc/clickhouse-server/test_function.xml) 。
<functions>
<function>
<type>executable</type>
<name>test_function_python</name>
<return_type>String</return_type>
<argument>
<type>UInt64</type>
<name>value</name>
</argument>
<format>TabSeparated</format>
<command>test_function.py</command>
</function>
</functions>ファイル test_function.yaml (デフォルトのパス設定では /etc/clickhouse-server/test_function.yaml) 。
functions:
type: executable
name: test_function_python
return_type: String
argument:
- type: UInt64
name: value
format: TabSeparated
command: test_function.pyuser_scripts フォルダー内にスクリプトファイル test_function.py を作成します (デフォルトのパス設定では /var/lib/clickhouse/user_scripts/test_function.py) 。
#!/usr/bin/python3
import sys
if __name__ == '__main__':
for line in sys.stdin:
print("Value " + line, end='')
sys.stdout.flush()SELECT test_function_python(toUInt64(2));┌─test_function_python(2)─┐
│ Value 2 │
└─────────────────────────┘STDIN から 2 つの値を読み取り、その合計を JSON オブジェクトとして返す
名前付き引数と JSONEachRow フォーマットを使用して、XML または YAML の設定で test_function_sum_json を作成します。
ファイル test_function.xml (デフォルトのパス設定では /etc/clickhouse-server/test_function.xml) 。
<functions>
<function>
<type>executable</type>
<name>test_function_sum_json</name>
<return_type>UInt64</return_type>
<return_name>result_name</return_name>
<argument>
<type>UInt64</type>
<name>argument_1</name>
</argument>
<argument>
<type>UInt64</type>
<name>argument_2</name>
</argument>
<format>JSONEachRow</format>
<command>test_function_sum_json.py</command>
</function>
</functions>ファイル test_function.yaml (デフォルトのパス設定では /etc/clickhouse-server/test_function.yaml) 。
functions:
type: executable
name: test_function_sum_json
return_type: UInt64
return_name: result_name
argument:
- type: UInt64
name: argument_1
- type: UInt64
name: argument_2
format: JSONEachRow
command: test_function_sum_json.pyuser_scripts フォルダー内にスクリプトファイル test_function_sum_json.py を作成します (デフォルトのパス設定では /var/lib/clickhouse/user_scripts/test_function_sum_json.py) 。
#!/usr/bin/python3
import sys
import json
if __name__ == '__main__':
for line in sys.stdin:
value = json.loads(line)
first_arg = int(value['argument_1'])
second_arg = int(value['argument_2'])
result = {'result_name': first_arg + second_arg}
print(json.dumps(result), end='\n')
sys.stdout.flush()SELECT test_function_sum_json(2, 2);┌─test_function_sum_json(2, 2)─┐
│ 4 │
└──────────────────────────────┘command 設定でパラメータを使用する
実行可能なユーザー定義関数では、command 設定で構成した定数パラメータを受け取れます (これは executable 型のユーザー定義関数でのみ機能します) 。
また、シェルの引数展開による脆弱性を防ぐため、execute_direct オプションも必要です。
ファイル test_function_parameter_python.xml (デフォルトのパス設定では /etc/clickhouse-server/test_function_parameter_python.xml) 。
<functions>
<function>
<type>executable</type>
<execute_direct>true</execute_direct>
<name>test_function_parameter_python</name>
<return_type>String</return_type>
<argument>
<type>UInt64</type>
</argument>
<format>TabSeparated</format>
<command>test_function_parameter_python.py {test_parameter:UInt64}</command>
</function>
</functions>ファイル test_function_parameter_python.yaml (デフォルトのパス設定では /etc/clickhouse-server/test_function_parameter_python.yaml) 。
functions:
type: executable
execute_direct: true
name: test_function_parameter_python
return_type: String
argument:
- type: UInt64
format: TabSeparated
command: test_function_parameter_python.py {test_parameter:UInt64}user_scripts フォルダ内にスクリプトファイル test_function_parameter_python.py を作成します (デフォルトのパス設定では /var/lib/clickhouse/user_scripts/test_function_parameter_python.py) 。
#!/usr/bin/python3
import sys
if __name__ == "__main__":
for line in sys.stdin:
print("Parameter " + str(sys.argv[1]) + " value " + str(line), end="")
sys.stdout.flush()SELECT test_function_parameter_python(1)(2);┌─test_function_parameter_python(1)(2)─┐
│ Parameter 1 value 2 │
└──────────────────────────────────────┘シェルスクリプトによるUDF
この例では、各値を 2 倍するシェルスクリプトを作成します。
ファイル test_function_shell.xml (デフォルトのパス設定の場合は /etc/clickhouse-server/test_function_shell.xml) 。
<functions>
<function>
<type>executable</type>
<name>test_shell</name>
<return_type>String</return_type>
<argument>
<type>UInt8</type>
<name>value</name>
</argument>
<format>TabSeparated</format>
<command>test_shell.sh</command>
</function>
</functions>ファイル test_function_shell.yaml (デフォルトのパス設定の場合は /etc/clickhouse-server/test_function_shell.yaml) 。
functions:
type: executable
name: test_shell
return_type: String
argument:
- type: UInt8
name: value
format: TabSeparated
command: test_shell.shuser_scripts フォルダー内に、スクリプトファイル test_shell.sh を作成します (デフォルトのパス設定の場合は /var/lib/clickhouse/user_scripts/test_shell.sh) 。
#!/bin/bash
while read read_data;
do printf "$(expr $read_data \* 2)\n";
doneSELECT test_shell(number) FROM numbers(10); ┌─test_shell(number)─┐
1. │ 0 │
2. │ 2 │
3. │ 4 │
4. │ 6 │
5. │ 8 │
6. │ 10 │
7. │ 12 │
8. │ 14 │
9. │ 16 │
10. │ 18 │
└────────────────────┘エラー処理
一部の関数では、データが無効な場合に例外がスローされることがあります。 この場合、クエリはキャンセルされ、エラーメッセージがクライアントに返されます。 分散処理では、いずれかのサーバーで例外が発生すると、他のサーバーでもクエリの中止が試みられます。
引数式の評価
ほとんどすべてのプログラミング言語では、特定の演算子では引数の1つが評価されないことがあります。
通常は、&&、||、?: といった演算子です。
ClickHouse では、関数 (演算子) の引数は常に評価されます。
これは、各行を個別に計算するのではなく、カラムのパーツ全体が一度に評価されるためです。
分散クエリ処理における関数の実行
分散クエリ処理では、クエリ処理の各段階のうち、可能な限り多くの段階がリモートサーバー上で実行され、残りの段階 (中間結果のマージとそれ以降のすべて) はリクエスト元のサーバーで実行されます。
つまり、関数が異なるサーバーで実行されることがあります。
たとえば、クエリ SELECT f(sum(g(x))) FROM distributed_table GROUP BY h(y), では、
distributed_tableに少なくとも2つの分片がある場合、関数 'g' と 'h' はリモートサーバーで実行され、関数 'f' はリクエスト元のサーバーで実行されます。distributed_tableに分片が1つしかない場合、'f'、'g'、'h' のすべての関数はこの分片のサーバーで実行されます。
通常、関数の結果は、どのサーバーで実行されるかに依存しません。ただし、これが重要になる場合もあります。
たとえば、Dictionary を扱う関数は、その関数が実行されているサーバー上にある Dictionary を使用します。
別の例として、hostName 関数は、自身が実行されているサーバーの名前を返します。これは、SELECT クエリでサーバーごとに GROUP BY できるようにするためです。
クエリ内の関数がリクエスト元のサーバーで実行されるものの、リモートサーバーで実行する必要がある場合は、その関数を 'any' 集約関数でラップするか、GROUP BY のキーに追加できます。
SQL ユーザー定義関数
ラムダ式からカスタム関数を作成するには、CREATE FUNCTION ステートメントを使用します。これらの関数を削除するには、DROP FUNCTION ステートメントを使用します。
WebAssembly ユーザー定義関数
WebAssembly ユーザー定義関数 (WASM UDFs) を使用すると、WebAssembly にコンパイルしたカスタムコードを ClickHouse サーバープロセス内で実行できます。
クイックスタート
ClickHouse の設定で、実験的な WebAssembly サポートを有効にします。
<clickhouse>
<allow_experimental_webassembly_udf>true</allow_experimental_webassembly_udf>
</clickhouse>コンパイル済みのWASMモジュールをシステムテーブルに挿入します:
INSERT INTO system.webassembly_modules (name, code)
SELECT 'my_module', base64Decode('AGFzbQEAAAA...');WASM モジュールを使って関数を作成します:
CREATE FUNCTION my_function
LANGUAGE WASM
ABI ROW_DIRECT
FROM 'my_module'
ARGUMENTS (x UInt32, y UInt32)
RETURNS UInt32;クエリではこの関数を使用します:
SELECT my_function(10, 20);詳細情報
詳細については、WebAssembly ユーザー定義関数 のドキュメントを参照してください。
ドライバーベースの実行可能なユーザー定義関数
ドライバー は、ユーザーのコードスニペットを実行可能な 実行可能 UDF に変換する、オペレーター提供のアダプターです。関数を ENGINE = DriverName(...) で作成すると、ClickHouse はドライバーの create_command を実行し、関数シグネチャとコードのボディを渡します。ドライバーはボディをコンパイルするか、別の方法で処理し、実行可能 UDF の設定を出力します。その後、ClickHouse はその設定を保存して読み込みます。
これにより管理者は、サーバーの設定ファイルやファイルシステムへのアクセスを与えることなく、任意の言語 (たとえば、サンドボックス化されたコンテナー内でコンパイルされる C) で関数を定義できる、安全かつ限定的な手段をユーザーに提供できます。利用可能なドライバーのセットは、オペレーターが完全に制御します。
ドライバーの有効化
ドライバーベースの実行可能 UDF は、デフォルトで無効になっています。有効にするには、次の手順を実行します。
-
server configuration で Experimental のゲートを設定します。
<clickhouse> <allow_experimental_executable_udf_drivers>true</allow_experimental_executable_udf_drivers> </clickhouse> -
user_defined_executable_function_drivers_configに 1 つ以上の ドライバー設定 file を指定します (glob を使用可能) 。必要に応じて、生成された実行可能 UDF の configuration が保存されるディレクトリであるdynamic_user_defined_executable_functions_pathも設定します。<clickhouse> <user_defined_executable_function_drivers_config>user_defined_executable_function_drivers_config.d/*_driver.xml</user_defined_executable_function_drivers_config> <dynamic_user_defined_executable_functions_path>/var/lib/clickhouse/dynamic_user_defined_executable_functions/</dynamic_user_defined_executable_functions_path> </clickhouse>
ドライバー registry は server の起動時に読み込まれ、SYSTEM RELOAD CONFIG で更新されるため、server を再起動せずにドライバーを追加、変更、削除できます。
ドライバー設定
ドライバーは、最上位要素として <driver> を持つ XML (または YAML) ファイルで記述します。サポートされるフィールドは次のとおりです。
| Field | Description | Required |
|---|---|---|
name |
CREATE FUNCTION ... ENGINE = <name>(...) で使用するドライバー名です。 |
はい |
create_command |
コードスニペットから UDF を作成する際に呼び出されるプログラムのパスです。相対パスは、ドライバー設定ファイルを基準に解決されます。 | はい |
drop_command |
このドライバーに基づく関数が削除されたときに呼び出されるプログラムのパスです。 | いいえ |
engine_arguments |
ENGINE = DriverName(...) 内で使用できる引数を宣言します。各子要素は引数名で、<required>true</required> という子要素を追加すると、その引数は必須になります。 |
いいえ |
env |
ドライバーコマンドの呼び出し時にエクスポートされる環境変数です。 | いいえ |
ドライバー設定の例:
<clickhouse>
<driver>
<name>DockerC</name>
<create_command>../user_defined_executable_function_drivers/docker_c_create.sh</create_command>
<drop_command>../user_defined_executable_function_drivers/docker_c_drop.sh</drop_command>
<engine_arguments>
<opt_level><required>false</required></opt_level>
</engine_arguments>
<env>
<CLICKHOUSE_C_DRIVER_MEMORY>256m</CLICKHOUSE_C_DRIVER_MEMORY>
<CLICKHOUSE_C_DRIVER_CPUS>1.0</CLICKHOUSE_C_DRIVER_CPUS>
</env>
</driver>
</clickhouse>ドライバー呼び出し契約
CREATE FUNCTION を実行すると、設定された env 変数がセットされた状態で create_command が呼び出され、次の引数が渡されます。
--name <function_name>--return <return_type>(RETURNS句がある場合)--args <signature>(ARGUMENTS句がある場合) 。ここで、signature は宣言された引数の一覧で、たとえばx UInt8, y DateTimeですENGINE = DriverName(key = value)で指定された、宣言済みの各 engine 引数について--<key> <value>
ユーザー code のボディ (AS の後のテキスト) は、コマンドの標準入力に送られます。コマンドは、実行可能 UDF の configuration を標準出力に出力する必要があります。フォーマットは自動検出され、< で始まる出力は XML、それ以外は YAML として扱われます。生成された configuration 内で定義される関数名は、作成する関数名と一致している必要があります。create_command がゼロ以外の status で終了した場合、ステートメントは終了コードとドライバーの標準エラーを含む例外とともに失敗します。
drop_command は、存在する場合、関数が削除されるときに同じ方法で呼び出されます (stdin に code のボディは渡されません) 。
function の作成
CREATE [OR REPLACE] FUNCTION [IF NOT EXISTS] name [ON CLUSTER cluster]
ARGUMENTS (a UInt8, b String) RETURNS UInt64
ENGINE = DriverName(key1 = 'value1', key2 = 42)
AS '...code body...'ClickHouse はドライバーの create_command を実行し、生成された設定を dynamic_user_defined_executable_functions_path に書き込みます。すると、既存の実行可能 UDF ローダーがそれを検出します。その後、その関数は他の関数と同様に呼び出せます。
関数の削除
DROP FUNCTION [IF EXISTS] name [ON CLUSTER cluster]DROP FUNCTION は、ドライバーの drop_command (存在する場合) を呼び出し、生成された動的設定と関数ごとの作業ディレクトリを削除し、実行可能 UDF ローダーを再読み込みして、永続化されたクエリを削除します。
永続化と再起動
元のクエリは、ユーザー定義 SQL オブジェクトディレクトリに ATTACH FUNCTION ... ステートメントとして永続化されるため、サーバーを再起動しても関数は保持されます。起動時には、dynamic_user_defined_executable_functions_path 内の生成済み構成が、ドライバーを再実行することなく直接読み込まれます。永続化された ATTACH FUNCTION に対応する生成済み構成がない場合 (たとえば動的ディレクトリが失われた場合) 、それを再作成するためにドライバーが再実行されます。
制限事項
- この機能は実験的で、
allow_experimental_executable_udf_driversを有効にした場合にのみ利用できます。 - ドライバーベースの関数は、レプリケーション対応のユーザー定義関数ストレージ (
ON CLUSTERおよび<user_defined_zookeeper_path>) ではサポートされていません。レプリケートされるのは生成されたアーティファクトではなく、元のクエリだけであるためです。 - バックアップされたドライバーベースの関数を
RESTOREすると、クエリ自体は保持されますが、ドライバーは再実行されません。生成された設定は、その後の再起動時の復旧によって実体化されます。
例: C ドライバー
ソースツリーには、C の関数本体をコンパイルして実行する概念実証用ドライバーが programs/server/user_defined_executable_function_drivers_config.d/ 配下に含まれています。これらはあくまで例であり、パッケージには含まれず、インストールもされません。
DockerC- サンドボックス化された Docker コンテナー内でコードをコンパイルして実行し (--network=none --read-only --cap-drop=ALL --security-opt=no-new-privilegesに加えて、メモリ/CPU/PID 制限も適用) 、executable_poolUDF を生成します。GVisorC- コンパイル済みバイナリーを gVisor のrunscランタイム上で実行するバリアントです。UnsafeC- サンドボックスを使用せず、ホスト上でコードを直接コンパイルして実行します。名前のとおり分離は行われないため、信頼できる環境とテスト用途でのみ使用することを想定しています。
これらのサンプルドライバーは出発点として用意されています。信頼できないユーザーに公開する前に、利用環境に合わせてサンドボックス化の内容を確認し、強化してください。