Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

사용자 정의 함수(UDFs)

ClickHouse는 여러 유형의 사용자 정의 함수(UDF)를 지원합니다.

  • 실행형 UDFs는 외부 프로그램이나 스크립트(Python, Bash 등)를 시작한 뒤, STDIN / STDOUT을 통해 데이터 블록을 해당 프로그램으로 스트리밍합니다. ClickHouse를 다시 컴파일하지 않고도 기존 코드나 도구를 통합할 때 사용할 수 있습니다. 프로세스 내부 옵션보다 호출당 오버헤드가 크므로, 더 무거운 로직을 처리하거나 다른 런타임이 필요한 경우에 가장 적합합니다.
  • SQL UDFsCREATE FUNCTION을 사용해 SQL만으로 정의합니다. 쿼리 계획에 인라인되어 확장되므로(프로세스 경계 없음) 가볍고, 표현식 로직을 재사용하거나 복잡한 계산 컬럼을 단순화하는 데 적합합니다.
  • Experimental WebAssembly UDFs는 서버 프로세스 내 샌드박스에서 WebAssembly로 컴파일된 코드를 실행합니다. 외부 실행형보다 호출당 오버헤드가 낮고 네이티브 확장 기능보다 격리 수준이 높아, WASM을 대상으로 할 수 있는 언어(예: C/C++/Rust)로 작성한 사용자 정의 알고리즘에 적합합니다.
  • Experimental driver-based executable UDFs는 연산자가 제공하는 "드라이버"가 CREATE FUNCTION ... ENGINE = DriverName(...) AS '...'에 제공된 코드 스니펫을 함수 생성 시점에 실행형 UDF로 변환하도록 합니다(예를 들어 컴파일을 통해). 이는 실행형 UDFs를 기반으로 하며, 서버 측 드라이버 구성이 필요합니다.

실행형 사용자 정의 함수

베타 기능

ClickHouse는 데이터를 처리하기 위해 외부 실행 프로그램이나 스크립트를 호출할 수 있습니다.

실행형 사용자 정의 함수의 구성은 하나 이상의 XML 파일에 둘 수 있습니다. 구성 경로는 user_defined_executable_functions_config 매개변수로 지정합니다.

함수 구성에는 다음 설정이 포함됩니다:

매개변수 설명 필수 기본값
name 함수 이름 -
command 실행할 스크립트 이름 또는 execute_direct가 false인 경우 실행할 명령 -
argument 인수의 type과 선택적 name을 포함하는 인수 설명입니다. 각 인수는 별도의 설정으로 설명합니다. Native 또는 JSONEachRow 같은 사용자 정의 함수 포맷에서 인수 이름이 직렬화의 일부인 경우에는 이름 지정이 필요합니다 c + argument_number
format 인수를 명령에 전달할 때 사용하는 포맷입니다. 명령의 출력도 동일한 포맷이어야 합니다 -
return_type 반환 값의 유형 -
return_name 반환 값의 이름입니다. Native 또는 JSONEachRow 같은 사용자 정의 함수 포맷에서 반환 이름이 직렬화의 일부인 경우에는 반환 이름 지정이 필요합니다 선택 사항 result
type 실행형 유형입니다. typeexecutable로 설정되면 단일 명령이 시작됩니다. executable_pool로 설정되면 명령 풀(pool)이 생성됩니다 -
max_command_execution_time 데이터 블록을 처리하는 최대 실행 시간(초)입니다. 이 설정은 executable_pool 명령에만 적용됩니다 선택 사항 10
command_termination_timeout 파이프가 닫힌 뒤 명령이 종료되어야 하는 시간(초)입니다. 이 시간이 지나면 명령을 실행 중인 프로세스에 SIGTERM이 전송됩니다 선택 사항 10
command_read_timeout 명령의 stdout에서 데이터를 읽을 때의 timeout(밀리초)입니다 선택 사항 10000
command_write_timeout 명령의 stdin에 데이터를 쓸 때의 timeout(밀리초)입니다 선택 사항 10000
pool_size 명령 풀의 크기입니다 선택 사항 16
send_chunk_header 프로세스에 데이터 청크를 보내기 전에 행 수를 먼저 전송할지 여부를 제어합니다 선택 사항 false
execute_direct execute_direct = 1이면 commanduser_scripts_path에 지정된 user_scripts 폴더에서 검색됩니다. 추가 스크립트 인수는 공백으로 구분해 지정할 수 있습니다. 예시: script_name arg1 arg2. execute_direct = 0이면 commandbin/sh -c의 인수로 전달됩니다 선택 사항 1
lifetime 함수의 다시 로드 간격(초)입니다. 0으로 설정하면 함수는 다시 로드되지 않습니다 선택 사항 0
deterministic 함수가 결정적인지 여부입니다(같은 입력에 대해 항상 같은 결과를 반환) 선택 사항 false
stderr_reaction 명령의 stderr 출력을 처리하는 방식입니다. 값: none(무시), log(모든 stderr를 즉시 기록), log_first(종료 후 처음 4 KiB를 기록), log_last(종료 후 마지막 4 KiB를 기록), throw(stderr 출력이 있으면 즉시 예외 발생). log_first 또는 log_last를 0이 아닌 종료 코드와 함께 사용하는 경우 stderr 내용이 예외 메시지에 포함됩니다 선택 사항 log_last
check_exit_code true이면 ClickHouse가 명령의 종료 코드를 확인합니다. 종료 코드가 0이 아니면 예외가 발생합니다 선택 사항 true

