外部関数を作成するための、プラットフォームに依存しない情報

プロキシサービスとリモートサービスの開発に関連する情報の多くは、プラットフォームによって異なります。ただし、プラットフォームに依存しない追加の詳細が多数あります。このトピックは、プラットフォームに依存しない詳細に焦点を当てています。ただし、一部にプラットフォーム固有の情報が含まれています。

このトピックの内容:

リモートサービス

データ形式

Snowflakeは、HTTP POSTリクエストを発行して、プロキシサービスのリソースを呼び出します。このPOSTには、ヘッダーとともに特定の形式のデータが含まれています。

リモートサービスから返されるデータも、特定の形式に準拠している必要があります。

Snowflakeから送信される形式とSnowflakeに返される形式の両方を以下に説明します。

POSTの送信者と受信者の両方で互換性のあるデータ型を使用することの重要性にも注意してください。詳細について、 外部関数の引数がリモートサービスにより解析される引数に対応していることを確認 もご参照ください。

Snowflakeから送信されたデータ形式

Snowflakeからの各HTTPリクエストは、POSTまたはGETです。

POSTリクエストには、ヘッダーとリクエスト本文が含まれています。リクエストの本文には、行の バッチ が含まれています。

GET にはヘッダーのみが含まれ、リモートサービスが 非同期的 に結果を返す場合のポーリングのみに使用されます。

ヘッダー情報は通常、キーと値のペアのセットとしてリモートサービスで利用できます。ヘッダーの情報には次が含まれます。

  • 次の HTTP ヘッダー:

    • リクエストの本文でデータがシリアル化される方法を説明するヘッダー:

      • 「sf-external-function-format」:現在、常に「json」に設定されています。

      • 「sf-external-function-format-version」:現在、常に「1.0」に設定されています。

    • 「sf-external-function-current-query-id」:これには、この外部関数を呼び出したクエリのクエリ ID が含まれています。これを使用して、Snowflakeクエリをリモートサービスの呼び出しに関連付け、たとえば問題のデバッグに役立てることができます。

    • 「sf-external-function-query-batch-id」: バッチ ID は、このリクエストで処理された行の特定のバッチを一意に識別します。リモートサービスでは、この ID を使用して、処理中のバッチのステータスを追跡できます。エラーが原因でリクエストが再試行された場合、IDはべき等トークンとしても使用できます。IDは、リモートサービスによるリクエストのログ記録/トレースにも使用できます。

      GETのバッチIDは、対応するPOSTのバッチIDと同じです。

      バッチIDは、Snowflakeによって生成された不透明な値です。形式は将来のリリースで変更される可能性があるため、リモートサービスは特定の形式に依存したり、値を解釈しようとしたりしないでください。

    • SQL クエリで呼び出された外部関数の署名(名前と引数の型)と戻り値の型を説明するヘッダー。これらの値には、Snowflake 識別子 の標準文字ではない文字を含めることができるため、情報のBase64バージョンが含まれ、非標準文字は、非Base64バージョンの空白に置き換えられます。具体的なヘッダーは次のとおりです。

      • sf-external-function-name

      • sf-external-function-name-base64

      • sf-external-function-signature

      • sf-external-function-signature-base64

      • sf-external-function-return-type

      • sf-external-function-return-type-base64

      たとえば、関数 ext_func(n integer)  returns varchar に送信されるヘッダーは次のとおりです。

      • sf-external-function-name: ext_func

      • sf-external-function-name-base64: <base64 value>

      • sf-external-function-signature: (N NUMBER)

      • sf-external-function-signature-base64: <base64 value>

      • sf-external-function-return-type: VARCHAR(16777216)

      • sf-external-function-return-type-base64: <base64 value>

      SQL INTEGER 値は SQL NUMBER として扱われるため、型 INTEGER として宣言された SQL 引数は、型 NUMBER として記述されることに注意してください。

  • CREATE EXTERNAL FUNCTION の「ヘッダー」および「コンテキストヘッダー」プロパティで説明されている追加のオプションメタデータ。

