Steuerung des globalen Zustands in skalaren Scala-UDFs¶
Wenn Sie eine UDF und einen Handler entwerfen, die Zugriff auf einen gemeinsamen Zustand benötigen, müssen Sie berücksichtigen, wie Snowflake UDFs ausführt, um Zeilen zu verarbeiten.
Für die meisten Handler sollten folgende Richtlinien befolgt werden:
Wenn Sie einen gemeinsamen Zustand (Shared State) initialisieren müssen, der sich über Zeilen hinweg nicht ändert, initialisieren Sie ihn außerhalb der Handler-Funktion, z. B. in einem Konstruktor.
Schreiben Sie Ihre Handler-Methode so, dass sie Thread-sicher ist.
Vermeiden Sie das Speichern und Freigeben eines dynamischen Zustands über Zeilen hinweg.
Wenn Ihre UDF diesen Richtlinien nicht folgen kann oder wenn Sie ein tieferes Verständnis der Gründe für diese Richtlinien wünschen, lesen Sie die nächsten Unterabschnitte.
Erläuterungen zur Parallelisierung¶
Um die Verarbeitungsleistung zu verbessern, nutzt Snowflake die Parallelisierung sowohl zwischen mehrere JVMs als auch innerhalb von JVMs.
Parallelisierung zwischen mehreren JVMs¶
Snowflake parallelisiert mehrere Ausführungen in einem Warehouse. Jede Ausführung nutzt eine (oder mehrere) JVMs. Dies bedeutet, dass es keinen globalen gemeinsamen Zustand gibt. Der Zustand kann höchstens innerhalb einer einzelnen JVM gemeinsam genutzt werden.
Parallelisierung innerhalb von JVMs¶
Jede JVM kann mehrere Threads ausführen, die parallel die Handler-Methode der gleichen Instanz aufrufen können. Das bedeutet, dass jede Handler-Methode thread-sicher sein muss.
Wenn eine UDF mit IMMUTABLE eingerichtet ist und eine SQL-Anweisung diese UDF mehr als einmal mit denselben Argumenten für dieselbe Zeile aufruft, dann gibt die UDF bei jedem Aufruf für diese Zeile den gleichen Wert zurück.
Im Folgenden wird z. B. zweimal derselbe Wert für jede Zeile zurückgegeben, wenn die UDF mit IMMUTABLE eingerichtet ist:
SELECT my_scala_udf(42), my_scala_udf(42) FROM table1;Wenn Sie möchten, dass mehrere Aufrufe unabhängige Werte zurückgeben, auch wenn die gleichen Argumente übergeben werden, und wenn Sie nicht die Funktion VOLATILE deklarieren möchten, dann binden Sie mehrere separate UDFs an die gleiche Handler-Methode.
Dazu können Sie die folgenden Schritte ausführen.
Erstellen Sie eine JAR-Datei mit dem Namen
@udf_libs/rand.jar
und mit dem folgenden Code:class MyClass { var x: Double = 0.0 // Constructor def this() = { x = Math.random() } // Handler def myHandler(): Double = x }Erstellen Sie die Scala-UDFs wie unten gezeigt.
Diese UDFs haben unterschiedliche Namen, verwenden aber die gleiche JAR-Datei und innerhalb dieser JAR-Datei den gleichen Handler.
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';Verwenden Sie den folgenden Code, um beide UDFs aufzurufen.
Die UDFs verweisen auf die gleiche JAR-Datei und den gleichen Handler. Diese Aufrufe erstellen zwei Instanzen der gleichen Klasse. Jede Instanz gibt einen unabhängigen Wert zurück, sodass das folgende Beispiel zwei unabhängige Werte zurückgibt, anstatt zweimal denselben Wert zu liefern:
SELECT my_scala_udf_1(), my_scala_udf_2() FROM table1;
Speichern von JVM-Statusinformationen¶
Ein Grund, sich nicht auf einen dynamischen gemeinsamen Zustand zu verlassen, besteht darin, dass Zeilen nicht unbedingt in einer vorhersehbaren Reihenfolge verarbeitet werden. Bei jeder Ausführung einer SQL-Anweisung kann Snowflake die Anzahl der Batches, die Reihenfolge, in der die Batches verarbeitet werden, und die Reihenfolge der Zeilen innerhalb eines Batches variieren. Wenn eine skalare UDF so gestaltet ist, dass eine Zeile den Rückgabewert einer nachfolgenden Zeile beeinflusst, dann kann die UDF bei jeder Ausführung unterschiedliche Ergebnisse zurückgeben.