スカラーScala UDFs でのグローバル状態の制御

共有状態へのアクセスを必要とする UDF とハンドラーを設計するときは、Snowflakeが UDFs を実行して行を処理する方法を考慮する必要があります。

ほとんどのハンドラーは次のガイドラインに従う必要があります。

  • 行全般で変化しない共有状態を初期化する必要がある場合は、ハンドラー関数の外部(コンストラクターなど)で初期化します。

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

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

UDF がこれらのガイドラインに従えない場合、またはこれらのガイドラインの理由をより深く理解したい場合は、次のいくつかのサブセクションをお読みください。

呼び出し全般での状態の共有

Snowflakeは、スカラー UDFs が独立して処理されることを期待しています。呼び出しの間で共有される状態に依存すると、予期しない動作が発生する可能性があります。これは、システムが任意の順序で行を処理し、それらの呼び出しを複数の JVMs に分散できるためです(JavaまたはScalaで記述されたハンドラーの場合)。

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

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

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

複数の行で状態を共有する必要があり、その状態が時間の経過とともに変化しない場合は、コンストラクターを使用してインスタンスレベルの変数を設定し、共有状態を作成します。コンストラクターはインスタンスごとに1回だけ実行されますが、ハンドラーは行ごとに1回呼び出されるため、ハンドラーが複数の行を処理する場合は、コンストラクターによる初期化の方が安価です。また、コンストラクターは1回だけ呼び出されるため、スレッドセーフになるようにコンストラクターを作成する必要はありません。

UDF に変更された共有状態が保存されている場合は、その状態への同時アクセスを処理できるようにコードを準備する必要があります。

並列処理と共有状態の詳細については、このトピック内の 並列化の理解JVM 状態情報の保存 をご参照ください。

並列化の理解

パフォーマンスを向上させるために、Snowflakeは JVMs の全体および内部で並列化します。

JVMs 全体の並列化

Snowflakeは、 ウェアハウス のワーカー間で並列化されます。各ワーカーは1つ(または複数)の JVMs を実行します。これは、グローバル共有状態がないことを意味します。最大で、状態は単一の JVM 内でのみ共有できます。

JVMs 内部での並列化

  • 各 JVM は、同じインスタンスのハンドラーメソッドを並行して呼び出すことができる複数のスレッドを実行できます。これは、各ハンドラーメソッドがスレッドセーフである必要があることを意味します。

  • UDF が IMMUTABLE で、 SQL ステートメントが同じ行に対して同じ引数を使用して UDF を複数回呼び出す場合、 UDF は、その行の呼び出しごとに同じ値を返します。

    たとえば、 UDF が IMMUTABLE の場合、以下は各行に対して同じ値を2回返します。

    SELECT my_scala_udf(42), my_scala_udf(42) FROM table1;
    
    Copy

    同じ引数が渡された場合でも複数の呼び出しで独立した値を返すようにし、関数 VOLATILE を宣言しない場合は、複数の個別の UDFs を同じハンドラーメソッドにバインドします。

    次のステップを使用してこれを実行できます。

    1. 次のコードを使用して、 @udf_libs/rand.jar という名前の JAR ファイルを作成します。

      class MyClass {
      
          var x: Double = 0.0
      
          // Constructor
          def this() = {
              x = Math.random()
          }
      
          // Handler
          def myHandler(): Double = x
      }
      
      Copy
    2. 以下に示すように、Scala UDFs を作成します。

      これらの UDFs の名前は異なりますが、同じ JAR ファイルと、その JAR ファイル内の同じハンドラーを使用します。

      CREATE FUNCTION my_scala_udf_1()
        RETURNS DOUBLE
        LANGUAGE SCALA
        IMPORTS = ('@udf_libs/rand.jar')
        HANDLER = 'MyClass.myHandler';
      
      CREATE FUNCTION my_scala_udf_2()
        RETURNS DOUBLE
        LANGUAGE SCALA
        IMPORTS = ('@udf_libs/rand.jar')
        HANDLER = 'MyClass.myHandler';
      
      Copy
    3. 次のコードを使用して、両方の UDFs を呼び出します。

      UDFs は、同じ JAR ファイルとハンドラーをポイントします。これらの呼び出しは、同じクラスのインスタンスを2つ作成します。各インスタンスは独立した値を返すため、以下の例では、同じ値を2回返すのではなく、2つの独立した値を返します。

      SELECT my_scala_udf_1(), my_scala_udf_2() FROM table1;
      
      Copy

JVM 状態情報の保存

動的共有状態に依存しない理由の1つは、行が必ずしも予測可能な順序で処理されるとは限らないことです。Snowflakeは、 SQL ステートメントが実行されるたびに、バッチの数、バッチが処理される順序、およびバッチ内の行の順序を変更できます。1つの行が後続の行の戻り値に影響を与えるようにスカラー UDF が設計されている場合、 UDF は、 UDF が実行されるたびに異なる結果を返す可能性があります。