WebAssembly ユーザー定義関数
ClickHouse は、WebAssembly で実装されたユーザー定義関数 (UDF) の作成をサポートしています。これにより、Rust や C、C++ などの言語で実装したカスタムロジックを WebAssembly モジュールにコンパイルし、実行できます。
概要
WebAssembly モジュールは、ClickHouse から呼び出すことができる 1 つ以上の関数を含むコンパイル済みバイナリファイルです。 モジュールは、一度ロードして何度も再利用するライブラリまたは共有オブジェクトと考えることができます。
UDF を含む WebAssembly モジュールは、Rust、C、C++ など、WebAssembly へコンパイル可能な任意の言語で記述できます。
WebAssembly にコンパイルされたコード (「ゲスト」コード) と、それを実行する ClickHouse (「ホスト」) は、専用のメモリ空間にのみアクセス可能なサンドボックス環境内で実行されます。
ゲストコードは ClickHouse が呼び出せる関数をエクスポートします。これには、カスタムロジックを実装する関数 (UDF を定義するために使用) に加えて、メモリ管理や、ClickHouse と WebAssembly コード間のデータ交換に必要なサポート関数が含まれます。
コードは、オペレーティングシステムや標準ライブラリへの依存を持たない「freestanding」WebAssembly (wasm32-unknown-unknown) としてコンパイルする必要があります。また、サポートされるのはデフォルトの 32 ビット WebAssembly ターゲットのみです (wasm64 拡張は使用できません) 。モジュールは、ClickHouse と連携するためにサポートされているいずれかの通信プロトコル (ABI) に従わなければなりません。
コンパイル後、モジュールのバイナリコードは system.webassembly_modules テーブルに挿入することで ClickHouse にロードされます。
その後、CREATE FUNCTION ... LANGUAGE WASM ステートメントを使用して、モジュールによってエクスポートされる関数を参照する UDF を作成できます。
前提条件
ClickHouse の設定で WebAssembly サポートを有効化します。
利用可能なエンジン実装:
クイックスタート
この例では、Collatz conjecture (コラッツ予想) の計算機を実装することで、WebAssembly UDF を作成するまでの一連のワークフローを示します。
ここでは WebAssembly Text 形式 (WAT) でコードを記述します。WAT は WebAssembly の人間が読みやすい表現であり、この段階では特定のプログラミング言語は必要ありません。
ClickHouse ではモジュールはバイナリ形式である必要があるため、トランスパイラを使って WAT を WASM に変換します。
この変換を行うには、WebAssembly Binary Toolkit (WABT) の wat2wasm か、wasm-tools の parse コマンドを使用できます。
上記のスニペットでは、バイナリの WASM コードを FORMAT RawBlob を用いてパイプし、直接 ClickHouse クライアントに渡して system.webassembly_modules テーブルに挿入しています。
次に、そのモジュールからエクスポートされている steps 関数を参照する UDF を定義します。
:: の後ろには、UDF 名とは異なるモジュール内の関数名を指定している点に注意してください。
これで、クエリ内で collatz_steps 関数を使用できるようになりました。
number カラムは UInt32 に明示的にキャストされています。これは、WebAssembly 関数が CREATE FUNCTION ステートメントで指定されたシグネチャと型が完全に一致していることを要求するためです。
結果として、1 から 100 までの数値に対する Collatz 手順の数列が得られ、これは OEIS の A006577 に対応します。
system テーブル経由で WASM モジュールを管理する
WebAssembly モジュールは、次の構造を持つ system.webassembly_modules テーブルに保存されます。
- Columns
nameString — モジュール名。空でないこと、かつ英数字およびアンダースコアのみ。codeString — 生のバイナリ形式の WASM コード。書き込み専用で、読み取り時は空文字列を返します。hashUInt256 — モジュールバイナリの SHA256 (ディスク上には存在するがまだロードされていない場合はゼロ) 。
モジュールの管理は、このテーブルに対する標準的な SQL 操作によって行います。
モジュールを追加する
必要に応じて、インテグリティハッシュを指定します:
指定されたハッシュ値がモジュールコードから計算された SHA256 と一致しない場合、挿入は行われません。これは、S3 や HTTP などの外部ソースからモジュールを読み込む際に役立ちます。
モジュールを一覧する
モジュールを削除する
削除は DELETE FROM system.webassembly_modules WHERE name = '...' ステートメントによって行います。
1 回のステートメントにつき、名前が完全一致するモジュールを 1 つだけ削除できます。
既存の UDF がそのモジュールを参照している場合、削除は失敗するため、まずそれらの UDF を削除する必要があります。
WebAssembly UDF を作成する
構文:
パラメータ:
function_name: ClickHouse 内での関数名。モジュール内でエクスポートされている関数名とは異なる場合があります。FROM 'module_name' :: 'source_function_name': 読み込まれる WASM モジュール名と、使用する WASM モジュール内の関数名 (省略時はfunction_nameが既定値) 。ARGUMENTS: 引数名と型のリスト (名前は任意。名前付きフィールドをサポートするシリアライゼーション形式で使用されます)ABI: Application Binary Interface のバージョンROW_DIRECT: 型を直接マッピングし、行単位で処理BUFFERED_V1: シリアライゼーションを伴うブロック単位の処理
SHA256_HASH: 検証用の期待されるモジュールハッシュ (省略時は自動設定) 。異なるレプリカ間で正しい WASM モジュールが読み込まれていることを保証するために使用できます。SETTINGS: 関数ごとの設定serialization_formatString — ABI がシリアライゼーション形式を必要とする場合に使用される形式。既定値:MsgPack。
ABI バージョン
ClickHouse とやり取りするために、WebAssembly モジュールはサポートされているいずれかの ABI (Application Binary Interface) に準拠する必要があります。
ROW_DIRECT: 直接型マッピング (プリミティブ型Int32,UInt32,Int64,UInt64,Float32,Float64のみ)BUFFERED_V1: シリアル化を用いた複合型
ABI ROW_DIRECT
エクスポートされた WASM 関数を、行ごとに直接呼び出します。
- 引数および戻り値の型は数値型
Int32/UInt32/Int64/UInt64/Float32/Float64/Int128/UInt128のみです。 - この ABI では文字列はサポートされません。
- シグネチャは WASM エクスポート (
i32/i64/f32/f64/v128) と一致している必要があります。 - モジュールからエクスポートされるべきサポート関数は不要です。
例えば、次のようなシグネチャを持つ関数です:
次のように作成できます。
WebAssembly は符号付き引数と符号なし引数を区別せず、値の解釈に異なる命令を使用します。そのため、引数のサイズは厳密に一致している必要があり、符号の有無は関数内で行われる演算によって決定されます。
ABI BUFFERED_V1
この ABI は実験的なものであり、将来のリリースで変更される可能性があります。
WASM メモリを介したシリアライズ/デシリアライズにより、一度にブロック全体を処理します。任意の引数および戻り値の型をサポートします。
シリアライズされたデータは、wasm メモリ上のバッファ (データへのポインタとデータサイズから構成される構造体) へのポインタとしてコピーされ、入力の行数とともに UDF 関数に渡されます。したがって、wasm 実行時のユーザー定義関数は常に 2 つの i32 引数を受け取り、単一の i32 値を返します。
ゲストコードはこのデータを処理し、シリアライズされた結果データを含む結果バッファへのポインタを返します。
ゲストコードは、これらのバッファを作成および破棄するための 2 つの関数を提供しなければなりません。
C による定義例:
Rust で UDF を開発する際の注意
Rust プログラム向けに、ClickHouse 用の WebAssembly UDF の開発を容易にするヘルパークレート clickhouse-wasm-udf を提供しています。このクレートはメモリ管理用の関数を提供しているため、clickhouse_create_buffer と clickhouse_destroy_buffer 関数を自前で実装する必要はなく、依存関係としてこのクレートを追加するだけで済みます。また、通常の Rust 関数を要求される ABI 形式にラップするためのマクロ #[clickhouse_wasm_udf] も用意されています。
このクレートを用いることで、次のように UDF を記述できます。
これらのマクロは、バッファ構造体を受け取りおよび返すラッパー関数を生成し、serde を用いてシリアライズ/デシリアライズ処理を自動的に行います。
モジュールで利用可能なホスト API
次のホスト関数をモジュールからインポートして使用できます。
clickhouse_server_version() -> i64— ClickHouse サーバーのバージョンを整数として返します (例: v25.11.1.1 の場合は 25011001) 。clickhouse_throw(ptr: i32, size: i32)— 指定されたメッセージでエラーをスローします。エラーメッセージ文字列を含むメモリ領域へのポインタと、その文字列のサイズを受け取ります。clickhouse_log(ptr: i32, size: i32)— メッセージを ClickHouse サーバーのテキストログに出力します。clickhouse_random(ptr: i32, size: i32)— メモリをランダムなバイトで埋めます。
設定
クエリレベルの以下の設定値により、WebAssembly UDF の実行を制御します:
-
webassembly_udf_max_fuel— WebAssembly UDF インスタンス 1 回の実行ごとの fuel 上限。各 WebAssembly 命令は一定量の fuel を消費します。無制限にするには 0 に設定します。 -
webassembly_udf_max_memory— WebAssembly UDF インスタンス 1 つあたりのメモリ上限 (バイト単位) 。 -
webassembly_udf_max_input_block_size— 単一ブロックで WebAssembly UDF に渡される最大行数。すべての行を一度に処理するには 0 に設定します。 -
webassembly_udf_max_instances— 1 つの関数につき並列実行できる WebAssembly UDF インスタンスの最大数。
使用例: