Java UDF 설계

이 항목은 Java UDF를 설계하는 데 도움이 됩니다.

이 항목의 내용:

데이터 타입 선택

코드를 작성하기 전에:

  • 함수가 인자로 수락해야 하는 데이터 타입, 그리고 함수가 반환해야 하는 데이터 타입을 선택하십시오.

  • 타임존 관련 문제를 고려하십시오.

  • NULL 값을 처리하는 방법을 결정하십시오.

매개 변수 및 반환 형식에 대한 SQL-Java 데이터 타입 매핑

Snowflake가 Java와 SQL 데이터 타입 간에 전환하는 방법에 대한 자세한 내용은 SQL 및 처리기 언어 간의 데이터 타입 매핑 섹션을 참조하십시오.

TIMESTAMP_LTZ 값 및 타임존

Java UDF는 호출되는 환경과 크게 분리되어 있습니다. 그러나 타임존은 호출 환경에서 상속됩니다. 호출자의 세션이 Java UDF 호출 전에 기본 타임존을 설정한 경우, Java UDF는 동일한 기본 타임존을 갖습니다. Java UDF는 기본 TIMEZONE Snowflake SQL이 사용하는 것과 동일한 IANA타임존 데이터베이스 데이터(즉, 타임존 데이터베이스의 릴리스 2021a 에서 가져온 데이터)를 사용합니다.

NULL 값

Snowflake는 두 가지 고유한 NULL 값을 지원하는데, 이는 SQL NULL 과 VARIANT의 JSON null 입니다. (Snowflake VARIANT NULL에 대한 자세한 내용은 NULL 값 을 참조하십시오.)

Java는 기본이 아닌 데이터 타입에만 해당하는 하나의 null 값을 지원합니다.

Java UDF에 대한 SQL NULL 인자는 Java null 값으로 변환되지만, null 을 지원하는 Java 데이터 타입에만 해당됩니다.

반환된 Java null 값은 SQL NULL 로 다시 변환됩니다.

배열 및 가변 인자 수

Java UDF는 다음 Java 데이터 타입의 배열을 수신할 수 있습니다.

  • String

  • boolean

  • double

  • float

  • int

  • long

  • short

전달된 SQL 값의 데이터 타입은 해당 Java 데이터 타입과 호환되어야 합니다. 데이터 타입 호환성에 대한 SQL-Java 데이터 타입 매핑 을 참조하십시오.

지정된 각 Java 데이터 타입에 대해 다음과 같은 추가 규칙이 적용됩니다.

  • boolean: Snowflake ARRAY는 BOOLEAN 요소만 포함해야 하며 NULL 값을 포함해서는 안 됩니다.

  • int/short/long: Snowflake ARRAY는 스케일이 0인 고정 소수점 요소만 포함해야 하며 NULL 값을 포함해서는 안 됩니다.

  • float/double: Snowflake ARRAY는 다음 중 하나를 포함해야 합니다.

    ARRAY는 NULL 값을 포함할 수 없습니다.

Java 메서드는 다음 두 가지 방법 중 하나로 이러한 배열을 수신할 수 있습니다.

  • Java의 배열 기능을 사용합니다.

  • Java의 varargs (가변 인자 수) 기능을 사용합니다.

두 경우 모두 SQL 코드는 ARRAY 를 전달해야 합니다.

ARRAY를 통해 전달

Java 매개 변수를 배열로 선언하십시오. 예를 들어, 다음 메서드의 세 번째 매개 변수는 문자열 배열입니다.

static int myMethod(int fixedArgument1, int fixedArgument2, String[] stringArray)
Copy

다음은 완전한 예입니다.

테이블을 만들고 로딩합니다.

CREATE TABLE string_array_table(id INTEGER, a ARRAY);
INSERT INTO string_array_table (id, a) SELECT
        1, ARRAY_CONSTRUCT('Hello');
INSERT INTO string_array_table (id, a) SELECT
        2, ARRAY_CONSTRUCT('Hello', 'Jay');
INSERT INTO string_array_table (id, a) SELECT
        3, ARRAY_CONSTRUCT('Hello', 'Jay', 'Smith');
Copy

