Saltar al contenido principal
ClickHouse admite la creación de funciones definidas por el usuario (UDFs) escritas en WebAssembly. Esto permite ejecutar lógica personalizada escrita en lenguajes como Rust, C, C++ u otros, tras compilarla en módulos de WebAssembly.

Descripción general

Un módulo de WebAssembly es un archivo binario compilado que contiene una o varias funciones a las que se puede llamar desde ClickHouse. Piense en un módulo como una biblioteca o un objeto compartido que se carga una vez y se reutiliza muchas veces. Los módulos de WebAssembly que contienen UDFs pueden escribirse en cualquier lenguaje que pueda compilarse a WebAssembly, como Rust, C o C++. El código compilado a WebAssembly (código “guest”) y ejecutado por ClickHouse (“host”) se ejecuta en un entorno aislado con acceso únicamente a un espacio de memoria dedicado. El código guest exporta funciones que ClickHouse puede invocar; entre ellas se incluyen las funciones que implementan su lógica personalizada (utilizadas para definir UDFs), así como las funciones auxiliares necesarias para la gestión de la memoria y el intercambio de datos entre ClickHouse y el código WebAssembly. Su código debe compilarse como WebAssembly “freestanding” (también conocido como wasm32-unknown-unknown) sin dependencias de un sistema operativo ni de una biblioteca estándar. Además, solo se admite el destino predeterminado de WebAssembly de 32 bits (sin la extensión wasm64). El módulo debe seguir uno de los protocolos de comunicación (ABI) compatibles para interactuar con ClickHouse. Una vez compilado, el código binario del módulo se carga en ClickHouse insertándolo en la tabla system.webassembly_modules. Después, puede crear UDFs que hagan referencia a las funciones exportadas por el módulo mediante la sentencia CREATE FUNCTION ... LANGUAGE WASM.

Requisitos previos

Active la compatibilidad con WebAssembly en la configuración de ClickHouse:
<clickhouse>
    <allow_experimental_webassembly_udf>true</allow_experimental_webassembly_udf>
    <webassembly_udf_engine>wasmtime</webassembly_udf_engine>
</clickhouse>
Implementaciones del motor disponibles:
  • wasmtime (predeterminado, recomendado) — usa WasmTime
  • wasmedge — usa WasmEdge

Inicio rápido

Este ejemplo muestra el flujo de trabajo completo para crear una UDF de WebAssembly implementando una calculadora de la conjetura de Collatz. Escribiremos el código en formato de texto de WebAssembly (WAT), que es una representación legible de WebAssembly, por lo que en esta etapa no se necesita ningún lenguaje de programación. ClickHouse requiere que el módulo esté en formato binario, así que usaremos el transpilador para convertir WAT a WASM. Para realizar esta conversión, puede usar wat2wasm de WebAssembly Binary Toolkit (WABT) o el comando parse de 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
En el fragmento anterior, enviamos el código WASM binario directamente al ClickHouse client mediante una tubería con FORMAT RawBlob para insertarlo en la tabla system.webassembly_modules. Luego definimos la UDF que hace referencia a la función steps exportada por el módulo:
CREATE FUNCTION collatz_steps LANGUAGE WASM ARGUMENTS (n UInt32) RETURNS UInt32 FROM 'collatz' :: 'steps';
Ten en cuenta que especificamos el nombre de la función del módulo después de ::, porque es distinto del nombre de la UDF. Ahora podemos usar la función collatz_steps en nuestras consultas:
SELECT groupArray(collatz_steps(number :: UInt32))
FROM numbers(1, 100)
FORMAT TSV
La columna number se convierte explícitamente a UInt32, porque las funciones de WebAssembly requieren una coincidencia exacta con los tipos de la firma especificada en la sentencia CREATE FUNCTION. En el resultado obtuvimos la secuencia de pasos de Collatz para los números del 1 al 100, correspondiente a la secuencia A006577 de la 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]

Administrar módulos WASM mediante la tabla del sistema

Los módulos de WebAssembly se almacenan en la tabla system.webassembly_modules, que tiene la siguiente estructura:
  • Columnas
    • name String — Nombre del módulo. No puede estar vacío; solo se permiten caracteres alfanuméricos y guiones bajos.
    • code String — Código WASM binario sin procesar. Solo escritura; las lecturas devuelven una cadena vacía.
    • hash UInt256 — SHA256 del binario del módulo (cero si está presente en disco, pero aún no se ha cargado).
