Descripción general
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
Inicio rápido
wat2wasm de WebAssembly Binary Toolkit (WABT) o el comando parse de wasm-tools.
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:
::, porque es distinto del nombre de la UDF.
Ahora podemos usar la función collatz_steps en nuestras consultas:
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.
Administrar módulos WASM mediante la tabla del sistema
system.webassembly_modules, que tiene la siguiente estructura:
- Columnas
nameString — Nombre del módulo. No puede estar vacío; solo se permiten caracteres alfanuméricos y guiones bajos.codeString — Código WASM binario sin procesar. Solo escritura; las lecturas devuelven una cadena vacía.hashUInt256 — SHA256 del binario del módulo (cero si está presente en disco, pero aún no se ha cargado).
Insertar un módulo
Distribuir un módulo en un clúster
system.webassembly_modules es una tabla por instancia: un INSERT solo se aplica a la réplica que gestiona la conexión. No existe una forma ON CLUSTER de la sentencia INSERT, por lo que un CREATE FUNCTION ... ON CLUSTER posterior fallará en las réplicas que no tengan el módulo:
cluster en lugar de la tabla local system.webassembly_modules:
Este patrón depende de que la ruta subyacente de escritura distribuida recorra todas las réplicas de cada segmento, lo que solo ocurre cuando el clúster está configurado con
internal_replication=false. Con internal_replication=true (el valor predeterminado en los clústeres que usan ReplicatedMergeTree para gestionar por sí mismos la replicación), el insert se envía a una sola réplica en buen estado por segmento, y system.webassembly_modules no se replica por esa ruta, por lo que algunas réplicas seguirán sin tener el módulo. En esa configuración, debes hacer el insert en cada réplica por separado, por ejemplo, iterando sobre system.clusters y escribiendo mediante remote(...) por host, o copiando el binario en user_scripts/wasm/ en cada host.Puedes consultar internal_replication de un clúster con SELECT cluster, shard_num, internal_replication FROM system.clusters.CREATE FUNCTION ... ON CLUSTER se ejecuta correctamente:
clusterAllReplicas:
system.webassembly_modules son idempotentes para el mismo par (name, hash), por lo que volver a ejecutar la inserción distribuida es seguro y una forma razonable de reparar el estado después de reemplazar una réplica. Tenga en cuenta que los servidores recién añadidos no reciben retroactivamente los módulos existentes; debe volver a ejecutar la inserción en el clúster actualizado, o colocar el binario en el directorio user_scripts/wasm/ del nuevo host.
Listar módulos
Eliminar un módulo
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.
Crear una UDF de WebAssembly
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ónROW_DIRECT: Mapeo directo de tipos, procesamiento fila por filaBUFFERED_V1: Procesamiento por bloques con serializaciónASSEMBLYSCRIPT: Procesamiento fila por fila para módulos generados por el compilador AssemblyScript. Los tipos numéricos se asignan a primitivas de AssemblyScript; ClickHouseStringse asigna astring.
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ónserialization_formatString — Formato utilizado para serializar los bloques de argumentos que se pasan al módulo y para analizar el resultado devuelto. Se usa únicamente conABI BUFFERED_V1. Valores admitidos:MsgPack,JSONEachRow,CSV,TSV,TSVRaw,RowBinaryyBuffers. Predeterminado:MsgPack. Los formatos basados en bloques, comoBuffers, deben devolver una sola columna cuyo tipo coincida con la firma de la función declarada.webassembly_udf_enable_fuelBool — Habilita un presupuesto de fuel finito para la función. Predeterminado:true. Cuando esfalse, la configuración de nivel de consultawebassembly_udf_max_fuelse ignora para esta función. Deshabilitar los límites de fuel puede mejorar el rendimiento al usar el enginewasmtime. Sin embargo, para código guest no confiable o con errores, puede aumentar el riesgo de ejecución descontrolada.
Versiones de las ABI
ROW_DIRECT: mapeo directo de tipos (solo tipos primitivosInt32,UInt32,Int64,UInt64,Float32,Float64)BUFFERED_V1: tipos complejos con serializaciónASSEMBLYSCRIPT: interoperabilidad fila por fila con módulos de AssemblyScript; admite tipos numéricos yString.
ABI ROW_DIRECT
- 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.
ABI BUFFERED_V1
Esta ABI es experimental y está sujeta a cambios en versiones futuras.
u32 en orden little-endian). Los búferes se pasan mediante un identificador: un puntero a esta estructura, no a los propios datos. El código guest debe exportar dos funciones para crear y destruir estos búferes.
Para cada bloque de entrada, ClickHouse:
- Serializa las columnas de argumentos mediante el
serialization_formatde la función (MsgPackde forma predeterminada). Los formatos basados en filas escriben los valores fila por fila, con los valores de los argumentos en el orden en que se declaran enARGUMENTS; los formatos con campos con nombre usan los nombres de los argumentos, por lo que debe declararlos enARGUMENTSal utilizar dichos formatos. - Llama a
clickhouse_create_buffer, exportada por el módulo, y copia los datos serializados en la memoria a la que apunta el búfer devuelto. - Llama a la función definida por el usuario con dos argumentos
i32: el identificador del búfer de entrada (0si la función no tiene argumentos) y el número de filas. La función devuelve un únicoi32: el identificador del búfer de resultados, que el propio código guest asigna; devolver0hace que la consulta falle con un error. - Lee el búfer de resultados: debe contener exactamente una columna con exactamente el mismo número de filas, serializada en el mismo formato. En formatos con campos con nombre, como
JSONEachRow, la columna de resultados debe llamarseresult. - Llama a
clickhouse_destroy_bufferpara los identificadores tanto del búfer de entrada (si existe) como del búfer de resultados. El código guest no debe liberar el búfer de resultados por sí mismo ni devolver el identificador de entrada como resultado; de lo contrario, se destruiría dos veces.
webassembly_udf_max_input_block_size), por lo que el número de filas pasado en cada llamada corresponde al tamaño de ese bloque, no al de toda la consulta.
Los bytes exactos de los búferes los determina el formato elegido, y el código guest debe decodificarlos y codificarlos por sí mismo. En particular, para valores
String:MsgPack: ClickHouse escribe los valoresStringmediante la familia de tiposbin(0xc4/0xc5/0xc6), no mediante la familiastr. Ambas familias se aceptan al analizar el resultado.RowBinary: cadaStringlleva como prefijo su longitud en bytes codificada como un varint sin signo (LEB128), no como un entero de ancho fijo.JSONEachRow: cada fila de resultados debe ser un objeto con la claveresult, por ejemplo{"result":"value"}.
str_reverse invierte los bytes de cada cadena de entrada mediante serialization_format = 'RowBinary'. Las instancias del módulo se reutilizan entre bloques, por lo que clickhouse_destroy_buffer debe liberar realmente la memoria; aquí el asignador se restablece una vez destruidos todos los búferes.
wasm-ld (distribuido con LLVM/lld):
__attribute__((export_name("..."))) como se indicó anteriormente, o enlácelo con wasm-ld --export-all. -fno-builtin evita que clang transforme bucles simples de bytes en llamadas a memcpy/memset, que no están disponibles sin una biblioteca estándar.
Cargue el módulo y cree la función:
ABI ASSEMBLYSCRIPT
string.
Tipos compatibles:
-
Numéricos:
Int8/UInt8,Int16/UInt16(ampliados ai32en el límite),Int32/UInt32,Int64/UInt64,Float32,Float64 -
String— se asigna astringde AssemblyScript (UTF-16 en la memoria de WASM). ClickHouse gestiona automáticamente la conversión UTF-8 ↔ UTF-16. - No se admiten clases personalizadas de AssemblyScript como tipos de argumento o de retorno; sus identificadores de clase en tiempo de ejecución no son estables entre compilaciones (consulta AssemblyScript#2982).
__new, __pin y __unpin. El manejo estándar de cadenas de entrada y salida da por hecho esto. La invocación recomendada:
env.abort para interrupciones del runtime (falta de memoria, comprobaciones de límites, etc.). ClickHouse proporciona esta importación automáticamente: cuando se activa un abort, la consulta activa falla con una excepción WASM_ERROR que incluye el mensaje de AssemblyScript decodificado y la ubicación en el código fuente.
Ejemplo:
asc y cargar el archivo .wasm resultante en system.webassembly_modules, declare las UDFs de la siguiente manera:
Nota para desarrollar UDFs en Rust
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í:
serde.
API del host disponible para 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.env.abort(message: i32, fileName: i32, line: i32, column: i32)— se proporciona para módulos compatibles con AssemblyScript. Al llamarlo (o al activar una trampa del runtime de AssemblyScript que lo invoque), finaliza la UDF con una excepciónWASM_ERRORque contiene el mensaje decodificado y la ubicación de origen. Los módulos que no importanenv.abortno se ven afectados.
Configuración
-
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. El valor se escala por 1024 antes de pasarse al runtime, por lo quewebassembly_udf_max_fuel = 1corresponde a aproximadamente 1024 unidades de fuel. Establézcalo en 0 para no tener ningún límite finito. Se aplica solo a las funciones cuya configuración por funciónwebassembly_udf_enable_fueles true, que es el valor predeterminado. -
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.