명령은 STDIN에서 인수를 읽고 결과를 STDOUT으로 출력해야 합니다. 또한 인수를 반복적으로 처리해야 합니다. 즉, 인수 청크 하나를 처리한 뒤 다음 청크를 기다려야 합니다.

실행형 사용자 정의 함수

예시

인라인 스크립트로 만든 UDF

XML 또는 YAML 구성을 사용해 execute_direct0으로 지정하고 test_function_sum을 수동으로 생성합니다.

파일 test_function.xml(기본 경로 설정 기준 /etc/clickhouse-server/test_function.xml).

/etc/clickhouse-server/test_function.xmlxml
<functions>
    <function>
        <type>executable</type>
        <name>test_function_sum</name>
        <return_type>UInt64</return_type>
        <argument>
            <type>UInt64</type>
            <name>lhs</name>
        </argument>
        <argument>
            <type>UInt64</type>
            <name>rhs</name>
        </argument>
        <format>TabSeparated</format>
        <command>cd /; clickhouse-local --input-format TabSeparated --output-format TabSeparated --structure 'x UInt64, y UInt64' --query "SELECT x + y FROM table"</command>
        <execute_direct>0</execute_direct>
        <deterministic>true</deterministic>
    </function>
</functions>

Querysql
SELECT test_function_sum(2, 2);
Resulttext
┌─test_function_sum(2, 2)─┐
│                       4 │
└─────────────────────────┘

Python 스크립트로 만든 UDF

이 예시에서는 STDIN에서 값을 읽어 문자열로 반환하는 UDF를 생성합니다.

XML 또는 YAML 구성으로 test_function을 생성합니다.

파일 test_function.xml(기본 경로 설정 기준: /etc/clickhouse-server/test_function.xml).

/etc/clickhouse-server/test_function.xmlxml
<functions>
    <function>
        <type>executable</type>
        <name>test_function_python</name>
        <return_type>String</return_type>
        <argument>
            <type>UInt64</type>
            <name>value</name>
        </argument>
        <format>TabSeparated</format>
        <command>test_function.py</command>
    </function>
</functions>

user_scripts 폴더에 스크립트 파일 test_function.py를 생성합니다(기본 경로 설정 기준: /var/lib/clickhouse/user_scripts/test_function.py).

#!/usr/bin/python3

import sys

if __name__ == '__main__':
    for line in sys.stdin:
        print("Value " + line, end='')
        sys.stdout.flush()
Querysql
SELECT test_function_python(toUInt64(2));
Resulttext
┌─test_function_python(2)─┐
│ Value 2                 │
└─────────────────────────┘

STDIN에서 두 값을 읽어 그 합을 JSON 객체로 반환하기

이름이 지정된 인수와 JSONEachRow 포맷을 사용하여 XML 또는 YAML 구성으로 test_function_sum_json을 생성합니다.

파일 test_function.xml(기본 경로 설정 기준: /etc/clickhouse-server/test_function.xml).