たとえば、ヘッダーをPythonディクショナリとして受け取る、Pythonで記述された AWS Lambda関数内から「sf-external-function-signature」ヘッダーを抽出するには、次を実行します。

def handler(event, context):

    request_headers = event["headers"]
    signature = request_headers["sf-external-function-signature"]

詳細は、他の言語や他のクラウドプラットフォームにより異なります。

(AWS で開発されたリモートサービスの場合の、ヘッダーとラムダプロキシ統合の詳細については、 API Gatewayのドキュメント を参照。)

POST リクエストの本文には、 JSON 形式でシリアル化されたデータが含まれます。JSON のスキーマは次のとおりです。

  • トップレベルは JSON オブジェクト(名前と値のペアのセットで、「ディクショナリ」とも呼ばれる)です。

  • 現在、オブジェクトには1つのアイテムしかありません。そのアイテムのキーの名前は「データ」です。

  • その「データ」アイテムの値は JSON 配列で、配列の各要素は1行のデータです。

  • データの各行は、1つ以上の列の JSON 配列です。

  • 最初の列は常に行番号です(つまり、バッチ内における行の0ベースインデックス)。

  • 残りの列には、関数の引数が含まれています。

  • データ型は次のようにシリアル化されます。

    • 番号は JSON 番号としてシリアル化されます。

    • ブール値は JSON ブール値としてシリアル化されます。

    • 文字列は JSON 文字列としてシリアル化されます。

    • バリアントは JSON オブジェクトとしてシリアル化されます。

    • サポートされている他のすべてのデータ型は、 JSON 文字列としてシリアル化されます。

    • NULL は JSON nullとしてシリアル化されます。

これは、署名 f(integer, varchar, timestamp) を持つ外部関数のシリアル化されたリクエストの例です。最初の列はバッチ内の行番号であり、次の3つの値は外部関数への引数であることに注意してください。

{
    "data": [
                [0, 10, "Alex", "Wed, 01 Jan 2014 16:00:00 -0800"],
                [1, 20, "Steve", "Wed, 01 Jan 2015 16:00:00 -0800"],
                [2, 30, "Alice", "Wed, 01 Jan 2016 16:00:00 -0800"],
                [3, 40, "Adrian", "Wed, 01 Jan 2017 16:00:00 -0800"]
            ]
}

データ抽出の例は、各プラットフォームでリモートサービスを作成するためのドキュメントに含まれています。

オプションで、ネットワークを介して送信するために JSON を圧縮できます。圧縮については、 CREATE EXTERNAL FUNCTION に記載されています。

Snowflakeはこのデータを直接リモートサービスではなく、プロキシサービスに送信します。したがって、プロキシサービスは、Snowflake互換の形式でデータを受信(および返す)する必要があります。通常、プロキシサービスはデータを変更せずに渡しますが、リモートサービスとSnowflakeの両方のニーズを満たすために、プロキシはデータを再フォーマット(送信と受信の両方)できます。

簡単にするためとSnowflakeが送受信する形式を説明するために、このセクションのほとんどの例では、リモートサービスがSnowflakeが期待する形式と同じ形式でデータを読み書きし、プロキシサービスが両方向でデータを変更せずに渡すことを想定しています。

Snowflakeが受信するデータ形式

リモートサービスがバッチの処理を終了すると、リモートサービスはSnowflakeによって送信されたデータの形式と同様の形式でSnowflakeにデータを送信する必要があります。戻り値はJSON形式です。このような応答のデータセクションの例を次に示します。

{
    "data":
        [
            [ 0, 1995 ],
            [ 1, 1974 ],
            [ 2, 1983 ],
            [ 3, 2001 ]
        ]
}

Snowflakeに返されるJSON応答には、Snowflakeによって送信された各行に1行が含まれている必要があります。返された各行には2つの値が含まれます。

  • 行番号(つまり、バッチ内における行の0ベースインデックス)。

  • その行の関数から返された値。値は複合値(たとえば、 VARIANT)にすることができますが、Snowflakeのスカラー関数すべて(外部またはその他)が単一の値を返すため、正確に単一の値でなければなりません。

返されるデータの行番号は、Snowflakeが送信したデータの行番号に対応している必要があり、受信した順序と同じ順序で返される必要があります。

