メインコンテンツへスキップ

WebAssembly ユーザー定義関数

ClickHouse では、WebAssembly で記述したユーザー定義関数 (UDF) を作成できます。これにより、Rust、C、C++ などの言語で実装したカスタムロジックを、WebAssembly モジュールとしてコンパイルして実行できます。

概要

WebAssembly モジュールは、ClickHouse から呼び出せる 1 つ以上の関数を含むコンパイル済みのバイナリファイルです。 モジュールは、一度読み込んだら何度でも再利用できるライブラリや共有オブジェクトのようなものだと考えてください。 UDF を含む WebAssembly モジュールは、Rust、C、C++ など、WebAssembly にコンパイル可能な任意の言語で記述できます。 WebAssembly にコンパイルされたコード (「ゲスト」コード) は、ClickHouse (「ホスト」) によって実行され、専用のメモリ空間にのみアクセスできるサンドボックス環境で動作します。 ゲストコードは、ClickHouse から呼び出せる関数をエクスポートします。これには、カスタムロジックを実装する関数 (UDF の定義に使用されるもの) に加え、メモリ管理や ClickHouse と WebAssembly コード間でのデータ交換に必要な補助関数も含まれます。 コードは、オペレーティングシステムや標準ライブラリに依存しない「フリースタンディング」WebAssembly (別名 wasm32-unknown-unknown) としてコンパイルする必要があります。また、サポートされるのはデフォルトの 32 ビット WebAssembly ターゲットのみです (wasm64 拡張はサポートされません) 。 モジュールは、ClickHouse と連携するために、サポートされている通信プロトコル (ABI) のいずれかに従う必要があります。 コンパイル後、モジュールのバイナリコードは system.webassembly_modules テーブルに insert することで ClickHouse に読み込まれます。 その後、CREATE FUNCTION ... LANGUAGE WASM ステートメントを使用して、モジュールがエクスポートした関数を参照する UDF を作成できます。

前提条件

ClickHouse の設定で、WebAssembly サポートを有効にします。
利用可能なエンジン実装:
  • wasmtime (デフォルト、推奨) — WasmTime を使用します
  • wasmedgeWasmEdge を使用します

クイックスタート

この例では、コラッツ予想の計算機を実装しながら、WebAssembly UDF を作成する一連のワークフロー全体を示します。 コードは WebAssembly Text 形式 (WAT) で記述します。これは WebAssembly を人間が読める形で表現したものなので、この段階ではプログラミング言語は不要です。 ClickHouse ではモジュールがバイナリ形式である必要があるため、トランスパイラを使用して WAT を WASM に変換します。 この変換には、WebAssembly Binary Toolkit (WABT)wat2wasm、または wasm-toolsparse コマンドを使用できます。
上のスニペットでは、FORMAT RawBlob を使ってバイナリの WASM コードを ClickHouse client に直接パイプし、system.webassembly_modules テーブルに挿入しています。 次に、モジュールからエクスポートされた steps 関数を参照する UDF を定義します。
UDF名とは異なるため、:: の後にはモジュール内の関数名を指定している点に注意してください。 これで、クエリで collatz_steps 関数を使用できます。
number カラムは、WebAssembly 関数では CREATE FUNCTION ステートメントで指定されたシグネチャどおりに型が完全に一致している必要があるため、明示的に UInt32 にキャストされています。 その結果、1 から 100 までの数に対する Collatz のステップ数列が得られ、これは OEIS の数列 A006577 に対応します。

system table 経由での WASM モジュール管理

WebAssembly モジュールは system.webassembly_modules テーブルに、次の構造で格納されます。
  • カラム
    • name String — モジュール名。空は不可で、使用できるのは単語文字のみです。
    • code String — 生のバイナリ WASM コード。書き込み専用で、読み出し時には空文字列が返されます。
    • hash UInt256 — モジュールバイナリの SHA256 (ディスク上には存在するが、まだ読み込まれていない場合は 0) 。
モジュールの管理は、このテーブルに対する標準的な SQL 操作で行います:

モジュールを追加する

必要に応じて、整合性確認用のハッシュを指定します。
指定されたハッシュ値がモジュールコードの算出済み SHA256 と一致しない場合、挿入は失敗します。これは、S3 や HTTP などの外部ソースからモジュールを読み込む際に役立ちます。

クラスター全体にモジュールを配布する

