메인 콘텐츠로 건너뛰기
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 지원을 사용 설정하세요:
<clickhouse>
    <allow_experimental_webassembly_udf>true</allow_experimental_webassembly_udf>
    <webassembly_udf_engine>wasmtime</webassembly_udf_engine>
</clickhouse>
사용 가능한 Engine 구현:
  • wasmtime (기본값, 권장) — WasmTime을 사용합니다
  • wasmedgeWasmEdge를 사용합니다

빠른 시작

이 예시는 콜라츠 추측 계산기를 구현하여 WebAssembly UDF를 만드는 전체 워크플로를 보여줍니다. 이 단계에서는 프로그래밍 언어가 필요하지 않으므로, WebAssembly를 사람이 읽을 수 있는 형태로 표현한 WebAssembly Text 포맷(WAT)으로 코드를 작성합니다. ClickHouse는 모듈이 바이너리 형식이어야 하므로, 트랜스파일러를 사용해 WAT를 WASM으로 변환합니다. 이 변환을 수행하려면 WebAssembly Binary Toolkit (WABT)wat2wasm 또는 wasm-toolsparse 명령을 사용할 수 있습니다.
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 TSV
number 컬럼은 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 테이블에 저장됩니다:
  • 컬럼
    • name String — 모듈 이름입니다. 비어 있을 수 없으며, 영문자, 숫자, 밑줄 문자만 사용할 수 있습니다.
    • code String — 원시 바이너리 WASM 코드입니다. 쓰기 전용이며, 읽기 시 빈 문자열이 반환됩니다.
    • hash UInt256 — 모듈 바이너리의 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 같은 외부 소스에서 모듈을 로드할 때 유용합니다.

모듈 목록 보기

SELECT name, lower(hex(reinterpretAsFixedString(hash))) AS sha256 FROM system.webassembly_modules

   ┌─name────┬─sha256───────────────────────────────────────────────────────────┐
1. │ collatz │ a084a10b7b5cb07db198bc93bf1f3c1f8cb8ef279df7a4f6b66b1cdd55d79c48 │
   └─────────┴──────────────────────────────────────────────────────────────────┘

모듈 삭제

삭제는 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]
[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: 직렬화를 사용하는 블록 기반 처리
  • DETERMINISTIC: 함수를 결정적으로 선언합니다 — 동일한 입력에 대해 항상 동일한 출력을 반환합니다. 지정하면 모든 인수가 상수인 호출에 대해 ClickHouse가 상수 폴딩을 수행할 수 있습니다. 함수는 쿼리 분석 시점에 한 번 평가되며, 결과는 모든 행에 재사용됩니다.
  • SHA256_HASH: 검증에 사용할 예상 모듈 해시입니다(생략하면 자동으로 채워짐). 서로 다른 레플리카에서 올바른 WASM 모듈이 로드되었는지 확인하는 데 사용할 수 있습니다.
  • SETTINGS: 함수별 설정
    • serialization_format String — ABI에 필요할 때 사용하는 직렬화 포맷입니다. 기본값: MsgPack.

ABI 버전

ClickHouse와 상호작용하려면 WebAssembly 모듈이 지원되는 ABI(Application Binary Interface) 중 하나를 따라야 합니다.
  • ROW_DIRECT: 직접 타입 매핑(기본 타입 Int32, UInt32, Int64, UInt64, Float32, Float64만 지원)
  • BUFFERED_V1: 직렬화를 사용하는 복합 타입

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

이 ABI는 실험적이며 향후 릴리스에서 변경될 수 있습니다.
WASM 메모리를 통한 (역)직렬화를 사용해 전체 블록을 한 번에 처리합니다. 모든 인수 및 반환 타입을 지원합니다. 직렬화된 데이터는 wasm 메모리에 복사되며, 버퍼를 가리키는 포인터(데이터 포인터와 데이터 크기로 구성됨)가 입력 행 수와 함께 UDF 함수에 전달됩니다. 따라서 WASM에서 실행되는 사용자 정의 함수는 항상 두 개의 i32 인수를 받아 하나의 i32 값을 반환합니다. 게스트 코드는 데이터를 처리한 후 직렬화된 결과 데이터가 들어 있는 결과 버퍼를 가리키는 포인터를 반환합니다. 게스트 코드는 이러한 버퍼를 생성하고 해제하는 두 개의 함수를 제공해야 합니다.
(module
  ;; 지정된 크기의 새 버퍼를 할당합니다
  ;; 반환값: Buffer 구조체에 대한 핸들 (직접 데이터 포인터가 아님!) - 데이터 포인터와 크기를 포함
  (func (export "clickhouse_create_buffer")
    (param $size i32)    ;; 할당할 데이터 크기
    (result i32))        ;; 충분한 공간을 가진 버퍼 핸들 반환

  ;; 핸들로 버퍼를 해제합니다
  (func (export "clickhouse_destroy_buffer")
    (param $handle i32)  ;; 해제할 버퍼 핸들
    (result))            ;; 반환값 없음

    ;; 사용자 정의 함수
    (func (export "user_defined_function1")
      (param $input_buffer_handle i32)  ;; 입력 버퍼 핸들
      (param $n i32)                    ;; 입력의 행 수
      (result i32))                     ;; 출력 버퍼 핸들 반환
)
C 정의 예시:
typedef struct {
    uint8_t * data;
    uint32_t size;
} ClickhouseBuffer;

ClickhouseBuffer * clickhouse_create_buffer(uint32_t size) { /* ... */ }

void clickhouse_destroy_buffer(ClickhouseBuffer * data) { /* ... */ }

/// 예시 사용자 정의 함수
ClickhouseBuffer * user_defined_function1(ClickhouseBuffer * span, uint32_t n) { /* ... */ }
ClickhouseBuffer * user_defined_function2(ClickhouseBuffer * span, uint32_t n) { /* ... */ }

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

Rust 프로그램용으로는 ClickHouse의 WebAssembly UDF 개발을 간소화할 수 있도록 헬퍼 크레이트 clickhouse-wasm-udf를 제공합니다. 이 크레이트는 메모리 관리 함수를 제공하므로 clickhouse_create_bufferclickhouse_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) — 메모리를 무작위 바이트로 채웁니다.

설정

다음 쿼리 수준 설정은 WebAssembly UDF 실행을 제어합니다:
  • webassembly_udf_max_fuel — WebAssembly UDF 인스턴스 실행당 연료 한도입니다. 각 WebAssembly 명령어는 일정량의 연료를 소비합니다. 제한을 두지 않으려면 0으로 설정합니다.
  • 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;

관련 항목

마지막 수정일 2026년 6월 10일