Pular para o conteúdo principal

Funções definidas pelo usuário (UDFs) em Scala e Java no Unity Catalog

Esta página descreve como criar funções definidas pelo usuário (UDFs) Scala e Java, registrá-las no Unity Catalog e compartilhá-las entre ambientes de compute. As UDFs do Unity Catalog permitem reutilizar a lógica JVM existente com a governança e os controles de acesso do Unity Catalog.

Ao contrário das UDFs Scala com escopo de sessão que são limitadas a um único Notebook ou cluster, as UDFs registradas no Unity Catalog são:

  • **Gerenciado**: Gerenciado com permissões e controles de acesso do Unity Catalog.
  • Reutilizável : Compartilhado entre equipes, notebooks, jobs e SQL warehouses.
  • Detectável : Visível no Catalog Explorer e em tabelas do sistema.
  • **Isolado**: Executado em sandboxes com um custo de inicialização a frio único por sessão. Chamadas subsequentes são rápidas.

Requisitos

Seu Workspace deve estar habilitado para o Unity Catalog. Os requisitos adicionais a seguir se aplicam.

Compute : Todos os tipos de compute são suportados, incluindo notebooks e jobs serverless, SQL warehouses e Spark Declarative Pipelines no Lakeflow. O compute clássico requer o Databricks Runtime 18.2 ou acima. Em compute serverless e SQL warehouses, a definição da UDF deve especificar a Versão do Ambiente 4 ou acima no campo environment_version. Este requisito se aplica à definição da UDF, não ao notebook ou job de chamada. Consulte versões de ambiente serverless.

Desenvolvimento :

  • **Scala**: 2.13.16. Scala 2.12 não é compatível.
  • JDK : 17.
  • Empacotamento : Um fat JAR contendo todas as dependências de terceiros usadas pela UDF.

Permissões :

  • Criar uma UDF: USAGE e CREATE FUNCTION no esquema, e USAGE no catálogo.
  • Execute uma UDF: EXECUTE na função e USAGE no esquema e no catálogo.
  • Acesse o arquivo JAR: READ VOLUME no volume onde o JAR está armazenado.

Consulte Gerenciar privilégios no Unity Catalog para obter mais informações sobre permissões do Unity Catalog.

Construa seu JAR de UDF

Empacote seu código compilado como um JAR e faça upload dele para um volume do Unity Catalog antes de registrar o UDF. Escolha um método de construção:

Compilar localmente

Siga estas etapas para criar um JAR grande usando um ambiente de desenvolvimento local.

Configure seu ambiente

Instale as ferramentas necessárias em sua máquina local. Os seguintes comandos são para macOS. Para outras plataformas, instale o JDK 17 e o sbt (Scala) ou Maven (Java) usando o gerenciador de pacotes da sua plataforma.

Instale JDK 17 e sbt:

Bash
brew install openjdk@17
brew install sbt

Verifique sua instalação:

Bash
java -version   # Should show Java 17
sbt --version # Should show sbt version

Crie seu projeto

Configure um projeto em Scala ou Java.

Criar um novo projeto Scala usando sbt:

Bash
sbt new scala/scala-seed.g8

Quando solicitado, insira um nome de projeto (por exemplo, my-udf-project).

Configure build.sbt

Substitua o conteúdo do seu arquivo build.sbt pela seguinte configuração:

Scala
scalaVersion := "2.13.16"

ThisBuild / organization := "com.example"

lazy val myUDF = (project in file("."))
.settings(
name := "my-udf"
)

Habilitar o plugin sbt-assembly

Crie ou edite project/assembly.sbt e adicione:

Scala
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.0.0")

Este plug-in cria um JAR fat contendo todas as suas dependências.

Escreva sua UDF

Ao escrever sua UDF, consulte Tipos de dados para tipos de dados compatíveis e Mapeamentos de linguagem para ver como os tipos Scala e Java mapeiam para tipos SQL.

Seu manipulador de UDF deve atender aos seguintes requisitos:

  • Scala : Defina o manipulador como um método em um object (não um class). O valor HANDLER é resolvido para um método em um Scala object.
  • **Java**: Defina o manipulador como um public static método.
  • **Assinatura**: os tipos de parâmetro, a ordem e o tipo de retorno do método devem corresponder à lista de argumentos e ao RETURNS tipo na sua CREATE FUNCTION instrução.
  • Apenas escalar : O manipulador deve retornar um único valor escalar. Tipos de retorno de tabela não são compatíveis.
  • Autocontido : o manipulador deve operar apenas em seus argumentos de entrada. Não pode usar APIs Spark ou depender de pacotes centrais do Spark. Consulte Limitações.