system.webassembly_modules はインスタンスごとのテーブルであり、INSERT は接続を処理しているレプリカにしか反映されません。INSERT ステートメントには ON CLUSTER 形式がないため、その後の CREATE FUNCTION ... ON CLUSTER は、モジュールがないレプリカでは失敗します。
insert をすべてのノードに分散するには、ローカルの system.webassembly_modules テーブルではなく、cluster テーブル関数に書き込みます。
この方法は、基盤となる分散書き込み経路が各分片内のすべてのレプリカを巡回することを前提としていますが、これはクラスターが internal_replication=false に設定されている場合にのみ起こります。internal_replication=true (ReplicatedMergeTree を使ってレプリケーションを自前で処理するクラスターのデフォルト) では、insert は分片ごとに正常な 1 つのレプリカにのみ送られ、system.webassembly_modules はこの経路ではレプリケーションされません。そのため、一部のレプリカには引き続きモジュールが存在しないままになります。この構成では、各レプリカに対して個別に insert する必要があります。たとえば、system.clusters を順に処理してホストごとに remote(...) 経由で書き込むか、すべてのホストの user_scripts/wasm/ に binary をコピーします。クラスターの internal_replication は、SELECT cluster, shard_num, internal_replication FROM system.clusters で確認できます。
このようにファンアウトして insert した後は、モジュールがすべてのレプリカに存在するようになり、CREATE FUNCTION ... ON CLUSTER が成功します:
clusterAllReplicas を使うと、すべてのレプリカでモジュールが読み込まれていることを確認できます。
system.webassembly_modules への挿入は、同じ (name, hash) の組み合わせに対しては冪等です。そのため、分散された insert を再実行しても安全であり、レプリカの置き換え後に状態を修復する現実的な方法でもあります。なお、新たに追加されたサーバーには既存のモジュールは自動では配布されません。更新後のクラスターに対して insert を再実行するか、新しいホストの user_scripts/wasm/ ディレクトリにバイナリを配置する必要があります。

モジュールを一覧表示

モジュールを削除する

削除には DELETE FROM system.webassembly_modules WHERE name = '...' ステートメントを使用します。 条件式には、完全一致の name = 'literal' または、名前がパターンに一致するすべてのモジュールを削除する name LIKE 'pattern' のいずれかを指定する必要があります。これ以外の形式は受け付けられません。
既存のUDFsのいずれかが該当するモジュールのいずれかを参照している場合、削除は失敗するため、先にそれらのUDFsをドロップする必要があります。

WebAssembly UDFを作成する

構文:
パラメータ:
  • function_name: ClickHouse 内の関数名。モジュール内でエクスポートされた関数名と異なる場合があります。
  • FROM 'module_name' :: 'source_function_name': 読み込まれた WASM モジュール名と、使用する WASM モジュール内の関数名 (デフォルトは function_name)
  • ARGUMENTS: 引数名と型の一覧 (名前は省略可能で、名前付きフィールドをサポートするシリアライゼーションフォーマットで使用されます)
  • ABI: Application Binary Interface のバージョン
    • ROW_DIRECT: 直接的な型マッピング、行単位の処理
    • BUFFERED_V1: シリアライゼーションを伴うブロックベースの処理
    • ASSEMBLYSCRIPT: AssemblyScript コンパイラで生成されたモジュール向けの行単位処理。数値型は AssemblyScript のプリミティブにマップされ、ClickHouse String は AssemblyScript string にマップされます。
  • DETERMINISTIC: 関数を決定論的として宣言します。つまり、同じ入力に対して常に同じ出力を返します。指定すると、ClickHouse はすべての引数が定数である呼び出しを定数畳み込みすることがあります。関数はクエリ解析時に一度評価され、その結果がすべての行で再利用されます。
  • SHA256_HASH: 検証用の想定モジュールハッシュ (省略した場合は自動入力) 。異なるレプリカ間で正しい WASM モジュールが読み込まれていることを保証するために使用できます。
  • SETTINGS: 関数ごとの設定
    • serialization_format String — ABI で必要なシリアライゼーションフォーマット。サポートされる値: MsgPack, JSONEachRow, CSV, TSV, TSVRaw, RowBinary, Buffers。デフォルト: MsgPackBuffers などのブロックベースのフォーマットでは、宣言された関数シグネチャに一致する型の単一カラムを返す必要があります。
    • webassembly_udf_enable_fuel Bool — 関数の有限 fuel バジェットを有効にします。デフォルト: truefalse の場合、この関数ではクエリレベルの設定 webassembly_udf_max_fuel は無視されます。fuel 制限を無効にすると、wasmtime engine の使用時にパフォーマンスが向上することがあります。ただし、信頼できない ゲストコード や不具合のある ゲストコード では、暴走実行のリスクが高まる可能性があります。

ABI バージョン

ClickHouse と連携するには、WebAssembly モジュールがサポート対象の ABI (Application Binary Interface) のいずれかに準拠している必要があります。
  • ROW_DIRECT: 直接型マッピング (プリミティブ型 Int32UInt32Int64UInt64Float32Float64 のみ)
  • BUFFERED_V1: シリアライゼーションを伴う複合型
  • ASSEMBLYSCRIPT: AssemblyScript モジュールとの行ごとの相互運用。数値型と String をサポートします。

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 メモリはバッファへのポインタ (データへのポインタとデータサイズで構成) として渡されます。したがって、wasm 側のユーザー定義関数は常に 2 つの i32 引数を受け取り、1 つの i32 値を返します。 ゲストコードはデータを処理し、シリアライズされた結果データを含む結果バッファへのポインタを返します。 ゲストコードは、これらのバッファを作成および破棄するための 2 つの関数を提供する必要があります。
C の定義例:

ABI ASSEMBLYSCRIPT

AssemblyScript コンパイラで生成されたモジュールを対象とします。各行につき 1 回、エクスポートされた関数が呼び出され、その際 ClickHouse の値は AssemblyScript のプリミティブ型および string オブジェクトにマッピングされます。 サポートされる型:
  • 数値: Int8/UInt8Int16/UInt16 (境界では i32 に拡張) 、Int32/UInt32Int64/UInt64Float32Float64
  • String — AssemblyScript の string にマッピングされます (WASM メモリ内では UTF-16) 。ClickHouse が UTF-8 ↔ UTF-16 の変換を自動的に処理します。
  • カスタム AssemblyScript クラスは、引数型または戻り値の型としてはサポートされていません。実行時クラス ID がコンパイルごとに安定しないためです (AssemblyScript#2982 を参照) 。
モジュール要件: モジュールは、__new__pin__unpin がエクスポートされるよう、AssemblyScript の managed runtime でコンパイルする必要があります。標準の入力/出力 string 処理はこれらを前提としています。推奨される呼び出しは次のとおりです:
AssemblyScript は、実行時トラップ (メモリ不足、境界チェックなど) 用に env.abort もインポートします。ClickHouse はこのインポートを自動的に提供します。abort がトリガーされると、実行中のクエリは、デコードされた AssemblyScript のメッセージとソース位置を含む WASM_ERROR 例外で失敗します。 :
asc でコンパイルし、生成された .wasmsystem.webassembly_modules にロードした後、UDFs を次のように宣言します。

RustでUDFを開発する際の注意

Rustプログラム向けには、ClickHouse用のWebAssembly UDF開発を簡単にするヘルパーcrate clickhouse-wasm-udf を提供しています。このcrateにはメモリ管理用の関数が含まれているため、clickhouse_create_bufferclickhouse_destroy_buffer を手動で実装する必要はなく、依存関係として追加するだけで済みます。さらに、通常のRust関数を必要なABI形式でラップするマクロ #[clickhouse_wasm_udf] も用意されています。 このcrateを使うと、次のようにUDFを書けます。
マクロは、バッファ構造体を受け取り、返すラッパー関数を生成し、serde を使用してシリアライゼーション/デシリアライゼーションを自動的に処理します。

モジュールから利用できるホスト API

モジュールは、以下のホスト関数をインポートして使用できます。
  • clickhouse_server_version() -> i64 — ClickHouse server のバージョンを整数で返します (例: v25.11.1.1 の場合は 25011001) 。
  • clickhouse_throw(ptr: i32, size: i32) — 指定されたメッセージでエラーをスローします。エラーメッセージ文字列を含むメモリ位置へのポインタと、その文字列のサイズを受け取ります。
  • clickhouse_log(ptr: i32, size: i32) — ClickHouse server のテキストログにメッセージを記録します。
  • clickhouse_random(ptr: i32, size: i32) — メモリをランダムなバイト列で埋めます。
  • env.abort(message: i32, fileName: i32, line: i32, column: i32) — AssemblyScript 互換モジュール向けに提供されています。これを呼び出すと (またはそれを呼び出す AssemblyScript runtime トラップがトリガーされると) 、デコードされたメッセージとソース位置を含む WASM_ERROR 例外によって UDF が終了します。env.abort をインポートしないモジュールには影響ありません。

設定

以下のクエリレベルの設定で、WebAssembly UDF の実行を制御できます。
  • webassembly_udf_max_fuel — WebAssembly UDF インスタンスの実行ごとの fuel 上限です。各 WebAssembly 命令は一定量の fuel を消費します。この値はランタイムに渡される前に 1024 倍されるため、webassembly_udf_max_fuel = 1 はおよそ 1024 fuel 単位に相当します。有限の制限を設けない場合は 0 に設定します。これは、関数ごとの設定 webassembly_udf_enable_fuel が true の関数にのみ適用され、これがデフォルトです。
  • webassembly_udf_max_memory — WebAssembly UDF インスタンスごとのメモリ上限です (バイト単位) 。
  • webassembly_udf_max_input_block_size — 1 つのブロックで WebAssembly UDF に渡される最大行数です。すべての行を一度に処理する場合は 0 に設定します。
  • webassembly_udf_max_instances — 関数ごとに並列実行できる WebAssembly UDF インスタンスの最大数です。
使用例:

関連項目

最終更新日 2026年6月25日