La gestión de módulos se realiza mediante operaciones SQL estándar sobre esta tabla:

Insertar un módulo

INSERT INTO system.webassembly_modules (name, code)
SELECT 'my_module', base64Decode('AGFzbQEAAAA...');
Si lo desea, proporcione el hash de integridad:
INSERT INTO system.webassembly_modules (name, code, hash)
SELECT 'my_module', base64Decode('...'), reinterpretAsUInt256(unhex('369f...c57d'));
Si el hash proporcionado no coincide con el SHA256 calculado para el código del módulo, la inserción falla. Esto puede resultar útil al cargar módulos desde fuentes externas, como S3 o HTTP.

Listar módulos

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

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

Eliminar un módulo

La eliminación se realiza mediante la sentencia DELETE FROM system.webassembly_modules WHERE name = '...'. El predicado debe ser name = 'literal' para una coincidencia exacta o name LIKE 'pattern' para eliminar todos los módulos cuyo nombre coincida con el patrón; no se acepta ninguna otra forma.
DELETE FROM system.webassembly_modules WHERE name = 'collatz';

-- Eliminar en masa todos los módulos cuyo nombre empiece por `tmp_` (el guion bajo literal se escapa como `\_`):
DELETE FROM system.webassembly_modules WHERE name LIKE 'tmp\_%';
Si alguna UDF existente hace referencia a uno de los módulos coincidentes, la eliminación fallará, por lo que primero debe eliminar esas UDFs.

Crear una UDF de WebAssembly

Sintaxis:
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[, ...]];
Parámetros:
  • function_name: Nombre de la función en ClickHouse. Puede ser diferente del nombre de la función exportada en el módulo.
  • FROM 'module_name' :: 'source_function_name': Nombre del módulo WASM cargado y nombre de la función del módulo WASM que se va a usar (de forma predeterminada, function_name)
  • ARGUMENTS: Lista de nombres y tipos de argumentos (los nombres son opcionales y se usan en formatos de serialización que admiten campos con nombre)
  • ABI: Versión de la interfaz binaria de aplicación
    • ROW_DIRECT: Mapeo directo de tipos, procesamiento fila por fila
    • BUFFERED_V1: Procesamiento por bloques con serialización
  • DETERMINISTIC: Declara la función como determinista: siempre devuelve el mismo resultado para la misma entrada. Cuando se especifica, ClickHouse puede plegar a constantes las llamadas en las que todos los argumentos son constantes: la función se evalúa una vez durante el análisis de la consulta y el resultado se reutiliza para cada fila.
  • SHA256_HASH: Hash esperado del módulo para su verificación (se completa automáticamente si se omite); puede usarse para garantizar que se cargue el módulo WASM correcto en distintas réplicas.
  • SETTINGS: Configuración por función
    • serialization_format String — Formato de serialización para ABI que lo requiera. Predeterminado: MsgPack.

Versiones de las ABI

Para interactuar con ClickHouse, los módulos de WebAssembly deben cumplir con una de las ABI (Application Binary Interface) compatibles.
  • ROW_DIRECT: mapeo directo de tipos (solo tipos primitivos Int32, UInt32, Int64, UInt64, Float32, Float64)
  • BUFFERED_V1: tipos complejos con serialización

ABI ROW_DIRECT

Llama directamente a una función WASM exportada para cada fila.
  • Los argumentos y los tipos de retorno deben ser tipos numéricos Int32/UInt32/Int64/UInt64/Float32/Float64/Int128/UInt128.
  • Las cadenas no son compatibles con esta ABI.
  • Las firmas deben coincidir con la exportación WASM (i32/i64/f32/f64/v128).
  • El módulo no necesita exportar funciones de soporte.
Por ejemplo, una función con la firma:
(func (param i32 i64 f32) (result f64) ...)
Se puede crear así:
CREATE FUNCTION my_func ARGUMENTS (Int32, UInt64, Float32) RETURNS Float64 ...
WebAssembly no distingue entre argumentos con signo y sin signo, sino que utiliza distintas instrucciones para interpretar los valores. Por lo tanto, el tamaño del argumento debe coincidir exactamente, mientras que si es con signo o sin signo lo determinan las operaciones dentro de la función.

ABI BUFFERED_V1

