Python UDFsの設計¶
このトピックは、Python UDFsを設計するための助けになります。
このトピックの内容:
注釈
Python UDFのバッチAPIがあります。これにより、入力行のバッチを Pandas DataFrames として受け取り、結果のバッチを Pandas配列 または シリーズ として返すPython関数を定義できます。バッチインターフェイスにより、機械学習の推論シナリオでのパフォーマンスが大幅に向上します。詳細については、 Python UDFのバッチAPI をご参照ください。
データ型の選択¶
コードを記述する前に、
関数が引数として受け入れるデータ型と、関数が返すデータ型を選択します。
タイムゾーン関連の問題を考慮に入れます。
NULL 値の処理方法を決定します。
パラメーターと戻り値型のSQL-Pythonデータ型のマッピング¶
次のテーブルは、SQLとPythonの間の型マッピングを示しています。これらのマッピングは通常、Python UDFに渡される引数とUDFから返される値の両方に適用されます。
SQL 型 |
Python型 |
注意 |
---|---|---|
NUMBER |
intまたはdecimal.Decimal |
NUMBER型のスケールが0の場合、int Python型が使用されます。それ以外の場合は、decimal.Decimal型が使用されます。 |
FLOAT |
float |
浮動小数点演算には小さな丸め誤差があり、特に集計関数が多数の行を処理する場合に累積する可能性があります。行が異なる順序で処理される場合、クエリが実行されるたびに丸め誤差が変化する可能性があります。詳細については、 数値データ型:浮動小数点 をご参照ください。 |
VARCHAR |
str |
|
BINARY |
bytes |
|
BOOLEAN |
bool |
|
DATE |
datetime.date |
|
TIME |
datetime.time |
Snowflakeはナノ秒の精度で時間値を保存できますが、Python datetime.time型はミリ秒の精度しか維持できません。SnowflakeとPythonの間でデータ型を変換すると、実効精度がミリ秒に低下する可能性があります。 |
TIMESTAMP_LTZ |
datetime.datetime |
ローカルタイムゾーンを使用して、内部UTC時刻をローカルの「ナイーブ」日時に変換します。戻り値の型として「ナイーブ」日時が必要です。 |
TIMESTAMP_NTZ |
datetime.datetime |
「ナイーブ」日時に直接変換します。戻り値の型として「ナイーブ」日時が必要です。 |
TIMESTAMP_TZ |
datetime.datetime |
タイムゾーン情報を使用して「認識」日時に変換します。戻り値の型として「認識」日時が必要です。 |
VARIANT |
dict、list、int、float、str、またはbool |
各バリアント行は、引数の場合は動的にPython型に変換され、戻り値の場合はその逆に変換されます。文字列型の入力は、 |
OBJECT |
dict |
Pythonのデータ型が OBJECT に変換される場合、Pythonのdecimalデータが埋め込まれていると、埋め込まれているPythonのdecimalは OBJECT の文字列に変換されます。 |
ARRAY |
list |
Pythonのデータ型が ARRAY に変換される場合、Pythonのdecimalデータが埋め込まれていると、埋め込まれているPythonのdecimalは ARRAY の文字列に変換されます。 |
GEOGRAPHY |
dict |
geographyを GeoJSON としてフォーマットしてから、Python dictに変換します。 |
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
はPythonNone
に変換されます。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バッチAPIの使用¶
コードで機械学習またはデータサイエンスライブラリを使用する場合は、PythonUDFのバッチAPIを使用します。バッチAPIを使用すると、これらのライブラリが動作するように最適化されたバッチで入力行を受け取るPython関数を定義できます。
詳細については、 Python UDFのバッチAPI をご参照ください。
シングルスレッドのUDFハンドラーの記述¶
シングルスレッドのUDFハンドラーを記述します。Snowflakeは、データのパーティション化と仮想ウェアハウスのコンピューティングリソース全体でのUDFのスケーリングを処理します。
モジュールへのコストがかかる初期化の配置¶
コストがかかる初期化コードをモジュールスコープに配置します。UDFが初期化されるときに1回実行されます。UDFハンドラーを呼び出すたびに、コストのかかる初期化コードを再実行しないでください。
エラーの処理¶
UDFとして使用されるPython関数は、通常のPython例外処理手法を使用して、関数内のエラーをキャッチできます。
関数内で例外が発生し、関数によってキャッチされない場合、Snowflakeは例外のスタックトレースを含むエラーを発生させます。
クエリを終了して SQL エラーを生成するために、例外をキャッチせずにスローできます。例:
if (x < 0):
raise ValueError("x must be non-negative.");
デバッグ時に、 SQL エラーメッセージテキストに値を含めることができます。そうするには、Python関数本文全体をtry-catchブロックに配置し、キャッチしたエラーのメッセージに引数値を追加し、拡張メッセージで例外をスローします。機密データが公開されないようにするには、実稼働環境に展開する前に引数値を削除します。
優れたセキュリティプラクティスに準拠¶
関数(および呼び出すライブラリ関数)は純粋関数として機能する必要があり、受信したデータのみに作用し、そのデータに基づいて、副作用を引き起こすことなく値を返します。コードは、妥当な量のメモリとプロセッサ時間を消費することを除いて、基になるシステムの状態に影響を与える可能性がないようにします。
Python UDFsは、制限されたエンジン内で実行されます。使用するコードも、呼び出すライブラリ関数内のコードも、次のような禁止されているシステムコールを呼び出さないようにしてください。
プロセス制御。たとえば、プロセスをフォークすることはできません。(ただし、複数のスレッドは使用可。)
ファイルシステムへのアクセス。
次の例外を除いて、Python UDFsはファイルを読み取ったり、書き込んだりすべきではありません。
Python UDFsは、
CREATE FUNCTION
コマンドのimports
句で指定されたファイルを読み取ることができます。詳細については、 UDFハンドラーを使用したファイルの読み取り をご参照ください。例については、 ステージからPython UDFへのファイルのロード をご参照ください。
Python UDFsは、ログファイルなどのファイルを
/tmp
ディレクトリに書き込むことができます。各クエリは、独自の/tmpが保存されている独自のメモリバックアップファイルシステムを取得するため、異なるクエリでファイル名が競合することはありません。ただし、1つのクエリが複数の UDF を呼び出し、それらの UDFs が同じファイル名に書き込もうとすると、クエリ内で競合が発生する可能性があります。また、Python UDFsは別々のワーカープロセスで並行して実行される可能性があるため、/tmpディレクトリへの書き込みには注意が必要です。
ファイルの書き込みの詳細については、 UDFハンドラーを使用したファイルの書き込み をご参照ください。例については、 ステージングされたファイルの解凍 をご参照ください。
ネットワークアクセス。
注釈
コードは、ネットワークに直接的または間接的にアクセスできないため、Snowflake Pythonコネクタのコードを使用してデータベースにアクセスすることはできません。UDF 自体がSnowflakeのクライアントとして機能することはできません。