nota

Para Scala, um manipulador com um tipo de parâmetro primitivo (como Int) é ignorado e retorna NULL quando qualquer argumento de entrada é SQL NULL. Para receber e lidar com valores NULL, agrupe o parâmetro em Option, como Option[Int].

Crie um objeto Scala em src/main/scala/com/example/MyUDF.scala e defina sua função UDF.

Exemplo básico

Scala
package com.example

object MyUDF {
def addOne(x: Int): Int = x + 1
}

Exemplo com dependência externa

Para usar bibliotecas externas, adicione-as ao seu arquivo build.sbt:

Scala
scalaVersion := "2.13.16"

ThisBuild / organization := "com.example"

lazy val myUDF = (project in file("."))
.settings(
name := "currency-udf",
libraryDependencies ++= Seq(
"org.apache.commons" % "commons-lang3" % "3.12.0"
)
)

Em seguida, use a dependência na sua UDF:

Scala
package com.example

import org.apache.commons.lang3.StringUtils

object CurrencyUDF {
private val rates: Map[String, Double] = Map(
"USD" -> 1.0,
"EUR" -> 1.1,
"GBP" -> 1.3,
"JPY" -> 0.007
)

def convertToUSD(price: Double, currency: String): Double = {
require(currency != null, "Currency must not be null")

val normalizedCurrency = StringUtils.upperCase(currency)

rates.get(normalizedCurrency) match {
case Some(rate) => price * rate
case None => throw new IllegalArgumentException(s"Unsupported currency: $currency")
}
}
}

Teste sua UDF com testes de unidade antes de implantar. Consulte Testando UDFs localmente.

nota

Sua UDF é executada em uma sandbox isolada sem uma sessão Spark ativa, então ela não pode usar as Spark API de dentro do corpo da função. Por exemplo, você não pode criar ou operar em DataFrames ou datasets, executar spark.sql(...), ou acessar SparkSession ou SparkContext. A UDF deve ser uma lógica autocontida sobre seus argumentos de entrada. Também não pode depender de pacotes core do Spark.

Crie seu fat JAR

Construa seu projeto para criar um JAR completo contendo todas as dependências.

No diretório raiz do seu projeto, execute:

Bash
sbt clean assembly

O JAR grande é criado em target/scala-2.13/ com um nome como my-udf-assembly-0.1.0-SNAPSHOT.jar.

Faça upload do seu JAR para um volume do Unity Catalog

Se você ainda não tem um volume do Unity Catalog, crie um:

SQL
CREATE VOLUME IF NOT EXISTS my_catalog.my_schema.udf_jars
COMMENT 'Storage for UDF JAR files';

Se outros usuários precisarem executar a UDF, conceda-lhes READ VOLUME no volume:

SQL
GRANT READ VOLUME ON VOLUME my_catalog.my_schema.udf_jars TO `user@example.com`;

Faça upload do seu arquivo JAR para o volume usando o Catalog Explorer:

  1. No seu workspace do Databricks, clique em Ícone de dados. Catálogo para abrir o Catalog Explorer.
  2. Selecione o catálogo e, em seguida, selecione o esquema que contém seu volume.
  3. Clique no nome do volume.
  4. Clique em Fazer upload para este volume e selecione seu arquivo JAR.
  5. Click upload .
  6. Após o upload ser concluído, clique no nome do seu arquivo JAR.
  7. Clique em Copiar caminho para copiar o caminho do volume para a área de transferência. Por exemplo, /Volumes/my_catalog/my_schema/udf_jars/my-udf-assembly-0.1.0-SNAPSHOT.jar (Scala) ou /Volumes/my_catalog/my_schema/udf_jars/my-udf-1.0-SNAPSHOT.jar (Java). Você precisa desse caminho quando registrar a UDF.

Compilar em um notebook

Você pode compilar uma UDF, empacotá-la como um JAR e fazer upload dela para um volume do Unity Catalog diretamente de um notebook Databricks. Esta abordagem funciona para UDFs pequenas e sem dependência. Para UDFs com bibliotecas de terceiros, use Compilar localmente.

A seguinte célula Python escreve uma UDF Java que limpa uma string (remove espaços em branco, condensa espaços repetidos e usa letras minúsculas), a compila com o JDK 17, a empacota como um JAR e a copia para um volume do Unity Catalog. Atualize o volume_path para apontar para um volume existente no qual você tem permissão de WRITE VOLUME.

Python
import os
import subprocess
import shutil