/etc/clickhouse-server/test_function.xmlxml
<functions>
    <function>
        <type>executable</type>
        <name>test_function_sum_json</name>
        <return_type>UInt64</return_type>
        <return_name>result_name</return_name>
        <argument>
            <type>UInt64</type>
            <name>argument_1</name>
        </argument>
        <argument>
            <type>UInt64</type>
            <name>argument_2</name>
        </argument>
        <format>JSONEachRow</format>
        <command>test_function_sum_json.py</command>
    </function>
</functions>

user_scripts 폴더에 스크립트 파일 test_function_sum_json.py를 생성합니다(기본 경로 설정 기준: /var/lib/clickhouse/user_scripts/test_function_sum_json.py).

#!/usr/bin/python3

import sys
import json

if __name__ == '__main__':
    for line in sys.stdin:
        value = json.loads(line)
        first_arg = int(value['argument_1'])
        second_arg = int(value['argument_2'])
        result = {'result_name': first_arg + second_arg}
        print(json.dumps(result), end='\n')
        sys.stdout.flush()
Querysql
SELECT test_function_sum_json(2, 2);
Resulttext
┌─test_function_sum_json(2, 2)─┐
│                            4 │
└──────────────────────────────┘

command 설정에서 매개변수 사용

실행형 사용자 정의 함수는 command 설정에 지정된 상수 매개변수를 받을 수 있습니다(이 기능은 executable 유형의 사용자 정의 함수에서만 작동합니다). 또한 셸 인수 확장 취약점을 방지하려면 execute_direct 옵션도 필요합니다.

파일 test_function_parameter_python.xml (기본 경로 설정 기준: /etc/clickhouse-server/test_function_parameter_python.xml).

/etc/clickhouse-server/test_function_parameter_python.xmlxml
<functions>
    <function>
        <type>executable</type>
        <execute_direct>true</execute_direct>
        <name>test_function_parameter_python</name>
        <return_type>String</return_type>
        <argument>
            <type>UInt64</type>
        </argument>
        <format>TabSeparated</format>
        <command>test_function_parameter_python.py {test_parameter:UInt64}</command>
    </function>
</functions>

user_scripts 폴더에 스크립트 파일 test_function_parameter_python.py를 생성하십시오(기본 경로 설정 기준: /var/lib/clickhouse/user_scripts/test_function_parameter_python.py).

#!/usr/bin/python3

import sys

if __name__ == "__main__":
    for line in sys.stdin:
        print("Parameter " + str(sys.argv[1]) + " value " + str(line), end="")
        sys.stdout.flush()
Querysql
SELECT test_function_parameter_python(1)(2);
Resulttext
┌─test_function_parameter_python(1)(2)─┐
│ Parameter 1 value 2                  │
└──────────────────────────────────────┘

셸 스크립트로 UDF 만들기

이 예시에서는 각 값을 2배로 곱하는 셸 스크립트를 만듭니다.

파일 test_function_shell.xml (/etc/clickhouse-server/test_function_shell.xml, 기본 경로 설정 기준).

/etc/clickhouse-server/test_function_shell.xmlxml
<functions>
    <function>
        <type>executable</type>
        <name>test_shell</name>
        <return_type>String</return_type>
        <argument>
            <type>UInt8</type>
            <name>value</name>
        </argument>
        <format>TabSeparated</format>
        <command>test_shell.sh</command>
    </function>
</functions>

user_scripts 폴더 안에 스크립트 파일 test_shell.sh를 만듭니다 (/var/lib/clickhouse/user_scripts/test_shell.sh, 기본 경로 설정 기준).

/var/lib/clickhouse/user_scripts/test_shell.shbash
#!/bin/bash

while read read_data;
    do printf "$(expr $read_data \* 2)\n";
done
Querysql
SELECT test_shell(number) FROM numbers(10);
Resulttext
    ┌─test_shell(number)─┐
 1. │ 0                  │
 2. │ 2                  │
 3. │ 4                  │
 4. │ 6                  │
 5. │ 8                  │
 6. │ 10                 │
 7. │ 12                 │
 8. │ 14                 │
 9. │ 16                 │
10. │ 18                 │
    └────────────────────┘

오류 처리

