Controle do estado global em UDFs escalares em Scala¶
Ao projetar uma UDF e um manipulador que exija acesso ao estado compartilhado, você precisará levar em conta a maneira como o Snowflake executa as UDFs para processar as linhas.
A maioria dos manipuladores deve seguir essas diretrizes:
Se você precisar inicializar o estado compartilhado que não muda entre linhas, inicialize-o fora da função do manipulador, como no construtor.
Escreva seu método do manipulador de forma a ser thread-safe.
Evite armazenar e compartilhar o estado dinâmico em linhas diferentes.
Se sua UDF não puder seguir essas diretrizes, ou se você quiser entender mais profundamente as razões para essas diretrizes, leia as próximas subseções.
O que é paralelização¶
Para melhorar o desempenho, o Snowflake paraleliza tanto entre JVMs diferente quanto dentro de JVMs.
Paralelização entre JVMs¶
O Snowflake paralela entre os trabalhadores em um warehouse. Cada trabalhador executa uma (ou mais) JVMs. Isso significa que não existe um estado global compartilhado. No máximo, o estado só pode ser compartilhado dentro de uma única JVM.
Paralelização dentro de JVMs¶
Cada JVM pode executar vários threads que podem chamar o mesmo método do manipulador da mesma instância em paralelo. Isso significa que cada método do manipulador precisa ser thread-safe.
Se uma UDF é IMMUTABLE e uma instrução SQL chama a UDF mais de uma vez com os mesmos argumentos para a mesma linha, a UDF retorna o mesmo valor para cada chamada para aquela linha.
Por exemplo, o código a seguir retorna o mesmo valor duas vezes para cada linha se a UDF for IMMUTABLE:
SELECT my_scala_udf(42), my_scala_udf(42) FROM table1;Se você quiser que múltiplas chamadas retornem valores independentes mesmo quando passarem os mesmos argumentos não quiser declarar a função como VOLATILE, ligue diferentes chamadas a UDFs separadas ao mesmo método do manipulador.
Você pode fazer isso usando as etapas a seguir.
Crie um arquivo JAR denominado
@udf_libs/rand.jar
com o seguinte código:class MyClass { var x: Double = 0.0 // Constructor def this() = { x = Math.random() } // Handler def myHandler(): Double = x }Crie as UDFs de Scala conforme mostrado abaixo.
Essas UDFs têm nomes diferentes, mas usam o mesmo arquivo JAR e o mesmo manipulador dentro desse arquivo 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';Use o código a seguir para chamar ambas as UDFs.
As UDFs apontam para o mesmo arquivo e manipulador JAR. Essas chamadas criam duas instâncias da mesma classe. Cada instância retorna um valor independente, portanto o exemplo abaixo retorna dois valores independentes em vez de retornar o mesmo valor duas vezes:
SELECT my_scala_udf_1(), my_scala_udf_2() FROM table1;
Como armazenar informações de estado de uma JVM¶
Uma razão para evitar confiar no estado dinâmico compartilhado é que as linhas não são necessariamente processadas em uma ordem previsível. Cada vez que uma instrução SQL é executada, o Snowflake pode variar o número de lotes, a ordem em que os lotes são processados e a ordem das linhas dentro de um lote. Se uma UDF escalar for projetada para que uma linha afete o valor de retorno para uma linha posterior, a UDF pode retornar resultados diferentes cada vez que a UDF for executada.