build_dir = "/tmp/udf_build"
package_dir = f"{build_dir}/src/com/databricks/udf"
classes_dir = f"{build_dir}/classes"
os.makedirs(package_dir, exist_ok=True)
os.makedirs(classes_dir, exist_ok=True)

# The UDF handler: a public static method on a plain Java class.
# The doubled backslashes produce a single backslash in the Java source (\\s+).
udf_code = """package com.databricks.udf;
public class StringCleanUDF {
public static String clean(String input) {
if (input == null) return null;
return input.trim().replaceAll("\\\\s+", " ").toLowerCase();
}
}
"""
with open(f"{package_dir}/StringCleanUDF.java", "w") as f:
f.write(udf_code)

# Compile with JDK 17 to match Environment Version 4.
subprocess.run(
["javac", "--release", "17", "-d", classes_dir, f"{package_dir}/StringCleanUDF.java"],
check=True,
)

# Package the compiled class into a JAR.
jar_path = f"{build_dir}/string_clean_udf.jar"
subprocess.run(["jar", "cf", jar_path, "-C", classes_dir, "."], check=True)

# Copy the JAR to a Unity Catalog volume.
volume_path = "/Volumes/my_catalog/my_schema/udf_jars/string_clean_udf.jar"
os.makedirs(os.path.dirname(volume_path), exist_ok=True)
shutil.copy2(jar_path, volume_path)

print(f"JAR uploaded to: {volume_path}")

Depois que o JAR estiver no volume, registre a UDF. Use LANGUAGE JAVA e defina o HANDLER para o método totalmente qualificado, como com.databricks.udf.StringCleanUDF.clean.

Registre seu UDF no Unity Catalog

Depois de construir e fazer upload do seu JAR, use a instrução CREATE FUNCTION para registrar sua UDF no Unity Catalog.

SQL
CREATE OR REPLACE FUNCTION my_catalog.my_schema.add_one(x INT)
RETURNS INT
LANGUAGE SCALA
DETERMINISTIC
ENVIRONMENT (
java_dependencies = '["/Volumes/my_catalog/my_schema/udf_jars/my-udf-assembly-0.1.0-SNAPSHOT.jar"]',
environment_version = '4'
)
HANDLER 'com.example.MyUDF.addOne';

A instrução CREATE FUNCTION usa os seguintes parâmetros:

  • LANGUAGE: O idioma da UDF.

  • HANDLER: Caminho totalmente qualificado para o método, no formato 'package.Object.method' (Scala) ou 'package.ClassName.method' (Java).

  • DETERMINISTIC: Declara que a função sempre retorna a mesma saída para a mesma entrada, permitindo a otimização da query.

nota

Remova DETERMINISTIC se sua função chamar APIs externas ou tiver qualquer outro comportamento não determinístico.

  • ENVIRONMENT: Define o ambiente de execução para a UDF.

    • java_dependencies: Um array JSON de caminhos de arquivos JAR em seus volumes do Unity Catalog. Este é o caminho do arquivo que você copiou no passo anterior. Use aspas simples ao redor do array e aspas duplas ao redor dos caminhos.
    • environment_version: Deve ser '4' ou acima para UDFs Scala e Java. A Versão 4 do Ambiente especifica Scala 2.13.16 e JDK 17. Consulte Versões de ambiente Serverless.

Chame sua UDF em SQL e Notebooks

Após o registro, você pode chamar a UDF em queries SQL, notebooks e views:

SQL
-- Simple select
SELECT my_catalog.my_schema.add_one(5) AS result;

-- With table data
SELECT
id,
price,
currency,
my_catalog.my_schema.convert_to_usd(price, currency) AS price_usd
FROM my_catalog.my_schema.transactions;

-- Filtering
SELECT *
FROM my_catalog.my_schema.products
WHERE my_catalog.my_schema.convert_to_usd(price, currency) > 100;

-- Aggregation
SELECT
category,
SUM(my_catalog.my_schema.convert_to_usd(price, currency)) AS total_usd
FROM my_catalog.my_schema.sales
GROUP BY category;

Governança e compartilhamento

Use permissões do Unity Catalog para controlar quem pode executar sua UDF e para torná-la detectável em toda a sua organização.

Conceder permissões

Use o Catalog Explorer ou SQL para conceder as permissões necessárias para que outros usuários executem suas UDFs.

  1. Na barra lateral, clique em Ícone de dados. Catálogo .
  2. Selecione o catálogo e, em seguida, o esquema que contém sua função.
  3. Clique no nome da função.
  4. Na tab Permissões , clique em Conceder .
  5. Selecione as entidades de segurança às quais deseja conceder acesso e selecione a permissão EXECUTE.
  6. Clique em Confirmar .