応答には、 HTTP ステータスコードも含まれています。Snowflakeは、次の HTTP ステータスコードを認識します。

コード

説明

200

バッチが正常に処理されました。

202

バッチを受信し、まだ処理中です。

その他の値はエラーとして扱われます。

このステータスコードのリストは、時間の経過とともに拡大する可能性があります。

応答には、次のオプションの HTTP ヘッダーを含めることもできます。

  • Content-MD5: Snowflakeは、オプションのContent-MD5 ヘッダーを使用して、応答の整合性をチェックします。このヘッダーが応答に含まれている場合、Snowflakeは、応答本文で MD5 チェックサムを計算して、返されたヘッダーの対応するチェックサムと一致することを確認します。値が一致しない場合、 SQL クエリは失敗します。チェックサムは、ヘッダーで返される前に、Base64表現でエンコードする必要があります。以下のサンプルコードをご参照ください。

オプションで、ネットワークを介して送信するために JSON を圧縮できます。圧縮については、 CREATE EXTERNAL FUNCTION に記載されています。

タイムアウトと再試行の詳細については、 タイムアウトエラーの説明 および リモートサービスが各行に1回だけ渡されると思い込まない をご参照ください。

以下のPythonコードの例は、 HTTP 応答コード、処理されたデータ、および MD5 ヘッダー(オプション)を含む適切な応答を返します。(これは、Python 3.8を使用して記述。)

この例は、 AWS Lambda関数に基づいています。一部のコードには、プラットフォームごとのカスタマイズが必要な場合があります。

import json
import hashlib
import base64

def handler(event, context):

    # The return value should contain an array of arrays (one inner array per input row for a scalar function).
    array_of_rows_to_return = [ ]

    ...

    json_compatible_string_to_return = json.dumps({"data" : array_of_rows_to_return})

    # Calculate MD5 checksum for the response
    md5digest = hashlib.md5(json_compatible_string_to_return.encode('utf-8')).digest()
    response_headers = {
        'Content-MD5' : base64.b64encode(md5digest)
    }

    # Return the HTTP status code, the processed data, and the headers (including the Content-MD5 header).
    return {
        'statusCode': 200,
        'body': json_compatible_string_to_return,
        'headers': response_headers
    }

ベストプラクティス

次のベストプラクティスは、ほとんどの外部機能、特にリモートサービス部分に適用されます。

次のドキュメントで追加のベストプラクティスを見つけることができます。

可能な場合は、リモートサービスのバッチ API を使用

一部のリモートサービスでは、バッチモードと単一行モードの両方を提供します。外部関数を使用するクエリが複数の行を送信することが予想される場合、Snowflakeは、パフォーマンスを向上させるためにリモートサービスのバッチモードを使用することをお勧めします。

次の場合は、このルールが必ずしも適用されるとは限りません。

  • 各行が非常に大きい(例: 数百キロバイト以上)。

  • リモートサービスは、行をバッチで受信する場合と個別に受信する場合で、行の処理方法が異なります。(詳細については、 1度に1行ずつ処理 を参照。)

1度に1行ずつ処理

ネットワークのオーバーヘッドを最小限に抑えるために、Snowflakeは通常、行をバッチ処理してリモートサービスに送信します。バッチの数と各バッチのサイズは異なる場合があります。

さらに、バッチの順序、およびバッチ内の行の順序は異なる場合があります。クエリに ORDER BY 句が含まれている場合でも、通常、 ORDER BY は外部関数が呼び出された後に適用されます。

バッチサイズと行の順序は保証されないため、このバッチまたは以前のバッチの他の行に依存する行の値を返す関数を作成すると、非決定的な結果が生成される可能性があります。

Snowflakeは、リモートサービスにおいて各行を個別に処理することを強くお勧めします。各入力行の戻り値は、他の入力行ではなく、その入力行のみに依存する必要があります。(たとえば現在、外部関数は ウィンドウ関数 をサポートしていません。)

また、バッチサイズは保証されていないため、バッチのカウントは意味がありません。

外部関数がステートレスであることを確認 もご参照ください。