일부 함수는 데이터가 유효하지 않으면 예외를 발생시킬 수 있습니다. 이 경우 쿼리가 취소되고 클라이언트에 오류 메시지가 반환됩니다. 분산 처리에서는 서버 중 하나에서 예외가 발생하면 다른 서버들도 쿼리를 중단하려고 합니다.

인수 표현식의 평가

거의 모든 프로그래밍 언어에서는 특정 연산자의 경우 일부 인수가 평가되지 않을 수 있습니다. 보통 &&, ||, ?: 같은 연산자가 여기에 해당합니다. ClickHouse에서는 함수(연산자)의 인수가 항상 평가됩니다. 이는 각 행을 개별적으로 계산하지 않고 컬럼의 전체 파트를 한 번에 평가하기 때문입니다.

분산 쿼리 처리에서 함수 실행

분산 쿼리 처리에서는 가능한 한 많은 쿼리 처리 단계가 원격 서버에서 수행되며, 나머지 단계(중간 결과 병합 및 그 이후의 모든 단계)는 요청자 서버에서 수행됩니다.

즉, 함수가 서로 다른 서버에서 실행될 수 있습니다. 예를 들어, 쿼리 SELECT f(sum(g(x))) FROM distributed_table GROUP BY h(y), 에서

  • distributed_table에 세그먼트가 2개 이상 있으면 함수 'g'와 'h'는 원격 서버에서 수행되고, 함수 'f'는 요청자 서버에서 수행됩니다.
  • distributed_table에 세그먼트가 1개만 있으면 'f', 'g', 'h' 함수가 모두 이 세그먼트가 있는 서버에서 수행됩니다.

함수의 결과는 일반적으로 어느 서버에서 수행되는지와 무관합니다. 하지만 경우에 따라서는 이것이 중요할 수 있습니다. 예를 들어, 딕셔너리를 사용하는 함수는 해당 함수가 실행되는 서버에 있는 딕셔너리를 사용합니다. 또 다른 예로 hostName 함수가 있습니다. 이 함수는 SELECT 쿼리에서 서버별로 GROUP BY를 수행할 수 있도록, 자신이 실행 중인 서버의 이름을 반환합니다.

쿼리의 함수가 요청자 서버에서 수행되지만 이를 원격 서버에서 수행해야 한다면, 해당 함수를 'any' 집계 함수로 감싸거나 GROUP BY의 키에 추가할 수 있습니다.

SQL 사용자 정의 함수

람다 표현식에서 사용자 정의 함수를 CREATE FUNCTION SQL 문으로 생성할 수 있습니다. 이러한 함수를 삭제하려면 DROP FUNCTION SQL 문을 사용하십시오.

WebAssembly 사용자 정의 함수

ClickHouse Cloud에서 지원되지 않음
실험 기능

WebAssembly 사용자 정의 함수(WASM UDF)를 사용하면 WebAssembly로 컴파일된 사용자 정의 코드를 ClickHouse 서버 프로세스 내부에서 실행할 수 있습니다.

빠른 시작

ClickHouse 구성에서 experimental WebAssembly 지원을 활성화하세요:

<clickhouse>
    <allow_experimental_webassembly_udf>true</allow_experimental_webassembly_udf>
</clickhouse>

컴파일된 WASM 모듈을 시스템 테이블에 삽입하세요:

INSERT INTO system.webassembly_modules (name, code)
SELECT 'my_module', base64Decode('AGFzbQEAAAA...');

WASM 모듈을 사용해 함수를 만드세요:

CREATE FUNCTION my_function
LANGUAGE WASM
ABI ROW_DIRECT
FROM 'my_module'
ARGUMENTS (x UInt32, y UInt32)
RETURNS UInt32;

쿼리에서 이 함수를 사용하세요:

SELECT my_function(10, 20);

추가 정보

자세한 내용은 WebAssembly 사용자 정의 함수 문서를 참고하십시오.

드라이버 기반 실행형 사용자 정의 함수

ClickHouse Cloud에서 지원되지 않음
실험 기능

