메인 콘텐츠로 건너뛰기

WebAssembly 사용자 정의 함수

ClickHouse는 WebAssembly로 작성된 사용자 정의 함수(UDF) 생성 기능을 지원합니다. 이를 통해 Rust, C, C++ 등으로 작성한 사용자 정의 로직을 WebAssembly 모듈로 컴파일해 실행할 수 있습니다.

개요

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 지원을 사용 설정하세요:
사용 가능한 Engine 구현:
  • wasmtime (기본값, 권장) — WasmTime을 사용합니다
  • wasmedgeWasmEdge를 사용합니다

빠른 시작

이 예시는 콜라츠 추측 계산기를 구현하여 WebAssembly UDF를 만드는 전체 워크플로를 보여줍니다. 이 단계에서는 프로그래밍 언어가 필요하지 않으므로, WebAssembly를 사람이 읽을 수 있는 형태로 표현한 WebAssembly Text 포맷(WAT)으로 코드를 작성합니다. ClickHouse는 모듈이 바이너리 형식이어야 하므로, 트랜스파일러를 사용해 WAT를 WASM으로 변환합니다. 이 변환을 수행하려면 WebAssembly Binary Toolkit (WABT)wat2wasm 또는 wasm-toolsparse 명령을 사용할 수 있습니다.
위 스니펫에서는 바이너리 WASM 코드를 FORMAT RawBlob을 사용해 ClickHouse client로 직접 파이프하여 system.webassembly_modules 테이블에 삽입합니다. 그런 다음 모듈이 내보내는 steps 함수를 참조하는 UDF를 정의합니다:
UDF 이름과 다르므로 :: 뒤에는 모듈의 함수 이름을 지정한다는 점에 유의하십시오. 이제 쿼리에서 collatz_steps 함수를 사용할 수 있습니다:
number 컬럼은 UInt32로 명시적으로 CAST됩니다. 이는 WebAssembly 함수가 CREATE FUNCTION 문에 지정된 시그니처와 정확히 일치하는 타입을 요구하기 때문입니다. 그 결과, 1부터 100까지의 수에 대한 Collatz 단계 수열을 얻을 수 있으며, 이는 OEIS의 A006577 수열에 해당합니다.

시스템 테이블을 통해 WASM 모듈 관리

WebAssembly 모듈은 다음 구조를 가진 system.webassembly_modules 테이블에 저장됩니다:
  • 컬럼
    • name String — 모듈 이름입니다. 비어 있을 수 없으며, 영문자, 숫자, 밑줄 문자만 사용할 수 있습니다.
    • code String — 원시 바이너리 WASM 코드입니다. 쓰기 전용이며, 읽기 시 빈 문자열이 반환됩니다.
    • hash UInt256 — 모듈 바이너리의 SHA256입니다(디스크에는 존재하지만 아직 로드되지 않은 경우 0).
모듈 관리는 이 테이블에 대한 표준 SQL 작업을 통해 수행됩니다:

모듈 삽입

필요한 경우 무결성 해시를 제공할 수 있습니다:
제공된 해시가 모듈 코드에서 계산된 SHA256과 일치하지 않으면 삽입이 실패합니다. S3 또는 HTTP 같은 외부 소스에서 모듈을 로드할 때 유용합니다.

클러스터 전체에 모듈 배포

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'여야 하며, 그 밖의 형태는 허용되지 않습니다.
기존 UDF 중 일치하는 모듈을 참조하는 UDF가 있으면 삭제에 실패하므로, 먼저 해당 UDF를 삭제해야 합니다.

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 기본 타입에 매핑되며, ClickHouse String은 AssemblyScript string에 매핑됩니다.
  • DETERMINISTIC: 함수를 결정적으로 선언합니다 — 동일한 입력에 대해 항상 동일한 출력을 반환합니다. 지정하면 모든 인수가 상수인 호출에 대해 ClickHouse가 상수 폴딩을 수행할 수 있습니다. 함수는 쿼리 분석 시점에 한 번 평가되며, 결과는 모든 행에 재사용됩니다.
  • SHA256_HASH: 검증에 사용할 예상 모듈 해시입니다(생략하면 자동으로 채워짐). 서로 다른 레플리카에서 올바른 WASM 모듈이 로드되었는지 확인하는 데 사용할 수 있습니다.
  • SETTINGS: 함수별 설정
    • serialization_format String — ABI에 필요할 때 사용하는 직렬화 포맷입니다. 지원되는 값: MsgPack, JSONEachRow, CSV, TSV, TSVRaw, RowBinary, Buffers. 기본값: MsgPack. Buffers와 같은 블록 기반 포맷은 선언된 함수 시그니처와 타입이 일치하는 단일 컬럼 하나를 반환해야 합니다.
    • webassembly_udf_enable_fuel Bool — 함수에 대한 제한된 연료 예산을 활성화합니다. 기본값: true. false이면 이 함수에서는 쿼리 수준 설정 webassembly_udf_max_fuel이 무시됩니다. wasmtime 엔진을 사용할 때 연료 제한을 비활성화하면 성능이 향상될 수 있습니다. 하지만 신뢰할 수 없거나 버그가 있는 게스트 코드의 경우, 제어되지 않는 실행 위험이 커질 수 있습니다.

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)와 일치해야 합니다.
  • 모듈에서 내보내야 하는 지원 함수는 필요하지 않습니다.