リモートサービスが各行に1回だけ渡されると思い込まない

Snowflakeがリモートサービスを呼び出し、リモートサービスがリクエストを受信して結果を返したが、一時的なネットワークの問題によりSnowflakeが結果を受信しない場合、Snowflakeはリクエストを繰り返す可能性があります。Snowflakeが再試行すると、リモートサービスは同じ行を2回(またはそれ以上)表示する可能性があります。

これにより、予期しない影響が生じる可能性があります。たとえば、同じ値に対してリモートサービスが複数回呼び出される可能性があるため、一意の IDs を割り当てるリモートサービスでは、それらの IDs のシーケンスにギャップが生じる場合があります。場合によっては、リクエストヘッダーの sf-external-function-query-batch-id フィールドのバッチ ID を追跡して、特定の行のバッチが以前に処理されたかどうかを判断することで、このような影響を減らすことができます。Snowflakeが特定のバッチのリクエストを再試行すると、Snowflakeは同じバッチに対して以前と同じバッチIDを使用します。

Snowflakeは、次のエラーを受け取ったときに再試行します。

  • すべての一時的なネットワーク転送エラー。

  • 429ステータスコードで失敗するすべてのリクエスト。

  • 5XXステータスコードで失敗するすべてのリクエスト。

再試行タイムアウトの合計に達するまで、要求は再試行されます。ユーザーは再試行タイムアウトの合計を構成できません。Snowflakeは将来この制限を調整する可能性があります。

再試行が成功せずに合計再試行タイムアウトに達すると、クエリは失敗します。

リモートサービスが機能しているときに外部関数呼び出しがタイムアウトし、Snowflakeとリモートサービス間のすべての要素が機能しているように見える場合は、バッチサイズを小さくして、タイムアウトエラーが減少するかどうかを試してください。

最大バッチサイズを設定する方法については、 CREATE EXTERNAL FUNCTION をご覧ください。

外部関数がステートレスであることを確認

一般的に、外部関数(リモートサービスを含む)は次の両方の状態情報の保存を回避する必要があります。

  • 内部状態(リモートサービスが内部的に保存する状態)。

  • 外部状態(リモートサービスの外部に保存されている状態。たとえば、状態を保持する別のリモートサービスとの間で送受信される状態情報)。

リモートサービスが状態情報を変更し、その情報を使用して将来の出力に影響を与える場合、関数は予想とは異なる値を返す可能性があります。

たとえば、内部カウンターを含み、リモートサービスが最初に開始されてから受信した行数を返す単純なリモートサービスを考えます。一時的なネットワークの問題があり、Snowflakeが同じデータで要求を繰り返す場合、リモートサービスは再送信された行を2回(またはそれ以上)カウントします。

外部状態を含む例については、 副作用の回避 をご参照ください。

関数がステートレスではないまれなケースでは、呼び出し元のドキュメントにおいて、関数がステートレスではなく、関数が揮発性としてマークされている必要があることを明確に示す必要があります。

リモートサービスがリクエストを 非同期に 処理する場合、リモートサービスの作成者は、ある状態を一時的に保存および管理するためにリモートサービスを作成する必要があります。たとえば、リモートサービスはHTTP POSTリクエストのバッチIDを保存する必要があります。これにより、同じバッチIDでHTTP GETを受信した場合、リモートサービスは指定されたバッチが処理中でもHTTPコード202を返すことができます。

クエリはさまざまな理由で中止される可能性があることに注意してください。つまり、リモートサービスが結果の生成を終了した後、最終的なGETが到着する保証はありません。非同期リクエストの状態を保存するリモートサービスは、最終的にタイムアウトになり、その内部状態をクリーンアップする必要があります。最適なタイムアウトは将来変更される可能性がありますが、Snowflakeは、非同期リクエストに関する情報を削除する前に少なくとも10分、できれば12時間保存することを現在推奨しています。

副作用の回避

外部関数(リモートサービスを含む)では、外部状態(リモートサービスの外部に保存されている情報)の変更などの副作用を回避する必要があります。

たとえば、リモートサービスが範囲外の値を政府機関に報告する場合、それは副作用です。