UDF를 만듭니다.

create or replace function concat_varchar_2(a ARRAY)
returns varchar
language java
handler='TestFunc_2.concatVarchar2'
target_path='@~/TestFunc_2.jar'
as
$$
    class TestFunc_2 {
        public static String concatVarchar2(String[] strings) {
            return String.join(" ", strings);
        }
    }
$$;
Copy

UDF를 호출합니다.

SELECT concat_varchar_2(a)
    FROM string_array_table
    ORDER BY id;
+---------------------+
| CONCAT_VARCHAR_2(A) |
|---------------------|
| Hello               |
| Hello Jay           |
| Hello Jay Smith     |
+---------------------+
Copy

Varargs를 통해 전달

varargs를 사용하는 것은 배열을 사용하는 것과 매우 유사합니다.

Java 코드에서 Java의 varargs 선언 스타일을 사용하십시오.

static int myMethod(int fixedArgument1, int fixedArgument2, String ... stringArray)
Copy

다음은 완전한 예입니다. 이 예와 이전 예(배열의 경우) 간의 유일한 중요한 차이점은 메서드에 대한 매개 변수 선언입니다.

테이블을 만들고 로딩합니다.

CREATE TABLE string_array_table(id INTEGER, a ARRAY);
INSERT INTO string_array_table (id, a) SELECT
        1, ARRAY_CONSTRUCT('Hello');
INSERT INTO string_array_table (id, a) SELECT
        2, ARRAY_CONSTRUCT('Hello', 'Jay');
INSERT INTO string_array_table (id, a) SELECT
        3, ARRAY_CONSTRUCT('Hello', 'Jay', 'Smith');
Copy

UDF를 만듭니다.

create or replace function concat_varchar(a ARRAY)
returns varchar
language java
handler='TestFunc.concatVarchar'
target_path='@~/TestFunc.jar'
as
$$
    class TestFunc {
        public static String concatVarchar(String ... stringArray) {
            return String.join(" ", stringArray);
        }
    }
$$;
Copy

UDF를 호출합니다.

SELECT concat_varchar(a)
    FROM string_array_table
    ORDER BY id;
+-------------------+
| CONCAT_VARCHAR(A) |
|-------------------|
| Hello             |
| Hello Jay         |
| Hello Jay Smith   |
+-------------------+
Copy

Snowflake에서 부과한 제약 조건 내에서 유지되는 Java UDF 설계

Snowflake에서 잘 실행되는 처리기 코드 설계에 대한 자세한 내용은 Snowflake에서 적용한 제약 조건 내에서 유지되는 처리기 설계하기 섹션을 참조하십시오.

클래스 설계

SQL 문이 Java UDF를 호출하면 Snowflake는 사용자가 작성한 Java 메서드를 호출합니다. Java 메서드를 《핸들러 메서드》 또는 줄여서 《핸들러》라고 합니다.

모든 Java 메서드와 마찬가지로 메서드는 클래스의 일부로 선언되어야 합니다. 핸들러 메서드는 클래스의 정적 메서드 또는 인스턴스 메서드일 수 있습니다. 핸들러가 인스턴스 메서드이고 클래스가 인자가 없는 생성자를 정의하는 경우, Snowflake는 초기화 시 생성자를 호출하여 클래스의 인스턴스를 만듭니다. 핸들러가 정적 메서드인 경우, 클래스에는 생성자가 필요하지 않습니다.

핸들러는 Java UDF에 전달된 각 행에 대해 한 번 호출됩니다. (참고: 클래스의 새 인스턴스는 각 행에 대해 만들어지지 않습니다. Snowflake는 동일한 인스턴스의 핸들러 메서드를 두 번 이상 호출하거나 동일한 정적 메서드를 두 번 이상 호출할 수 있습니다.)

코드 실행을 최적화하기 위해 Snowflake는 초기화가 느릴 수 있는 반면 핸들러 메서드의 실행은 빠르다고 가정합니다. Snowflake는 핸들러를 실행하는 것(한 행의 입력으로 핸들러를 호출하는 시간)보다 초기화 실행(UDF를 로딩하는 시간, 그리고 생성자가 정의된 경우 핸들러 메서드의 포함된 클래스의 생성자를 호출하는 시간 포함)에 더 긴 시간 제한을 설정합니다.

