Skip to main content
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++ ou outras, ao compilá-las 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 tipos 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 usado para serializar blocos de argumentos passados ao módulo e analisar o resultado retornado. Usado apenas por ABI BUFFERED_V1. 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 está sujeita a alterações em lançamentos futuros.
Processa blocos inteiros de uma só vez usando (des)serialização por meio da memória WASM. Compatível com quaisquer tipos de argumentos e de retorno. Os dados são trocados por meio de buffers na memória WASM. Um buffer é uma estrutura de 8 bytes que contém um ponteiro para os dados e o tamanho deles (dois valores u32 little-endian). Os buffers são passados por identificador — um ponteiro para essa estrutura, e não para os próprios dados. O código guest deve exportar duas funções para criar e destruir esses buffers. Para cada bloco de entrada, o ClickHouse:
  1. Serializa as colunas de argumentos usando o serialization_format da função (MsgPack por padrão). Formatos baseados em linhas gravam os valores linha a linha, com os valores dos argumentos na ordem em que são declarados em ARGUMENTS; formatos com campos nomeados usam os nomes dos argumentos, portanto, declare-os em ARGUMENTS ao usar esses formatos.
  2. Chama clickhouse_create_buffer, exportada pelo módulo, e copia os dados serializados para a memória apontada pelo buffer retornado.
  3. Chama a função definida pelo usuário com dois argumentos i32: o identificador do buffer de entrada (0 se a função não tiver argumentos) e o número de linhas. A função retorna um único i32 — o identificador do buffer de resultado, que o próprio código guest aloca; retornar 0 faz a consulta falhar com um erro.
  4. Lê o buffer de resultado: ele deve conter exatamente uma coluna com exatamente o mesmo número de linhas, serializada no mesmo formato. Para formatos com campos nomeados, como JSONEachRow, a coluna de resultado deve se chamar result.
  5. Chama clickhouse_destroy_buffer nos identificadores dos buffers de entrada (se houver) e de resultado. O código guest não deve liberar o buffer de resultado por conta própria nem retornar o identificador de entrada como resultado — caso contrário, ele seria destruído duas vezes.
A função é invocada uma vez por bloco de entrada: uma consulta grande é dividida em vários blocos pelo pipeline de consulta (o número máximo de linhas por chamada também pode ser limitado pela configuração webassembly_udf_max_input_block_size). Portanto, a contagem de linhas passada em cada chamada corresponde ao tamanho desse bloco, e não ao da consulta inteira.
Os bytes exatos dentro dos buffers são definidos pelo formato escolhido, e o código guest deve decodificá-los e codificá-los por conta própria. Em particular, para valores String:
  • MsgPack: o ClickHouse grava valores String usando a família de tipos bin (0xc4/0xc5/0xc6), e não a família str. Ambas as famílias são aceitas durante a conversão do resultado.
  • RowBinary: cada String é prefixada com seu comprimento em bytes como um varint sem sinal (LEB128), e não como um inteiro de largura fixa.
  • JSONEachRow: cada linha de resultado deve ser um objeto com a chave result, por exemplo, {"result":"value"}.
Um exemplo completo em C freestanding: str_reverse inverte os bytes de cada string de entrada usando serialization_format = 'RowBinary'. As instâncias do módulo são reutilizadas entre blocos, portanto clickhouse_destroy_buffer precisa realmente liberar a memória — aqui, o allocator é redefinido quando todos os buffers são destruídos.
Compile usando clang e wasm-ld (incluídos no LLVM/lld):
As funções devem estar visíveis nas exportações do módulo: marque-as com __attribute__((export_name("..."))), como acima, ou faça a vinculação com wasm-ld --export-all. -fno-builtin impede que o clang converta loops simples de bytes em chamadas a memcpy/memset, que não estão disponíveis sem uma biblioteca padrão. Carregue o módulo e crie a função:

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 18 de agosto de 2026