Revogar permissões

Use o Catalog Explorer ou SQL para revogar permissões de outros usuários.

  1. Na barra lateral, clique em Ícone de dados. Catálogo .
  2. Selecione o catálogo e, em seguida, o esquema que contém sua função.
  3. Clique no nome da função.
  4. Na tab **Permissões**, selecione a caixa de seleção ao lado do principal do qual você deseja revogar o acesso. Clique em **Revogar**.
  5. Na notificação, clique em Revogar .

Descobrir UDFs

Para encontrar UDFs gerenciadas no Unity Catalog, query a tabela information_schema.routines, substituindo os valores my_catalog e my_schema:

SQL
SELECT
routine_catalog,
routine_schema,
routine_name,
routine_definition,
created
FROM system.information_schema.routines
WHERE routine_catalog = 'my_catalog'
AND routine_schema = 'my_schema';

Atualize sua UDF

Para atualizar uma UDF existente do Unity Catalog com código novo:

  1. Fazer alterações em seu código localmente.

  2. Reconstrua o JAR com um novo número de versão.

    • Scala: sbt clean assembly (por exemplo, my-udf-assembly-0.2.0-SNAPSHOT.jar)
    • Java: mvn clean package (por exemplo, my-udf-2.0-SNAPSHOT.jar)
  3. Faça upload do novo JAR para o volume do Unity Catalog.

  4. Use CREATE OR REPLACE FUNCTION com o mesmo nome de função para atualizar a UDF. Verifique se você referencia o JAR mais recente em seu java_dependencies.

Databricks usa o novo código na próxima invocação. Não é necessário reiniciar seu cluster.

Otimização de desempenho

Latência de cold start

A primeira chamada de UDF em uma sessão inicializa o sandbox isolado, o que adiciona latência. Chamadas subsequentes na mesma sessão são mais rápidas. Considere isso ao realizar benchmark ou projetar workloads sensíveis à latência.

Armazenamento em cache de cálculos caros

Se a sua UDF executar inicialização ou compute caros, armazene em cache o resultado para computá-lo apenas uma vez.

Use um campo val no objeto Scala para armazenar o resultado em cache:

Scala
package example

object CachedUDF {
// Computed once and cached
val expensiveData: Map[String, Double] = {
// Load data from somewhere expensive
Map("key1" -> 1.0, "key2" -> 2.0)
}

def lookup(key: String): Double = {
expensiveData.getOrElse(key, 0.0)
}
}

Use DETERMINISTIC quando apropriado

Marque sua UDF como DETERMINISTIC se ela sempre produzir a mesma saída para a mesma entrada. Isso permite que o otimizador de query armazene em cache os resultados e melhore o desempenho.

Limitações

  • Apenas UDFs escalares são suportadas. Funções de agregação definidas pelo usuário (UDAFs) e funções de tabela definidas pelo usuário (UDTFs) não são suportadas.
  • As UDFs são executadas em um sandbox isolado, sem uma sessão Spark ativa. Spark API (SparkSession, SparkContext, spark.sql(...), operações de DataFrame e dataset) não estão disponíveis.
  • UDFs não podem depender de pacotes Spark core.
  • UDFs não têm acesso a arquivos de Workspace ou volumes do Unity Catalog em Runtime.

Práticas recomendadas

A Databricks recomenda as seguintes práticas:

  • Faça o versionamento dos seus arquivos JAR. Por exemplo, my-udf-0.1.0.jar, my-udf-0.2.0.jar.
  • Valide os mapeamentos de tipo SQL antes da implantação. Consulte Mapeamentos de idioma.
  • Conceda as permissões READ VOLUME e EXECUTE apenas aos usuários que precisam executar a UDF. Use a propriedade de grupo para UDFs compartilhadas entre equipes.

Testando UDFs localmente

Teste a sua UDF com testes de unidade antes de implantar na produção.

Para testar src/main/scala/example/MyUDF.scala, crie um arquivo de teste em src/test/scala/example/MyUDFTest.scala:

Scala
package example

import org.scalatest.funsuite.AnyFunSuite

class MyUDFTest extends AnyFunSuite {
test("addOne should add 1 to input") {
assert(MyUDF.addOne(5) == 6)
}

test("addOne should handle negative numbers") {
assert(MyUDF.addOne(-1) == 0)
}
}

Adicionar a dependência de teste a build.sbt:

Scala
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.15" % Test

Para executar os testes:

Bash
sbt test

Recursos adicionais