클래스 설계에 대한 추가 정보는 Java UDF 처리기 생성하기 에 있습니다.

스칼라 UDF에서 초기화 최적화 및 전역 상태 제어

대부분의 함수 및 프로시저 처리기는 아래 지침을 따라야 합니다.

  • 행 간에 변경되지 않는 공유 상태를 초기화해야 하는 경우 처리기 함수 외부에서(예: 모듈이나 생성자에서) 초기화하십시오.

  • 스레드로부터 안전하도록 처리기 함수 또는 메서드를 작성하십시오.

  • 행 간에 동적 상태를 저장 및 공유하지 마십시오.

UDF가 이러한 지침을 따를 수 없거나 이러한 지침의 이유를 더 깊이 이해하려는 경우, 다음 몇 가지 하위 섹션을 읽으십시오.

호출 간 상태 공유하기

Snowflake는 스칼라 UDF가 독립적으로 처리될 것으로 예상합니다. 호출 간에 공유되는 상태에 의존하면 예기치 않은 동작이 발생할 수 있습니다. 이는 시스템에서 행을 임의의 순서로 처리하고 여러 JVM(Java 또는 Scala로 작성된 처리기의 경우) 또는 인스턴스(Python으로 작성된 처리기의 경우)에 걸쳐 이러한 호출을 분산시킬 수 있기 때문입니다.

UDF는 핸들러 메서드에 대한 호출에서 공유 상태에 의존하는 것을 피해야 합니다. 그러나 사용자가 UDF에서 공유 상태를 저장하려 할 수 있는 다음 두 가지 상황이 있습니다.

  • 각 행에 대해 반복하고 싶지 않은, 부담이 큰 초기화 논리가 포함된 코드입니다.

  • 캐시와 같이 행 간에 공유 상태를 활용하는 코드입니다.

여러 행에서 상태를 공유해야 하고 해당 상태가 시간이 지나도 변경되지 않는 경우, 생성자를 사용하여 인스턴스 수준 변수를 설정해 공유 상태를 만드십시오. 생성자는 인스턴스당 한 번만 실행되는 반면 핸들러는 행당 한 번 호출되므로 핸들러가 여러 행을 처리할 때 생성자에서 초기화하는 것이 부담이 더 적습니다. 그리고 생성자는 한 번만 호출되기 때문에 생성자는 스레드로부터 안전하도록 작성할 필요가 없습니다.

변경되는 공유 상태를 UDF가 저장하는 경우, 해당 상태에 대한 동시 액세스를 처리할 수 있도록 코드를 준비해야 합니다. 다음 두 섹션에서는 병렬 처리 및 공유 상태에 대한 자세한 정보를 제공합니다.

Java UDF 병렬 처리 이해

