Funções Definidas pelo Usuário em WebAssembly
Visão geral
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
Início rápido
wat2wasm, do WebAssembly Binary Toolkit (WABT), ou o comando parse, do wasm-tools.
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:
::, pois ele difere do nome da UDF.
Agora podemos usar a função collatz_steps em nossas consultas:
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
system.webassembly_modules, que tem a seguinte estrutura:
- Colunas
nameString — Nome do módulo. Não pode estar vazio; apenas caracteres de palavra.codeString — Código WASM binário bruto. Somente para escrita; as leituras retornam uma string vazia.hashUInt256 — SHA256 do binário do módulo (zero se estiver presente em disco, mas ainda não tiver sido carregado).
Inserir um módulo
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:
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.CREATE FUNCTION ... ON CLUSTER é executado com sucesso:
clusterAllReplicas:
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
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.
Criar uma UDF em WebAssembly
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çãoROW_DIRECT: Mapeamento direto de tipos, processamento linha por linhaBUFFERED_V1: Processamento baseado em blocos com serializaçãoASSEMBLYSCRIPT: Processamento linha por linha para módulos produzidos pelo compilador AssemblyScript. Tipos numéricos são mapeados para primitivos do AssemblyScript;Stringdo ClickHouse é mapeado parastringdo 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çãoserialization_formatString — Formato de serialização para a ABI, caso ela exija. Valores compatíveis:MsgPack,JSONEachRow,CSV,TSV,TSVRaw,RowBinaryeBuffers. Padrão:MsgPack. Formatos baseados em blocos, comoBuffers, devem retornar uma única coluna cujo tipo corresponda à assinatura da função declarada.webassembly_udf_enable_fuelBool — Habilita um orçamento finito de fuel para a função. Padrão:true. Quandofalse, a configuração no nível da consultawebassembly_udf_max_fuelé ignorada para esta função. Desabilitar os limites de fuel pode melhorar o desempenho ao usar o enginewasmtime. 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
ROW_DIRECT: Mapeamento direto de tipos (somente tipos primitivosInt32,UInt32,Int64,UInt64,Float32,Float64)BUFFERED_V1: Tipos complexos com serializaçãoASSEMBLYSCRIPT: Interoperação linha por linha com módulos AssemblyScript; oferece suporte a tipos numéricos eString.
ABI ROW_DIRECT
- 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.
ABI BUFFERED_V1
Esta ABI é experimental e pode mudar em versões futuras.
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.
ABI ASSEMBLYSCRIPT
-
Numéricos:
Int8/UInt8,Int16/UInt16(ampliados parai32no limite),Int32/UInt32,Int64/UInt64,Float32,Float64 -
String— corresponde astringdo 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).
__new, __pin e __unpin sejam exportados. O tratamento padrão de strings de entrada e saída pressupõe isso. A invocação recomendada:
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:
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
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:
serde.
API do host disponível para 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çãoWASM_ERRORcontendo a mensagem decodificada e a localização no código-fonte. Módulos que não importamenv.abortnão são afetados.
Configurações
-
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, portantowebassembly_udf_max_fuel = 1corresponde 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çãowebassembly_udf_enable_fuelsejatrue, 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.