드라이버는 사용자 코드 조각을 실행 가능한 실행형 UDF로 변환하는 운영자 제공 어댑터입니다. 함수를 ENGINE = DriverName(...)으로 생성하면 ClickHouse는 드라이버의 create_command를 실행하면서 함수 시그니처와 코드 본문을 전달합니다. 그러면 드라이버는 본문을 컴파일하거나 다른 방식으로 처리한 뒤 실행형 UDF 구성 정보를 출력하고, ClickHouse는 이를 저장한 후 로드합니다.

이를 통해 관리자는 사용자에게 서버의 설정 파일이나 파일 시스템에 대한 접근 권한을 부여하지 않고도, 임의의 언어(예: 샌드박스된 컨테이너 내부에서 컴파일한 C)로 함수를 정의할 수 있는 안전하고 제한된 방식을 제공할 수 있습니다. 사용 가능한 드라이버는 전적으로 운영자가 제어합니다.

드라이버 활성화

드라이버 기반 실행형 UDF는 기본적으로 비활성화되어 있습니다. 이를 활성화하려면 다음을 수행하십시오.

  1. 서버 구성에서 experimental gate를 설정합니다.

    <clickhouse>
        <allow_experimental_executable_udf_drivers>true</allow_experimental_executable_udf_drivers>
    </clickhouse>
  2. user_defined_executable_function_drivers_config이 하나 이상의 드라이버 설정 파일(glob 지원)을 가리키도록 설정하고, 필요에 따라 생성된 실행형 UDF 구성이 저장되는 디렉터리인 dynamic_user_defined_executable_functions_path도 설정합니다.

    <clickhouse>
        <user_defined_executable_function_drivers_config>user_defined_executable_function_drivers_config.d/*_driver.xml</user_defined_executable_function_drivers_config>
        <dynamic_user_defined_executable_functions_path>/var/lib/clickhouse/dynamic_user_defined_executable_functions/</dynamic_user_defined_executable_functions_path>
    </clickhouse>

드라이버 레지스트리는 서버 시작 시 로드되고 SYSTEM RELOAD CONFIG 시 갱신되므로, 서버를 재시작하지 않고도 드라이버를 추가, 변경 또는 제거할 수 있습니다.

드라이버 구성

드라이버는 최상위 <driver> 요소를 포함하는 XML(또는 YAML) 파일로 정의됩니다. 지원되는 필드는 다음과 같습니다.

Field Description Required
name CREATE FUNCTION ... ENGINE = <name>(...)에 사용되는 드라이버 이름입니다.
create_command 코드 스니펫에서 UDF를 생성하기 위해 호출되는 프로그램의 경로입니다. 상대 경로는 드라이버 설정 파일을 기준으로 해석됩니다.
drop_command 이 드라이버를 기반으로 하는 함수가 삭제될 때 호출되는 프로그램의 경로입니다. 아니요
engine_arguments ENGINE = DriverName(...) 내에서 허용되는 인수를 선언합니다. 각 하위 요소는 인수 이름이며, <required>true</required> 하위 요소로 필수 여부를 표시합니다. 아니요
env 드라이버 명령을 호출할 때 내보내는 환경 변수입니다. 아니요

예시 드라이버 구성:

<clickhouse>
    <driver>
        <name>DockerC</name>
        <create_command>../user_defined_executable_function_drivers/docker_c_create.sh</create_command>
        <drop_command>../user_defined_executable_function_drivers/docker_c_drop.sh</drop_command>
        <engine_arguments>
            <opt_level><required>false</required></opt_level>
        </engine_arguments>
        <env>
            <CLICKHOUSE_C_DRIVER_MEMORY>256m</CLICKHOUSE_C_DRIVER_MEMORY>
            <CLICKHOUSE_C_DRIVER_CPUS>1.0</CLICKHOUSE_C_DRIVER_CPUS>
        </env>
    </driver>
</clickhouse>

Driver 호출 규약

CREATE FUNCTION이 실행되면, 구성된 env 변수가 설정된 상태에서 create_command가 다음 인수와 함께 호출됩니다.

  • --name <function_name>
  • --return <return_type> (RETURNS 절이 있는 경우)
  • --args <signature> (ARGUMENTS 절이 있는 경우). 여기서 시그니처는 선언된 인수 목록이며, 예를 들면 x UInt8, y DateTime입니다.
  • ENGINE = DriverName(key = value)에 제공된 선언된 각 엔진 인수마다 --<key> <value>

사용자 코드 본문(AS 뒤의 텍스트)은 명령의 표준 입력으로 전달됩니다. 명령은 실행형 UDF의 구성을 표준 출력에 출력해야 합니다. 포맷은 자동으로 감지됩니다. 출력이 <로 시작하면 XML로 처리되고, 그렇지 않으면 YAML로 처리됩니다. 생성된 구성에 정의된 함수 이름은 생성 중인 이름과 일치해야 합니다. create_command가 0이 아닌 종료 상태로 끝나면 종료 코드와 드라이버의 표준 오류를 포함한 예외와 함께 구문이 실패합니다.

drop_command가 있으면 함수가 삭제될 때도 동일한 방식으로 호출됩니다(stdin에는 코드 본문이 없음).

함수 생성

CREATE [OR REPLACE] FUNCTION [IF NOT EXISTS] name [ON CLUSTER cluster]
    ARGUMENTS (a UInt8, b String) RETURNS UInt64
    ENGINE = DriverName(key1 = 'value1', key2 = 42)
    AS '...code body...'

ClickHouse는 드라이버의 create_command를 실행하고, 생성된 구성을 dynamic_user_defined_executable_functions_path에 기록한 뒤 기존 실행형 UDF 로더가 이를 읽어들입니다. 이후 이 함수는 다른 함수와 동일하게 호출할 수 있습니다.

함수 삭제

DROP FUNCTION [IF EXISTS] name [ON CLUSTER cluster]

DROP FUNCTION은 드라이버의 drop_command(있는 경우)를 호출한 다음, 생성된 동적 구성과 함수별 작업 디렉터리를 제거하고, 실행형 UDF 로더를 다시 로드하며, 영구 저장된 쿼리를 삭제합니다.

영속성 및 재시작

원본 쿼리는 사용자 정의 SQL 객체 디렉터리에 ATTACH FUNCTION ... SQL 문으로 저장되므로, 서버를 재시작해도 함수가 유지됩니다. 시작 시에는 dynamic_user_defined_executable_functions_path에 생성된 구성이 드라이버를 다시 실행하지 않고 직접 로드됩니다. 저장된 ATTACH FUNCTION에 해당하는 생성된 구성이 없으면(예: 동적 디렉터리가 손실된 경우) 이를 다시 만들기 위해 드라이버를 다시 실행합니다.

제한 사항

  • 이 기능은 실험 단계 기능이며, allow_experimental_executable_udf_drivers를 활성화해야 사용할 수 있습니다.
  • 드라이버 기반 함수는 복제된 사용자 정의 함수 저장소(ON CLUSTER<user_defined_zookeeper_path>)와 함께 사용할 수 없습니다. 생성된 artifact는 복제되지 않고, 원래 쿼리만 복제되기 때문입니다.
  • 백업된 드라이버 기반 함수를 RESTORE하면 쿼리는 유지되지만 드라이버는 다시 실행되지 않습니다. 생성된 구성은 이후 재시작 복구 과정에서 구체화됩니다.

예시 C 드라이버

소스 트리에는 C 함수 본문을 컴파일하고 실행하는 개념 증명용 드라이버가 programs/server/user_defined_executable_function_drivers_config.d/ 아래에 제공됩니다. 이들은 예시이며 패키지로는 설치되지 않습니다:

  • DockerC - 격리된 Docker 컨테이너 내부에서 코드를 컴파일하고 실행하며 (--network=none --read-only --cap-drop=ALL --security-opt=no-new-privileges와 메모리/CPU/PID 제한 사용), executable_pool UDF를 생성합니다.
  • GVisorC - 컴파일된 바이너리를 gVisor runsc 런타임에서 실행하는 변형입니다.
  • UnsafeC - 샌드박스 없이 호스트에서 직접 코드를 컴파일하고 실행합니다. 이름에서 알 수 있듯이 격리를 전혀 제공하지 않으며, 신뢰할 수 있는 환경과 테스트 용도로만 사용하도록 설계되었습니다.

이 예시 드라이버는 출발점으로 삼기 위한 것입니다. 신뢰할 수 없는 사용자에게 노출하기 전에 환경에 맞게 샌드박스 구성을 검토하고 강화하십시오.

Navigation