Перейти к основному содержанию
ClickHouse поддерживает создание пользовательских функций (UDF) на WebAssembly. Это позволяет выполнять собственную логику, написанную на Rust, C, C++ и других языках, скомпилировав её в модули WebAssembly.

Обзор

Модуль WebAssembly — это скомпилированный бинарный файл, содержащий одну или несколько функций, которые можно вызывать из ClickHouse. Модуль можно рассматривать как библиотеку или разделяемый объект, который загружается один раз и затем многократно используется. Модуль WebAssembly с пользовательскими функциями (UDF) можно написать на любом языке, который компилируется в WebAssembly, например на Rust, C или C++. Код, скомпилированный в WebAssembly («гостевой» код) и выполняемый ClickHouse («хостом»), работает в изолированной песочнице и имеет доступ только к выделенной области памяти. Гостевой код экспортирует функции, которые может вызывать ClickHouse. К ним относятся как функции, реализующие вашу пользовательскую логику (используемую для определения UDF), так и вспомогательные функции, необходимые для управления памятью и обмена данными между ClickHouse и кодом WebAssembly. Ваш код должен быть скомпилирован в «автономный» WebAssembly (то есть wasm32-unknown-unknown) без каких-либо зависимостей от операционной системы или стандартной библиотеки. Кроме того, поддерживается только стандартная 32-битная целевая платформа WebAssembly (без расширения wasm64). Модуль должен соответствовать одному из поддерживаемых протоколов взаимодействия (ABI) для работы с ClickHouse. После компиляции бинарный код модуля загружается в ClickHouse путём вставки в таблицу system.webassembly_modules. После этого вы можете создавать UDF, ссылающиеся на функции, экспортируемые модулем, с помощью оператора CREATE FUNCTION ... LANGUAGE WASM.

Предварительные требования

Включите поддержку WebAssembly в конфигурации ClickHouse:
<clickhouse>
    <allow_experimental_webassembly_udf>true</allow_experimental_webassembly_udf>
    <webassembly_udf_engine>wasmtime</webassembly_udf_engine>
</clickhouse>
Доступные реализации движка:
  • wasmtime (по умолчанию; рекомендуется) — использует WasmTime
  • wasmedge — использует WasmEdge

Быстрый старт

Этот пример демонстрирует полный процесс создания WebAssembly UDF на примере реализации вычисления гипотезы Коллатца. Мы напишем код в текстовом формате WebAssembly (WAT) — удобочитаемом представлении WebAssembly, поэтому на этом этапе не потребуется какой-либо язык программирования. ClickHouse требует, чтобы модуль был в бинарном формате, поэтому мы воспользуемся транспилятором, чтобы преобразовать WAT в WASM. Для этого преобразования можно использовать wat2wasm из WebAssembly Binary Toolkit (WABT) или команду parse из wasm-tools.
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 в клиент ClickHouse с помощью FORMAT RawBlob, чтобы выполнить вставку в таблицу system.webassembly_modules. Затем мы определяем UDF, которая ссылается на функцию steps, экспортируемую этим модулем:
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, поскольку функции WebAssembly ожидают точного соответствия типов сигнатуре, указанной в операторе CREATE FUNCTION. В результате мы получили последовательность шагов Коллатца для чисел от 1 до 100, соответствующую последовательности A006577 из OEIS.
[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 бинарного файла модуля (ноль, если он есть на диске, но ещё не загружен).
Управление модулями выполняется с помощью стандартных 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).

Создание 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 как указатель на буфер (который состоит из указателя на данные и их размера) вместе с количеством входных строк. Таким образом, user-defined function в 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) { /* ... */ }

Примечание о разработке пользовательских функций (UDF) на Rust

Для программ на Rust мы предоставляем вспомогательный крейт clickhouse-wasm-udf, который упрощает разработку пользовательских функций (UDF) WebAssembly для ClickHouse. Крейт предоставляет функции для управления памятью, поэтому вам не нужно вручную реализовывать clickhouse_create_buffer и clickhouse_destroy_buffer — достаточно добавить этот крейт в зависимости. Также в нём есть макросы #[clickhouse_wasm_udf], которые оборачивают обычные функции Rust в требуемый ABI-формат. С этим крейтом вы можете писать пользовательские функции (UDF) так:

use clickhouse_wasm_udf_bindgen::clickhouse_udf;

#[clickhouse_udf]
pub fn some_udf(data: String) -> HashMap<String, String> {
    // Ваша реализация здесь
}

Макросы сгенерируют функцию-обёртку, которая принимает и возвращает структуры буфера и автоматически выполняет сериализацию и десериализацию с помощью serde.

API хоста, доступный модулям

Модули могут импортировать и использовать следующие функции хоста:
  • clickhouse_server_version() -> i64 — возвращает версию сервера ClickHouse в виде целого числа (например, 25011001 для v25.11.1.1).
  • 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 — Лимит fuel для каждого запуска экземпляра WebAssembly UDF. Каждая инструкция WebAssembly расходует некоторое количество fuel. Установите 0, чтобы снять ограничение.
  • webassembly_udf_max_memory — Лимит памяти в байтах для каждого экземпляра WebAssembly UDF.
  • webassembly_udf_max_input_block_size — Максимальное количество строк, передаваемых в WebAssembly UDF в одном block. Установите 0, чтобы обрабатывать все строки сразу.
  • webassembly_udf_max_instances — Максимальное количество экземпляров WebAssembly UDF, которые могут выполняться параллельно для одной функции.
Пример использования:
SET webassembly_udf_max_fuel = 200000;
SELECT my_wasm_udf(column) FROM table;

См. также

Последнее изменение 10 июня 2026 г.