INSERT, telemetria de execução e sinais de erro. É o protocolo usado pelo cliente de linha de comando, pelo driver nativo em C++ e pela maioria dos drivers nativos de terceiros.
Esta página aborda o protocolo em si: estruturação de pacotes, a máquina de estados da conexão, negociação de versão e o corpo de todas as mensagens que não sejam Block. Os bytes dentro dos pacotes da família Data (o Block, suas colunas e as codificações por tipo) são um assunto à parte, documentado na especificação de Formato Nativo.
Especificação complementarEsta página faz par com a especificação complementar de Formato Nativo e é publicada junto com ela. As duas especificações dividem o trabalho de forma clara: esta página cobre a camada de pacotes e transporte; a especificação de Formato Nativo cobre os bytes dentro dos pacotes da família
Data.BlockInfo, portanto um único byte fora do lugar dessincroniza tudo o que vem a seguir. Ele é com estado, e cada conexão TCP processa uma consulta por vez — não há multiplexação. Inteiros de largura fixa são codificados em little-endian.
Visão geral
| Propriedade | Valor |
|---|---|
| Transporte | TCP, opcionalmente protegido por TLS |
| Ordem dos bytes | little-endian para inteiros de largura fixa |
| Codificação | Binária e posicional (sem tags de campo, exceto em BlockInfo) |
| Modelo de conexão | Com estado, uma consulta por vez, sem multiplexação |
| Versionamento | Negociado no handshake; recursos individuais condicionados à versão |
| Formato de dados | O Formato Nativo para todos os dados tabulares |
VarUInt, seguido de um corpo cuja estrutura depende desse código e da versão negociada do protocolo.
Uma conexão passa por três fases — um handshake único, depois qualquer número de trocas Ping ou Query e, por fim, o encerramento:
O protocolo TCP nativo sempre transporta dados tabulares no formato Native, independentemente de qualquer cláusula FORMAT no SQL. Reformatar para RowBinary, CSV, JSON e assim por diante é tarefa do cliente, feita depois que ele decodifica os blocos Native. (A interface HTTP segue um caminho de código diferente e de fato respeita a cláusula FORMAT; HTTP está fora do escopo aqui.)
Segurança
Segurança de transporte (TLS)
Autenticação
ClientHello. Os campos user e password são transmitidos como strings em texto simples, portanto a criptografia na camada de transporte (TLS) é a responsável por proteger as credenciais em trânsito.
A autenticação SSH por challenge-response está disponível a partir da versão 54466 do protocolo — consulte autenticação SSH por challenge-response.
Segredo entre servidores
auth_hash SHA-256 de 32 bytes no campo 4 de Query, calculado a partir de um salt, um nonce, do segredo configurado e da consulta; o servidor receptor o recalcula e o compara. Isso é controlado pelo recurso INTERSERVER_SECRET (v54441). Clientes externos sempre enviam uma string vazia aqui. Consulte Autenticação entre servidores.
Versionamento e feature gates
Negociação de versão
Feature gates
Tabela de funcionalidades
| Funcionalidade | Versão | Afeta | Impacto no wire |
|---|---|---|---|
| BLOCK_INFO | all | Block | Adiciona o prefixo BlockInfo (is_overflows, bucket_number) a cada Block. |
| CLIENT_INFO | 54032 | Query | Adiciona o bloco ClientInfo ao corpo de Query. |
| TIMEZONE | 54058 | ServerHello | Adiciona o campo timezone a ServerHello. |
| QUOTA_KEY_IN_CLIENT_INFO | 54060 | ClientInfo | Adiciona o campo quota_key a ClientInfo. |
| DISPLAY_NAME | 54372 | ServerHello | Adiciona o campo display_name a ServerHello. |
| VERSION_PATCH | 54401 | ServerHello, ClientInfo | Adiciona o campo version_patch a ambos. |
| SERVER_LOGS | 54406 | Log | O servidor emite pacotes Log quando send_logs_level está definido. |
| COLUMN_DEFAULTS_METADATA | 54410 | TableColumns | O servidor pode enviar o pacote TableColumns (tipo 11) com metadados de valores padrão das colunas antes do bloco de schema de INSERT/entrada. Enviado somente quando a versão negociada ≥ 54410 e input_format_defaults_for_omitted_fields estiver habilitado. Abaixo dessa versão, o pacote nunca é enviado; os clientes não devem aguardar por ele. |
| WRITE_CLIENT_INFO | 54420 | Progress | Adiciona wrote_rows e wrote_bytes a Progress. (Apesar do nome, isso não controla o bloco ClientInfo — isso é feito por CLIENT_INFO (v54032).) |
| SETTINGS_SERIALIZED_AS_STRINGS | 54429 | Query (settings encoding) | Altera como a lista de settings, sempre presente, é codificada; não controla se as settings são enviadas. v54429+ grava cada setting como (name, flags, value-as-string); peers mais antigos gravam (name, type-specific-binary-value) sem flags. Veja Setting. |
| INTERSERVER_SECRET | 54441 | Query | Adiciona o campo auth_hash entre servidores a Query — um SHA-256 com salt sobre o secret do cluster, não o secret bruto. Clientes externos enviam uma string vazia. Veja Autenticação entre servidores. |
| OPEN_TELEMETRY | 54442 | ClientInfo | Adiciona o contexto de rastreamento do OpenTelemetry a ClientInfo. |
| DISTRIBUTED_DEPTH | 54448 | ClientInfo | Adiciona o campo distributed_depth a ClientInfo. |
| INITIAL_QUERY_START_TIME | 54449 | ClientInfo | Adiciona o campo initial_time (Int64, largura fixa). |
| PROFILE_EVENTS | 54451 | ProfileEvents | O servidor emite pacotes ProfileEvents durante a execução da consulta. |
| PARALLEL_REPLICAS | 54453 | ClientInfo | Adiciona campos de coordination de réplicas paralelas a ClientInfo. |
| CUSTOM_SERIALIZATION | 54454 | Block (Column) | Adiciona o byte has_custom_serialization após a string de tipo de cada coluna. |
| ADDENDUM | 54458 | Handshake | O cliente envia um addendum (quota_key) após a troca de handshake. |
| PARAMETERS | 54459 | Query | Adiciona a lista de parâmetros ao corpo de Query. |
| SERVER_QUERY_TIME_IN_PROGRESS | 54460 | Progress | Adiciona o campo elapsed_ns a Progress. |
| PASSWORD_COMPLEXITY_RULES | 54461 | ServerHello | Adiciona a ServerHello uma lista de Patterns regex da política de senha e mensagens legíveis por humanos. |
| INTERSERVER_SECRET_V2 | 54462 | ServerHello | Adiciona um nonce UInt64 de 8 bytes a ServerHello. Usado para assinatura de consultas entre servidores; clientes externos o decodificam e o ignoram. |
| TOTAL_BYTES_IN_PROGRESS | 54463 | Progress | Adiciona o campo total_bytes_to_read (VarUInt) a Progress, entre total_rows e wrote_rows. |
| TIMEZONE_UPDATES | 54464 | TimezoneUpdate | Adiciona o pacote de servidor TimezoneUpdate (tipo 17). Corpo: um único String que carrega a session timezone. Enviado somente pelo inicializador da table function input, logo após o bloco de schema de entrada, para que o cliente faça parse das linhas que envia com a session_timezone do servidor. Veja TimezoneUpdate. |
| SPARSE_SERIALIZATION | 54465 | Block (Column) | O servidor pode definir has_custom_serialization = 1 e emitir uma coluna codificada de forma esparsa. Formato wire: kind de 1 byte (0x01 = SPARSE), seguido por um fluxo de offsets VarUInt terminado por EOG, depois os valores não padrão codificados densamente no tipo interno. Veja kind_stack and sparse encoding. |
| SSH_AUTHENTICATION | 54466 | Auth flow | Adiciona autenticação SSH por desafio-resposta. Opt-in: o cliente envia um user no formato " SSH KEY AUTHENTICATION " + <real_user> com senha vazia para acioná-la. Veja Autenticação SSH por desafio-resposta. |
| TABLE_READ_ONLY_CHECK | 54467 | TablesStatusResponse | Adiciona um flag is_readonly à linha de cada tabela em TablesStatusResponse. Clientes externos que não emitem TablesStatusRequest não veem nenhuma mudança no wire. |
| SYSTEM_KEYWORDS_TABLE | 54468 | system tables | O servidor popula system.keywords para que o clickhouse-client canônico possa autocompletar palavras-chave. Não há mudança no wire do protocolo nativo. |
| ROWS_BEFORE_AGGREGATION | 54469 | ProfileInfo | Adiciona applied_aggregation (Bool) e rows_before_aggregation (VarUInt) a ProfileInfo, nessa ordem, no final. |
| CHUNKED_PROTOCOL | 54470 | Connection framing | O enquadramento em fragmentos por pacote envolve cada packet body. Negociado em Addendum. ServerHello carrega a preferência do servidor para cada direção; Addendum carrega a escolha final do cliente. Veja chunked framing. |
| VERSIONED_PARALLEL_REPLICAS_PROTOCOL | 54471 | ServerHello, Addendum | Ambos os lados trocam um VarUInt com a versão do protocolo de coordenação de réplicas paralelas. O campo de ServerHello fica imediatamente após protocol_version (antes de timezone). O campo de Addendum é anexado após as strings do protocolo em blocos. Valor atual: 7 (DBMS_PARALLEL_REPLICAS_PROTOCOL_VERSION). |
| INTERSERVER_EXTERNALLY_GRANTED_ROLES | 54472 | Query | Adiciona um campo String external_roles ao corpo de Query, entre o terminador de settings e o hash do segredo interserver. Clientes externos enviam uma lista de roles vazia (um único byte 0x00, ou seja, VarUInt 0 dentro de um envelope String). |
| V2_DYNAMIC_AND_JSON_SERIALIZATION | 54473 | Column body | O servidor pode emitir serialização V2 para os tipos de coluna Dynamic e JSON — isso determina qual versão de state_prefix eles usam. Veja tipos versionados. |
| SERVER_SETTINGS | 54474 | ServerHello | O servidor transmite seus settings fora do padrão como uma lista no final de ServerHello, após nonce. Formato: triplas (key, flags, value) terminadas por uma key vazia — igual à lista de settings do pacote Query. |
| QUERY_AND_LINE_NUMBERS | 54475 | ClientInfo | Adiciona script_query_number (VarUInt) e script_line_number (VarUInt) ao final de ClientInfo. Usado pelo clickhouse-client para atribuir erros em scripts com várias instruções; clientes externos enviam 0, 0. |
| JWT_IN_INTERSERVER | 54476 | ClientInfo | Adiciona um UInt8 indicando a presença de JWT + um String jwt opcional ao final de ClientInfo. Clientes externos (sem JWT) enviam o byte 0x00. (Escrito como DBMS_MIN_REVISON_WITH_JWT_IN_INTERSERVER em C++ — observe o erro de digitação no nome da constante.) |
| QUERY_PLAN_SERIALIZATION | 54477 | ServerHello, QueryPlan packet | ServerHello anexa VarUInt query_plan_serialization_version após os server settings. Também introduz ClientPacket::QueryPlan (código 13) para entrega entre servidores de planos de consulta pré-construídos — clientes externos nunca enviam. |
| PARALLEL_BLOCK_MARSHALLING | 54478 | Block (Column) | O servidor pode encapsular colunas em ColumnBLOB (comprimido inline) para processamento paralelo. Isso é controlado por a consulta ter compressão habilitada AND rows > 1; caso contrário, aplica-se o formato wire normal da coluna. Clientes que nunca habilitam compressão em pacotes Query de saída não veem alteração no wire. |
| VERSIONED_CLUSTER_FUNCTION_PROTOCOL | 54479 | ServerHello | Adiciona VarUInt cluster_function_protocol_version ao final de ServerHello. Usado para funções de tabela *Cluster (s3Cluster, etc.). Clientes externos decodificam e ignoram. |
| OUT_OF_ORDER_BUCKETS_IN_AGGREGATION | 54480 | BlockInfo | Adiciona o campo 3 (out_of_order_buckets: Vec<Int32>) ao fluxo com tags de campo de BlockInfo. Decodificado como [VarUInt count][Int32]*count. Clientes externos não emitem isso por conta própria; o decodificador lê qualquer lista não vazia que o servidor enviar. |
| COMPRESSED_LOGS_PROFILE_EVENTS_COLUMNS | 54481 | Log, ProfileEvents, TableColumns | O servidor pode encapsular os corpos dos pacotes Log, ProfileEvents e TableColumns no frame de compressão. Nesta versão, os corpos dos três trafegam pelo mesmo caminho de saída opcionalmente comprimido, que se torna um frame de compressão real apenas quando a consulta tem compression = true. Clientes que nunca habilitam compressão em pacotes Query de saída não veem alteração no wire. |
| REPLICATED_SERIALIZATION | 54482 | Block (Column) | O servidor pode emitir colunas com kind_stack 0x04 = REPLICATED — uma forma compacta no estilo Dicionário para valores repetidos — veja kind_stack e codificação esparsa. Abaixo desta versão, o gravador expandia essas colunas antes de enviá-las. A decodificação é feita por busca de índice (elements[indexes[i]] por linha); há suporte a tipos folha e a tipos internos Nullable/Array/Tuple/Map/Nested/LowCardinality. |
| NULLABLE_SPARSE_SERIALIZATION | 54483 | Block (Column) | Combina serialização esparsa com Nullable(T). Abaixo desta versão, o gravador expandia o formato esparso para colunas Nullable antes do envio; em v54483+, os dados no wire ficam em formato esparso sobre Nullable. Veja kind_stack e codificação esparsa. |
| PROGRESS_IN_ASYNC_INSERT | 54484 | Progress (INSERT) | Em um INSERT assíncrono (async_insert = 1), assim que o insert é descarregado, o servidor envia um pacote extra Progress e, em seguida, os ProfileEvents do insert, antes de EndOfStream. Isso é controlado pela versão negociada ≥ 54484; abaixo disso, o servidor omite esse Progress final. O formato wire de Progress não muda — apenas a emissão é nova. Na prática, o incremento carrega o tempo decorrido; os contadores de linhas gravadas são reportados pelos ProfileEvents correspondentes. Um cliente que já consome Progress intercalado não precisa de mudança de formato, apenas tolerar mais um pacote. |
| CLIENT_AGENT_IN_CLIENT_INFO | 54485 | ClientInfo | Adiciona um String client_agent ao final de ClientInfo. O cliente canônico detecta automaticamente um identificador de agent a partir do ambiente (por exemplo, claude-code, cursor, gemini-cli ou o valor da variável AGENT); um cliente externo sem nada detectado envia uma string vazia. Obrigatório quando a versão negociada for ≥ 54485 — omiti-lo dessincroniza o restante do pacote Query. |
Encapsulamento do pacote
VarUInt, não um byte de largura fixa. Para valores abaixo de 128, um VarUInt produz o mesmo único byte, mas as implementações devem usar a codificação VarUInt para permanecer compatíveis caso tipos de pacote futuros cheguem a 128 ou mais.
A referência de mensagens documenta apenas o corpo de cada pacote — os bytes após o código do tipo de pacote. A numeração dos campos começa em 1, com o primeiro campo do corpo.
Enquadramento em fragmentos (v54470+)
CHUNKED_PROTOCOL é negociado (consulte o handshake), cada pacote no wire é envolvido por um enquadramento em fragmentos. Esse encapsulamento é por direção: cliente→servidor e servidor→cliente são negociados separadamente e podem acabar em modos diferentes (em fragmentos versus sem enquadramento).
Layout no wire de cada pacote:
VarUInt está dentro do fluxo em fragmentos: ele é o primeiro byte do payload do pacote (o primeiro byte do primeiro fragmento), não um byte separado enviado antes do framing. O payload em fragmentos de cada pacote é o [VarUInt packet_type_code][message body] completo do envelope do pacote. Um cliente que deixa o tipo de pacote fora do fluxo em fragmentos faz com que a outra ponta leia esse byte de tipo como o primeiro byte do tamanho do fragmento u32, dessincronizando a conexão.
Um único pacote pode ser dividido em vários fragmentos se o buffer do gravador encher no meio do pacote; uma divisão pode ocorrer em qualquer ponto, inclusive dentro do VarUInt do tipo de pacote. O leitor concatena os payloads dos fragmentos e trata o zero final de 4 bytes como um limite de pacote transparente — ele o consome, mas não o expõe ao que estiver lendo os corpos dos pacotes.
Pacotes sem corpo ainda são encapsulados: um pacote de um único byte, como Ping ou Pong, torna-se [u32 size = 1][0x04][u32 0] assim que o envio em fragmentos é negociado. Qualquer descrição de “byte único no wire” em outro ponto desta página se refere à forma anterior ao envio em fragmentos.
Negociação. ServerHello e Addendum carregam, cada um, dois campos String, um por direção, com valores de {"chunked", "notchunked", "chunked_optional", "notchunked_optional"}:
chunked/notchunkedsão estritos: esse lado exige exatamente esse modo.- As variantes
_optionalsão flexíveis: aceitam qualquer modo que o outro lado escolher.
| Preferência do servidor | Preferência do cliente | Acordado |
|---|---|---|
*_optional | qualquer valor | seguir o CLIENT (seu starts_with("chunked")) |
| qualquer valor | *_optional | seguir o SERVER |
chunked estrito | chunked estrito | chunked |
notchunked estrito | notchunked estrito | notchunked |
| incompatibilidade estrita | incompatibilidade estrita | erro de protocolo — a conexão DEVE ser encerrada |
Ciclo de vida da conexão
HANDSHAKE, READY, READING_RESPONSE ou encerrada. Como o protocolo não oferece multiplexação, um cliente que envia uma nova solicitação antes de consumir totalmente a resposta anterior intercala bytes no wire e corrompe o fluxo.
Estados
HANDSHAKE → READY → READING_RESPONSE → READY — com o loop de Ping/Pong e todas as transições de falha convergindo para o único sink Terminated.
| Estado | Descrição |
|---|---|
HANDSHAKE | Estado inicial após a abertura da conexão TCP. Somente mensagens de handshake são válidas. Faz a transição para READY em caso de sucesso ou é encerrado em caso de falha. |
READY | Ocioso. O cliente pode enviar Ping, consulta ou fechar a conexão. A conexão pode permanecer em READY indefinidamente (sujeita a idle_connection_timeout, veja limites de conexão). |
READING_RESPONSE | É acessado quando o cliente envia uma consulta. O cliente deve consumir completamente o fluxo de resposta do servidor antes de retornar a READY. O único pacote cliente→servidor permitido aqui é Cancel (não especificado nesta página). |
| Terminated | Não pode mais ser usado. O cliente deve abrir uma nova conexão TCP e reiniciar o handshake. |
Fase de handshake
-
O cliente envia
ClientHellocom a versão máxima de protocolo compatível. -
O cliente lê a resposta e a encaminha de acordo com o tipo de pacote:
Tipo de pacote Ação Hello(0)Decodifica ServerHello. Calculanegotiated_version = min(client_ver, server_ver). Prossiga para o passo 3.Exception(2)Decodifica Exception. Retorna como erro e encerra a conexão.qualquer outro Violação de protocolo. Encerra a conexão. -
Se
negotiated_version ≥ 54458(o recursoADDENDUM), o cliente envia umAddendum. Essa decisão se baseia na versão negociada, não na versão declarada pelo cliente.
READY; em caso de erro, ela é encerrada.
Fase de Ping
READY, o fluxo é:
-
O cliente envia
Ping. -
O cliente lê a resposta:
Tipo de pacote Ação Pong(4)Disponibilidade confirmada. Retorne para READY.Exception(2)Decodifique Exceptione retorne como erro.qualquer outro Violação de protocolo.
Fase da consulta
EndOfStream ou Exception.
Partindo de READY, o fluxo é:
Em caso de erro em qualquer ponto, o servidor envia uma Exception em vez de EndOfStream, o que encerra a consulta.
-
O cliente envia
Querycom umquery_idexclusivo (normalmente um UUID). -
O cliente envia quaisquer tabelas externas e, em seguida, o marcador
Datavazio. O pacoteDatavazio temtable_name = "",num_columns = 0,num_rows = 0. O servidor não começa a executar a consulta até receber esse marcador. -
O cliente passa para
READING_RESPONSEe faz flush do buffer de escrita. -
O cliente lê os pacotes de resposta em loop, despachando por tipo:
Tipo de pacote Ação Data(1)Decodifique o bloco. O primeiro Dataé o cabeçalho do esquema; os seguintes são blocos de resultado (acumule-os); um bloco vazio é um marcador de limite.num_rows == 0não é fim de consulta.Progress(3)Métricas de execução. Cada pacote é um incremento em relação ao anterior — acumule localmente. EndOfStream(5)Consulta concluída. Saia do loop e retorne para READY.ProfileInfo(6)Dados de profiling pós-execução. Totals(7)Bloco de totais da agregação (mesmo wire format de Data).Extremes(8)Bloco de valores mínimo/máximo (mesmo wire format de Data).Log(10)Linha de log do servidor. TableColumns(11)Metadados de valores padrão de coluna. ProfileEvents(14)Counters de desempenho. Exception(2)Decodifique e retorne como erro. Saia do loop e retorne para READY.qualquer outro Inesperado durante a fase da consulta. Encerre a conexão.
EndOfStream ou em uma Exception tratada, a conexão retorna para READY. Uma violação de protocolo ou erro de E/S a encerra.
O caso
num_rows == 0 costuma confundir novas implementações. Um bloco com zero linhas é um marcador de limite ou um cabeçalho de esquema, não um sinal de fim de stream. Somente EndOfStream ou Exception encerra a resposta.Fase INSERT
INSERT; o servidor responde com um bloco de esquema que descreve a tabela de destino; o cliente transmite pacotes Data com as linhas e, em seguida, o marcador Data vazio; o servidor finaliza com EndOfStream ou Exception.
Partindo de READY, o SQL é um INSERT no formato INSERT INTO <table> [(<cols>)] VALUES — sem um literal VALUES (...) embutido, já que os dados das linhas fluem por meio de pacotes Data. O fluxo:
- O cliente envia
Querycombodydefinido como o SQL de INSERT. - O cliente envia quaisquer tabelas externas (raro em INSERT). Diferentemente da fase de consulta, ele não envia aqui um marcador Data vazio. O pacote
QuerydeINSERTé enviado com dados pendentes, portanto o bloco vazio de fim de dados é adiado para a etapa 5; enviá-lo antes do bloco de esquema faria o servidor interpretá-lo como o fim do stream de linhas, concluir o INSERT sem linhas e então analisar o primeiro pacote de linha real como um pacote avulso de nível superior. - O cliente drena os pacotes de metadados (TableColumns, Progress, ProfileInfo, Log, ProfileEvents) até ler o pacote Data de esquema — um Block com 0 linhas, mas com a estrutura completa das colunas (nomes e tipos). O bloco de esquema é o contrato: as linhas que o cliente envia em seguida devem corresponder a essas estruturas de coluna.
- O cliente envia bloco(s) de dados. Para cada bloco, ele grava
VarUInt(ClientPacket::Data = 2), depoisString("")para o nome vazio da tabela externa, e então o Block. Os tipos das colunas devem corresponder, por posição, às colunas do bloco de esquema. - O cliente envia o terminador de fim de entrada: um pacote Data com um Block vazio (0 colunas, 0 linhas).
- O cliente drena o stream de resposta até
EndOfStream(sucesso) ouException(falha).
async_insert = 1, o servidor enfileira as linhas e faz o flush delas como parte de um batch. Na versão negociada ≥ 54484 (PROGRESS_IN_ASYNC_INSERT), assim que o flush é concluído, o servidor emite um pacote extra de Progress, imediatamente seguido pelos ProfileEvents do insert e então por EndOfStream. Abaixo de 54484, o servidor omite esse Progress final. O pacote é um Progress comum; como o servidor redefine o pipeline da consulta antes de incorporar as contagens de escrita, na prática o incremento carrega apenas o tempo decorrido, e as estatísticas de linhas e bytes gravados chegam ao cliente por meio dos ProfileEvents correspondentes. Um cliente que já drena pacotes Progress intercalados na etapa 6 só precisa aceitar mais um pacote.
A conexão retorna para READY em EndOfStream ou em uma Exception tratada. Violações de protocolo e erros de E/S a encerram.
Referência de mensagens
Type usa:
VarUInt— inteiro sem sinal de comprimento variável (consulte VarUInt).String— bytes com prefixo VarUInt (consulte String).UInt8,Int32e assim por diante — inteiros little-endian de largura fixa.Bool— um único byte,0x00ou0x01.
Role indica quem usa cada campo:
- client — definido por clientes externos.
- inter-server — relevante apenas para a comunicação entre servidores; clientes externos gravam um valor padrão.
- universal — usado por ambos.
ClientHello (tipo de pacote 0)
| # | Campo | Tipo | Função | Descrição |
|---|---|---|---|---|
| 1 | client_name | String | universal | Identificador do cliente (por exemplo, "clickhouse-client") |
| 2 | version_major | VarUInt | universal | Versão principal do cliente |
| 3 | version_minor | VarUInt | universal | Versão secundária do cliente |
| 4 | protocol_version | VarUInt | universal | Versão máxima do protocolo compatível com o cliente |
| 5 | database | String | universal | nome do banco de dados padrão |
| 6 | user | String | universal | Nome de usuário para autenticação |
| 7 | password | String | universal | Senha (em texto simples) |
ServerHello (tipo de pacote 0)
| # | Campo | Tipo | Papel | Condição | Descrição |
|---|---|---|---|---|---|
| 1 | server_name | String | universal | sempre | Identificador do servidor |
| 2 | version_major | VarUInt | universal | sempre | Versão principal do servidor |
| 3 | version_minor | VarUInt | universal | sempre | Versão secundária do servidor |
| 4 | protocol_version | VarUInt | universal | sempre | Versão do protocolo do servidor |
| 4a | parallel_replicas_protocol_version | VarUInt | universal | VERSIONED_PARALLEL_REPLICAS_PROTOCOL (v54471) | Versão do protocolo de coordination de réplicas paralelas do servidor. Posição no wire: imediatamente após protocol_version, antes de timezone. Atual: 7. |
| 5 | timezone | String | universal | TIMEZONE (v54058) | Fuso horário do servidor (por exemplo, "UTC") |
| 6 | display_name | String | universal | DISPLAY_NAME (v54372) | Nome legível por humanos do servidor |
| 7 | version_patch | VarUInt | universal | VERSION_PATCH (v54401) | Versão de patch do servidor |
| 8 | proto_send_chunked_srv | String | universal | CHUNKED_PROTOCOL (v54470) | Fragmentação de saída preferida do servidor. Um de "chunked", "notchunked", "chunked_optional", "notchunked_optional". Consulte enquadramento por fragmentos. Fica ANTES de password_complexity_rules no wire, embora seu gate de versão seja mais alto. |
| 9 | proto_recv_chunked_srv | String | universal | CHUNKED_PROTOCOL (v54470) | Fragmentação de entrada preferida do servidor. Mesmo conjunto de valores do campo 8. |
| 10 | password_complexity_rules | Rule[] | universal | PASSWORD_COMPLEXITY_RULES (v54461) | Política de senhas do servidor. VarUInt count seguido de count × Rule. Veja abaixo. |
| 11 | nonce | UInt64 | inter-servidor | INTERSERVER_SECRET_V2 (v54462) | Nonce aleatório LE de 8 bytes. O esquema de assinatura de consultas inter-servidor do servidor o utiliza. Clientes externos DEVEM decodificá-lo (para manter o fluxo alinhado) e DEVERIAM ignorar o valor. |
| 12 | server_settings | Setting[] | universal | SERVER_SETTINGS (v54474) | Broadcast das configurações não padrão do servidor. Formato: zero ou mais triplas (String key, VarUInt flags, String value), terminadas por uma chave vazia. Igual à lista de settings do pacote Query. |
| 13 | query_plan_serialization_version | VarUInt | universal | QUERY_PLAN_SERIALIZATION (v54477) | Versão de serialização do plano de consulta compatível com o servidor. Clientes externos decodificam e ignoram. |
| 14 | cluster_function_protocol_version | VarUInt | universal | VERSIONED_CLUSTER_FUNCTION_PROTOCOL (v54479) | Versão do protocolo da table function *Cluster do servidor. Clientes externos decodificam e ignoram. |
password_complexity_rules:
| # | Campo | Tipo | Descrição |
|---|---|---|---|
| 1 | pattern | String | Expressão regular à qual uma senha compatível deve corresponder. |
| 2 | message | String | Explicação legível por humanos exibida quando uma senha falha nesta regra. |
Para limitar o uso de recursos diante de um servidor hostil ou mal configurado, limite o
count decodificado a 256 entradas e cada String pattern e message a 4096 bytes. Um count de 0 (sem pares subsequentes) é o caso comum para servidores sem política de senhas configurada.Adendo (sem tipo de pacote)
ADDENDUM (v54458). Enviado imediatamente após a conclusão da troca de handshake. Não é um tipo de pacote distinto — os campos vão pelo wire em bruto, sem prefixo de byte de tipo de pacote.
| # | Campo | Tipo | Função | Condição | Descrição |
|---|---|---|---|---|---|
| 1 | quota_key | String | universal | sempre | Chave de quota de recurso para quotas com chave do lado do servidor. Clientes que não usam quota com chave enviam uma string vazia. |
| 2 | proto_send_chunked | String | universal | CHUNKED_PROTOCOL (v54470) | Fragmentação de saída negociada pelo cliente: "chunked" ou "notchunked". Calculada com base em proto_recv_chunked_srv de ServerHello. |
| 3 | proto_recv_chunked | String | universal | CHUNKED_PROTOCOL (v54470) | Fragmentação de entrada negociada pelo cliente. Calculada com base em proto_send_chunked_srv. |
| 4 | parallel_replicas_protocol_version | VarUInt | universal | VERSIONED_PARALLEL_REPLICAS_PROTOCOL (v54471) | Versão do protocolo de coordination de réplicas paralelas compatível com o cliente. Clientes externos que não participam de consultas distribuídas AINDA ASSIM DEVEM enviar uma versão válida (atualmente 7) para que a verificação de compatibilidade do servidor seja bem-sucedida. |
Ping (tipo de pacote 4)
0x04 antes do enquadramento por fragmentos; quando o uso de fragmentos é negociado, o byte passa a ser o payload de um byte de um fragmento (consulte enquadramento por fragmentos).
Pong (tipo de pacote 4)
0x04 antes do enquadramento por fragmentos; quando a fragmentação é negociada, o byte se torna o payload de um byte de um fragmento (consulte enquadramento por fragmentos).
Exception (tipo de pacote 2)
| # | Campo | Tipo | Papel | Descrição |
|---|---|---|---|---|
| 1 | code | Int32 | universal | Código de erro |
| 2 | name | String | universal | Classe da Exception (por exemplo, "DB::Exception") |
| 3 | message | String | universal | Mensagem de erro legível para humanos |
| 4 | stack_trace | String | universal | Stack trace no servidor |
| 5 | has_nested (obsoleto) | Bool | universal | Byte de compatibilidade obsoleto. Sempre gravado como false pelo servidor |
Consulta (tipo de pacote 1)
| # | Campo | Type | Função | Condição | Descrição |
|---|---|---|---|---|---|
| 1 | query_id | String | universal | sempre | Identificador único da consulta (UUID) |
| 2 | client_info | ClientInfo | universal | CLIENT_INFO (v54032) | Veja ClientInfo |
| 3 | settings | Setting[] | universal | sempre | Veja Setting. Sempre presente (terminado por uma chave vazia); apenas a codificação de cada configuração depende da versão — veja a observação sobre codificação em Setting. Um cliente não deve omitir este campo para versões negociadas abaixo de 54429. |
| 3a | external_roles | String | universal | INTERSERVER_EXTERNALLY_GRANTED_ROLES (v54472) | Lista serializada de nomes de roles concedidos externamente. Lista vazia = byte 0x00 (VarUInt 0) encapsulado em um envelope String ([VarUInt 1][0x00] no wire). Clientes externos sempre enviam uma lista vazia. |
| 4 | auth_hash | String | inter-server | INTERSERVER_SECRET (v54441) | Hash de autenticação entre servidores — não o segredo bruto do cluster. Veja Autenticação entre servidores abaixo. Clientes externos (e qualquer InitialQuery) enviam uma string vazia. |
| 5 | stage | VarUInt | universal | sempre | Estágio de processamento da consulta. 0 = FetchColumns, 1 = WithMergeableState, 2 = Complete, 3 = WithMergeableStateAfterAggregation, 4 = WithMergeableStateAfterAggregationAndLimit, 7 = QueryPlan. Os valores 3/4 aparecem em consultas distribuídas; 7 acompanha um plano de consulta serializado. Clientes externos normalmente enviam 2. |
| 6 | compression | VarUInt | universal | sempre | 0 = desativado, 1 = ativado |
| 7 | query_body | String | universal | sempre | Texto SQL |
| 8 | parameters | Parameter[] | client | PARAMETERS (v54459) | Veja Parameter. Terminado por chave vazia. |
ClientInfo (embutido em consulta)
CLIENT_INFO (v54032). (Alguns campos dentro de ClientInfo são condicionados a versões posteriores, como indicado abaixo em cada campo.)
| # | Campo | Tipo | Papel | Condição | Descrição |
|---|---|---|---|---|---|
| 1 | query_kind | UInt8 | universal | sempre | 0 = NoQuery, 1 = InitialQuery, 2 = SecondaryQuery. Clientes externos enviam 1. |
| 2 | initial_user | String | universal | sempre | Usuário que iniciou a consulta |
| 3 | initial_query_id | String | universal | sempre | ID original da consulta |
| 4 | initial_address | String | universal | sempre | Endereço do socket do cliente de origem no formato host:port |
| 5 | initial_time | Int64 | client | INITIAL_QUERY_START_TIME (v54449) | Horário de início da consulta (microssegundos). Largura fixa de 8 bytes, não VarUInt |
| 6 | query_interface | UInt8 | universal | sempre | 1 = TCP, 2 = HTTP |
| 7 | os_user | String | client | se interface = TCP | Nome de usuário do sistema operacional |
| 8 | client_hostname | String | client | se interface = TCP | Nome do host da máquina cliente |
| 9 | client_name | String | client | se interface = TCP | Nome da aplicação cliente |
| 10 | version_major | VarUInt | universal | se interface = TCP | Versão principal do cliente |
| 11 | version_minor | VarUInt | universal | se interface = TCP | Versão secundária do cliente |
| 12 | protocol_version | VarUInt | universal | se interface = TCP | A própria versão do protocolo TCP do cliente de origem (DBMS_TCP_PROTOCOL_VERSION), não a versão negociada. A revisão do peer apenas decide quais campos estão presentes; este valor é a versão compilada no iniciador, portanto, em um cliente mais novo se comunicando com um servidor mais antigo, ele pode ser maior que a revisão negociada/do servidor. |
| 13 | quota_key | String | universal | QUOTA_KEY_IN_CLIENT_INFO (v54060) | Chave de cota de recursos para cotas com chave no lado do servidor. Clientes que não usam uma cota com chave enviam uma string vazia. |
| 14 | distributed_depth | VarUInt | inter-server | DISTRIBUTED_DEPTH (v54448) | Profundidade de aninhamento da consulta distribuída. Clientes externos enviam 0. |
| 15 | version_patch | VarUInt | universal | VERSION_PATCH (v54401), somente TCP | Versão de patch do cliente |
| 16 | open_telemetry | (abaixo) | client | OPEN_TELEMETRY (v54442) | contexto de rastreamento. Clientes sem rastreamento enviam 0. |
| 17 | collaborate_with_initiator | VarUInt | inter-server | PARALLEL_REPLICAS (v54453) | Bool como VarUInt. Clientes externos enviam 0. |
| 18 | count_participating_replicas | VarUInt | inter-server | PARALLEL_REPLICAS (v54453) | Clientes externos enviam 0. |
| 19 | number_of_current_replica | VarUInt | inter-server | PARALLEL_REPLICAS (v54453) | Clientes externos enviam 0. |
| 20 | script_query_number | VarUInt | client | QUERY_AND_LINE_NUMBERS (v54475) | Posição da instrução, com índice iniciado em 1, em um script com múltiplas instruções. Clientes externos enviam 0. |
| 21 | script_line_number | VarUInt | client | QUERY_AND_LINE_NUMBERS (v54475) | Número da linha, com índice iniciado em 1, dentro do script de origem. Clientes externos enviam 0. |
| 22 | jwt_present | UInt8 | inter-server | JWT_IN_INTERSERVER (v54476) | 0 = sem JWT; 1 = JWT em seguida. Clientes externos sem autenticação JWT enviam 0. |
| 23 | jwt | String | inter-server | JWT_IN_INTERSERVER (v54476), se jwt_present=1 | Bearer token JWT, presente somente quando o campo 22 = 1. |
| 24 | client_agent | String | client | CLIENT_AGENT_IN_CLIENT_INFO (v54485) | Campo final. Identificador da ferramenta/agente cliente, detectado automaticamente a partir do ambiente (por exemplo, claude-code, cursor, gemini-cli ou a variável de ambiente AGENT). Clientes externos sem agente detectado enviam uma string vazia. Presente no caminho normal de Query quando a versão negociada é ≥ 54485 (enviado em todas as interfaces, não apenas TCP). |
Layout dependente da interface (campos 7–12)Os campos 7–12 acima correspondem ao ramo TCP. Quando
query_interface (campo 6) não é TCP, esses campos são substituídos por um layout de wire diferente — não se trata apenas de omissões opcionais, portanto um decodificador deve seguir o ramo com base no campo 6.query_interface = 2(HTTP): nesse caso, são gravadas as informações da requisição HTTP encaminhada pelo servidor —http_method(UInt8),http_user_agent(String), depoisforwarded_for(String, condicionado aX_FORWARDED_FOR_IN_CLIENT_INFOv54443) ehttp_referer(String, condicionado aREFERER_IN_CLIENT_INFOv54447). Os camposos_user/client_hostname/client_name/version_*/protocol_versionnão estão presentes.- Qualquer outra interface: nenhum dos campos TCP (7–12) nem dos campos HTTP é gravado; o fluxo segue diretamente para
quota_key.
quota_key (campo 13) e distributed_depth (campo 14) vêm em seguida para todas as interfaces, e version_patch (campo 15) é gravado apenas para TCP.Esse ramo importa principalmente para o tráfego entre servidores, quando o servidor de origem encaminha uma consulta que chegou originalmente por HTTP. Um decodificador que sempre ler os campos TCP interpretará esses pacotes de forma incorreta — tratando http_method ou http_user_agent como quota_key.Autenticação entre servidores
auth_hash) não é o segredo compartilhado do cluster no wire. Enviar o segredo puro faria a autenticação falhar e ainda o exporia. Em vez disso, um servidor atuando como cliente interservidor prova que conhece o segredo com um hash SHA-256 com salt:
- Entre no modo interservidor. O servidor que está se conectando sinaliza isso em
ClientHello: o campouseré o marcador interservidor epasswordfica vazio. Em seguida, ele acrescenta mais duas strings — o nome do cluster e umsaltde 32 bytes recém-gerado (encodeSHA256de um valor aleatório) — imediatamente após os camposuser/password, como parte do mesmo pacoteClientHello. O servidor lê essas duas strings antes de enviarServerHello, então o cliente precisa escrevê-las logo de saída; esperar primeiro porServerHellocausa deadlock, porque o servidor fica bloqueado tentando lê-las. - Obtenha o nonce.
ServerHellotraz um nonceUInt64de 8 bytes quandoINTERSERVER_SECRET_V2(v54462) é negociado. - Calcule o hash. Para cada pacote de consulta que não seja
InitialQuery, o cliente escreveencodeSHA256(salt + nonce + cluster_secret + query + query_id + initial_user + external_roles)no campo 4 — um digest de 32 bytes. (nonceusa sua forma de string decimal, presente somente quando negociado ≥ v54462;external_rolesé acrescentado somente quandoINTERSERVER_EXTERNALLY_GRANTED_ROLES(v54472) é negociado.) Para umInitialQuery, ou quando nenhum segredo de cluster está configurado, o cliente escreve uma string vazia. - Verifique. O servidor lê o campo 4 com um limite de 32 bytes e recompõe a mesma concatenação usando sua própria cópia do segredo do cluster; a conexão é rejeitada se os digests forem diferentes.
auth_hash vazio.
SETTING
key vazia — um único VarUInt 0, sem flags nem value em seguida. Apenas a codificação de cada configuração depende da versão negociada, condicionada por SETTINGS_SERIALIZED_AS_STRINGS (v54429).
v54429+ (STRINGS_WITH_FLAGS) — cada configuração é a tripla mostrada aqui:
| # | Campo | Tipo | Função | Descrição |
|---|---|---|---|---|
| 1 | key | String | universal | Nome da configuração. Vazio = fim da lista. |
| 2 | flags | VarUInt | universal | Flags de bits de metadados; veja abaixo. |
| 3 | value | String | universal | Valor da configuração como string |
key está vazia.
Pré-54429 (BINARY) — cada configuração é [String key][type-specific binary value]: o campo flags não é gravado, e o valor é codificado na forma binária nativa da configuração (por exemplo, um inteiro de largura fixa ou uma string com prefixo de comprimento), em vez de como uma string decimal/textual. A lista continua sendo terminada por uma key vazia. Um cliente que tenha como alvo uma versão negociada inferior a 54429 deve ler e gravar essa forma binária, não a tripla acima. (As configurações personalizadas definidas pelo usuário são a exceção: elas sempre incluem flags e um valor em string, em ambas as codificações.)
O campo flags agrupa:
0x01— Important: a configuração afeta os resultados da consulta e não deve ser ignorada silenciosamente por pares mais antigos.0x02— Custom: uma custom setting definida pelo usuário.0x0c— um campo de tier de 2 bits, não uma flag independente:0x00= Production,0x04= Obsolete,0x08= Experimental,0x0c= Beta. Leia os 2 bits completos (flags & 0x0c) — um teste ingênuo comflags & 0x04classificaria Beta (0x0c) incorretamente como Obsolete.0x80— HotReload (recarregamento de config sem reinicialização; definido no enum de flags, encontrado principalmente em configurações de coordination).
Parâmetro
SELECT {x:UInt64}. Codificados exatamente como uma SETTING, com o sinalizador Custom (0x02) ativado, e encerrados da mesma forma, por uma chave vazia.
| # | Campo | Tipo | Função | Descrição |
|---|---|---|---|---|
| 1 | key | String | cliente | Nome do parâmetro. Vazio = fim da lista. |
| 2 | flags | VarUInt | cliente | Sempre 0x02 (Custom) |
| 3 | value | String | cliente | Valor do parâmetro como string. Veja a observação abaixo sobre aspas. |
O valor do parâmetro é a representação SQL do valor, não um literal bruto. Parâmetros do tipo string devem ser passados já entre aspas simples (por exemplo, o valor de
{name:String} é 'Alice', não Alice); caso contrário, o analisador de valores do servidor os rejeitará.Data (tipo de pacote 1 servidor→cliente, tipo de pacote 2 cliente→servidor)
table_name antes do Block. Apenas o byte do tipo de pacote difere.
| Campo | Tipo | Função | Descrição |
|---|---|---|---|
| table_name | String | universal | Nome da tabela externa. Vazio ("") é o caso mais comum — para a tabela principal, os resultados da consulta e o fluxo de linhas de INSERT. table_name vazio, por si só, não é o marcador de fim de dados (pacotes normais de linhas de INSERT também carregam ""). |
| Corpo do bloco | — | — | Consulte Block & column structure. |
0 colunas e 0 linhas — independentemente de table_name. O servidor trata um pacote Data do cliente como terminador apenas quando o bloco decodificado está vazio (block.empty()); um pacote com table_name = "" e um bloco não vazio é um pacote comum de linhas, não um terminador. Portanto, um fluxo de linhas de INSERT é uma sequência de blocos Data não vazios, seguida por um bloco Data vazio que o encerra.
As variantes de bloco e seus significados estão documentados em Block variants.
Progress (tipo de pacote 3)
Progress anterior, não totais acumulados. Antes de enviar, o servidor lê seus contadores, reinicia-os atomicamente para zero e calcula elapsed_ns como o delta de tempo desde o último envio. Portanto, um cliente deve acumular os pacotes sucessivos localmente para obter totais correntes — tratar um pacote como um valor absoluto faz a exibição do progresso retroceder ou subestimar a contagem quando mais de um pacote chega.
| # | Field | Type | Role | Condition | Description |
|---|---|---|---|---|---|
| 1 | rows | VarUInt | universal | always | Linhas lidas desde o pacote anterior (adicione ao total corrente) |
| 2 | bytes | VarUInt | universal | always | Bytes lidos desde o pacote anterior (adicione ao total corrente) |
| 3 | total_rows | VarUInt | universal | always | Incremento no total estimado de linhas a serem lidas; acumule (pode ser 0 em um determinado pacote) |
| 4 | total_bytes | VarUInt | universal | TOTAL_BYTES_IN_PROGRESS (v54463) | Incremento no total estimado de bytes a serem lidos; acumule. Fica ENTRE total_rows e wrote_rows no wire. |
| 5 | wrote_rows | VarUInt | universal | WRITE_CLIENT_INFO (v54420) | Linhas gravadas desde o pacote anterior (para INSERT); acumule |
| 6 | wrote_bytes | VarUInt | universal | WRITE_CLIENT_INFO (v54420) | Bytes gravados desde o pacote anterior (para INSERT); acumule |
| 7 | elapsed_ns | VarUInt | universal | SERVER_QUERY_TIME_IN_PROGRESS (v54460) | Nanosegundos decorridos desde o pacote anterior (um delta, não o tempo total da consulta); acumule |
ProfileInfo (tipo de pacote 6)
| # | Campo | Tipo | Função | Condição | Descrição |
|---|---|---|---|---|---|
| 1 | rows | VarUInt | universal | sempre | Total de linhas processadas |
| 2 | blocks | VarUInt | universal | sempre | Total de blocos processados |
| 3 | bytes | VarUInt | universal | sempre | Total de bytes processados |
| 4 | applied_limit | Bool | universal | sempre | Indica se uma cláusula LIMIT foi aplicada |
| 5 | rows_before_limit | VarUInt | universal | sempre | Número de linhas antes de LIMIT |
| 6 | obsolete | Bool | universal | sempre | Byte de compatibilidade obsoleto. O servidor sempre escreve true aqui, e o cliente o lê e descarta; não é um indicador de que “rows_before_limit foi calculado”. O estado de limite relevante é o campo 4 (applied_limit) em conjunto com o campo 5. Leia e ignore. |
| 7 | applied_aggregation | Bool | universal | ROWS_BEFORE_AGGREGATION (v54469) | Indica se GROUP BY foi aplicado |
| 8 | rows_before_aggregation | VarUInt | universal | ROWS_BEFORE_AGGREGATION (v54469) | Número de linhas antes da agregação |
Totais (tipo de pacote 7)
WITH TOTALS. O formato wire é idêntico a Data: uma string table_name (sempre vazia), seguida por um Block. Apenas o byte do tipo de pacote difere.
Extremes (tipo de pacote 8)
extremes está ativada. O formato wire é idêntico a Data. O bloco tem exatamente 2 linhas: a linha 0 contém o mínimo de cada coluna, e a linha 1 contém o máximo.
Log (tipo de pacote 10)
send_logs_level; consulte streaming de logs).
Mesmo formato de envelope e corpo que Data. O bloco tem num_columns = 8 fixo e um esquema predefinido. Cada linha de log corresponde a uma linha nas 8 colunas, e um único pacote Log pode carregar muitas linhas.
| # | Nome | Tipo | Descrição |
|---|---|---|---|
| 1 | event_time | DateTime | Timestamp do evento (segundos desde a epoch) |
| 2 | event_time_microseconds | UInt32 | Componente de microssegundos |
| 3 | host_name | String | Hostname do servidor que emite o log |
| 4 | query_id | String | ID da consulta à qual o log pertence |
| 5 | thread_id | UInt64 | ID da thread do SO |
| 6 | priority | Int8 | Nível de log (prioridade do Poco: 1 = Fatal, … 8 = Trace) |
| 7 | source | String | Nome do logger |
| 8 | text | String | Texto da mensagem de log |
ProfileEvents (tipo de pacote 14)
num_columns = 6 fixo e um esquema predefinido. Cada evento corresponde a uma linha.
| # | Nome | Tipo | Descrição |
|---|---|---|---|
| 1 | host_name | String | Hostname do servidor |
| 2 | current_time | DateTime | Timestamp do evento |
| 3 | thread_id | UInt64 | ID da thread |
| 4 | type | Enum8 | Tipo de evento: 1 = Incremento (contador), 2 = Gauge. O armazenamento subjacente é um byte com sinal. |
| 5 | name | String | Nome do evento (por exemplo, "Query", "NetworkReceiveBytes") |
| 6 | value | Int64 | Valor do contador ou medição do gauge |
O tipo de elemento da coluna
value não é fixo de um pacote para outro — servidores mais antigos emitem UInt64, e os mais novos, Int64. Leia a string de tipo da coluna no cabeçalho do bloco, em vez de presumir um tamanho fixo.TableColumns (tipo de pacote 11)
COLUMN_DEFAULTS_METADATA (v54410). O servidor o envia antes do bloco de esquema do INSERT para carregar os metadados de valores padrão das colunas, mas apenas quando a versão negociada é ≥ 54410 e a configuração input_format_defaults_for_omitted_fields está habilitada. Abaixo de 54410, o pacote nunca é enviado, portanto um cliente mais antigo não deve esperar por ele — o bloco de esquema Data vem diretamente. Um cliente v54410+ deve estar preparado para qualquer uma das ordens: um TableColumns opcional, seguido pelo bloco de esquema.
| # | Field | Type | Role | Description |
|---|---|---|---|---|
| 1 | external_table | String | universal | Nome da tabela externa. Vazio = tabela principal. |
| 2 | columns_description | String | universal | Definições textuais das colunas, por exemplo, "id Int32, name String DEFAULT ''". Texto livre — interprete como uma string. |
Corpo comprimido a partir da v54481Na versão negociada ≥ 54481 (
COMPRESSED_LOGS_PROFILE_EVENTS_COLUMNS), o servidor grava ambos os campos pelo mesmo caminho de saída opcionalmente comprimido; portanto, quando a consulta tem compression = true, todo o corpo de TableColumns (external_table + columns_description) fica dentro do frame de compressão, e o cliente o lê pelo fluxo descomprimido correspondente. Quando a consulta não tem compressão, o corpo vai pela wire sem compressão, exatamente como a tabela acima mostra. Isso é importante para respostas de esquema de INSERT: um cliente que altere o tratamento de compressão para Log e ProfileEvents, mas não para TableColumns, lerá a resposta incorretamente quando a compressão da consulta estiver habilitada.TimezoneUpdate (tipo de pacote 17)
TIMEZONE_UPDATES (v54464). Enviado em exatamente um lugar: no inicializador da table function input (uma consulta no formato INSERT INTO <table> SELECT ... FROM input('<structure>'), que transmite linhas do cliente). Logo após o servidor enviar o bloco Data do esquema de entrada (veja a fase de INSERT), ele emite TimezoneUpdate com o session_timezone atual do contexto da consulta, para que o cliente analise as linhas que está prestes a enviar com o mesmo fuso horário. O servidor não emite esse pacote para alterações arbitrárias de SET session_timezone no meio da consulta, nem para informar ao cliente como formatar blocos de resultado posteriores.
| # | Field | Type | Role | Description |
|---|---|---|---|---|
| 1 | timezone | String | universal | O novo fuso horário padrão da sessão (por exemplo, "UTC", "Europe/Berlin"). |
TimezoneUpdate AINDA DEVE consumir a String final para manter o wire alinhado.
Autenticação SSH por desafio-resposta (tipos de pacote 11, 12, 18)
SSH_AUTHENTICATION (v54466) e habilitada apenas por ativação explícita. Uma conexão entra no fluxo SSH quando ClientHello envia user = " SSH KEY AUTHENTICATION " + <real_user> (com os espaços iniciais e finais) e password = "". O servidor lê o prefixo, remove-o para recuperar o usuário real e passa para o modo desafio-resposta.
| Packet | Code | Direction | Body |
|---|---|---|---|
| SSHChallengeRequest | 11 | Client → Server | (sem corpo) |
| SSHChallenge | 18 | Server → Client | String challenge — bytes aleatórios; um dos componentes da string que é assinada (veja abaixo) |
| SSHChallengeResponse | 12 | Client → Server | String signature — assinatura SSH sobre a concatenação definida abaixo, não sobre o desafio bruto |
ServerHello — o servidor adia sua resposta Hello até que a autenticação seja concluída com sucesso:
-
O cliente envia
ClientHellocom o prefixo marcador SSH e uma senha vazia. -
O cliente envia
SSHChallengeRequest(pacote 11). O servidor ainda não enviouServerHello— ele primeiro processa a autenticação e fica aguardando esse pacote. -
O servidor responde com
SSHChallengetrazendo bytes aleatórios (pacote 18). -
O cliente monta a string a ser assinada e assina essa string, não o desafio bruto, depois envia
SSHChallengeResponse(pacote 12) com a assinatura. A mensagem assinada é a concatenação byte a byte, sem separadores, de quatro partes nesta ordem exata:Part Source decimal(protocol_version)A versão do protocolo do cliente como uma string ASCII decimal (por exemplo, "54466") — o número da versão como string, não comoVarUIntnem como inteiro de largura fixa. O servidor valida usando a mesma versão do protocolo que recebeu emClientHello.default_databaseO campo databasedeClientHello(string vazia se não houver).userO nome do usuário real com o prefixo marcador " SSH KEY AUTHENTICATION "removido — o mesmo nome que o servidor recupera após remover o prefixo.challengeOs bytes brutos de challengedo pacoteSSHChallenge. -
O servidor verifica a assinatura usando a chave pública registrada do usuário, reconstruindo a mesma string
decimal(protocol_version) + default_database + user + challenge. Em caso de sucesso, ele enviaServerHello— a mesma resposta do fluxo por senha — e o handshake continua normalmente (Addendum etc.); em caso de falha, retorna umaExceptione encerra a conexão. Um cliente que assinar apenas os bytes brutos dechallengefalhará na autenticação.
Isso é o inverso do handshake de senha, em que
ServerHello vem imediatamente após ClientHello. Na autenticação SSH, ServerHello fica retido até que a assinatura seja verificada, de modo que o mecanismo de desafio-resposta do SSH é intercalado ao handshake antes que qualquer ServerHello apareça.Referência de tipos de pacote
Cliente → Servidor
| Código | Nome | Formato do corpo | Descrição |
|---|---|---|---|
| 0 | Hello | ClientHello | Início do handshake |
| 1 | consulta | consulta | Solicitação de execução de consulta |
| 2 | Data | Data | Bloco de dados (dados de INSERT, tabelas externas, marcador de fim dos dados) |
| 3 | Cancel | (sem corpo) | Cancelar consulta em execução |
| 4 | Ping | Ping | Verificação de disponibilidade |
| 5 | TablesStatusRequest | não especificado | Verificação de status das tabelas |
| 6 | KeepAlive | não especificado | Manutenção da conexão ativa |
| 7 | Scalar | não especificado | Bloco de dados escalar |
| 8 | IgnoredPartUUIDs | não especificado | Partes a serem excluídas da consulta |
| 9 | ReadTaskResponse | não especificado | Resposta de leitura do cluster S3 |
| 10 | MergeTreeReadTaskResponse | não especificado | Resposta da tarefa de leitura paralela |
| 11 | SSHChallengeRequest | autenticação SSH | Solicitação de desafio de autenticação SSH |
| 12 | SSHChallengeResponse | autenticação SSH | Resposta ao desafio de autenticação SSH |
| 13 | QueryPlan | não especificado | Plano de consulta |
Servidor → Cliente
| Código | Nome | Formato do corpo | Descrição |
|---|---|---|---|
| 0 | Hello | ServerHello | Resposta do handshake |
| 1 | Data | Data | Bloco de dados do resultado |
| 2 | Exception | Exception | Erro |
| 3 | Progress | Progress | Progresso da execução da consulta |
| 4 | Pong | Pong | Resposta de liveness |
| 5 | EndOfStream | (sem corpo) | Consulta concluída |
| 6 | ProfileInfo | ProfileInfo | Dados de profiling após a execução |
| 7 | Totals | Totals | Linha de GROUP BY WITH TOTALS |
| 8 | Extremes | Extremes | Valores mínimos/máximos (bloco de 2 linhas) |
| 9 | TablesStatusResponse | não especificado | Resposta de status da tabela |
| 10 | Log | Log | Linhas de log da execução da consulta |
| 11 | TableColumns | TableColumns | Descrições de colunas para valores padrão |
| 12 | PartUUIDs | não especificado | IDs únicos de partes |
| 13 | ReadTaskRequest | não especificado | Solicitação de tarefa de leitura do cluster |
| 14 | ProfileEvents | ProfileEvents | Contadores de desempenho |
| 15 | MergeTreeAllRangesAnnouncement | não especificado | Inicialização da leitura paralela |
| 16 | MergeTreeReadTaskRequest | não especificado | Atribuição de tarefa de leitura paralela |
| 17 | TimezoneUpdate | TimezoneUpdate | Atualização do fuso horário do servidor |
| 18 | SSHChallenge | autenticação SSH | Desafio de autenticação SSH |
Configuração
- Configurações da camada de transporte — opções de socket TCP e timeouts, que afetam o comportamento da própria conexão TCP.
- Configurações da camada de aplicação — ajustes por consulta incluídos na lista de configurações do pacote consulta, que afetam o que o servidor envia no wire ou como isso é estruturado.
- Configurações fora do escopo — configurações frequentemente confundidas com configurações de protocolo, mas que na verdade controlam a execução de SQL ou o armazenamento.
Configurações da camada de transporte
Opções de socket
| Opção | Padrão | Lado | Descrição |
|---|---|---|---|
TCP_NODELAY | ativado | ambos | Algoritmo de Nagle desativado. Pacotes pequenos são enviados imediatamente. |
SO_KEEPALIVE | ativado (cliente), padrão do SO (servidor) | assimétrico | Sondagens de keepalive TCP no nível do kernel. O cliente ativa isso explicitamente quando tcp_keep_alive_timeout > 0. O servidor herda o padrão do SO. |
SO_RCVBUF / SO_SNDBUF | padrões do SO | — | Tamanhos de buffer do socket. Não são ajustados pelo protocolo. |
Timeouts
| Setting | Default | Unit | Side | Description |
|---|---|---|---|---|
connect_timeout | 10 | segundos | client | Tempo limite para estabelecer a conexão TCP inicial. |
handshake_timeout_ms | 10000 | milissegundos | client | Tempo limite para receber o ServerHello durante o handshake. |
send_timeout | 300 | segundos | both | Se nenhum byte puder ser enviado dentro deste intervalo, a conexão gera uma exceção. |
receive_timeout | 300 | segundos | both | Se nenhum byte puder ser lido dentro deste intervalo, a conexão gera uma exceção. |
tcp_keep_alive_timeout | 290 | segundos | client | Tempo de inatividade antes de o SO enviar a primeira sondagem de keepalive do TCP. |
receive_data_timeout_ms | 2000 | milissegundos | client | Tempo limite para receber o primeiro pacote Data de uma réplica. |
connect_timeout_with_failover_ms | 1000 | milissegundos | client | Tempo limite de conexão por tentativa ao percorrer as réplicas. |
connect_timeout_with_failover_secure_ms | 1000 | milissegundos | client | Tempo limite de conexão por tentativa ao percorrer as réplicas por TLS. |
hedged_connection_timeout_ms | 50 | milissegundos | client | Tempo limite de conexão por tentativa para requisições hedged. |
poll_interval | 10 | segundos | server | Granularidade do loop do servidor para verificar conexões ociosas e desligamento. |
Limites de conexão
| Configuração | Padrão | Unidade | Lado | Descrição |
|---|---|---|---|---|
max_connections | 4096 | quantidade | servidor | Número máximo de conexões TCP simultâneas. |
idle_connection_timeout | 3600 | segundos | servidor | Tempo máximo que uma conexão ociosa pode permanecer aberta. |
tcp_close_connection_after_queries_num | 0 (ilimitado) | quantidade | servidor | Número máximo de consultas por conexão antes de um encerramento forçado. |
tcp_close_connection_after_queries_seconds | 0 (ilimitado) | segundos | servidor | Ciclo de vida total máximo da conexão, independentemente da atividade. |
Configurações da camada de aplicação
Compressão
| Configuração | Padrão | Unidade | Descrição |
|---|---|---|---|
network_compression_method | "LZ4" | string | Codec de compressão usado quando o sinalizador compression do pacote consulta está definido. Valores: "LZ4", "LZ4HC", "ZSTD", "NONE". |
network_zstd_compression_level | 1 | 1–15 | Nível de ZSTD quando network_compression_method == "ZSTD". |
compression no pacote consulta (campo 6) ativa e desativa a compressão; essas configurações definem qual codec é usado quando ele está ativado.
Streaming de logs
| Configuração | Padrão | Unidade | Descrição |
|---|---|---|---|
send_logs_level | "fatal" | string | Nível mínimo de log. Valores: "none", "fatal", "error", "warning", "information", "debug", "trace". |
send_logs_source_regexp | "" | string | Filtro Regex para a origem do logger. Vazio = todas as origens são aceitas. |
send_logs_level com qualquer valor diferente de "none" faz com que o servidor emita pacotes de Log durante a execução da consulta.
Relatório de Progress
| Configuração | Padrão | Unidade | Descrição |
|---|---|---|---|
interactive_delay | 100000 | microssegundos | Intervalo mínimo esperado entre pacotes Progress consecutivos. |
Envelope do resultado
| Configuração | Padrão | Unidade | Descrição |
|---|---|---|---|
extremes | false | bool | Quando definido como true, o servidor envia um pacote Extremes com valores mínimos/máximos por coluna. |
max_result_rows | 0 (ilimitado) | contagem | Limite de linhas transmitidas. O comportamento é controlado por result_overflow_mode. |
max_result_bytes | 0 (ilimitado) | bytes não comprimidos | Limite do volume de bytes não comprimidos. O comportamento é controlado por result_overflow_mode. |
result_overflow_mode | "throw" | string | "throw" encerra o fluxo com Exception; "break" envia resultados parciais seguidos por EndOfStream. |
INSERT assíncrono
| Configuração | Padrão | Unidade | Descrição |
|---|---|---|---|
async_insert | true | bool | Quando true, os dados de INSERT são enfileirados no servidor e agrupados em lote. |
wait_for_async_insert | true | bool | Quando true (com async_insert ativado), o servidor mantém a resposta em espera até que os dados enfileirados sejam gravados no armazenamento. |
wait_for_async_insert_timeout | 120 | segundos | Tempo máximo que o servidor espera por uma gravação no armazenamento antes de retornar. |
Rastreamento distribuído
| Configuração | Padrão | Unidade | Descrição |
|---|---|---|---|
opentelemetry_start_trace_probability | 0.0 | probabilidade 0–1 | Probabilidade, no servidor, de associar o contexto do OpenTelemetry à telemetria de resposta. |
Configurações fora do escopo
max_threads— paralelismo na execução da consulta.max_memory_usage— limite de memória por consulta.max_block_size,preferred_block_size_bytes— dimensionamento interno de blocos durante o processamento da consulta; os blocos wire são independentes dessas configurações.compile_expressions— compilação JIT; afeta apenas a CPU.async_insert_max_data_size— buffer da fila no servidor.- Todas as configurações
input_format_*eoutput_format_*, exceto a famíliainput_format_native_*/output_format_native_*— as não-nativeselecionam ou ajustam outros formatos (por exemplo, via HTTP) e não alteram os blocosDatado protocolo nativo.
*_native_* são a exceção: elas alteram os bytes dentro dos blocos Data do native TCP, portanto uma implementação de protocolo deve levá-las em conta. output_format_native_encode_types_in_binary_format muda o campo type da coluna de uma string textual para uma codificação binária de tipo, output_format_native_write_json_as_string emite colunas JSON como String, e output_format_native_use_flattened_dynamic_and_json_serialization seleciona o layout FLATTENED de Dynamic/JSON. Como elas afetam o body do bloco, e não o envelope do pacote, elas são especificadas na especificação Native Format — consulte layout wire da coluna e tipos versionados.
Glossário
- Consulta normal (
SELECT, etc.): enviado após o pacote consulta e quaisquer pacotes Data de tabelas externas para sinalizar “não há mais dados externos”. O servidor então inicia a execução. INSERT: o cliente não envia um marcador antes do esquema. O servidor envia primeiro o bloco de esquema, o cliente transmite seus blocos Data de linhas e só então envia o pacote Data vazio para encerrar o fluxo de linhas. Enviar um marcador vazio antes do bloco de esquema seria interpretado como um fim imediato das linhas, e os dados seriam perdidos.
min(client_version, server_version), calculada durante o handshake. Determina quais recursos ficam ativos durante o ciclo de vida da conexão.
Pacote — uma mensagem wire: um código de tipo de pacote VarUInt seguido por um body cujo formato depende do tipo. Veja envelope do pacote.
Código de tipo de pacote — o VarUInt inicial de um pacote que identifica seu formato. Os valores de 0 a 18 estão atribuídos no momento. Veja a referência de tipos de pacote.
Fluxo de resposta — a sequência de pacotes que o servidor emite durante uma consulta. Seu comprimento é indefinido e ele é encerrado por exatamente um EndOfStream (sucesso) ou Exception (falha). Veja a fase da consulta.
Bloco de esquema — o bloco de cabeçalho (um Block com colunas, mas 0 linhas) que o servidor envia durante a fase de INSERT para informar os formatos de coluna esperados antes de o cliente enviar os dados.
Lista de configurações — uma sequência de tuplas (key, flags, value) no body de consulta, terminada por uma chave vazia. Carrega a configuração por consulta na camada de aplicação. Veja SETTING.
Estágio — um campo VarUInt no pacote consulta (campo 5) que controla até onde o servidor executa a consulta. Clientes externos normalmente enviam 2 (Complete); consultas distribuídas e planos de consulta serializados usam os valores mais altos. Veja o campo 5 de consulta para o conjunto completo de valores wire.
Terminador — um pacote que encerra um fluxo. A resposta de consulta termina em EndOfStream (sucesso) ou Exception (falha). O fluxo de entrada do cliente termina no marcador Data vazio.