ClickHouse는 WebAssembly로 작성된 사용자 정의 함수(UDF)를 생성할 수 있도록 지원합니다. 이를 통해 Rust, C, C++ 등의 언어로 작성한 사용자 정의 로직을 WebAssembly 모듈로 컴파일해 실행할 수 있습니다.
ClickHouse Cloud에서 지원되지 않음 실험 기능개요
WebAssembly 모듈은 ClickHouse에서 호출할 수 있는 하나 이상의 함수를 포함하는 컴파일된 바이너리 파일입니다. 모듈은 한 번 로드해 여러 번 재사용하는 라이브러리나 공유 객체로 생각하면 됩니다.
UDF를 포함하는 WebAssembly 모듈은 Rust, C, C++처럼 WebAssembly로 컴파일할 수 있는 모든 언어로 작성할 수 있습니다.
WebAssembly로 컴파일된 코드("guest" 코드)와 ClickHouse에서 실행되는 코드("host")는 전용 메모리 공간에만 접근할 수 있는 샌드박스 환경에서 실행됩니다.
게스트 코드는 ClickHouse가 호출할 수 있는 함수를 export합니다. 여기에는 사용자 지정 로직을 구현하는 함수(UDF 정의에 사용됨)뿐 아니라 메모리 관리와 ClickHouse와 WebAssembly 코드 간 데이터 교환에 필요한 지원 함수도 포함됩니다.
코드는 운영 체제나 표준 라이브러리에 의존하지 않는 "freestanding" WebAssembly(즉, wasm32-unknown-unknown)로 컴파일해야 합니다. 또한 기본 32비트 WebAssembly 대상만 지원됩니다(wasm64 확장은 지원되지 않음).
모듈은 ClickHouse와 상호작용하기 위해 지원되는 통신 프로토콜(ABI) 중 하나를 따라야 합니다.
컴파일이 완료되면 모듈의 바이너리 코드는 system.webassembly_modules 테이블에 삽입되어 ClickHouse에 로드됩니다.
그 후 CREATE FUNCTION ... LANGUAGE WASM 구문을 사용해 모듈이 내보낸 함수를 참조하는 UDF를 생성할 수 있습니다.
사전 요구 사항
ClickHouse 구성에서 WebAssembly 지원을 사용 설정하세요:
<clickhouse>
<allow_experimental_webassembly_udf>true</allow_experimental_webassembly_udf>
<webassembly_udf_engine>wasmtime</webassembly_udf_engine>
</clickhouse>사용 가능한 Engine 구현:
wasmtime(기본값이며 현재 유일한 구현) — Wasmtime을 사용합니다
빠른 시작
이 예시는 콜라츠 추측 계산기를 구현하여 WebAssembly UDF를 만드는 전체 워크플로를 보여줍니다.
이 단계에서는 프로그래밍 언어가 필요하지 않으므로, WebAssembly를 사람이 읽을 수 있는 형태로 표현한 WebAssembly Text 포맷(WAT)으로 코드를 작성합니다.
ClickHouse는 모듈이 바이너리 형식이어야 하므로, 트랜스파일러를 사용해 WAT를 WASM으로 변환합니다.
이 변환을 수행하려면 WebAssembly Binary Toolkit (WABT)의 wat2wasm 또는 wasm-tools의 parse 명령을 사용할 수 있습니다.
cat << 'EOF' | wasm-tools parse | clickhouse client -q "INSERT INTO system.webassembly_modules (name, code) SELECT 'collatz', code FROM input('code String') FORMAT RawBlob"
(module
(func $next (param $n i32) (result i32)
local.get $n i32.const 1 i32.and
(if (result i32)
(then local.get $n i32.const 3 i32.mul i32.const 1 i32.add)
(else local.get $n i32.const 2 i32.div_u)))
(func $steps (export "steps") (param $n i32) (result i32)
(local $count i32)
local.get $n i32.const 1 i32.lt_u
(if (then i32.const 0 return))
(block $done (loop $loop
local.get $n i32.const 1 i32.eq br_if $done
local.get $n call $next local.set $n
local.get $count i32.const 1 i32.add local.set $count
br $loop))
local.get $count)
)
EOF위 스니펫에서는 바이너리 WASM 코드를 FORMAT RawBlob을 사용해 ClickHouse client로 직접 파이프하여 system.webassembly_modules 테이블에 삽입합니다.
그런 다음 모듈이 내보내는 steps 함수를 참조하는 UDF를 정의합니다:
CREATE FUNCTION collatz_steps LANGUAGE WASM ARGUMENTS (n UInt32) RETURNS UInt32 FROM 'collatz' :: 'steps';UDF 이름과 다르므로 :: 뒤에는 모듈의 함수 이름을 지정한다는 점에 유의하십시오.
이제 쿼리에서 collatz_steps 함수를 사용할 수 있습니다:
SELECT groupArray(collatz_steps(number :: UInt32))
FROM numbers(1, 100)
FORMAT TSVnumber 컬럼은 UInt32로 명시적으로 CAST됩니다. 이는 WebAssembly 함수가 CREATE FUNCTION 문에 지정된 시그니처와 정확히 일치하는 타입을 요구하기 때문입니다.
그 결과, 1부터 100까지의 수에 대한 Collatz 단계 수열을 얻을 수 있으며, 이는 OEIS의 A006577 수열에 해당합니다.
[0,1,7,2,5,8,16,3,19,6,14,9,9,17,17,4,12,20,20,7,7,15,15,10,23,10,111,18,18,18,106,5,26,13,13,21,21,21,34,8,109,8,29,16,16,16,104,11,24,24,24,11,11,112,112,19,32,19,32,19,19,107,107,6,27,27,27,14,14,14,102,22,115,22,14,22,22,35,35,9,22,110,110,9,9,30,30,17,30,17,92,17,17,105,105,12,118,25,25,25]시스템 테이블을 통해 WASM 모듈 관리
WebAssembly 모듈은 다음 구조를 가진 system.webassembly_modules 테이블에 저장됩니다:
- 컬럼
nameString — 모듈 이름입니다. 비어 있을 수 없으며, 영문자, 숫자, 밑줄 문자만 사용할 수 있습니다.codeString — 원시 바이너리 WASM 코드입니다. 쓰기 전용이며, 읽기 시 빈 문자열이 반환됩니다.hashUInt256 — 모듈 바이너리의 SHA256입니다(디스크에는 존재하지만 아직 로드되지 않은 경우 0).
모듈 관리는 이 테이블에 대한 표준 SQL 작업을 통해 수행됩니다:
모듈 삽입
INSERT INTO system.webassembly_modules (name, code)
SELECT 'my_module', base64Decode('AGFzbQEAAAA...');필요한 경우 무결성 해시를 제공할 수 있습니다:
INSERT INTO system.webassembly_modules (name, code, hash)
SELECT 'my_module', base64Decode('...'), reinterpretAsUInt256(unhex('369f...c57d'));제공된 해시가 모듈 코드에서 계산된 SHA256과 일치하지 않으면 삽입이 실패합니다. S3 또는 HTTP 같은 외부 소스에서 모듈을 로드할 때 유용합니다.
클러스터 전체에 모듈 배포
system.webassembly_modules는 인스턴스별 table이므로 INSERT는 connection을 처리하는 레플리카에만 적용됩니다. INSERT 문에는 ON CLUSTER 형식이 없으므로, 이어서 CREATE FUNCTION ... ON CLUSTER를 실행하면 모듈이 없는 레플리카에서는 실패합니다:
Code: 674. DB::Exception: WebAssembly module 'collatz' not found:
while adding user defined function `collatz_steps`. (RESOURCE_NOT_FOUND)삽입이 모든 노드로 전파되도록 하려면 로컬 system.webassembly_modules 테이블 대신 cluster 테이블 함수에 쓰세요:
cat collatz.wasm | clickhouse client -q "
INSERT INTO FUNCTION cluster('default', 'system', 'webassembly_modules') (name, code)
SELECT 'collatz', code FROM input('code String') FORMAT RawBlob"이처럼 분산된 삽입이 완료되면 모든 레플리카에 모듈이 존재하게 되며, CREATE FUNCTION ... ON CLUSTER가 성공합니다:
CREATE FUNCTION collatz_steps ON CLUSTER 'default'
LANGUAGE WASM FROM 'collatz' :: 'steps'
ARGUMENTS (n UInt32) RETURNS UInt32;clusterAllReplicas를 사용하면 모든 레플리카에 모듈이 로드되었는지 확인할 수 있습니다:
SELECT hostName(), name FROM clusterAllReplicas('default', system.webassembly_modules) WHERE name = 'collatz';system.webassembly_modules에 대한 삽입은 동일한 (name, hash) 쌍에 대해 멱등적이므로, 팬아웃된 삽입을 다시 실행해도 안전하며 레플리카 교체 후 상태를 복구하는 합리적인 방법입니다. 새로 추가된 서버에는 기존 모듈이 자동으로 전달되지 않는다는 점에 유의하십시오. 업데이트된 클러스터를 대상으로 삽입을 다시 실행하거나, 새 호스트의 user_scripts/wasm/ 디렉터리에 바이너리를 배치해야 합니다.
모듈 목록 보기
SELECT name, lower(hex(reinterpretAsFixedString(hash))) AS sha256 FROM system.webassembly_modules
┌모듈 삭제
삭제는 DELETE FROM system.webassembly_modules WHERE name = '...' 문을 사용해 수행합니다.
프레디케이트는 정확한 일치에는 name = 'literal', 패턴과 일치하는 모든 모듈을 삭제할 때는 name LIKE 'pattern'여야 하며, 그 밖의 형태는 허용되지 않습니다.
DELETE FROM system.webassembly_modules WHERE name = 'collatz';
-- 이름이 `tmp_`로 시작하는 모든 모듈을 일괄 삭제합니다 (리터럴 밑줄은 `\_`로 이스케이프 처리됩니다):
DELETE FROM system.webassembly_modules WHERE name LIKE 'tmp\_%';기존 UDF 중 일치하는 모듈을 참조하는 UDF가 있으면 삭제에 실패하므로, 먼저 해당 UDF를 삭제해야 합니다.
WebAssembly UDF 생성하기
구문:
CREATE [OR REPLACE] FUNCTION function_name
LANGUAGE WASM
FROM 'module_name' [:: 'source_function_name']
ARGUMENTS ( [name type[, ...]] | [type[, ...]] )
RETURNS return_type
[ABI ROW_DIRECT | ABI BUFFERED_V1 | ABI ASSEMBLYSCRIPT]
[DETERMINISTIC]
[SHA256_HASH 'hex']
[SETTINGS key = value[, ...]];매개변수:
function_name: ClickHouse에서의 함수 이름입니다. 모듈의 내보낸 함수 이름과 다를 수 있습니다.FROM 'module_name' :: 'source_function_name': 사용할 로드된 WASM 모듈 이름과 WASM 모듈 내 함수 이름입니다(기본값은 function_name).ARGUMENTS: 인수 이름과 타입 목록입니다(이름은 선택 사항이며, 이름이 지정된 필드를 지원하는 직렬화 포맷에서 사용됩니다).ABI: Application Binary Interface 버전입니다.ROW_DIRECT: 직접 타입 매핑, 행 단위 처리BUFFERED_V1: 직렬화를 사용하는 블록 기반 처리ASSEMBLYSCRIPT: AssemblyScript 컴파일러로 생성된 모듈을 위한 행 단위 처리입니다. 숫자 타입은 AssemblyScript 기본 타입에 매핑되며, ClickHouseString은 AssemblyScriptstring에 매핑됩니다.
DETERMINISTIC: 함수를 결정적으로 선언합니다 — 동일한 입력에 대해 항상 동일한 출력을 반환합니다. 지정하면 모든 인수가 상수인 호출에 대해 ClickHouse가 상수 폴딩을 수행할 수 있습니다. 함수는 쿼리 분석 시점에 한 번 평가되며, 결과는 모든 행에 재사용됩니다.SHA256_HASH: 검증에 사용할 예상 모듈 해시입니다(생략하면 자동으로 채워짐). 서로 다른 레플리카에서 올바른 WASM 모듈이 로드되었는지 확인하는 데 사용할 수 있습니다.SETTINGS: 함수별 설정serialization_formatString — 모듈에 전달되는 인수 블록을 직렬화하고 반환된 결과를 파싱하는 데 사용하는 포맷입니다.ABI BUFFERED_V1에서만 사용됩니다. 지원되는 값:MsgPack,JSONEachRow,CSV,TSV,TSVRaw,RowBinary,Buffers. 기본값:MsgPack.Buffers와 같은 블록 기반 포맷은 선언된 함수 시그니처와 타입이 일치하는 단일 컬럼 하나를 반환해야 합니다.webassembly_udf_enable_fuelBool — 함수에 대한 제한된 연료 예산을 활성화합니다. 기본값:true.false이면 이 함수에서는 쿼리 수준 설정webassembly_udf_max_fuel이 무시됩니다. 연료 제한을 비활성화하면 성능이 향상될 수 있습니다. 하지만 신뢰할 수 없거나 버그가 있는 게스트 코드의 경우, 제어되지 않는 실행 위험이 커질 수 있습니다.
ABI 버전
ClickHouse와 상호작용하려면 WebAssembly 모듈이 지원되는 ABI(Application Binary Interface) 중 하나를 따라야 합니다.
ROW_DIRECT: 직접 타입 매핑(기본 타입Int32,UInt32,Int64,UInt64,Float32,Float64만 지원)BUFFERED_V1: 직렬화를 사용하는 복합 타입ASSEMBLYSCRIPT: AssemblyScript 모듈과의 행 단위 상호 운용; 숫자 타입과String을 지원
ABI ROW_DIRECT
내보낸 WASM 함수를 각 행에 대해 직접 호출합니다.
- 인수와 반환 타입은 숫자 타입
Int32/UInt32/Int64/UInt64/Float32/Float64/Int128/UInt128입니다. - 이 ABI에서는 문자열을 지원하지 않습니다.
- 시그니처는 WASM 내보내기(
i32/i64/f32/f64/v128)와 일치해야 합니다. - 모듈에서 내보내야 하는 지원 함수는 필요하지 않습니다.
예를 들어, 다음과 같은 시그니처를 가진 함수가 있습니다:
(func (param i32 i64 f32) (result f64) ...)다음과 같이 만들 수 있습니다:
CREATE FUNCTION my_func ARGUMENTS (Int32, UInt64, Float32) RETURNS Float64 ...WebAssembly는 부호 있는 인수와 부호 없는 인수를 구분하지 않으며, 대신 값을 해석할 때 서로 다른 명령어를 사용합니다. 따라서 인수의 크기는 정확히 일치해야 하고, 부호 여부는 함수 내부에서 수행하는 연산에 따라 결정됩니다.
ABI BUFFERED_V1
WASM 메모리를 통해 (역)직렬화하여 전체 블록을 한 번에 처리합니다. 모든 인수 및 반환 타입을 지원합니다.
데이터는 WASM 메모리의 버퍼를 통해 교환됩니다. 버퍼는 데이터에 대한 포인터와 데이터 크기를 저장하는 8바이트 구조체이며, 리틀 엔디언 u32 값 2개로 구성됩니다. 버퍼는 데이터 자체가 아니라 이 구조체를 가리키는 포인터인 handle로 전달됩니다. 게스트 코드는 이러한 버퍼를 생성하고 제거하기 위한 2개의 함수를 내보내야 합니다.
각 입력 블록에 대해 ClickHouse는 다음을 수행합니다.
- 함수의
serialization_format(기본값은MsgPack)을 사용하여 인수 컬럼을 직렬화합니다. 행 기반 포맷은ARGUMENTS에 선언된 순서대로 인수 값을 행별로 작성합니다. 이름이 지정된 필드가 있는 포맷은 인수 이름을 사용하므로, 이러한 포맷을 사용할 때는ARGUMENTS에 인수 이름을 선언하십시오. - 모듈에서 내보낸
clickhouse_create_buffer를 호출하고, 반환된 버퍼가 가리키는 메모리에 직렬화된 데이터를 복사합니다. - 입력 버퍼 handle(함수에 인수가 없으면
0)과 행 수라는 2개의i32인수로 사용자 정의 함수를 호출합니다. 함수는 게스트 코드가 직접 할당한 결과 버퍼의 handle인 단일i32를 반환합니다.0을 반환하면 쿼리가 오류와 함께 실패합니다. - 결과 버퍼를 읽습니다. 이 버퍼에는 동일한 포맷으로 직렬화된, 정확히 같은 수의 행을 포함하는 컬럼이 정확히 하나 있어야 합니다.
JSONEachRow처럼 이름이 지정된 필드가 있는 포맷에서는 결과 컬럼의 이름이result여야 합니다. - 입력 버퍼(있는 경우)와 결과 버퍼 handle 모두에 대해
clickhouse_destroy_buffer를 호출합니다. 게스트 코드는 결과 버퍼를 직접 해제해서는 안 되며, 입력 handle을 결과로 반환해서도 안 됩니다. 그렇게 하면 버퍼가 두 번 제거됩니다.
함수는 입력 블록마다 한 번 호출됩니다. 대규모 쿼리는 쿼리 파이프라인에 의해 여러 블록으로 분할되므로(webassembly_udf_max_input_block_size 설정으로 호출당 최대 행 수를 추가로 제한할 수 있음), 각 호출에서 전달되는 행 수는 전체 쿼리가 아니라 해당 블록의 크기입니다.
(module
;; Allocate a new buffer of specified size
;; Returns: handle to Buffer structure (not direct data pointer!) with pointer to data and size
(func (export "clickhouse_create_buffer")
(param $size i32) ;; Size of data to allocate
(result i32)) ;; Returns buffer handle with enough space
;; Free a buffer by its handle
(func (export "clickhouse_destroy_buffer")
(param $handle i32) ;; Buffer handle to free
(result)) ;; No return value
;; User-defined function
(func (export "user_defined_function1")
(param $input_buffer_handle i32) ;; Input buffer handle
(param $n i32) ;; Number of rows in input
(result i32)) ;; Returns output buffer handle
)freestanding C로 작성한 완전한 예시입니다. str_reverse는 serialization_format = 'RowBinary'를 사용해 각 입력 문자열의 바이트 순서를 뒤집습니다. 모듈 인스턴스는 여러 블록에서 재사용되므로 clickhouse_destroy_buffer가 실제로 메모리를 회수해야 합니다. 여기서는 모든 버퍼가 소멸되면 할당자가 재설정됩니다.
#include <stdint.h>
typedef struct {
uint8_t * data;
uint32_t size;
} ClickHouseBuffer;
#define HEAP_SIZE (1 << 24)
static _Alignas(16) uint8_t heap[HEAP_SIZE];
static uint32_t heap_pos = 0;
static uint32_t live_buffers = 0;
__attribute__((export_name("clickhouse_create_buffer")))
ClickHouseBuffer * clickhouse_create_buffer(uint32_t size)
{
uint32_t total = (sizeof(ClickHouseBuffer) + size + 15u) & ~15u;
if (heap_pos + total > HEAP_SIZE)
return 0; /* a zero handle makes the host fail the query with an error */
ClickHouseBuffer * buf = (ClickHouseBuffer *) &heap[heap_pos];
buf->data = &heap[heap_pos + sizeof(ClickHouseBuffer)];
buf->size = size;
heap_pos += total;
++live_buffers;
return buf;
}
__attribute__((export_name("clickhouse_destroy_buffer")))
void clickhouse_destroy_buffer(ClickHouseBuffer * buf)
{
if (--live_buffers == 0)
heap_pos = 0;
}
/* RowBinary prefixes each String with its byte length as an unsigned varint (LEB128) */
static uint64_t read_varint(const uint8_t ** pp)
{
uint64_t value = 0;
for (int shift = 0;; shift += 7)
{
uint8_t b = *(*pp)++;
value |= (uint64_t)(b & 0x7f) << shift;
if (!(b & 0x80))
return value;
}
}
static void write_varint(uint8_t ** pp, uint64_t value)
{
while (value >= 0x80)
{
*(*pp)++ = (uint8_t)(value | 0x80);
value >>= 7;
}
*(*pp)++ = (uint8_t)value;
}
/* Reverses the bytes of each input string */
__attribute__((export_name("str_reverse")))
ClickHouseBuffer * str_reverse(ClickHouseBuffer * input, uint32_t num_rows)
{
ClickHouseBuffer * out = clickhouse_create_buffer(input->size);
if (!out)
return 0;
const uint8_t * in = input->data;
uint8_t * o = out->data;
for (uint32_t row = 0; row < num_rows; ++row)
{
uint64_t len = read_varint(&in);
write_varint(&o, len);
for (uint64_t i = 0; i < len; ++i)
o[i] = in[len - 1 - i];
in += len;
o += len;
}
out->size = (uint32_t)(o - out->data);
return out;
}clang 및 wasm-ld(LLVM/lld에 포함됨)를 사용하여 빌드합니다:
clang --target=wasm32 -ffreestanding -nostdlib -fno-builtin -c str_reverse.c
wasm-ld --no-entry str_reverse.o -o str_reverse.wasm함수는 모듈에서 내보내야 합니다. 위와 같이 __attribute__((export_name("...")))로 표시하거나 wasm-ld --export-all로 링크하십시오. -fno-builtin은 clang이 단순한 바이트 루프를 표준 라이브러리 없이는 사용할 수 없는 memcpy/memset 호출로 변환하는 것을 방지합니다.
모듈을 로드하고 함수를 생성합니다:
cat str_reverse.wasm | clickhouse client -q "INSERT INTO system.webassembly_modules (name, code) SELECT 'str_reverse', code FROM input('code String') FORMAT RawBlob"CREATE FUNCTION str_reverse LANGUAGE WASM ABI BUFFERED_V1
FROM 'str_reverse' :: 'str_reverse'
ARGUMENTS (s String) RETURNS String
SETTINGS serialization_format = 'RowBinary';
SELECT str_reverse(toString(number + 100)) FROM numbers(3);001
101
201ABI ASSEMBLYSCRIPT
AssemblyScript 컴파일러로 생성된 모듈을 대상으로 합니다. 각 행마다 내보낸 함수가 한 번 호출되며, ClickHouse 값은 AssemblyScript 기본 타입과 문자열 객체로 매핑됩니다.
지원되는 타입:
-
숫자형:
Int8/UInt8,Int16/UInt16(경계에서i32로 확장),Int32/UInt32,Int64/UInt64,Float32,Float64 -
String— AssemblyScriptstring에 매핑됩니다(WASM 메모리에서는 UTF-16). ClickHouse가 UTF-8 ↔ UTF-16 변환을 자동으로 처리합니다. -
사용자 정의 AssemblyScript 클래스는 인수 또는 반환 타입으로 지원되지 않습니다. 런타임 클래스 id가 컴파일할 때마다 안정적이지 않기 때문입니다(AssemblyScript#2982 참조).
모듈 요구 사항:
모듈은 __new, __pin, __unpin이 내보내지도록 AssemblyScript 관리 런타임으로 컴파일되어야 합니다. 표준 문자열 입출력 처리는 이를 전제로 합니다. 권장 호출 방식은 다음과 같습니다.
asc src.ts --runtime incremental --exportRuntime -o src.wasmAssemblyScript는 런타임 트랩(메모리 부족, 경계 검사 등)을 처리하기 위해 env.abort도 가져옵니다. ClickHouse는 이 import를 자동으로 제공합니다. abort가 트리거되면 현재 실행 중인 쿼리가 실패하며, 디코딩된 AssemblyScript 메시지와 소스 위치가 포함된 WASM_ERROR 예외가 반환됩니다.
예시:
// src.ts
export function add(a: u32, b: u32): u32 {
return a + b;
}
export function greet(name: string): string {
return "Hello, " + name + "!";
}asc로 컴파일하고 생성된 .wasm을 system.webassembly_modules에 로드한 후, UDF를 다음과 같이 선언합니다:
CREATE FUNCTION as_add
LANGUAGE WASM ABI ASSEMBLYSCRIPT
FROM 'as_example' :: 'add'
ARGUMENTS (a UInt32, b UInt32) RETURNS UInt32;
CREATE FUNCTION as_greet
LANGUAGE WASM ABI ASSEMBLYSCRIPT
FROM 'as_example' :: 'greet'
ARGUMENTS (name String) RETURNS String;Rust로 UDF를 개발할 때 참고할 사항
Rust 프로그램용으로는 ClickHouse의 WebAssembly UDF 개발을 간소화할 수 있도록 헬퍼 크레이트 clickhouse-wasm-udf를 제공합니다. 이 크레이트는 메모리 관리 함수를 제공하므로 clickhouse_create_buffer와 clickhouse_destroy_buffer 함수를 직접 구현할 필요 없이 크레이트를 의존성으로 추가하면 됩니다. 또한 일반적인 Rust 함수를 필요한 ABI 형식으로 감싸는 매크로 #[clickhouse_wasm_udf]도 제공합니다.
이 크레이트를 사용하면 다음과 같이 UDF를 작성할 수 있습니다:
use clickhouse_wasm_udf_bindgen::clickhouse_udf;
#[clickhouse_udf]
pub fn some_udf(data: String) -> HashMap<String, String> {
// 여기에 구현 내용을 작성하세요
}매크로는 버퍼 구조체를 인자로 받아 버퍼 구조체를 반환하는 래퍼 함수를 생성하고, serde를 사용해 직렬화/역직렬화를 자동으로 처리합니다.
모듈에서 사용할 수 있는 호스트 API
다음 호스트 함수는 import하여 모듈에서 사용할 수 있습니다:
clickhouse_server_version() -> i64— ClickHouse 서버 버전을 정수로 반환합니다(예: v25.11.1.1은 25011001).clickhouse_throw(ptr: i32, size: i32)— 지정된 메시지로 오류를 발생시킵니다. 오류 메시지 문자열이 저장된 메모리 위치의 포인터와 문자열 크기를 인수로 받습니다.clickhouse_log(ptr: i32, size: i32)— 메시지를 ClickHouse 서버 텍스트 로그에 기록합니다.clickhouse_random(ptr: i32, size: i32)— 메모리를 무작위 바이트로 채웁니다.env.abort(message: i32, fileName: i32, line: i32, column: i32)— AssemblyScript 호환 모듈을 위해 제공됩니다. 이를 호출하거나(또는 이를 호출하는 AssemblyScript 런타임 트랩이 트리거되면) 디코딩된 메시지와 소스 위치가 포함된WASM_ERROR예외와 함께 UDF가 종료됩니다.env.abort를 import하지 않는 모듈은 영향을 받지 않습니다.
설정
다음 쿼리 수준 설정은 WebAssembly UDF 실행을 제어합니다:
-
webassembly_udf_max_fuel— WebAssembly UDF 인스턴스 실행당 연료 한도입니다. 각 WebAssembly 명령어는 일정량의 연료를 소비합니다. 이 값은 런타임에 전달되기 전에 1024배로 스케일되므로,webassembly_udf_max_fuel = 1은 약 1024 연료 단위에 해당합니다. 유한한 제한을 두지 않으려면 0으로 설정합니다. 이는 함수별 설정인webassembly_udf_enable_fuel이 true인 함수에만 적용되며, 기본값은 true입니다. -
webassembly_udf_max_memory— WebAssembly UDF 인스턴스당 바이트 단위의 메모리 한도입니다. -
webassembly_udf_max_input_block_size— 단일 블록에서 WebAssembly UDF에 전달되는 최대 행 수입니다. 모든 행을 한 번에 처리하려면 0으로 설정합니다. -
webassembly_udf_max_instances— 함수별로 병렬 실행할 수 있는 WebAssembly UDF 인스턴스의 최대 개수입니다.
예시 사용법:
SET webassembly_udf_max_fuel = 200000;
SELECT my_wasm_udf(column) FROM table;