예를 들어, 다음과 같은 시그니처를 가진 함수가 있습니다:
다음과 같이 만들 수 있습니다:
WebAssembly는 부호 있는 인수와 부호 없는 인수를 구분하지 않으며, 대신 값을 해석할 때 서로 다른 명령어를 사용합니다. 따라서 인수의 크기는 정확히 일치해야 하고, 부호 여부는 함수 내부에서 수행하는 연산에 따라 결정됩니다.

ABI BUFFERED_V1

이 ABI는 실험적이며 향후 릴리스에서 변경될 수 있습니다.
WASM 메모리를 통한 (역)직렬화를 사용해 전체 블록을 한 번에 처리합니다. 모든 인수 및 반환 타입을 지원합니다. 직렬화된 데이터는 wasm 메모리에 복사되며, 버퍼를 가리키는 포인터(데이터 포인터와 데이터 크기로 구성됨)가 입력 행 수와 함께 UDF 함수에 전달됩니다. 따라서 WASM에서 실행되는 사용자 정의 함수는 항상 두 개의 i32 인수를 받아 하나의 i32 값을 반환합니다. 게스트 코드는 데이터를 처리한 후 직렬화된 결과 데이터가 들어 있는 결과 버퍼를 가리키는 포인터를 반환합니다. 게스트 코드는 이러한 버퍼를 생성하고 해제하는 두 개의 함수를 제공해야 합니다.
C 정의 예시:

ABI ASSEMBLYSCRIPT

AssemblyScript 컴파일러로 생성된 모듈을 대상으로 합니다. 각 행마다 내보낸 함수가 한 번 호출되며, ClickHouse 값은 AssemblyScript 기본 타입과 문자열 객체로 매핑됩니다. 지원되는 타입:
  • 숫자형: Int8/UInt8, Int16/UInt16 (경계에서 i32로 확장), Int32/UInt32, Int64/UInt64, Float32, Float64
  • String — AssemblyScript string에 매핑됩니다(WASM 메모리에서는 UTF-16). ClickHouse가 UTF-8 ↔ UTF-16 변환을 자동으로 처리합니다.
  • 사용자 정의 AssemblyScript 클래스는 인수 또는 반환 타입으로 지원되지 않습니다. 런타임 클래스 id가 컴파일할 때마다 안정적이지 않기 때문입니다(AssemblyScript#2982 참조).
모듈 요구 사항: 모듈은 __new, __pin, __unpin이 내보내지도록 AssemblyScript 관리 런타임으로 컴파일되어야 합니다. 표준 문자열 입출력 처리는 이를 전제로 합니다. 권장 호출 방식은 다음과 같습니다.
AssemblyScript는 런타임 트랩(메모리 부족, 경계 검사 등)을 처리하기 위해 env.abort도 가져옵니다. ClickHouse는 이 import를 자동으로 제공합니다. abort가 트리거되면 현재 실행 중인 쿼리가 실패하며, 디코딩된 AssemblyScript 메시지와 소스 위치가 포함된 WASM_ERROR 예외가 반환됩니다. 예시:
asc로 컴파일하고 생성된 .wasmsystem.webassembly_modules에 로드한 후, UDF를 다음과 같이 선언합니다:

Rust로 UDF를 개발할 때 참고할 사항

Rust 프로그램용으로는 ClickHouse의 WebAssembly UDF 개발을 간소화할 수 있도록 헬퍼 크레이트 clickhouse-wasm-udf를 제공합니다. 이 크레이트는 메모리 관리 함수를 제공하므로 clickhouse_create_bufferclickhouse_destroy_buffer 함수를 직접 구현할 필요 없이 크레이트를 의존성으로 추가하면 됩니다. 또한 일반적인 Rust 함수를 필요한 ABI 형식으로 감싸는 매크로 #[clickhouse_wasm_udf]도 제공합니다. 이 크레이트를 사용하면 다음과 같이 UDF를 작성할 수 있습니다:
매크로는 버퍼 구조체를 인자로 받아 버퍼 구조체를 반환하는 래퍼 함수를 생성하고, 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 인스턴스의 최대 개수입니다.
예시 사용법:

관련 항목

마지막 수정일 2026년 7월 2일