副作用は有用ですが、外部関数を呼び出すことによる副作用は常に予測できるとは限りません。たとえば、匿名化されたヘルスレコードを分析して診断を返すリモートサービスを呼び出すとします。また、診断が患者に伝染病があることが診断された場合、その診断はその疾患の症例数を記録する機関に報告されると仮定します。これは便利な副作用です。ただし、次のような問題に対して脆弱です。

  • 外部関数呼び出しがロールバックされるトランザクション内にある場合、副作用はロールバックされません。

  • リモートサービスが同じ行で複数回呼び出された場合(例:一時的なネットワーク障害と再試行が原因)、副作用が複数回発生する可能性があります。たとえば、感染した患者は統計で2回カウントされる可能性があります。

行が過大にカウントされるのではなく、過小にカウントされる状況もあります。

関数に副作用がある非常にまれなケースでは、呼び出し元のドキュメントに副作用が何であるかを明確に記述し、関数に揮発性のマークを付ける必要があります。

関数を揮発性または不変として分類

関数は、揮発性または不変に分類できます。(CREATE EXTERNAL FUNCTION ステートメントを使用すると、関数の揮発性または不変の指定が可能。)

外部関数が不変であると見なされるには、次の基準を満たす必要があります。

  • 同じ入力値が指定された場合、関数は同じ出力値を返します。(たとえば、 SQRT 関数は同じ入力が与えられたときに同じ出力を返しますが、 CURRENT_TIMESTAMP 関数は同じ入力が与えられたときに必ずしも同じ出力を返すとは限らない。)

  • この関数には副作用がありません。

関数がこれらの2つの基準を満たす場合、Snowflakeは特定のタイプの最適化を使用して、リモートサービスに送信される行またはバッチの数を減らすことができます。(これらの最適化は時間とともに進化する可能性があるため、詳細の説明を省略。)

Snowflakeは、不変性、または不変性に影響を与える要因(たとえば、副作用)を検出または強制できません。リモートサービスの作成者は、リモートサービスが不変のラベルを付ける基準を満たすかどうかを文書化する必要があります。リモートサービスに副作用がある場合は、関数呼び出しが同じ入力値に対して同じ出力値を返す場合でも、そのリモートサービスを呼び出す外部関数を揮発性としてマークする必要があります。リモートサービスが不変であるかどうかわからない場合は、そのリモートサービスを呼び出す外部関数に揮発性のラベルを付ける必要があります。

タイムアウトエラーの説明

外部関数呼び出しには、Snowflake、リモートサービス、プロキシサービス、および場合によってはチェーン内の他の要素が含まれます。これらの要素ではいずれも、特定の関数呼び出しにかかる時間を把握していないため、待機を停止してタイムアウトエラーを返すタイミングを正確に把握できません。各ステップには、独自のタイムアウトがあります。タイムアウトと再試行の詳細については、 タイムアウトエラーの説明 および 再試行 をご参照ください。

遅延を最小化する

遅延を最小限に抑え、外部関数呼び出しのパフォーマンスを向上させるために、Snowflakeは可能であれば以下を実行することをお勧めします。

  • API Gatewayを最も頻繁に(または最大量のデータで)呼び出すSnowflakeインスタンスと同じクラウドプラットフォームおよび地域に、Gatewayを配置します。

  • リモートサービスを作成した場合は(既存のサービスを使用するのではなく)、呼び出し元と同じクラウドプラットフォームと地域でそのリモートサービスを展開します。

  • できるだけ少ないデータを送信します。たとえば、リモートサービスが入力値を調べてそれらのサブセットのみを操作する場合、通常、すべての行をリモートサービスに送信するよりも、 SQL でフィルタリングして関連する行のみをリモートサービスに送信する方が効率的です。リモートサービスを使用して、フィルタリングします。

    別の例として、大きな半構造化データ値を含む列を処理していて、リモートサービスはこれらの各データ値のごく一部のみを使用する場合、通常は、処理の前に列全体を送信してリモートサービスにより小さな断片を抽出するよりも、Snowflake SQL を使用して関連する部分のみを抽出して送信する方が効率的です。