성능을 향상시키기 위해 Snowflake는 JVM 전체에 걸쳐, 그리고 JVM 내에서 병렬 처리를 합니다.

  • JVM 전체에 걸쳐:

    Snowflake는 웨어하우스 의 작업자 전체에 걸쳐 병렬 처리를 합니다. 각 작업자는 하나 이상의 JVM을 실행합니다. 이는 전역 공유 상태가 없음을 의미합니다. 기껏해야 단일 JVM 내에서만 상태를 공유할 수 있습니다.

  • JVM 내에서:

    • 각 JVM은 동일한 인스턴스의 핸들러 메서드를 병렬로 호출할 수 있는 여러 스레드를 실행할 수 있습니다. 이는 각 핸들러 메서드가 스레드로부터 안전해야 함을 의미합니다.

    • UDF가 IMMUTABLE이고 SQL 문이 동일 행에 대해 동일 인자를 사용하여 UDF를 두 번 이상 호출하는 경우, UDF는 해당 행의 각 호출에 대해 동일 값을 반환합니다. 예를 들어, UDF가 IMMUTABLE인 경우 다음은 각 행에 대해 동일 값을 두 번 반환합니다.

      select
             my_java_udf(42),
             my_java_udf(42)
          from table1;
      
      Copy

      동일 인자가 전달된 경우에도 여러 호출이 독립 값을 반환하고 함수 VOLATILE을 선언하지 않으려면 여러 개별 UDF를 동일 핸들러 메서드에 바인딩하십시오. 예:

      1. 다음 코드를 사용하여 @java_udf_stage/rand.jar 이라는 JAR 파일을 만듭니다.

        class MyClass {
        
            private double x;
        
            // Constructor
            public MyClass()  {
                x = Math.random();
            }
        
            // Handler
            public double myHandler() {
                return x;
            }
        }
        
        Copy
      2. 아래와 같이 Java UDF를 만듭니다. 이러한 UDF는 이름이 다르지만, 동일 JAR 파일을 사용하며, 해당 JAR 파일 내에서 동일 핸들러를 사용합니다.

        create function my_java_udf_1()
            returns double
            language java
            imports = ('@java_udf_stage/rand.jar')
            handler = 'MyClass.myHandler';
        
        create function my_java_udf_2()
            returns double
            language java
            imports = ('@java_udf_stage/rand.jar')
            handler = 'MyClass.myHandler';
        
        Copy
      3. 다음 코드는 두 UDF를 모두 호출합니다. UDF는 동일한 JAR 파일 및 핸들러를 가리킵니다. 이러한 호출은 동일한 클래스의 두 인스턴스를 만듭니다. 각 인스턴스는 독립적인 값을 반환하므로 아래 예에서는 동일 값을 두 번 반환하는 대신 두 개의 독립적인 값을 반환합니다.

        select
                my_java_udf_1(),
                my_java_udf_2()
            from table1;
        
        Copy

JVM 상태 정보 저장

동적 공유 상태에 의존하지 않는 한 가지 이유는 행이 반드시 예측 가능한 순서로 처리되는 것은 아니기 때문입니다. SQL 문이 실행될 때마다 Snowflake는 배치 수, 배치가 처리되는 순서, 배치 내의 행 순서를 변경할 수 있습니다. 한 행이 후속 행의 반환 값에 영향을 미치도록 스칼라 UDF가 설계된 경우, UDF는 UDF가 실행될 때마다 다른 결과를 반환할 수 있습니다.

오류 처리

UDF로 사용되는 Java 메서드는 일반 Java 예외 처리 기술을 사용하여 메서드 내에서 오류를 포착할 수 있습니다.

메서드 내에서 예외가 발생하고 이 예외가 메서드에서 포착되지 않으면 Snowflake는 예외에 대한 스택 추적을 포함하는 오류를 발생시킵니다. 처리되지 않은 예외 로깅 이 활성화되면 Snowflake는 이벤트 테이블에 처리되지 않은 예외에 대한 데이터를 기록합니다.

쿼리를 종료하고 SQL 오류를 생성하기 위해 예외를 포착하지 않고 명시적으로 예외를 발생시킬 수 있습니다. 예:

if (x < 0)  {
    throw new IllegalArgumentException("x must be non-negative.");
    }
Copy

디버깅할 때 SQL 오류 메시지 텍스트에 값을 포함할 수 있습니다. 이렇게 하려면 전체 Java 메서드 본문을 try-catch 블록에 배치하고, 포착된 오류 메시지에 인자 값을 추가하고, 확장된 메시지와 함께 예외를 발생시키십시오. 민감한 데이터가 노출되지 않도록 하려면 JAR 파일을 프로덕션 환경에 배포하기 전에 인자 값을 제거하십시오.

모범 사례 따르기

  • 플랫폼 독립적인 코드를 작성하십시오.

    • 특정 CPU 아키텍처(예: x86)를 가정하는 코드를 피하십시오.

    • 특정 운영 체제를 가정하는 코드를 피하십시오.

  • 초기화 코드를 실행해야 하지만, 호출하는 메서드에 이를 포함하지 않으려면 초기화 코드를 정적 초기화 블록에 넣을 수 있습니다.

참고 항목:

우수한 보안 관행 따르기

처리기가 안전한 방식으로 작동하도록 보장하려면 UDF 및 프로시저의 보안 모범 사례 에 설명된 모범 사례를 참조하십시오.