Esta ABI es experimental y puede cambiar en futuras versiones.
Procesa bloques completos de una sola vez mediante (des)serialización a través de la memoria de WASM. Admite cualquier tipo de argumento y de retorno. Los datos serializados se copian en la memoria de WASM, que se pasa a la función UDF como un puntero a un búfer (compuesto por un puntero a los datos y el tamaño de los datos), junto con el número de filas de entrada. Por lo tanto, la función definida por el usuario en WASM siempre acepta dos argumentos i32 y devuelve un único valor i32. El código guest procesa los datos y devuelve un puntero al búfer de resultados con los datos del resultado serializados. El código guest debe proporcionar dos funciones para crear y destruir estos búferes.
(module
  ;; Asignar un nuevo buffer del tamaño especificado
  ;; Devuelve: identificador de la estructura Buffer (¡no un puntero directo a los datos!) con puntero a los datos y tamaño
  (func (export "clickhouse_create_buffer")
    (param $size i32)    ;; Tamaño de los datos a asignar
    (result i32))        ;; Devuelve el identificador del buffer con espacio suficiente

  ;; Liberar un buffer por su identificador
  (func (export "clickhouse_destroy_buffer")
    (param $handle i32)  ;; Identificador del buffer a liberar
    (result))            ;; Sin valor de retorno

    ;; Función definida por el usuario
    (func (export "user_defined_function1")
      (param $input_buffer_handle i32)  ;; Identificador del buffer de entrada
      (param $n i32)                    ;; Número de filas en la entrada
      (result i32))                     ;; Devuelve el identificador del buffer de salida
)
Definiciones de ejemplo en C:
typedef struct {
    uint8_t * data;
    uint32_t size;
} ClickhouseBuffer;

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

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

/// Ejemplo de funciones definidas por el usuario
ClickhouseBuffer * user_defined_function1(ClickhouseBuffer * span, uint32_t n) { /* ... */ }
ClickhouseBuffer * user_defined_function2(ClickhouseBuffer * span, uint32_t n) { /* ... */ }

Nota para desarrollar UDFs en Rust

Para los programas en Rust, proporcionamos un crate auxiliar clickhouse-wasm-udf para simplificar el desarrollo de WebAssembly UDFs para ClickHouse. El crate proporciona funciones para la gestión de memoria, por lo que no necesitas implementar manualmente las funciones clickhouse_create_buffer y clickhouse_destroy_buffer, sino añadir el crate como dependencia. También incluye macros #[clickhouse_wasm_udf] para encapsular tus funciones habituales de Rust en el formato ABI requerido. Con este crate, puedes escribir UDFs así:

use clickhouse_wasm_udf_bindgen::clickhouse_udf;

#[clickhouse_udf]
pub fn some_udf(data: String) -> HashMap<String, String> {
    // Tu implementación aquí
}

Las macros generarán una función de envoltura que acepta y devuelve estructuras de búfer, y gestionarán automáticamente la serialización y deserialización mediante serde.

API del host disponible para los módulos

Las siguientes funciones del host pueden importarse y usarse en los módulos:
  • clickhouse_server_version() -> i64 — devuelve la versión del servidor ClickHouse como un entero (p. ej., 25011001 para v25.11.1.1).
  • clickhouse_throw(ptr: i32, size: i32) — genera un error con el mensaje proporcionado. Acepta un puntero a la ubicación de memoria que contiene la cadena con el mensaje de error y el tamaño de la cadena.
  • clickhouse_log(ptr: i32, size: i32) — registra un mensaje en el log de texto del servidor ClickHouse.
  • clickhouse_random(ptr: i32, size: i32) — rellena la memoria con bytes aleatorios.

Configuración

Las siguientes configuraciones a nivel de consulta controlan la ejecución de WebAssembly UDF:
  • webassembly_udf_max_fuel — Límite de fuel por ejecución de una instancia de WebAssembly UDF. Cada instrucción de WebAssembly consume cierta cantidad de fuel. Establézcalo en 0 para no tener límite.
  • webassembly_udf_max_memory — Límite de memoria en bytes por instancia de WebAssembly UDF.
  • webassembly_udf_max_input_block_size — Número máximo de filas que se pasan a una WebAssembly UDF en un solo bloque. Establézcalo en 0 para procesar todas las filas a la vez.
  • webassembly_udf_max_instances — Número máximo de instancias de WebAssembly UDF que pueden ejecutarse en paralelo por función.
Ejemplo de uso:
SET webassembly_udf_max_fuel = 200000;
SELECT my_wasm_udf(column) FROM table;

Véase también

Última modificación el 10 de junio de 2026