- SQL を使用する
- コンソールと独自のコード を使用する
- Cloud API を使用する (ベータ)
- Terraform を使用する (ベータ)
SQL ユーザー定義関数
SQL UDF は、ラムダ式を使ってCREATE FUNCTION ステートメントで作成できます。
この例では、シンプルな実行可能ユーザー定義関数 isBusinessHours を作成します。
この関数は、特定のタイムスタンプが通常の営業時間内かどうかを確認し、該当すれば true、そうでなければ false を返します。
- Cloud Console にログインし、SQL コンソールを開きます
- 次の SQL クエリを入力して
isBusinessHours関数を作成します:
- 新しく作成したUDFをテストするには、以下を実行します。
DROP FUNCTIONコマンドを使って、先ほど作成した UDF を削除できます。
- セッションレベルの設定 (
SETステートメントで設定したもの) は、UDF の実行コンテキストには引き継がれません - ユーザープロファイルの設定は UDFs に継承されません
- クエリレベルの設定は UDF の実行中には適用されません
実行可能ユーザー定義関数
実行可能 UDF は、サービスの各レプリカと並行して動作するサンドボックス化されたプロセス内でコードを実行します。python3.11 ランタイム上で動作する Python スクリプト、または Native ランタイム 上で動作するプリコンパイル済みのバイナリをアップロードできます。
この例では、特定の timestamp が通常の営業時間内かどうかを判定する、前回と同じシンプルな実行可能ユーザー定義関数 isBusinessHours を作成します。
前回は SQL を使って作成しましたが、今回は Python で実装し、コンソールから設定します。
1
Pythonファイルを作成する
ローカルに新しいファイル Python のランタイムは
main.py を作成します:python3.11 です。Python スクリプトでサードパーティ製パッケージをインポートする場合は、それらを requirements.txt ファイルに記載してください。ClickHouse Cloud が稼働する 2 つの CPU アーキテクチャ (x86-64 と ARM64) の両方に対して、ClickHouse Cloud がパッケージを自動でインストールします。依存関係を ZIP に直接同梱することもできますが、その場合は両方の CPU アーキテクチャ向けにキャッシュ済みパッケージを含める必要があります。そのため、requirements.txt を使う方が簡単です。例:ClickHouse Cloud は、次の手順でコンソールからアップロードする zip ファイル内に
main.py が含まれていることを前提としています。
ファイル名を別の名前にすると、エラーが発生します。2
依存関係とローカルファイルをまとめる
このチュートリアルでは依存関係や追加ファイルがないため、アーカイブには 依存パッケージや追加のローカルファイル (Python コード内では、これは、次のような場合に便利です:
main.py だけを含めれば十分です:requirements.txt、wheel ファイル、設定ファイル、データファイルなど) を含める場合は、それらを main.py と同じディレクトリに配置したうえで、そのディレクトリ内で zip を実行し、main.py がアーカイブのルートに配置されるようにします。os.path.dirname(os.path.abspath(__file__)) を使用して、ローカルにバンドルされたパスのベースディレクトリを参照できます。これは ZIP アーカイブ内で main.py が置かれているディレクトリの絶対パスを返すため、同梱されている他のバンドルファイルにもアクセスできます:- UDF に同梱した設定ファイルにアクセスする
- カスタム依存関係用の wheel パッケージを読み込む
- 追加のスクリプトやデータファイルを参照する
3
コンソールで UDF を作成する
- Cloud console のホームページで、左下のメニューにある組織名をクリックします。
- メニューから User-defined functions を選択します。
- ユーザー定義関数のページで Set up a UDF をクリックします。画面右側に設定パネルが開きます。
- 関数名を入力します。この例では
isBusinessHoursを使用します。 - 関数の種別として、Executable pool または Executable のいずれかを選択します。
- Executable pool: 長時間稼働するプロセスのプールを一度だけ起動し、ブロックやクエリをまたいで再利用します。ほぼすべてのワークロードでこちらを推奨します。
- Executable: 処理するデータブロックごとに新しいプロセスを起動します。呼び出し間でプロセスに状態を保持させてはならない場合にのみ使用してください。
- Runtime type を選択します。この例では
python3.11のままにします。プリコンパイル済みのバイナリをアップロードする場合は、代わりに Native を選択します。詳細は Native runtime を参照してください。 - この例では、デフォルトの設定を使用します。設定パラメーターの完全な一覧と Cloud でのデフォルト値については、設定リファレンス を参照してください。
- Browse File をクリックし、このチュートリアルの冒頭で作成した
.zipファイルをアップロードします。 - 新しい引数を追加します。この例では、型が
DateTimeの引数timestampを追加します。 - 戻り値の型を選択します。この例では
Boolを選択します。 - Create UDF をクリックします。現在のビルドステータスを示すダイアログが表示されます。
- 問題がある場合、ステータスは error に変わります。
- 問題がなければ、ステータスは building から provisioning へと進みます。プロビジョニングを完了するには、サービスが起動している必要があります。サービスがアイドル状態の場合は、UDF details パネルでサービス名の横にある Wake Up Service をクリックしてください。
- 完了すると、ステータスは deployed に変わります。
4
UDF をテストする
- ページ左上の Settings - return to your service view をクリックして、SQL Console のホームページに戻ります
- 左側のメニューで SQL Console をクリックします
- 次のクエリを入力します。
5
新しいバージョンを作成
UDF のコードや設定を変更するには、新しいバージョンを作成します。バージョンはイミュータブル (変更不可) です。Edit パネルで管理できるのは UDF をどのサービスに割り当てるかのみです。そこでファイルをアップロードしても、デプロイ済みのコードは置き換えられません。
- Cloud console のホームページで、左下のメニューにある組織名をクリックします。
- メニューから User-defined functions を選択します。
isBusinessHoursUDF の Actions の下にある三点メニューを選択し、Create new version をクリックします。- 変更したコードを含む zip をアップロードするか設定を変更し、新しいバージョンを適用するサービスを選択してから、Create new version をクリックします。
executable_pool UDF の場合、長時間稼働する pool プロセスは、pool がリロードされるまで以前のバージョンを実行し続けます。UDF パネルでサービスの横にある Reload UDF アクションを使用してください。このアクションは、そのサービス上で SYSTEM RELOAD FUNCTION ON CLUSTER default isBusinessHours を実行します。これで、コンソールから最初のユーザー定義関数を追加して動作を確認し、必要に応じて新しいバージョンを作成する方法も確認できました。Native ランタイム (コンパイル済みバイナリ)
Native ランタイムを使用すると、Python スクリプトの代わりにプリコンパイル済みのバイナリをアップロードできます。UDF の作成時に ランタイム種別 を Native に設定してください (API ではruntime: native) 。サンドボックスには何もインストールされないため、バイナリが必要とするものはすべて事前にコンパイルして組み込んでおく必要があります。
現在サポートされているのは、Rust、Go、C++、および Bun でバイナリにコンパイルした JavaScript です。Bun はバージョン 1.4.0 および 1.4.2 で動作確認済みであり、それ以外のバージョンでは動作しない可能性があります。
アーカイブには、CPU アーキテクチャごとに実行可能ファイルを 1 つずつ、それぞれ個別のフォルダに格納する必要があります。
main とする必要があり、引数なしで実行されます。モードやサブコマンドを指定したい場合は、通常の関数引数として渡してください。
静的リンクされたバイナリの使用を強く推奨します。静的リンクされたバイナリはホストのシステムライブラリに依存しないためです (システムライブラリが発行するシステムコールはサーバーのバージョンによって異なる場合があります) 。また、サンドボックスのシステムコールポリシーも静的リンクされたバイナリを前提に調整されています。動的リンクされたバイナリは、glibc のみにリンクし、かつ glibc 2.35 以前でビルドされている場合に限り動作します。Bun でコンパイルされた JavaScript は常に動的リンクになります。
Native UDF ではネットワークアクセスを有効にできません。アウトバウンドのネットワークアクセスを利用できるのは Python ランタイムのみです。ネットワークアクセスを参照してください。
両方のアーキテクチャ向けにバイナリをビルドします。Rust の場合は、まず両方のターゲットを一度だけ追加し、それぞれビルドします。
amd64/main と arm64/main にコピーしてください。
wire protocol は Python ランタイムと同じです。プロセスは stdin から引数を読み取り、設定されたフォーマットで結果を stdout に書き込みます。詳しくは フォーマットと chunk ヘッダー を参照してください。llm-token-udf リポジトリには、Rust および Go で実装された Native UDF とビルドスクリプトが含まれています。
設定リファレンス
以下の設定は、コンソールおよび API で指定できます。デフォルト値は ClickHouse Cloud のものであり、一部はオープンソース版のデフォルトと異なります。
以下のオープンソース版の設定は ClickHouse Cloud 側で管理されており、UDF ごとに変更することはできません:
command_termination_timeout、lifetime、stderr_reaction、check_exit_code、execute_direct。pool_size のデフォルトは、オープンソース版では 16 ですが、Cloud では 3 です。オープンソース版の設定の完全な一覧については、実行可能ユーザー定義関数を参照してください。
関数名は ^[A-Za-z][A-Za-z0-9_]*$ に一致し、組織内で一意である必要があります。また、ClickHouse の組み込み関数と同じ名前は使用できず、予約名は API によって拒否されます。すべての引数には同じパターンに従う名前を付ける必要があり、API および Terraform ではフォーマットを問わずこれが必須です。シンボリックリンクを含むアーカイブは拒否されます。
フォーマットと chunk ヘッダー
TabSeparated はデフォルトのフォーマットであり、最も単純なフォーマットでもあります。双方でタブ、改行、バックスラッシュをエスケープする必要があります。RowBinary はバイナリセーフで、ClickHouse 側でのテキストのフォーマットやパースが不要になります。大きな文字列を扱う場合はこちらを推奨します。
Send chunk header を有効にすると、ClickHouse は各 chunk の前に行数を 10 進数のテキストで書き込み、その後に \n を続け、さらに設定されたフォーマットで行を書き込みます。プロセスはちょうどその行数だけ読み取り、同じ数の結果を書き込んで、1 回だけ flush する必要があります。このヘッダーがない場合、TabSeparated、RowBinary、JSONEachRow などの行指向フォーマットを読み取り、出力をバッファリングするプロセスには、Blockの終わりを判断する手段がありません。その結果、クエリは読み取りタイムアウトに達するまでハングします。行ごとに flush しない UDF では、必ずこの設定を有効にしてください。Native フォーマットはBlock境界の情報をもともと含んでいるため、Native を使用する UDF はヘッダーなしでも、1 つのBlockを読み取り、1 つのBlockを書き込んで flush できます。
ネットワークアクセス
ネットワークアクセスは Python ランタイムでのみ利用できます。Native UDF ではネットワークアクセスを有効にできません。コンソールで作成した Python UDF はネットワークアクセスが有効な状態でビルドされ、コンソール上にこれを切り替える設定はありません。API では、sandboxType で、アウトバウンド接続を許可する netenable と許可しない basic のいずれかを選択します。Terraform プロバイダーでは、同じ選択肢が sandbox_type として提供されています。
ネットワークアクセスを有効にすると、パブリックインターネット上のエンドポイントへのアウトバウンド接続が許可されます。プライベートネットワーク、リンクローカルアドレス、内部アドレスへの接続はブロックされます。サンドボックスにはクラウドのアイデンティティがありません。インスタンスロールもメタデータエンドポイントもなく、環境変数も継承されないため、UDF が使用できるのはユーザーが明示的に渡した認証情報のみです。ネットワークアクセスは UDF のバージョンをビルドする時点で適用されます。そのため、ネットワークアクセスなしで作成した既存のバージョンで利用するには、新しいバージョンを作成する必要があります。
現時点では、UDF 用のシークレットストアはありません。設定ファイルを zip にバンドルし、__file__ からの相対パスで読み込んでください。キーは決して SQL に記述しないでください。
メモリ制限
メモリ制限は、各サンドボックスプロセスが使用できるメモリの上限を MiB 単位で設定します。未設定の場合は、サンドボックスのデフォルト値である 4 GiB が適用されます。 この制限は常駐メモリではなく仮想アドレス空間に対して適用されます。そのため、大きなアドレス範囲を予約するランタイムでは、実際の使用量を大きく上回るヘッドルームを確保する必要があります。たとえば Go は、起動時に 1 GiB を大きく超えるアドレス空間を予約します。プロセスが制限を超えると、プロセス内でメモリの割り当てが失敗します。Python UDF の場合はMemoryError のトレースバックを出力して終了し、クエリはその stderr の内容をエラーメッセージに含めて失敗します。
決定論的関数とクエリキャッシュ
デフォルトでは、UDF は非決定論的として扱われます。use_query_cache = 1 を指定して UDF を呼び出すクエリは、query_cache_nondeterministic_function_handling を変更しない限り、QUERY_CACHE_USED_WITH_NONDETERMINISTIC_FUNCTIONS エラーで失敗します。
決定論的 は、関数が同じ引数に対して常に同じ結果を返す場合にのみ有効にしてください。有効にすると、その関数を呼び出すクエリの結果をクエリキャッシュに保存し、キャッシュから返せるようになります。ただし、同じクエリ内に now、today、rand などの他の非決定論的関数が含まれている場合は、デフォルトの throw ではやはりキャッシュされません。query_cache_nondeterministic_function_handling = 'save' を指定するとそのようなクエリもキャッシュされ、'ignore' を指定するとキャッシュせずに実行されます。
サービスの再起動
監視
ClickHouse 26.6 以降では、実行可能 UDF のリソース使用量が 8 つのカウンターに記録されます。これらのカウンター名にはすべてExecutableUserDefinedFunction というプレフィックスが付きます。クエリ単位では、system.query_log の ProfileEvents Map カラムのエントリとして記録されます。サーバー全体では、サーバー起動以降の累積値として system.events の行に、また時系列でサンプリングされた値として system.metric_log の ProfileEvent_<name> カラムに記録されます。カウンターの接尾辞は次のとおりです。
system.asynchronous_metrics には ExecutableUserDefinedFunctionProcesses と ExecutableUserDefinedFunctionMemoryResidentBytes があります。後者は生存中の UDF プロセスの VmRSS を合計した値です。共有ページはプロセスごとに重複してカウントされるため、この値は上限値となります。
system.query_log.memory_usage の対象は ClickHouse サーバープロセスのみで、UDF プロセスのメモリ使用量は含まれません。パフォーマンスに関するガイダンス
executable_pool を使用してください。executable ではデータのBlockごとに新しいプロセスが起動されるため、Blockのたびに起動コストが発生します。
呼び出しのたびに固定コストがかかります。処理された行数に比べて ExecutableUserDefinedFunctionInvocations が非常に大きい場合は、そのクエリの preferred_block_size_bytes を引き上げ、1 つのBlockにより多くの行が含まれるようにしてください。
1 つのクエリが同時に使用するプールプロセスは最大で max_threads 個です。そのため、プールサイズを max_threads より大きくしても、そのクエリには効果がありません。まずは小さい値から始め、ExecutableUserDefinedFunctionPoolWaitMicroseconds が無視できない値になったらプールサイズを引き上げてください。
すべての行がプロセス境界を越えて受け渡されます。UDF は SQL では表現できないロジックに使うものであり、組み込み関数や SQL UDF で済む単純な行単位の処理に使うべきではありません。
トラブルシューティング
UDF が失敗すると、クエリはエラーコード754 (UDF_EXECUTION_FAILED) で失敗します。子プロセスの stderr はエラーメッセージに含まれるため、診断情報は stderr に出力し、0 以外の終了コードで終了するようにしてください。
- クエリがハングした後、タイムアウトで失敗する: Send chunk header を有効にしないまま、プロセスが行指向フォーマットで出力をバッファリングしています。この設定を有効にして chunk ごとに 1 回 flush するか、
Nativeフォーマットを使用している場合は Block ごとに 1 回 flush してください。 - リターンコード 1 で終了し、
MemoryErrorのトレースバックが出力される (Python): メモリ制限を超過しています。メモリ制限 を引き上げるか、プロセスあたりのメモリ使用量を削減してください。 - 高負荷時に種別
executableの関数が断続的に失敗する:executable_poolに切り替えてください。 - ビルドのステータスが error になる: zip のルートに
main.py(Python) またはamd64/mainとarm64/main(Native) が存在しないか、アーカイブにシンボリックリンクが含まれています。 - 関数を作成できない: 関数名が組み込み関数と重複しています。
Cloud API を使用して UDF を管理する
コンソールのライフサイクル操作は、ClickHouse Cloud API を通じてプログラムからも利用できます。 UDF エンドポイントを使用すると、ソースアーカイブのアップロード、関数とバージョンの作成、サービスへのアタッチとデタッチ、バージョンと関数の削除をスクリプトで自動化できます。API にはリロード操作がありません。executable_pool UDF の新しいバージョンをサービスにアタッチした後は、そのサービス上で SQL により SYSTEM RELOAD FUNCTION ON CLUSTER default <name> を実行し、プールのプロセスに新しいバージョンを読み込ませてください。
これらのエンドポイントはベータ版であり、API コントラクトは変更される可能性があります。
- 署名付き
application/zipアップロード URL を取得するためにアップロード URL を作成し、その URL に ZIP アーカイブをアップロードします。各アップロード ID は、作成またはバージョン作成の 1 回の試行にのみ使用できます。再試行する場合は、新しいアップロード URL をリクエストしてください。 - アップロードしたアーカイブから UDF を作成し、関数名、ランタイム、引数、戻り値の型を指定します。
- UDF をサービスにアタッチします。バージョンを省略した場合は、最新の準備完了バージョンがアタッチされます。サービスは実行中である必要があります。アイドル状態のサービスは事前に起動できます。
エンドポイントの一覧は次のとおりです。
リクエストおよびレスポンスのスキーマについては、UDF API リファレンスを参照してください。
Terraform で UDF を管理する
公式の ClickHouse Terraform プロバイダーには、Infrastructure as Code として UDF を管理するためのリソースが 2 つ含まれています。clickhouse_udfは、関数自体を管理します。関数のソースコードを含む ZIP アーカイブを受け取り、アーカイブのハッシュが変更されるたびに新しいバージョンをパブリッシュし、ビルドの完了を待機します。clickhouse_udf_attachmentは、UDF のバージョンをサービスにアタッチします。サービスに関連付けられる関数のバージョンは、一度に最大 1 つです。固定のバージョン番号を指定することも、clickhouse_udf.<name>.versionを参照してサービスを最新バージョンに自動更新することもできます。
これらのリソースは、プロバイダーのバージョン 3.24.0 以降で利用できます。ベータ版であり、今後のプロバイダーバージョンで動作が変更される可能性があります。
isBusinessHours UDF を Terraform でデプロイするには、次のようにします。
clickhouse_udf リソースは format、pool_size、max_command_execution_time、command_read_timeout、command_write_timeout、send_chunk_header、sandbox_type、sandbox_version、return_name、fail_on_build_error も受け付けます。完全なスキーマについては、リソースのドキュメントを参照してください。
新しいバージョンを本番環境に反映する前に開発用サービスでテストするには、一方のアタッチメントは最新バージョンに追従させ、もう一方は変数でバージョンを固定します。開発用のアタッチメントは新しいビルドのたびに自動的に追従し、本番用のアタッチメントは var.prod_udf_version を変更したときにのみ更新されます。
deterministic とメモリ制限は、プロバイダーではまだサポートされていません。どちらもバージョンの作成時に設定する項目であるため、プロバイダーでパブリッシュしたバージョンにはこれらを設定できません。これらを使用する場合は、コンソールまたは API からバージョンを作成してください。executable_pool UDF の場合は、アタッチメントのバージョンを変更した後に、サービス上で SYSTEM RELOAD FUNCTION ON CLUSTER default <name> を実行してください。clickhouse_udf リソースを削除すると、関数のすべてのバージョンが削除され、すべてのサービスからデタッチされます。