Пользовательские функции на WebAssembly
ClickHouse поддерживает создание пользовательских функций (UDF), реализованных на WebAssembly. Это позволяет выполнять пользовательскую логику, написанную на таких языках, как Rust, C, C++ и другие, компилируя их в модули WebAssembly.
Обзор
Модуль WebAssembly — это скомпилированный двоичный файл, который содержит одну или несколько функций, вызываемых из ClickHouse. Модуль можно рассматривать как библиотеку или разделяемый объект, который загружается один раз и многократно переиспользуется.
Модуль WebAssembly, содержащий UDF, может быть написан на любом языке, компилируемом в WebAssembly, например Rust, C или C++.
Код, скомпилированный в WebAssembly («гостевой» код) и выполняемый ClickHouse («хост»), запускается в изолированном окружении (sandbox), имеющем доступ только к выделенному участку памяти.
Гостевой код экспортирует функции, которые ClickHouse может вызывать; сюда входят функции, реализующие вашу прикладную логику (используемые для определения UDF), а также вспомогательные функции, необходимые для управления памятью и обмена данными между ClickHouse и кодом WebAssembly.
Ваш код должен быть скомпилирован в «автономный» WebAssembly (также известный как wasm32-unknown-unknown) без каких-либо зависимостей от операционной системы или стандартной библиотеки. Также поддерживается только стандартная 32-битная цель WebAssembly (расширение wasm64 не поддерживается).
Модуль должен следовать одному из поддерживаемых протоколов взаимодействия (ABI) для интеграции с ClickHouse.
После компиляции двоичный код модуля загружается в ClickHouse путём вставки его в таблицу system.webassembly_modules.
После этого вы можете создавать UDF, которые ссылаются на функции, экспортируемые модулем, с помощью выражения CREATE FUNCTION ... LANGUAGE WASM.
Предварительные требования
Включите поддержку WebAssembly в конфигурации ClickHouse:
Доступные реализации движка:
Быстрый старт
В этом примере демонстрируется полный рабочий процесс создания WebAssembly UDF путём реализации калькулятора гипотезы Коллатца.
Мы напишем код в текстовом формате WebAssembly (WAT), который является человекочитаемым представлением WebAssembly, поэтому на этом этапе знание какого-либо языка программирования не требуется.
ClickHouse требует, чтобы модуль был в двоичном формате, поэтому мы воспользуемся транспайлером для преобразования WAT в WASM.
Для выполнения этого преобразования вы можете использовать wat2wasm из WebAssembly Binary Toolkit (WABT) или команду parse из wasm-tools.
В приведённом выше фрагменте мы передаём бинарный код WASM непосредственно в клиент ClickHouse, используя FORMAT RawBlob, чтобы вставить его в таблицу system.webassembly_modules.
Затем мы определяем UDF, который ссылается на функцию steps, экспортируемую из модуля:
Обратите внимание, что мы указываем имя функции из модуля после ::, так как оно отличается от имени UDF.
Теперь мы можем использовать функцию collatz_steps в наших запросах:
Столбец number явно приводится к типу UInt32, потому что функции WebAssembly ожидают точного соответствия типов сигнатуре, указанной в операторе CREATE FUNCTION.
В результате мы получили последовательность шагов Коллатца для чисел от 1 до 100, соответствующую последовательности A006577 из OEIS.
Управление модулями WASM через системную таблицу
Модули WebAssembly хранятся в таблице system.webassembly_modules, имеющей следующую структуру:
- Столбцы
nameString — Имя модуля. Непустое, допускаются только буквенно-цифровые символы и подчёркивания.codeString — Сырые двоичные данные WASM. Только для записи, при чтении возвращается пустая строка.hashUInt256 — SHA256 бинарного файла модуля (ноль, если модуль присутствует на диске, но ещё не загружен).
Управление модулями осуществляется с помощью стандартных SQL-операций над этой таблицей:
Добавить модуль
При необходимости укажите контрольную сумму:
Если указанный хэш не совпадает с вычисленным SHA256‑хэшем кода модуля, вставка завершается с ошибкой. Это может быть полезно при загрузке модулей из внешних источников, таких как S3 или HTTP.
Список модулей
Удаление модуля
Удаление выполняется запросом DELETE FROM system.webassembly_modules WHERE name = '...'.
Поддерживается только удаление одного модуля за один запрос по его точному имени.
Если какие-либо существующие UDF ссылаются на модуль, удаление завершится с ошибкой, поэтому сначала нужно удалить эти UDF.
Создайте UDF на WebAssembly
Синтаксис:
Параметры:
function_name: Имя функции в ClickHouse. Может отличаться от имени экспортируемой функции в модуле.FROM 'module_name' :: 'source_function_name': Имя загруженного модуля WASM и имя функции в модуле WASM, которое следует использовать (по умолчанию —function_name).ARGUMENTS: Список имён и типов аргументов (имена необязательны и используются для форматов сериализации, которые поддерживают именованные поля).ABI: Версия Application Binary InterfaceROW_DIRECT: Прямое сопоставление типов, построчная обработкаBUFFERED_V1: Блочная обработка с сериализацией
SHA256_HASH: Ожидаемый хэш модуля для проверки (автоматически заполняется, если опущен); может использоваться для обеспечения загрузки корректного модуля WASM на разных репликах.SETTINGS: Настройки для отдельной функцииserialization_formatString — Формат сериализации для 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). - Модуль не обязан экспортировать какие-либо вспомогательные функции.
Например, функция с сигнатурой:
Его можно создать следующим образом:
WebAssembly не различает знаковые и беззнаковые аргументы, а использует разные инструкции для интерпретации значений. Поэтому размер аргумента должен строго совпадать, тогда как его знаковость определяется операциями внутри функции.
ABI BUFFERED_V1
Этот ABI является экспериментальным и может измениться в будущих релизах.
Обрабатывает целые блоки целиком, используя (де)сериализацию через память WASM. Поддерживает любые типы аргументов и возвращаемых значений.
Сериализованные данные копируются в память WASM, передаваемую как указатель на буфер (состоящий из указателя на данные и размера этих данных) в функцию UDF вместе с числом строк во входных данных. Таким образом, функция, определяемая пользователем, на стороне WASM всегда принимает два аргумента i32 и возвращает одно значение i32. Гостевой код обрабатывает данные и возвращает указатель на буфер результата с сериализованными данными результата.
Гостевой код должен реализовать две функции для создания и уничтожения этих буферов.
Примеры определений на C:
Примечание по разработке UDF на Rust
Для программ на Rust мы предоставляем вспомогательный crate clickhouse-wasm-udf, который упрощает разработку UDF на WebAssembly для ClickHouse. Этот crate содержит функции для управления памятью, поэтому вам не нужно вручную реализовывать функции clickhouse_create_buffer и clickhouse_destroy_buffer — достаточно добавить crate как зависимость. Также доступны макросы #[clickhouse_wasm_udf], которые оборачивают ваши обычные функции Rust в требуемый формат ABI.
С этим crate вы можете писать UDF следующим образом:
Макросы сгенерируют обёрточную функцию, принимающую и возвращающую структуры буферов, и автоматически обработают (де)сериализацию с использованием serde.
Host 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— лимит топлива на выполнение экземпляра 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, которые могут выполняться одновременно для одной функции.
Пример использования: