Pular para o conteúdo principal

Funções Definidas pelo Usuário em WebAssembly

O ClickHouse oferece suporte à criação de Funções Definidas pelo Usuário (UDFs) escritas em WebAssembly. Isso permite executar lógica personalizada escrita em linguagens como Rust, C, C++ e outras, compilando-a em módulos WebAssembly.

Visão geral

Um módulo WebAssembly é um arquivo binário compilado que contém uma ou mais funções que podem ser chamadas pelo ClickHouse. Pense em um módulo como uma biblioteca ou objeto compartilhado que você carrega uma vez e reutiliza muitas vezes. Um módulo WebAssembly que contém UDFs pode ser escrito em qualquer linguagem que possa ser compilada para WebAssembly, como Rust, C ou C++. O código compilado para WebAssembly (código “guest”) e executado pelo ClickHouse (“host”) roda em um ambiente isolado, com acesso apenas a um espaço de memória dedicado. O código guest exporta funções que o ClickHouse pode invocar — incluindo as funções que implementam sua lógica personalizada (usadas para definir UDFs), bem como funções de suporte necessárias para o gerenciamento de memória e a troca de dados entre o ClickHouse e o código WebAssembly. Seu código deve ser compilado para WebAssembly “freestanding” (também conhecido como wasm32-unknown-unknown), sem nenhuma dependência de sistema operacional ou biblioteca padrão. Além disso, apenas o alvo padrão de WebAssembly de 32 bits é compatível (sem a extensão wasm64). O módulo deve seguir um dos protocolos de comunicação (ABIs) compatíveis para interagir com o ClickHouse. Depois de compilado, o código binário do módulo é carregado no ClickHouse por meio da inserção na tabela system.webassembly_modules. Depois disso, você pode criar UDFs que fazem referência a funções exportadas pelo módulo usando a instrução CREATE FUNCTION ... LANGUAGE WASM.

Pré-requisitos

Ative o suporte a WebAssembly na configuração do ClickHouse:
Implementações de engine disponíveis:

Início rápido

Este exemplo demonstra o fluxo completo de criação de uma WebAssembly UDF, implementando uma calculadora da conjectura de Collatz. Vamos escrever o código no formato WebAssembly Text (WAT), que é uma representação legível do WebAssembly, então não é necessário usar nenhuma linguagem de programação nesta etapa. O ClickHouse exige que o módulo esteja em formato binário, por isso usaremos um transpilador para converter WAT em WASM. Para realizar essa conversão, você pode usar o wat2wasm, do WebAssembly Binary Toolkit (WABT), ou o comando parse, do wasm-tools.
No trecho acima, enviamos o código binário WASM diretamente para o ClickHouse client usando FORMAT RawBlob para inseri-lo na tabela system.webassembly_modules. Em seguida, definimos a UDF que faz referência à função steps exportada pelo módulo:
Observe que especificamos o nome da função no módulo após ::, pois ele difere do nome da UDF. Agora podemos usar a função collatz_steps em nossas consultas:
A coluna number é convertida explicitamente para UInt32, porque as funções WebAssembly exigem uma correspondência exata de tipos com a assinatura especificada na instrução CREATE FUNCTION. No resultado, obtivemos a sequência de etapas de Collatz para números de 1 a 100, correspondente à sequência A006577 da OEIS.

Gerencie módulos WASM por meio da tabela de sistema

Os módulos WebAssembly são armazenados na tabela system.webassembly_modules, que tem a seguinte estrutura:
  • Colunas
    • name String — Nome do módulo. Não pode estar vazio; apenas caracteres de palavra.
    • code String — Código WASM binário bruto. Somente para escrita; as leituras retornam uma string vazia.
    • hash UInt256 — SHA256 do binário do módulo (zero se estiver presente em disco, mas ainda não tiver sido carregado).
O gerenciamento de módulos é feito por meio de operações SQL padrão nessa tabela:

Inserir um módulo

Opcionalmente, informe o hash de integridade:
Se o hash fornecido não corresponder ao SHA256 calculado para o código do módulo, a inserção falhará. Isso pode ser útil ao carregar módulos a partir de fontes externas, como S3 ou HTTP.

Distribuir um módulo em um cluster

system.webassembly_modules é uma tabela por instância — um INSERT é gravado apenas na réplica que atende à conexão. Não existe uma forma ON CLUSTER para a instrução INSERT, portanto, um CREATE FUNCTION ... ON CLUSTER subsequente falhará nas réplicas que não têm o módulo:
Para propagar um insert para todos os nós, grave na função de tabela cluster em vez de na tabela local system.webassembly_modules:
Esse padrão depende de o caminho subjacente de gravação distribuída passar por cada réplica em cada shard, o que só acontece quando o cluster está configurado com internal_replication=false. Com internal_replication=true (o padrão para clusters que usam ReplicatedMergeTree para gerenciar a própria replicação), o insert é enviado para uma única réplica íntegra por shard, e system.webassembly_modules não é replicado por esse caminho — portanto, algumas réplicas ainda ficarão sem o módulo. Nessa configuração, você precisa fazer o insert em cada réplica individualmente, por exemplo iterando sobre system.clusters e gravando via remote(...) por host, ou copiando o binário para user_scripts/wasm/ em cada host.Você pode verificar internal_replication de um cluster com SELECT cluster, shard_num, internal_replication FROM system.clusters.
Após o insert distribuído, o módulo passa a estar presente em todas as réplicas e CREATE FUNCTION ... ON CLUSTER é executado com sucesso:
Você pode verificar se o módulo está carregado em todo o cluster com clusterAllReplicas:
As inserções em system.webassembly_modules são idempotentes para o mesmo par (name, hash), portanto executar novamente a inserção distribuída é seguro e é uma forma razoável de reparar o estado depois que uma réplica for substituída. Observe que servidores adicionados recentemente não recebem módulos existentes retroativamente — você deve executar novamente a inserção no cluster atualizado ou colocar o binário no diretório user_scripts/wasm/ no novo host.

Listar módulos

Excluir um módulo

A exclusão é feita pela instrução DELETE FROM system.webassembly_modules WHERE name = '...'. O predicado deve ser name = 'literal' para correspondência exata ou name LIKE 'pattern' para excluir todos os módulos cujo nome corresponda ao padrão; nenhum outro formato é aceito.
Se alguma UDF existente fizer referência a um dos módulos encontrados, a exclusão falhará; portanto, primeiro você deve excluir essas UDFs.

Criar uma UDF em WebAssembly

Sintaxe:
Parâmetros:
  • function_name: Nome da função no ClickHouse. Pode ser diferente do nome da função exportada no módulo.
  • FROM 'module_name' :: 'source_function_name': Nome do módulo WASM carregado e nome da função no módulo WASM a ser usada (o padrão é function_name)
  • ARGUMENTS: Lista de nomes e tipos de argumentos (os nomes são opcionais e usados em formatos de serialização compatíveis com campos nomeados)
  • ABI: Versão da Interface Binária de Aplicação
    • ROW_DIRECT: Mapeamento direto de tipos, processamento linha por linha
    • BUFFERED_V1: Processamento baseado em blocos com serialização
    • ASSEMBLYSCRIPT: Processamento linha por linha para módulos produzidos pelo compilador AssemblyScript. Tipos numéricos são mapeados para primitivos do AssemblyScript; String do ClickHouse é mapeado para string do AssemblyScript.
  • DETERMINISTIC: Declara a função como determinística — sempre retorna a mesma saída para a mesma entrada. Quando especificado, o ClickHouse pode fazer o constant folding de chamadas em que todos os argumentos são constantes: a função é avaliada uma vez durante a análise da consulta, e o resultado é reutilizado para cada linha.
  • SHA256_HASH: Hash esperado do módulo para verificação (preenchido automaticamente se omitido); pode ser usado para garantir que o módulo WASM correto seja carregado em diferentes réplicas.
  • SETTINGS: Configurações por função
    • serialization_format String — Formato de serialização para a ABI, caso ela exija. Valores compatíveis: MsgPack, JSONEachRow, CSV, TSV, TSVRaw, RowBinary e Buffers. Padrão: MsgPack. Formatos baseados em blocos, como Buffers, devem retornar uma única coluna cujo tipo corresponda à assinatura da função declarada.
    • webassembly_udf_enable_fuel Bool — Habilita um orçamento finito de fuel para a função. Padrão: true. Quando false, a configuração no nível da consulta webassembly_udf_max_fuel é ignorada para esta função. Desabilitar os limites de fuel pode melhorar o desempenho ao usar o engine wasmtime. No entanto, para código guest não confiável ou com bugs, isso pode aumentar o risco de execução descontrolada.

Versões de ABI

Para interagir com o ClickHouse, os módulos WebAssembly devem seguir uma das ABIs (Application Binary Interfaces) compatíveis.
  • ROW_DIRECT: Mapeamento direto de tipos (somente tipos primitivos Int32, UInt32, Int64, UInt64, Float32, Float64)
  • BUFFERED_V1: Tipos complexos com serialização
  • ASSEMBLYSCRIPT: Interoperação linha por linha com módulos AssemblyScript; oferece suporte a tipos numéricos e String.

ABI ROW_DIRECT

Chama diretamente uma função WASM exportada para cada linha.
  • Argumentos e tipos de retorno devem ser tipos numéricos Int32/UInt32/Int64/UInt64/Float32/Float64/Int128/UInt128.
  • Strings não são compatíveis com esta ABI.
  • As assinaturas devem corresponder à exportação WASM (i32/i64/f32/f64/v128).
  • O módulo não precisa exportar funções de suporte.
Por exemplo, uma função com a assinatura:
Pode ser criado da seguinte forma:
O WebAssembly não faz distinção entre argumentos com sinal e sem sinal; em vez disso, usa instruções diferentes para interpretar os valores. Assim, o tamanho do argumento deve corresponder exatamente, enquanto o uso de sinal é determinado pelas operações dentro da função.

ABI BUFFERED_V1

Esta ABI é experimental e pode mudar em versões futuras.
Processa blocos inteiros de uma só vez usando (de)serialização por meio da memória WASM. Suporta quaisquer tipos de argumento e de retorno. Os dados serializados são copiados para a memória wasm, passada para a função UDF como um ponteiro para o buffer (que consiste em um ponteiro para os dados e no tamanho dos dados), junto com o número de linhas na entrada. Assim, a user-defined function em wasm sempre aceita dois argumentos i32 e retorna um único valor i32. O código guest processa os dados e retorna um ponteiro para o buffer de resultado com os dados serializados do resultado. O código guest deve fornecer duas funções para criar e destruir esses buffers.
Exemplo de definições em C:

ABI ASSEMBLYSCRIPT

Destina-se a módulos produzidos pelo compilador AssemblyScript. Cada linha aciona uma chamada para a função exportada, mapeando valores do ClickHouse para tipos primitivos do AssemblyScript e objetos string. Tipos compatíveis:
  • Numéricos: Int8/UInt8, Int16/UInt16 (ampliados para i32 no limite), Int32/UInt32, Int64/UInt64, Float32, Float64
  • String — corresponde a string do AssemblyScript (UTF-16 na memória WASM). O ClickHouse lida automaticamente com a conversão UTF-8 ↔ UTF-16.
  • Classes personalizadas do AssemblyScript não são compatíveis como tipos de argumento ou de retorno — seus IDs de classe do runtime não são estáveis entre compilações (consulte AssemblyScript#2982).
Requisitos do módulo: O módulo deve ser compilado com o runtime gerenciado do AssemblyScript para que __new, __pin e __unpin sejam exportados. O tratamento padrão de strings de entrada e saída pressupõe isso. A invocação recomendada:
O AssemblyScript também importa env.abort para traps de runtime (falta de memória, verificações de limites etc.). O ClickHouse fornece essa importação automaticamente: quando um abort é disparado, a consulta ativa falha com uma exceção WASM_ERROR que inclui a mensagem decodificada do AssemblyScript e o local no código-fonte. Exemplo:
Após compilar com asc e carregar o .wasm resultante em system.webassembly_modules, declare as UDFs da seguinte forma:

Observação sobre o desenvolvimento de UDFs em Rust

Para programas em Rust, fornecemos um crate auxiliar clickhouse-wasm-udf para simplificar o desenvolvimento de WebAssembly UDFs para o ClickHouse. O crate fornece funções para gerenciamento de memória, então você não precisa implementar manualmente as funções clickhouse_create_buffer e clickhouse_destroy_buffer; basta adicionar o crate como dependência. Também há macros #[clickhouse_wasm_udf] para encapsular suas funções Rust comuns no formato ABI exigido. Com o crate, você pode escrever UDFs assim:
Macros geram uma função wrapper que aceita e retorna estruturas de buffer e lida automaticamente com a serialização/desserialização usando serde.

API do host disponível para módulos

As funções de host a seguir podem ser importadas e usadas por módulos:
  • clickhouse_server_version() -> i64 — retorna a versão do ClickHouse server como um inteiro (por exemplo, 25011001 para v25.11.1.1).
  • clickhouse_throw(ptr: i32, size: i32) — gera um erro com a mensagem fornecida. Aceita um ponteiro para a posição de memória que contém a string da mensagem de erro e o tamanho da string.
  • clickhouse_log(ptr: i32, size: i32) — registra uma mensagem no log de texto do ClickHouse server.
  • clickhouse_random(ptr: i32, size: i32) — preenche a memória com bytes aleatórios.
  • env.abort(message: i32, fileName: i32, line: i32, column: i32) — fornecido para módulos compatíveis com AssemblyScript. Chamá-lo (ou disparar uma trap de runtime do AssemblyScript que o chame) encerra a UDF com uma exceção WASM_ERROR contendo a mensagem decodificada e a localização no código-fonte. Módulos que não importam env.abort não são afetados.

Configurações

As configurações a seguir, no nível da consulta, controlam a execução de UDFs em WebAssembly:
  • webassembly_udf_max_fuel — Limite de fuel por execução de uma instância de UDF em WebAssembly. Cada instrução de WebAssembly consome uma certa quantidade de fuel. O valor é escalado por 1024 antes de ser passado ao runtime, portanto webassembly_udf_max_fuel = 1 corresponde a aproximadamente 1024 unidades de fuel. Defina como 0 para não haver limite finito. Aplica-se apenas a funções cuja configuração por função webassembly_udf_enable_fuel seja true, que é o padrão.
  • webassembly_udf_max_memory — Limite de memória, em bytes, por instância de UDF em WebAssembly.
  • webassembly_udf_max_input_block_size — Número máximo de linhas passadas para uma UDF em WebAssembly em um único bloco. Defina como 0 para processar todas as linhas de uma só vez.
  • webassembly_udf_max_instances — Número máximo de instâncias de UDF em WebAssembly que podem ser executadas em paralelo por função.
Exemplo de uso:

Veja também

Última modificação em 3 de julho de 2026