一度に1ステップずつ外部機能を開発およびテストする

Snowflakeでテストする前に、Snowflakeなしでテストすることをお勧めします。

外部関数の開発の初期ステージで、クラウドプラットフォームプロキシサービスコンソール(例: Amazon API Gatewayコンソール)およびリモートサービス開発コンソール(例: AWS Lambdaコンソール)を使用して、プロキシサービスとリモートサービスの開発とテストを支援します。

例えば、Lambda関数を開発した場合は、Snowflakeから呼び出してテストする前に、Lambdaコンソールで広範囲にテストすることができます。

プロキシサービスコンソールとリモートサービスコンソールによるテストには、通常、次の利点があります。

  • 問題の原因を探す箇所が限られるため、問題の診断が容易になります。

  • データペイロードを表示すると、有用なデバッグ情報が得られる場合があります。Snowflakeでは、データペイロードのどの部分もエラーメッセージに表示しません。これによりセキュリティが強化されますが、デバッグが遅くなる可能性があります。

  • Snowflakeは HTTP 5xxエラーの場合に自動で再試行するため、状況によってはデバッグが遅くなったり困難になったりする可能性があります。

  • Snowflakeによるテストでは、クラウドプラットフォームクレジットに加えて、Snowflakeクレジットが使用されます。

もちろん、リモートサービスとプロキシサービスをSnowflakeなしでできる限りテストした後は、Snowflakeでテストする必要があります。Snowflakeでテストする利点は次のとおりです。

  • 外部関数に関連するすべてのステップをテストしています。

  • データソースとしてSnowflakeテーブルを使用すると、大量のデータで簡単にテストして、外部関数のパフォーマンスの現実的な見積もりを取得できます。

次のテストケースを検討します。

  • NULL 値。

  • 「空」の値(たとえば、空の文字列、空の半構造化データ型)。

  • 非常に長い VARCHAR および BINARY 値(該当する場合)。

リモートサービスを非同期化

リモートサービスを作成していて、リモートサービスが予想されるタイムアウト内に結果を返さない可能性がある場合は、リモートサービスを 非同期 にすることを検討してください。詳細については、 非同期外部関数の作成 をご参照ください。

外部関数の引数がリモートサービスにより解析される引数に対応していることを確認

外部関数との間で引数を渡すときは、データ型が適切であることを確認してください。送信された値が受信中のデータ型に適合しない場合、値が切り捨てられるか、破損する可能性があり、あるいはリモートサービスの呼び出しが失敗する可能性があります。

例えば、一部のSnowflake SQL 数値データ型は、一般的に使用される JavaScript データ型よりも大きな値を格納できるため、JSON からの大きな数値の逆シリアル化は JavaScript で特に敏感です。

独自のリモートサービスを作成し、リモートサービスへの引数の数、データ型、または順序を変更する場合は、外部関数に対応する変更を加えることを忘れないでください。現在、ALTER EXTERNAL FUNCTION コマンドにはパラメーターを変更するオプションがないため、引数を変更するには、外部関数を削除して再作成する必要があります。

プロキシサービス

プロキシサービスエンドポイントの保護

Snowflakeでは、プロキシサービスのエンドポイントを保護することを強くお勧めします。

Snowflakeは、資格情報のない API 統合オブジェクトを使用して、プロキシサービスエンドポイントへの認証を行います。資格情報なしの API 統合は、管理者とユーザー間の責任を分離します。管理者は API 統合により、クラウドプロバイダーのネイティブ認証および承認メカニズムを使用して、Snowflakeとクラウドプロバイダーの間に信頼ポリシーを作成できます。Snowflakeがクラウドプロバイダーに接続すると、クラウドプロバイダーはこの信頼ポリシーを通じてアクセスを認証および承認します。管理者は特定のAPI統合を使用して、Snowflakeが使用できるプロキシサービスとリソースを制限することもできます。これにより、管理者はデータの送受信に組織のポリシーを適用できます。

Amazon API Gatewayなどの特定のプロキシサービスエンドポイントを保護するための詳細な手順は、プラットフォーム固有の手順にあります。