개요
wasm32-unknown-unknown)로 컴파일해야 합니다. 또한 기본 32비트 WebAssembly 대상만 지원됩니다(wasm64 확장은 지원되지 않음).
모듈은 ClickHouse와 상호작용하기 위해 지원되는 통신 프로토콜(ABI) 중 하나를 따라야 합니다.
컴파일이 완료되면 모듈의 바이너리 코드는 system.webassembly_modules 테이블에 삽입되어 ClickHouse에 로드됩니다.
그 후 CREATE FUNCTION ... LANGUAGE WASM 구문을 사용해 모듈이 내보낸 함수를 참조하는 UDF를 생성할 수 있습니다.
사전 요구 사항
빠른 시작
wat2wasm 또는 wasm-tools의 parse 명령을 사용할 수 있습니다.
FORMAT RawBlob을 사용해 ClickHouse client로 직접 파이프하여 system.webassembly_modules 테이블에 삽입합니다.
그런 다음 모듈이 내보내는 steps 함수를 참조하는 UDF를 정의합니다:
:: 뒤에는 모듈의 함수 이름을 지정한다는 점에 유의하십시오.
이제 쿼리에서 collatz_steps 함수를 사용할 수 있습니다:
number 컬럼은 UInt32로 명시적으로 CAST됩니다. 이는 WebAssembly 함수가 CREATE FUNCTION 문에 지정된 시그니처와 정확히 일치하는 타입을 요구하기 때문입니다.
그 결과, 1부터 100까지의 수에 대한 Collatz 단계 수열을 얻을 수 있으며, 이는 OEIS의 A006577 수열에 해당합니다.
시스템 테이블을 통해 WASM 모듈 관리
system.webassembly_modules 테이블에 저장됩니다:
- 컬럼
nameString — 모듈 이름입니다. 비어 있을 수 없으며, 영문자, 숫자, 밑줄 문자만 사용할 수 있습니다.codeString — 원시 바이너리 WASM 코드입니다. 쓰기 전용이며, 읽기 시 빈 문자열이 반환됩니다.hashUInt256 — 모듈 바이너리의 SHA256입니다(디스크에는 존재하지만 아직 로드되지 않은 경우 0).
모듈 삽입
클러스터 전체에 모듈 배포
system.webassembly_modules는 인스턴스별 table이므로 INSERT는 connection을 처리하는 레플리카에만 적용됩니다. INSERT 문에는 ON CLUSTER 형식이 없으므로, 이어서 CREATE FUNCTION ... ON CLUSTER를 실행하면 모듈이 없는 레플리카에서는 실패합니다:
system.webassembly_modules 테이블 대신 cluster 테이블 함수에 쓰세요:
이 패턴은 기본 분산 쓰기 경로가 각 세그먼트 내의 모든 레플리카를 거치는 방식에 의존하며, 이는 클러스터가
internal_replication=false로 구성된 경우에만 가능합니다. internal_replication=true인 경우(ReplicatedMergeTree를 사용해 자체적으로 복제를 수행하는 클러스터의 기본 설정), 삽입은 세그먼트당 정상인 단일 레플리카에만 전달되고 system.webassembly_modules는 이 경로로 복제되지 않으므로 일부 레플리카에는 여전히 모듈이 없을 수 있습니다. 이 구성에서는 각 레플리카에 대해 개별적으로 삽입해야 합니다. 예를 들어 system.clusters를 순회하면서 각 호스트별로 remote(...)를 통해 기록하거나, 모든 호스트의 user_scripts/wasm/에 바이너리를 복사할 수 있습니다.클러스터의 internal_replication 설정은 SELECT cluster, shard_num, internal_replication FROM system.clusters로 확인할 수 있습니다.CREATE FUNCTION ... ON CLUSTER가 성공합니다:
clusterAllReplicas를 사용하면 모든 레플리카에 모듈이 로드되었는지 확인할 수 있습니다:
system.webassembly_modules에 대한 삽입은 동일한 (name, hash) 쌍에 대해 멱등적이므로, 팬아웃된 삽입을 다시 실행해도 안전하며 레플리카 교체 후 상태를 복구하는 합리적인 방법입니다. 새로 추가된 서버에는 기존 모듈이 자동으로 전달되지 않는다는 점에 유의하십시오. 업데이트된 클러스터를 대상으로 삽입을 다시 실행하거나, 새 호스트의 user_scripts/wasm/ 디렉터리에 바이너리를 배치해야 합니다.
모듈 목록 보기
모듈 삭제
DELETE FROM system.webassembly_modules WHERE name = '...' 문을 사용해 수행합니다.
프레디케이트는 정확한 일치에는 name = 'literal', 패턴과 일치하는 모든 모듈을 삭제할 때는 name LIKE 'pattern'여야 하며, 그 밖의 형태는 허용되지 않습니다.
WebAssembly UDF 생성하기
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이 무시됩니다.wasmtime엔진을 사용할 때 연료 제한을 비활성화하면 성능이 향상될 수 있습니다. 하지만 신뢰할 수 없거나 버그가 있는 게스트 코드의 경우, 제어되지 않는 실행 위험이 커질 수 있습니다.
ABI 버전
ROW_DIRECT: 직접 타입 매핑(기본 타입Int32,UInt32,Int64,UInt64,Float32,Float64만 지원)BUFFERED_V1: 직렬화를 사용하는 복합 타입ASSEMBLYSCRIPT: AssemblyScript 모듈과의 행 단위 상호 운용; 숫자 타입과String을 지원
ABI ROW_DIRECT
- 인수와 반환 타입은 숫자 타입
Int32/UInt32/Int64/UInt64/Float32/Float64/Int128/UInt128입니다. - 이 ABI에서는 문자열을 지원하지 않습니다.
- 시그니처는 WASM 내보내기(
i32/i64/f32/f64/v128)와 일치해야 합니다. - 모듈에서 내보내야 하는 지원 함수는 필요하지 않습니다.
ABI BUFFERED_V1
이 ABI는 실험적 기능이며 향후 릴리스에서 변경될 수 있습니다.
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 설정으로 호출당 최대 행 수를 추가로 제한할 수 있음), 각 호출에서 전달되는 행 수는 전체 쿼리가 아니라 해당 블록의 크기입니다.
버퍼 내부의 정확한 바이트는 선택한 포맷에 따라 결정되며, 게스트 코드는 이를 직접 디코딩하고 인코딩해야 합니다. 특히
String 값의 경우:MsgPack: ClickHouse는String값을str계열이 아닌bin타입 계열(0xc4/0xc5/0xc6)로 작성합니다. 결과를 파싱할 때는 두 계열 모두 허용됩니다.RowBinary: 각String앞에는 고정 폭 정수가 아닌 부호 없는 varint(LEB128) 형식의 바이트 길이가 접두사로 추가됩니다.JSONEachRow: 각 결과 행은 예를 들어{"result":"value"}와 같이result키를 가진 객체여야 합니다.
str_reverse는 serialization_format = 'RowBinary'를 사용해 각 입력 문자열의 바이트 순서를 뒤집습니다. 모듈 인스턴스는 여러 블록에서 재사용되므로 clickhouse_destroy_buffer가 실제로 메모리를 회수해야 합니다. 여기서는 모든 버퍼가 소멸되면 할당자가 재설정됩니다.
wasm-ld(LLVM/lld에 포함됨)를 사용하여 빌드합니다:
__attribute__((export_name("...")))로 표시하거나 wasm-ld --export-all로 링크하십시오. -fno-builtin은 clang이 단순한 바이트 루프를 표준 라이브러리 없이는 사용할 수 없는 memcpy/memset 호출로 변환하는 것을 방지합니다.
모듈을 로드하고 함수를 생성합니다:
ABI 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 관리 런타임으로 컴파일되어야 합니다. 표준 문자열 입출력 처리는 이를 전제로 합니다. 권장 호출 방식은 다음과 같습니다.
env.abort도 가져옵니다. ClickHouse는 이 import를 자동으로 제공합니다. abort가 트리거되면 현재 실행 중인 쿼리가 실패하며, 디코딩된 AssemblyScript 메시지와 소스 위치가 포함된 WASM_ERROR 예외가 반환됩니다.
예시:
asc로 컴파일하고 생성된 .wasm을 system.webassembly_modules에 로드한 후, UDF를 다음과 같이 선언합니다:
Rust로 UDF를 개발할 때 참고할 사항
clickhouse_create_buffer와 clickhouse_destroy_buffer 함수를 직접 구현할 필요 없이 크레이트를 의존성으로 추가하면 됩니다. 또한 일반적인 Rust 함수를 필요한 ABI 형식으로 감싸는 매크로 #[clickhouse_wasm_udf]도 제공합니다.
이 크레이트를 사용하면 다음과 같이 UDF를 작성할 수 있습니다:
serde를 사용해 직렬화/역직렬화를 자동으로 처리합니다.
모듈에서 사용할 수 있는 호스트 API
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_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 인스턴스의 최대 개수입니다.