사용자 정의 함수

사용자 정의 함수 (User-Defined Functions, UDFs)

Pinot은 현재 두 가지 방식으로 사용자 정의 함수를 구현할 수 있게 지원해요:

  • Groovy 스크립트
  • 스칼라 함수(Scalar Functions)

출처: 문서

본문

Groovy 스크립트

Pinot은 Apache Groovy 스크립트를 사용해 어떤 함수든 실행할 수 있게 해줘요. 쿼리 안에서 Groovy 스크립트를 실행하는 문법은 다음과 같습니다:

GROOVY('result value metadata json', 'groovy script', arg0, arg1, arg2...)

이 함수는 제공된 인자를 사용해 groovy 스크립트를 실행하고 제공된 결과 값 메타데이터와 일치하는 결과를 반환해요. 이 함수는 다음 인자를 요구합니다:

  • Result value metadata json - 결과 값 메타데이터를 나타내는 json 문자열. null이 아닌 키 resultType과 isSingleValue를 포함해야 해요.
  • Groovy script to execute - 스크립트 안에서 제공된 인자를 참조하는 데 arg0, arg1, arg2 등을 사용하는 groovy 스크립트 문자열
  • arguments - groovy 스크립트의 인자인 pinot 컬럼/다른 변환 함수

예시

  • colA와 colB를 더해 단일 값 INT를 반환
    groovy( '{"returnType":"INT","isSingleValue":true}', 'arg0 + arg1', colA, colB)

  • mvColumn 배열에서 max 요소를 찾아 단일 값 INT를 반환

    groovy('{"returnType":"INT","isSingleValue":true}', 'arg0.toList().max()', mvColumn)

  • 배열 mvColumn의 모든 요소를 찾아 다중 값 LONG 컬럼으로 반환

    groovy('{"returnType":"LONG","isSingleValue":false}', 'arg0.findIndexValues{ it > 5 }', mvColumn)

  • 배열 mvColumn의 길이를 colB와 곱해 단일 값 DOUBLE을 반환

    groovy('{"returnType":"DOUBLE","isSingleValue":true}', 'arg0 * arg1', arraylength(mvColumn), colB)

  • mvColumnA에서 foo 값을 가진 모든 인덱스를 찾아 mvColumnB에서 해당 인덱스의 값을 더함

    groovy( '{"returnType":"DOUBLE","isSingleValue":true}', 'def x = 0; arg0.eachWithIndex{item, idx-> if (item == "foo") {x = x + arg1[idx] }}; return x' , mvColumnA, mvColumnB)

  • mvCol 배열 길이에 따라 FLOAT 값을 반환하는 switch case

    groovy('{"returnType":"FLOAT", "isSingleValue":true}', 'def result; switch(arg0.length()) { case 10: result = 1.1; break; case 20: result = 1.2; break; default: result = 1.3;}; return result.floatValue()', mvCol)

  • 인자를 받지 않는 모든 Groovy 스크립트

    groovy('new Date().format( "yyyyMMdd" )', '{"returnType":"STRING","isSingleValue":true}')

:warning: Groovy 스크립트는 Pinot 쿼리에 특화된 내장 스칼라 함수를 받아들이지 않습니다. 자세한 내용은 아래 섹션을 참고하세요.

:warning: Groovy 활성화

쿼리에서 실행 가능한 Groovy를 허용하면 보안 취약점이 될 수 있어요. Groovy를 허용하기로 결정하면 보안 위험을 인지하고 주의하세요. Pinot 쿼리에서 Groovy를 활성화하려면 다음 브로커 설정을 지정할 수 있습니다.

pinot.broker.disable.query.groovy=false

설정하지 않으면 쿼리의 Groovy는 기본적으로 비활성화됩니다.

위 설정은 전체 Pinot 클러스터에 적용됩니다. 테이블 수준에서 Groovy 쿼리를 활성화/비활성화하는 재정의가 필요하다면 쿼리 테이블 설정에서 다음 속성을 지정할 수 있어요.

{
  "tableName": "myTable",
  "tableType": "OFFLINE",
 
  "query" : {
    "disableGroovy": false
  }
}

스칼라 함수 (Scalar Functions)

0.5.0 릴리스부터 Pinot은 여러 입력에 대해 단일 출력을 반환하는 사용자 정의 함수를 지원해요. 스칼라 함수의 예시는 StringFunctions와 DateTimeFunctions에서 찾을 수 있어요.

Pinot은 @ScalarFunction 어노테이션이 있는 모든 함수를 자동으로 식별하고 등록해요.

Java 메서드만 지원됩니다.

사용자 정의 스칼라 함수 추가

다음과 같이 새 스칼라 함수를 추가할 수 있어요:

  • 새 java 프로젝트를 만듭니다. 패키지 이름이 org.apache.pinot로 시작하고 그 안에 .function.이 포함되어 있어야 해요.
  • java 프로젝트에 의존성을 포함합니다.

Maven

<dependency>
  <groupId>org.apache.pinot</groupId>
  <artifactId>pinot-common</artifactId>
  <version>1.2.0</version>
 </dependency>

Gradle

include 'org.apache.pinot:pinot-common:1.2.0'
  • 메서드에 @ScalarFunction 어노테이션을 붙입니다. 메서드가 static이고 단일 값 출력만 반환하는지 확인하세요. 입력과 출력은 다음 타입 중 하나일 수 있습니다.
    • Integer
    • Long
    • Double
    • String
//Example Scalar function

@ScalarFunction
static String mySubStr(String input, Integer beginIndex) {
  return input.substring(beginIndex);
}

수집을 위한 함수 휘발성(volatility) 선언

스칼라 UDF를 영속 수집 변환(persisted ingestion transform)에 사용할 수 있다면 그 휘발성을 정확히 선언하세요. Pinot은 새롭거나 변경된 수집 변환에서 IMMUTABLE 스칼라 함수만 허용해요. IMMUTABLE 함수는 명시적 인자에만 의존하고, STABLE 함수는 쿼리 간에 바뀔 수 있으며, VOLATILE 함수는 호출마다 바뀌거나 부작용이 있을 수 있습니다. STABLE과 VOLATILE 함수는 쿼리에서 계속 사용할 수 있지만, 새롭거나 변경된 영속 수집 변환에서는 유효하지 않습니다.

@ScalarFunction은 호환성을 위해 기본적으로 IMMUTABLE입니다. 함수에 맞을 때만 기본값을 그대로 두세요. 시간, 난수, 변경 가능한 상태 또는 외부 시스템을 읽는 UDF라면 적절한 카테고리를 명시적으로 설정하세요:

import org.apache.pinot.spi.annotations.FunctionVolatility;
import org.apache.pinot.spi.annotations.ScalarFunction;

@ScalarFunction(volatility = FunctionVolatility.VOLATILE)
static long currentTime() {
  return System.currentTimeMillis();
}

함수 휘발성은 Pinot의 컴파일 타임 쿼리 평가 동작을 제어하는 isDeterministic과 별개입니다. isDeterministic = false인 UDF는 영속 수집 변환 검증에서도 VOLATILE로 처리됩니다.

  • 컴파일된 JAR을 pinot의 /plugins 디렉토리에 배치합니다. Pinot 인스턴스가 이미 실행 중이라면 모두 재시작해야 합니다.
  • 이제 쿼리에서 함수를 다음과 같이 사용할 수 있어요.
SELECT mysubstr(playerName, 4) 
FROM baseballStats

:warning: SQL의 함수 이름은 Java의 함수 이름과 동일합니다. SQL 함수 이름은 대소문자를 구분하지 않습니다.

더 알아보기 (Learn more)