Python UDFsの設計

このトピックは、Python UDFsを設計するための助けになります。

このトピックの内容:

注釈

ベクトル化されたPython UDFs を使用すると、入力行のバッチを Pandas DataFrames として受け取り、結果のバッチを Pandas配列 または Series として返すPython関数を定義します。バッチインターフェイスにより、機械学習の推論シナリオでのパフォーマンスが大幅に向上します。詳細については、 ベクトル化されたPython UDFs をご参照ください。

データ型の選択

コードを記述する前に、

  • 関数が引数として受け入れるデータ型と、関数が返すデータ型を選択します。

  • タイムゾーン関連の問題を考慮に入れます。

  • NULL 値の処理方法を決定します。

SnowflakeがPythonと SQL データ型をマッピングする方法の詳細については、 SQL-Pythonデータ型マッピング をご参照ください。

TIMESTAMP_LTZ 値とタイムゾーン

Python UDFはほとんどが呼び出される環境から分離されています。ただし、タイムゾーンは呼び出し元の環境から継承されます。呼び出し元のセッションがPython UDFを呼び出す前にデフォルトのタイムゾーンを設定した場合、Python UDFのデフォルトのタイムゾーンは同じになります。タイムゾーンの詳細については、 TIMEZONE をご参照ください。

NULL 値

バリアントを除くすべてのSnowflake型の場合、Python UDFへのSQL NULL 引数はPython None 値に変換され、返されるPython None 値はSQL NULL に変換されます。

バリアント型の値は、SQL NULL またはVARIANT JSON null です。Snowflake VARIANT NULL については、 NULL 値 をご参照ください。

  • VARIANT JSON null はPython None に変換されます。

  • SQL NULL は、 is_sql_null 属性を持つPythonオブジェクトに変換されます。

例については、 NULLPythonでの処理UDFs をご参照ください。

Snowflakeによって課せられた制約の範囲内にとどまるPython UDFsの設計

Snowflake環境内の安定性を確保するために、SnowflakeはPython UDFsに次の制限を設けています。特に明記されていない限り、これらの制限は、 UDF が作成されたときではなく、実行されたときに適用されます。

機械学習(ML)モデルのトレーニングは、リソースを大量に消費する場合があります。Snowpark用に最適化されたウェアハウスは、大量のメモリとコンピューティングリソースを必要とするワークロードに使用できるSnowflake仮想ウェアハウスの一種です。機械学習モデルとSnowpark Pythonの詳細については、 Snowpark Pythonを使用した機械学習モデルのトレーニング をご参照ください。

メモリ

あまり多くのメモリを消費しないようにしてください。

  • データ値が大きいと、大量のメモリを消費する可能性があります。

  • スタックが深すぎると、大量のメモリを消費する可能性があります。

UDFs がメモリを消費しすぎると、エラーを返します。特定の制限は変更される場合があります。

メモリを消費しすぎて UDFs が失敗する場合は、 Snowpark用に最適化されたウェアハウス の使用を検討してください。

時間

呼び出しごとに長い時間がかかるアルゴリズムは避けてください。

UDF の完了に時間がかかりすぎると、Snowflakeは SQL ステートメントを強制終了し、ユーザーにエラーを返します。これにより、無限ループなどのエラーの影響およびコストが制限されます。

モジュールの設計

SQLステートメントがPython UDFを呼び出すと、Snowflakeは作成したPython関数を呼び出します。Python関数は、「ハンドラー関数」または略して「ハンドラー」と呼ばれます。ハンドラーは、ユーザー提供のモジュール内に実装された関数です。

他のPython関数と同様に、関数はモジュールの一部として宣言する必要があります。

ハンドラーは、Python UDFに渡される行ごとに1回呼び出されます。関数を含むモジュールは、行ごとに再インポートされません。Snowflakeは、同じモジュールのハンドラー関数を複数回呼び出すことができます。

コードの実行を最適化するために、Snowflakeは、ハンドラー関数の実行が速い一方で、初期化が遅い可能性があると想定しています。Snowflakeは、初期化を実行するためのタイムアウト(UDF をロードする時間と、モジュールを初期化する時間を含む)を、ハンドラーを実行するためのタイムアウト(入力1行でハンドラーを呼び出す時間)よりも長く設定します。

モジュールの設計に関する追加情報は Python UDFsの作成 にあります。

スカラー UDFs における初期化の最適化とグローバル状態の制御

ほとんどのスカラー UDFs は、以下のガイドラインに従う必要があります。

  • 行間で変化しない共有状態を初期化する必要がある場合は、ハンドラー関数の代わりにモジュールで初期化します。

  • スレッドセーフになるようにハンドラー関数を記述します。

  • 行間で動的状態を保存および共有することは避けてください。

UDFがこれらのガイドラインに従うことができない場合、SnowflakeはスカラーUDFsが独立して処理されることを期待していることに注意してください。システムは任意の順序で行を処理し、それらの呼び出しを複数のインスタンスに分散できるため、呼び出し間で共有される状態に依存すると、予期しない動作が発生する可能性があります。さらに、複数のスレッドで同じPythonインタープリター内で同じハンドラー関数が複数実行される可能性があります。

UDFsはハンドラー関数への呼び出し全体で共有状態に依存することは避けてください。ただし、 UDF に共有状態を保存する必要がある状況は2つあります。

  • 行ごとに繰り返すことを望まない、高価な初期化ロジックが含まれるコード。

  • キャッシュなど、行間で共有状態を活用するコード。

ハンドラー呼び出し間で共有されるグローバル状態を維持する必要がある場合は、 スレッド化 — スレッドベースの並列処理 で説明されている同期プリミティブを使用して、グローバル状態をデータ競合から保護する必要があります。

スケールとパフォーマンスの最適化

データサイエンスライブラリでベクトル化されたPython UDFs を使用する

コードで機械学習またはデータサイエンスライブラリを使用する場合は、ベクトル化された UDFs を使用して、これらのライブラリの動作に最適化されたバッチで入力行を受け取るPython関数を定義します。

詳細については、 ベクトル化されたPython UDFs をご参照ください。

シングルスレッドのUDFハンドラーの記述

シングルスレッドのUDFハンドラーを記述します。Snowflakeは、データのパーティション化と仮想ウェアハウスのコンピューティングリソース全体でのUDFのスケーリングを処理します。

モジュールへのコストがかかる初期化の配置

コストがかかる初期化コードをモジュールスコープに配置します。UDFが初期化されるときに1回実行されます。UDFハンドラーを呼び出すたびに、コストのかかる初期化コードを再実行しないでください。

エラーの処理

UDFとして使用されるPython関数は、通常のPython例外処理手法を使用して、関数内のエラーをキャッチできます。

関数内で例外が発生し、関数によってキャッチされない場合、Snowflakeは例外のスタックトレースを含むエラーを発生させます。 未処理の例外のログ を有効にすると、Snowflakeは未処理の例外に関するデータをイベントテーブルに記録します。

クエリを終了して SQL エラーを生成するために、例外をキャッチせずにスローできます。例:

if (x < 0):
  raise ValueError("x must be non-negative.");
Copy

デバッグ時に、 SQL エラーメッセージテキストに値を含めることができます。そうするには、Python関数本文全体をtry-catchブロックに配置し、キャッチしたエラーのメッセージに引数値を追加し、拡張メッセージで例外をスローします。機密データが公開されないようにするには、実稼働環境に展開する前に引数値を削除します。

優れたセキュリティプラクティスに準拠

ハンドラーがセキュアに機能するようにするには、 UDFs とプロシージャのセキュリティプラクティス で説明されているベストプラクティスをご参照ください。