Skip to main content
El protocolo nativo es el protocolo binario orientado a conexión que los clientes y servidores de ClickHouse usan sobre TCP. Transporta consultas SQL, datos de resultados, payloads de INSERT, telemetría de ejecución y señales de error. Es el protocolo en el que se basan el Client de línea de comandos, el driver nativo de C++ y la mayoría de los drivers nativos de terceros. Esta página describe el protocolo en sí: el encapsulado de paquetes, la máquina de estados de la conexión, la negociación de versiones y el cuerpo de todos los mensajes que no son Block. Los bytes dentro de los paquetes de la familia Data (el Block, sus columnas y las codificaciones de cada tipo) son un aspecto aparte, documentado en la especificación de Formato nativo.
Especificación complementariaEsta página es una de las dos partes de un conjunto y se publica junto con la especificación complementaria de Formato nativo. Ambas especificaciones reparten claramente el trabajo: esta página cubre la capa de paquetes y transporte; la especificación de Formato nativo cubre los bytes dentro de los paquetes de la familia Data.
Hay varias propiedades que se mantienen en todo el protocolo. Es binario y posicional: no hay etiquetas de campo salvo dentro de BlockInfo, así que un solo byte fuera de lugar desincroniza todo lo que viene después. Tiene estado, y cada conexión TCP procesa una consulta cada vez; no hay multiplexación. Los enteros de ancho fijo están en formato little-endian.

Descripción general

PropiedadValor
TransporteTCP, opcionalmente protegido con TLS
Orden de byteslittle-endian para enteros de ancho fijo
CodificaciónBinaria y posicional (sin etiquetas de campo excepto en BlockInfo)
Modelo de conexiónCon estado, una consulta a la vez, sin multiplexación
Control de versionesSe negocia en el handshake; las funcionalidades individuales se habilitan según la versión
Formato de datosEl Formato nativo para todos los datos tabulares
Cada mensaje en el wire comienza con un código de tipo de paquete VarUInt, seguido de un cuerpo cuya estructura depende de ese código y de la versión del protocolo negociada. Una conexión pasa por tres fases: un handshake único, luego cualquier cantidad de intercambios Ping o Query, y por último el cierre: El protocolo TCP nativo siempre transporta datos tabulares en el formato Native, independientemente de cualquier cláusula FORMAT en SQL. Volver a formatear en RowBinary, CSV, JSON, etc. es tarea del Client, y se hace después de decodificar los bloques Native. (La interfaz HTTP sigue una ruta de código distinta que respeta la cláusula FORMAT; HTTP queda fuera del alcance de esta explicación.)

Seguridad

Seguridad de transporte (TLS)

TLS opera en la capa de transporte, por debajo del protocolo. Cuando está habilitado, se cifra todo el flujo TCP, y los mensajes del protocolo son idénticos byte por byte, tanto si se usa TLS como si no.

Autenticación

La autenticación se realiza durante el handshake, en el mensaje ClientHello. Los campos user y password se transmiten como cadenas de texto en claro, por lo que el cifrado a nivel de transporte (TLS) es lo que protege las credenciales durante el tránsito. La autenticación SSH de desafío-respuesta está disponible a partir de la versión 54466 del protocolo; consulta Autenticación SSH de desafío-respuesta.

Secreto entre servidores

Para la ejecución distribuida de consultas, los servidores se autentican entre sí demostrando que conocen un secreto compartido, sin transmitir el secreto por la red. Cada Query incluye un auth_hash SHA-256 de 32 bytes en el campo 4 de Query, calculado a partir de una sal, un nonce, el secreto configurado y la consulta; el servidor receptor lo vuelve a calcular y lo compara. Esto está controlado por la funcionalidad INTERSERVER_SECRET (v54441). Los Clients externos siempre envían aquí una cadena vacía. Consulte Autenticación entre servidores.

Versionado y control de funcionalidad

Negociación de versiones

Tanto el Client como el servidor indican la versión máxima del protocolo que admiten durante el handshake. La versión negociada es la menor de las dos:
negotiated_version = min(client_version, server_version)
Cada mensaje posterior usa la versión negociada para determinar qué campos están presentes en el wire.

Controles de funcionalidad

Una funcionalidad se identifica por la versión del protocolo en la que se introdujo y está activa cuando la versión negociada es mayor o igual a ese número.
Cuando una funcionalidad está activa, sus campos deben estar presentes en la representación binaria transmitida. El protocolo es estrictamente posicional, por lo que omitir un campo protegido por un control de funcionalidad corrompe el flujo de bytes de todos los campos posteriores.

Tabla de funcionalidades

FuncionalidadVersiónAfecta aImpacto a nivel wire
BLOCK_INFOallBlockAñade el prefijo BlockInfo (is_overflows, bucket_number) a cada Block.
CLIENT_INFO54032QueryAñade el bloque ClientInfo al cuerpo de Query.
TIMEZONE54058ServerHelloAñade el campo timezone a ServerHello.
QUOTA_KEY_IN_CLIENT_INFO54060ClientInfoAñade el campo quota_key a ClientInfo.
DISPLAY_NAME54372ServerHelloAñade el campo display_name a ServerHello.
VERSION_PATCH54401ServerHello, ClientInfoAñade el campo version_patch a ambos.
SERVER_LOGS54406LogEl servidor emite paquetes Log cuando se configura send_logs_level.
COLUMN_DEFAULTS_METADATA54410TableColumnsEl servidor puede enviar el paquete TableColumns (tipo 11) con metadatos de valores predeterminados de columnas antes del bloque de esquema de INSERT/input. Solo se envía cuando la versión negociada es ≥ 54410 y input_format_defaults_for_omitted_fields está habilitado. Por debajo de esta versión, el paquete no se envía nunca; los client no deben esperarlo.
WRITE_CLIENT_INFO54420ProgressAñade wrote_rows y wrote_bytes a Progress. (A pesar del nombre, esto no controla el bloque ClientInfo; eso lo hace CLIENT_INFO (v54032).)
SETTINGS_SERIALIZED_AS_STRINGS54429Query (settings encoding)Cambia cómo se codifica la lista de settings, que siempre está presente; no controla si se envían settings. v54429+ escribe cada setting como (name, flags, value-as-string); los peers anteriores escriben (name, type-specific-binary-value) sin flags. Consulta Setting.
INTERSERVER_SECRET54441QueryAñade a Query el campo auth_hash entre servidores: un SHA-256 con salt del secreto del clúster, no el secreto sin procesar. Los client externos envían una cadena vacía. Consulta Inter-server authentication.
OPEN_TELEMETRY54442ClientInfoAñade el trace context de OpenTelemetry a ClientInfo.
DISTRIBUTED_DEPTH54448ClientInfoAñade el campo distributed_depth a ClientInfo.
INITIAL_QUERY_START_TIME54449ClientInfoAñade el campo initial_time (Int64, de ancho fijo).
PROFILE_EVENTS54451ProfileEventsEl servidor emite paquetes ProfileEvents durante la ejecución de la consulta.
PARALLEL_REPLICAS54453ClientInfoAñade campos de coordinación de réplicas paralelas a ClientInfo.
CUSTOM_SERIALIZATION54454Block (Column)Añade el byte has_custom_serialization después de la cadena de tipo de cada columna.
ADDENDUM54458HandshakeEl client envía un addendum (quota_key) después del intercambio de handshake.
PARAMETERS54459QueryAñade la lista de parámetros al cuerpo de Query.
SERVER_QUERY_TIME_IN_PROGRESS54460ProgressAñade el campo elapsed_ns a Progress.
PASSWORD_COMPLEXITY_RULES54461ServerHelloAñade a ServerHello una lista de patrones regex de políticas de contraseñas y mensajes legibles para humanos.
INTERSERVER_SECRET_V254462ServerHelloAñade un nonce UInt64 de 8 bytes a ServerHello. Se usa para la firma de consultas entre servidores; los client externos lo decodifican y lo ignoran.
TOTAL_BYTES_IN_PROGRESS54463ProgressAñade el campo total_bytes_to_read (VarUInt) a Progress, entre total_rows y wrote_rows.
TIMEZONE_UPDATES54464TimezoneUpdateAñade el paquete de servidor TimezoneUpdate (tipo 17). Cuerpo: un único String que transporta la session timezone. Solo lo envía el inicializador de la table function input, justo después del bloque de esquema de entrada, para que el client analice las filas que envía con la session_timezone del servidor. Consulta TimezoneUpdate.
SPARSE_SERIALIZATION54465Block (Column)El servidor puede establecer has_custom_serialization = 1 y emitir una columna codificada de forma dispersa. Wire format: kind de 1 byte (0x01 = SPARSE), seguido de un flujo de offsets VarUInt terminado por EOG, y luego los valores no predeterminados codificados densamente en el tipo interno. Consulta kind_stack and sparse encoding.
SSH_AUTHENTICATION54466Auth flowAñade autenticación SSH de challenge-response. Participación voluntaria: el client envía un user con la forma " SSH KEY AUTHENTICATION " + <real_user> con contraseña vacía para activarla. Consulta SSH challenge-response authentication.
TABLE_READ_ONLY_CHECK54467TablesStatusResponseAñade un indicador is_readonly a la fila de cada table en TablesStatusResponse. Los client externos que no envían TablesStatusRequest no ven ningún cambio a nivel wire.
SYSTEM_KEYWORDS_TABLE54468system tablesEl servidor puebla system.keywords para que el clickhouse-client canónico pueda autocompletar keywords. No hay cambios a nivel wire en el protocolo nativo.
ROWS_BEFORE_AGGREGATION54469ProfileInfoAñade applied_aggregation (Bool) y rows_before_aggregation (VarUInt) a ProfileInfo, en ese orden, al final.
CHUNKED_PROTOCOL54470Connection framingEl framing por fragmentos por paquete envuelve cada cuerpo de paquete. Se negocia en Addendum. ServerHello transporta la preferencia del servidor para cada dirección; Addendum transporta la elección final del client. Consulta chunked framing.
VERSIONED_PARALLEL_REPLICAS_PROTOCOL54471ServerHello, AddendumAmbos lados intercambian una versión VarUInt del protocolo de coordinación de réplicas paralelas. El campo de ServerHello está situado inmediatamente después de protocol_version (antes de timezone). El campo de Addendum se añade después de las cadenas del protocolo por fragmentos. Valor actual: 7 (DBMS_PARALLEL_REPLICAS_PROTOCOL_VERSION).
INTERSERVER_EXTERNALLY_GRANTED_ROLES54472QueryAñade un campo String external_roles al cuerpo de Query, entre el terminador de settings y el hash del secreto interserver. Los clients externos envían una lista de roles vacía (un único byte 0x00, es decir, VarUInt 0 dentro de un String contenedor).
V2_DYNAMIC_AND_JSON_SERIALIZATION54473Column bodyEl server puede emitir serialización V2 para los tipos de columna Dynamic y JSON; esto determina qué versión de state_prefix usan. Consulta versioned types.
SERVER_SETTINGS54474ServerHelloEl server difunde sus settings distintos de los predeterminados como una lista al final de ServerHello, después de nonce. Formato: triples (key, flags, value) terminados por una key vacía, igual que la lista de settings del paquete Query.
QUERY_AND_LINE_NUMBERS54475ClientInfoAñade script_query_number (VarUInt) y script_line_number (VarUInt) al final de ClientInfo. Lo usa clickhouse-client para atribuir errores en scripts con múltiples sentencias; los clients externos envían 0, 0.
JWT_IN_INTERSERVER54476ClientInfoAñade un UInt8 que indica la presencia de JWT más un String jwt opcional al final de ClientInfo. Los clients externos (sin JWT) envían el byte 0x00. (En C++ se escribe DBMS_MIN_REVISON_WITH_JWT_IN_INTERSERVER; observa la errata en el nombre de la constante).
QUERY_PLAN_SERIALIZATION54477ServerHello, QueryPlan packetServerHello añade VarUInt query_plan_serialization_version después de los settings del server. También introduce ClientPacket::QueryPlan (código 13) para el envío entre servers de planes de consulta preconstruidos; los clients externos nunca lo envían.
PARALLEL_BLOCK_MARSHALLING54478Block (Column)El server puede encapsular columnas en ColumnBLOB (comprimido en línea) para procesamiento paralelo. Se activa solo si la consulta tiene la compresión habilitada Y rows > 1; de lo contrario, se aplica el formato wire normal de columna. Los clients que nunca habilitan la compresión en paquetes Query salientes no ven ningún cambio en wire.
VERSIONED_CLUSTER_FUNCTION_PROTOCOL54479ServerHelloAñade VarUInt cluster_function_protocol_version al final de ServerHello. Se usa para funciones de tabla *Cluster (s3Cluster, etc.). Los clients externos lo decodifican y lo ignoran.
OUT_OF_ORDER_BUCKETS_IN_AGGREGATION54480BlockInfoAñade el campo 3 (out_of_order_buckets: Vec<Int32>) al flujo etiquetado por campos de BlockInfo. Se decodifica como [VarUInt count][Int32]*count. Los clients externos no emiten esto por sí mismos; el decodificador lee cualquier lista no vacía que envíe el server.
COMPRESSED_LOGS_PROFILE_EVENTS_COLUMNS54481Log, ProfileEvents, TableColumnsEl server puede encapsular los cuerpos de los paquetes Log, ProfileEvents y TableColumns en el compression frame. En esta versión, los tres cuerpos recorren la misma ruta de salida opcionalmente comprimida, que se convierte en un compression frame real solo cuando la consulta tiene compression = true. Los clients que nunca habilitan la compresión en paquetes Query salientes no ven ningún cambio en wire.
REPLICATED_SERIALIZATION54482Block (Column)El server puede emitir columnas con kind_stack 0x04 = REPLICATED: una forma compacta de estilo diccionario para valores repetidos; consulta kind_stack and sparse encoding. Por debajo de esta versión, el escritor expandía esas columnas antes de enviarlas. Se decodifica mediante búsqueda por índice (elements[indexes[i]] por fila); admite tipos hoja, además de internos Nullable/Array/Tuple/Map/Nested/LowCardinality.
NULLABLE_SPARSE_SERIALIZATION54483Block (Column)Combina la serialización dispersa con Nullable(T). Por debajo de esta versión, el escritor expandía la representación dispersa de columnas Nullable antes de enviarlas; en v54483+ los datos wire son sparse-over-Nullable. Consulta kind_stack and sparse encoding.
PROGRESS_IN_ASYNC_INSERT54484Progress (INSERT)En un INSERT asíncrono (async_insert = 1), una vez volcado el insert, el server envía un paquete Progress adicional y luego los ProfileEvents del insert, antes de EndOfStream. Depende de que la versión negociada sea ≥ 54484; por debajo de eso, el server omite este Progress final. El formato wire de Progress no cambia; la novedad está solo en su emisión. En la práctica, el incremento transporta el tiempo transcurrido; los contadores de filas escritas se informan mediante los ProfileEvents adjuntos. Un client que ya consume Progress entrelazados no necesita cambios de formato, solo tolerar un paquete más.
CLIENT_AGENT_IN_CLIENT_INFO54485ClientInfoAñade un String client_agent al final de ClientInfo. El client canónico detecta automáticamente un identificador de agent a partir de su entorno (por ejemplo, claude-code, cursor, gemini-cli o el valor de la variable AGENT); un client externo sin nada detectado envía una cadena vacía. Es obligatorio una vez que la versión negociada sea ≥ 54485; omitirlo desincroniza el resto del paquete Query.

Encapsulado del paquete

Todos los mensajes transmitidos comparten la misma estructura externa en ambos sentidos:
[VarUInt: packet_type_code]    always encoded as VarUInt
[message body]                 format depends on packet_type_code
Las tablas completas de los tipos de paquetes están en la referencia de tipos de paquetes. El tipo de paquete es un VarUInt, no un byte de ancho fijo. Para valores inferiores a 128, un VarUInt produce el mismo byte, pero las implementaciones deben usar la codificación VarUInt para seguir siendo compatibles si en el futuro los tipos de paquetes llegan a 128 o más. La referencia de mensajes documenta solo el cuerpo de cada paquete: los bytes que vienen después del código de tipo de paquete. La numeración de los campos comienza en 1 con el primer campo del cuerpo.

Enmarcado por fragmentos (v54470+)

Cuando se negocia la funcionalidad CHUNKED_PROTOCOL (ver el handshake), cada paquete en el wire se encapsula con enmarcado por fragmentos. Este encapsulado es por dirección: Client→servidor y servidor→Client se negocian por separado y pueden quedar en modos distintos (por fragmentos o sin enmarcar). Formato en el wire de cada paquete:
<chunk>...   one or more chunks; their payloads concatenated form the whole packet
[u32 LE = 0] zero-size terminator marking end of packet
Formato en el wire por fragmento:
[u32 LE: chunk_size]   chunk_size in [1, UINT32_MAX]
[chunk_size bytes]     packet bytes (see note below)
El tipo de paquete VarUInt está dentro del flujo fragmentado: es el primer byte de la carga útil del paquete (el primer byte del primer fragmento), no un byte separado enviado por delante del enmarcado. La carga útil fragmentada de cada paquete es el [VarUInt packet_type_code][message body] completo de la envoltura del paquete. Un Client que deja el tipo de paquete fuera del flujo fragmentado hace que el par lea ese byte de tipo como el primer byte del tamaño del fragmento u32, desincronizando la conexión. Un solo paquete puede dividirse en varios fragmentos si el búfer del escritor se llena a mitad del paquete; la división puede caer en cualquier punto, incluso dentro del VarUInt del tipo de paquete. El lector concatena las cargas útiles de los fragmentos y trata el cero final de 4 bytes como un límite de paquete transparente: lo consume, pero no se lo expone a quien esté leyendo los cuerpos de los paquetes. Los paquetes sin cuerpo siguen yendo encapsulados: un paquete de un solo byte como Ping o Pong se convierte en [u32 size = 1][0x04][u32 0] una vez que se negocia la fragmentación. Cualquier descripción de “un solo byte en el wire” en otra parte de esta página corresponde a la forma previa a la fragmentación. Negociación. ServerHello y Addendum llevan cada uno dos campos String, uno por dirección, con valores tomados de {"chunked", "notchunked", "chunked_optional", "notchunked_optional"}:
  • chunked / notchunked son estrictos: ese lado requiere exactamente ese modo.
  • Las variantes _optional son flexibles: aceptan el modo que elija el otro lado.
El valor acordado para cada dirección se calcula por pares:
Preferencia del servidorPreferencia del ClientAcordado
*_optionalcualquieraseguir al CLIENT (su starts_with("chunked"))
cualquiera*_optionalseguir al SERVER
chunked estrictochunked estrictochunked
notchunked estrictonotchunked estrictonotchunked
desacuerdo estrictodesacuerdo estrictoerror de protocolo — la conexión DEBE cerrarse
En el lado del Client, la preferencia de ENVÍO del Client se negocia frente a la preferencia de RECEPCIÓN del servidor, y viceversa. Temporización. Las cadenas de negociación viajan por el wire sin enmarcado: ClientHello → ServerHello (preferencias del servidor) → Addendum (valores negociados del Client). El cambio al enmarcado se aplica a cada byte enviado después de que se vacíe el Addendum. El propio Addendum, ClientHello y ServerHello siempre van sin enmarcado.

Ciclo de vida de la conexión

En cualquier momento, una conexión está exactamente en uno de cuatro estados: HANDSHAKE, READY, READING_RESPONSE o terminada. Como el protocolo no multiplexa, un Client que envía una nueva solicitud antes de terminar de leer la respuesta anterior intercala bytes en tránsito y corrompe el flujo.

Estados

El flujo nominal sigue una línea recta — HANDSHAKE → READY → READING_RESPONSE → READY — con el bucle de Ping/Pong y todas las transiciones de error convergiendo en el único estado terminal Terminated.
EstadoDescripción
HANDSHAKEEstado inicial después de que se abre la conexión TCP. Solo son válidos los mensajes de handshake. Pasa a READY si se completa correctamente o termina si falla.
READYInactivo. El Client puede enviar Ping, consulta o cerrar la conexión. La conexión puede permanecer en READY indefinidamente (sujeta a idle_connection_timeout; consulte los límites de conexión).
READING_RESPONSESe entra en este estado cuando el Client envía una consulta. El Client debe consumir por completo el flujo de respuesta del servidor antes de volver a READY. El único paquete Client→servidor permitido aquí es Cancel (no se especifica en esta página).
TerminatedYa no se puede usar. El Client debe abrir una nueva conexión TCP y reiniciar el handshake.

Fase de handshake

En ella se autentica y se negocia la versión del protocolo. Ocurre exactamente una vez por conexión, antes de cualquier otra cosa. La conexión TCP acaba de establecerse y aún no se ha intercambiado ningún mensaje. El flujo:
  1. El Client envía ClientHello con la versión máxima del protocolo que admite.
  2. El Client lee la respuesta y actúa según el tipo de paquete:
    Tipo de paqueteAcción
    Hello (0)Decodifica ServerHello. Calcula negotiated_version = min(client_ver, server_ver). Continúa con el paso 3.
    Exception (2)Decodifica Exception. Lo devuelve como error y termina la conexión.
    cualquier otroViolación del protocolo. Termina la conexión.
  3. Si negotiated_version ≥ 54458 (la funcionalidad ADDENDUM), el Client envía un Addendum. Esta decisión se basa en la versión negociada, no en la versión declarada del Client.
Si la operación tiene éxito, la conexión pasa a READY; ante cualquier error, se termina.

Fase de Ping

Una comprobación de liveness a nivel de aplicación, independiente del keepalive de TCP. Un intercambio de Ping/Pong completado con éxito confirma que la conexión TCP está activa en ambas direcciones y que el servidor responde. Ping es sin estado y no está correlacionado con ninguna consulta, por lo que varios Pings secuenciales son independientes. Partiendo de READY, el flujo es:
  1. El Client envía Ping.
  2. El Client lee la respuesta:
    Tipo de paqueteAcción
    Pong (4)Se confirma que sigue activo. Volver a READY.
    Exception (2)Decodificar Exception y devolverla como error.
    cualquier otro valorViolación del protocolo.

Fase de consulta

El Client envía una sentencia SQL; el servidor devuelve en streaming los bloques de resultados y la telemetría de ejecución. La respuesta es una secuencia de paquetes terminada por un único EndOfStream o una Exception. Partiendo de READY, el flujo es: Ante cualquier error, el servidor envía una Exception en lugar de EndOfStream, lo que termina la consulta.
  1. El Client envía Query con un query_id único (normalmente, un UUID).
  2. El Client envía cualquier tabla externa y luego el marcador Data vacío. El paquete Data vacío tiene table_name = "", num_columns = 0, num_rows = 0. El servidor no empieza a ejecutar la consulta hasta que recibe este marcador.
  3. El Client pasa a READING_RESPONSE y vacía su búfer de escritura.
  4. El Client lee los paquetes de respuesta en un bucle y los procesa según su tipo:
    Tipo de paqueteAcción
    Data (1)Decodifica el bloque. El primer Data es la cabecera del esquema; los posteriores son bloques de resultados (acumúlalos); un bloque vacío es un marcador de límite. num_rows == 0 no indica el fin de la consulta.
    Progress (3)Métricas de ejecución. Cada paquete es un incremento con respecto al anterior; acumúlalo localmente.
    EndOfStream (5)Consulta completada. Sal del bucle y vuelve a READY.
    ProfileInfo (6)Datos de profiling posteriores a la ejecución.
    Totals (7)Bloque de totales de aggregation (mismo wire format que Data).
    Extremes (8)Bloque de valores mínimos/máximos (mismo wire format que Data).
    Log (10)Línea del server log.
    TableColumns (11)Metadatos de valores predeterminados de columnas.
    ProfileEvents (14)Counters de rendimiento.
    Exception (2)Decodifica y devuelve como error. Sal del bucle y vuelve a READY.
    anything elseInesperado durante la fase de consulta. Termina la conexión.
Con EndOfStream o una Exception controlada, la conexión vuelve a READY. Una infracción del protocolo o un error de I/O la termina.
El caso num_rows == 0 suele confundir a las implementaciones nuevas. Un bloque de cero filas es un marcador de límite o una cabecera de esquema, no una señal de fin de stream. Solo EndOfStream o Exception pone fin a la respuesta.

Fase INSERT

La fase INSERT es la fase de consulta con dos intercambios adicionales. El Client envía una instrucción INSERT; el servidor responde con un bloque de esquema que describe la tabla de destino; el Client transmite paquetes Data con las filas y, después, el marcador Data vacío; el servidor finaliza con EndOfStream o Exception. Partiendo de READY, el SQL es un INSERT con la forma INSERT INTO <table> [(<cols>)] VALUES — sin ningún literal VALUES (...) en línea, ya que los datos de las filas fluyen a través de paquetes Data. El flujo:
  1. El Client envía Query con body establecido en el SQL de INSERT.
  2. El Client envía cualquier tabla externa (algo poco habitual en INSERT). A diferencia de la fase consulta, no envía aquí un marcador Data vacío. El paquete consulta de INSERT se envía con datos pendientes, por lo que el bloque vacío de fin de datos se difiere hasta el paso 5; enviarlo antes del bloque de esquema haría que el servidor lo interpretara como el final del flujo de filas, completara el INSERT sin filas y luego analizara el primer paquete de fila real como un paquete suelto de nivel superior.
  3. El Client consume los paquetes de metadatos (TableColumns, Progress, ProfileInfo, Log, ProfileEvents) hasta leer el paquete Data del esquema: un Block con 0 filas, pero con la estructura completa de columnas (nombres y tipos). El bloque de esquema es el contrato: las filas que el Client envíe a continuación deben coincidir con estas estructuras de columna.
  4. El Client envía uno o varios bloques de datos. Para cada bloque, escribe VarUInt(ClientPacket::Data = 2), luego String("") para el nombre vacío de la tabla externa y, después, el Block. Los tipos de columna deben alinearse por posición con las columnas del bloque de esquema.
  5. El Client envía el terminador de fin de entrada: un paquete Data con un Block vacío (0 columnas, 0 filas).
  6. El Client consume el flujo de respuesta hasta EndOfStream (éxito) o Exception (fallo).
INSERT asíncrono (v54484+). Cuando la consulta lleva async_insert = 1, el servidor pone las filas en cola y las vacía como parte de un lote. Con una versión negociada ≥ 54484 (PROGRESS_IN_ASYNC_INSERT), una vez completado el vaciado, el servidor emite un paquete adicional de Progress, seguido inmediatamente por los ProfileEvents del insert y luego EndOfStream. Por debajo de 54484, el servidor omite ese Progress final. El paquete es un Progress normal; como el servidor restablece el query pipeline antes de incorporar los recuentos de escritura, en la práctica el incremento solo contiene el tiempo transcurrido, y las estadísticas de filas y bytes escritos llegan al Client mediante los ProfileEvents correspondientes. Un Client que ya consume paquetes Progress entrelazados en el paso 6 solo necesita aceptar uno más. La conexión vuelve a READY en EndOfStream o tras una Exception controlada. Las infracciones del protocolo y los errores de I/O la terminan.

Referencia de mensajes

Los campos se enumeran en el orden del wire. La columna Type usa:
  • VarUInt — entero sin signo de longitud variable (consulta VarUInt).
  • String — bytes con prefijo VarUInt (consulta String).
  • UInt8, Int32, etcétera — enteros little-endian de ancho fijo.
  • Bool — un único byte, 0x00 o 0x01.
La columna Role indica quién usa cada campo:
  • Client — lo establecen los clientes externos.
  • inter-server — solo tiene sentido para la comunicación entre servidores; los clientes externos escriben un valor predeterminado.
  • universal — lo usan ambos.
Estas tablas documentan solo el body de cada paquete, después del código de tipo de paquete.

ClientHello (tipo de paquete 0)

Client → servidor. El primer mensaje tras abrirse la conexión TCP.
#CampoTipoRolDescripción
1client_nameStringuniversalIdentificador del client (p. ej., "clickhouse-client")
2version_majorVarUIntuniversalVersión principal del client
3version_minorVarUIntuniversalVersión secundaria del client
4protocol_versionVarUIntuniversalVersión máxima del protocolo que admite el client
5databaseStringuniversalNombre de la base de datos predeterminada
6userStringuniversalNombre de usuario para la autenticación
7passwordStringuniversalContraseña (en texto no cifrado)

ServerHello (tipo de paquete 0)

Servidor → Client. La respuesta a ClientHello tras una autenticación correcta.
#CampoTipoRolCondiciónDescripción
1server_nameStringuniversalalwaysIdentificador del servidor
2version_majorVarUIntuniversalalwaysVersión principal del servidor
3version_minorVarUIntuniversalalwaysVersión secundaria del servidor
4protocol_versionVarUIntuniversalalwaysVersión del protocolo del servidor
4aparallel_replicas_protocol_versionVarUIntuniversalVERSIONED_PARALLEL_REPLICAS_PROTOCOL (v54471)Versión del protocolo de coordinación de réplicas paralelas del servidor. Posición en el wire: inmediatamente después de protocol_version, antes de timezone. Actual: 7.
5timezoneStringuniversalTIMEZONE (v54058)Zona horaria del servidor (p. ej., "UTC")
6display_nameStringuniversalDISPLAY_NAME (v54372)Nombre del servidor legible para humanos
7version_patchVarUIntuniversalVERSION_PATCH (v54401)Versión de parche del servidor
8proto_send_chunked_srvStringuniversalCHUNKED_PROTOCOL (v54470)Fragmentación saliente preferida del servidor. Uno de "chunked", "notchunked", "chunked_optional", "notchunked_optional". Consulte enmarcado por fragmentos. Va ANTES de password_complexity_rules en el wire aunque su versión de activación sea posterior.
9proto_recv_chunked_srvStringuniversalCHUNKED_PROTOCOL (v54470)Fragmentación entrante preferida del servidor. Mismo conjunto de valores que el campo 8.
10password_complexity_rulesRule[]universalPASSWORD_COMPLEXITY_RULES (v54461)Política de contraseñas del servidor. VarUInt count seguido de count × Rule. Véase más abajo.
11nonceUInt64inter-serverINTERSERVER_SECRET_V2 (v54462)nonce aleatorio LE de 8 bytes. Lo usa el esquema de firma de consultas interservidor del servidor. Los Clients externos DEBEN decodificarlo (para mantener alineado el stream) y DEBERÍAN ignorar el valor.
12server_settingsSetting[]universalSERVER_SETTINGS (v54474)Difusión de settings no predeterminados del servidor. Formato: cero o más ternas (String key, VarUInt flags, String value), terminadas por una key vacía. Igual que la lista de settings del paquete Query.
13query_plan_serialization_versionVarUIntuniversalQUERY_PLAN_SERIALIZATION (v54477)Versión de serialización del plan de consulta compatible con el servidor. Los Clients externos la decodifican y la ignoran.
14cluster_function_protocol_versionVarUIntuniversalVERSIONED_CLUSTER_FUNCTION_PROTOCOL (v54479)Versión del protocolo de la función de tabla *Cluster del servidor. Los Clients externos la decodifican y la ignoran.
Rule — un elemento de password_complexity_rules:
#CampoTipoDescripción
1patternStringPatrón de expresión regular que debe cumplir una contraseña válida.
2messageStringExplicación legible para humanos que se muestra cuando una contraseña no cumple esta regla.
La lista refleja la configuración de la política de contraseñas del operador del servidor y es puramente informativa: el servidor no aplica estas reglas durante el handshake. Un Client que exponga funcionalidad para cambiar o establecer contraseñas puede usar las reglas para señalar errores antes de enviar al servidor una contraseña no válida.
Para limitar el uso de recursos frente a un servidor hostil o mal configurado, limite el count decodificado a 256 entradas y cada String pattern y message a 4096 bytes. Un count de 0 (sin pares posteriores) es el caso habitual en servidores sin ninguna política de contraseñas configurada.

Apéndice (sin tipo de paquete)

Client → Server, controlado por ADDENDUM (v54458). Se envía inmediatamente después de que se completa el intercambio de handshake. No es un tipo de paquete distinto: los campos van por el wire en bruto, sin prefijo de byte de tipo de paquete.
#FieldTypeRoleConditionDescription
1quota_keyStringuniversalsiempreClave de cuota de recurso para QUOTA con clave del lado del server. Los Clients que no usan una quota con clave envían una cadena vacía.
2proto_send_chunkedStringuniversalCHUNKED_PROTOCOL (v54470)Fragmentación saliente negociada del Client: "chunked" o "notchunked". Se calcula con respecto a proto_recv_chunked_srv de ServerHello.
3proto_recv_chunkedStringuniversalCHUNKED_PROTOCOL (v54470)Fragmentación entrante negociada del Client. Se calcula con respecto a proto_send_chunked_srv.
4parallel_replicas_protocol_versionVarUIntuniversalVERSIONED_PARALLEL_REPLICAS_PROTOCOL (v54471)Versión del protocolo de coordinación de réplicas paralelas admitida por el Client. Los Clients externos que no participan en consultas distribuidas DEBERÍAN seguir enviando una versión válida (la actual, 7) para que la comprobación de compatibilidad del server se complete correctamente.
El cambio al enmarcado por fragmentos se aplica después de que este Apéndice se haya enviado por completo; el propio Apéndice no lleva enmarcado.

Ping (tipo de paquete 4)

Client → Server. Sin cuerpo: el paquete consta de un único byte 0x04 antes del enmarcado por fragmentos; cuando se negocia la fragmentación, el byte pasa a ser el payload de un byte de un fragmento (consulte enmarcado por fragmentos).

Pong (tipo de paquete 4)

Servidor → client. Sin body: el paquete es un solo byte 0x04 antes del enmarcado por fragmentos; cuando se negocia la fragmentación, el byte pasa a ser el payload de un byte de un fragmento (consulta el enmarcado por fragmentos).

Exception (tipo de paquete 2)

Servidor → client. Se envía cuando el servidor detecta un error en cualquier fase.
#CampoTipoRolDescripción
1codeInt32universalCódigo de error
2nameStringuniversalClase de excepción (p. ej., "DB::Exception")
3messageStringuniversalMensaje de error legible para humanos
4stack_traceStringuniversalStack trace del servidor
5has_nested (obsoleto)BooluniversalByte de compatibilidad obsoleto. El server siempre lo escribe como false

Consulta (tipo de paquete 1)

Cliente → servidor.
#CampoTipoRolCondiciónDescripción
1query_idStringuniversalsiempreIdentificador único de consulta (UUID)
2client_infoClientInfouniversalCLIENT_INFO (v54032)Consulte ClientInfo
3settingsSetting[]universalsiempreConsulte Setting. Siempre presente (terminado por una clave vacía); solo la codificación de cada configuración depende de la versión; consulte la nota sobre codificación en Setting. Un cliente no debe omitir este campo en versiones negociadas inferiores a 54429.
3aexternal_rolesStringuniversalINTERSERVER_EXTERNALLY_GRANTED_ROLES (v54472)Lista serializada de nombres de roles otorgados externamente. Lista vacía = byte 0x00 (VarUInt 0) encapsulado en una cadena String ([VarUInt 1][0x00] en el wire). Los clientes externos siempre envían una lista vacía.
4auth_hashStringentre servidoresINTERSERVER_SECRET (v54441)Hash de autenticación entre servidores; no es el secret sin procesar del cluster. Consulte Inter-server authentication más abajo. Los clientes externos (y cualquier InitialQuery) envían una cadena vacía.
5stageVarUIntuniversalsiempreEtapa de procesamiento de la consulta. 0 = FetchColumns, 1 = WithMergeableState, 2 = Complete, 3 = WithMergeableStateAfterAggregation, 4 = WithMergeableStateAfterAggregationAndLimit, 7 = QueryPlan. Los valores 3/4 aparecen en consultas distribuidas; 7 acompaña a un plan de consulta serializado. Los clientes externos normalmente envían 2.
6compressionVarUIntuniversalsiempre0 = deshabilitado, 1 = habilitado
7query_bodyStringuniversalsiempreTexto SQL
8parametersParameter[]clientPARAMETERS (v54459)Consulte Parameter. Terminado por una clave vacía.

ClientInfo (incluido en consulta)

Client → Server, incluido en el cuerpo de consulta (campo 2). Condicionado por CLIENT_INFO (v54032). (Algunos campos dentro de ClientInfo están condicionados por versiones posteriores, como se indica más abajo en cada campo.)
#CampoTipoRolCondiciónDescripción
1query_kindUInt8universalsiempre0 = NoQuery, 1 = InitialQuery, 2 = SecondaryQuery. Los clientes externos envían 1.
2initial_userStringuniversalsiempreUsuario que inició la consulta
3initial_query_idStringuniversalsiempreID de la consulta original
4initial_addressStringuniversalsiempreDirección del socket del cliente de origen en formato host:port
5initial_timeInt64ClientINITIAL_QUERY_START_TIME (v54449)Hora de inicio de la consulta (microsegundos). Ancho fijo de 8 bytes, no VarUInt
6query_interfaceUInt8universalsiempre1 = TCP, 2 = HTTP
7os_userStringClientsi interface = TCPusername del SO
8client_hostnameStringClientsi interface = TCPhostname de la máquina cliente
9client_nameStringClientsi interface = TCPNombre de la aplicación cliente
10version_majorVarUIntuniversalsi interface = TCPVersión principal del Client
11version_minorVarUIntuniversalsi interface = TCPVersión secundaria del Client
12protocol_versionVarUIntuniversalsi interface = TCPLa propia versión del protocolo TCP del cliente de origen (DBMS_TCP_PROTOCOL_VERSION), no la versión negociada. La revisión del peer solo determina qué campos están presentes; este valor es la versión compilada en el initiator, por lo que, en un cliente más nuevo que se comunica con un servidor más antiguo, puede ser superior a la revisión negociada o a la del servidor.
13quota_keyStringuniversalQUOTA_KEY_IN_CLIENT_INFO (v54060)Clave de cuota de recursos para quotas con clave del lado del servidor. Los clientes que no usan una quota con clave envían una cadena vacía.
14distributed_depthVarUIntentre servidoresDISTRIBUTED_DEPTH (v54448)Profundidad de anidamiento de la consulta Distributed. Los clientes externos envían 0.
15version_patchVarUIntuniversalVERSION_PATCH (v54401), solo TCPVersión de parche del cliente
16open_telemetry(abajo)ClientOPEN_TELEMETRY (v54442)Contexto de trace. Los clientes sin tracing envían 0.
17collaborate_with_initiatorVarUIntentre servidoresPARALLEL_REPLICAS (v54453)Bool como VarUInt. Los clientes externos envían 0.
18count_participating_replicasVarUIntentre servidoresPARALLEL_REPLICAS (v54453)Los clientes externos envían 0.
19number_of_current_replicaVarUIntentre servidoresPARALLEL_REPLICAS (v54453)Los clientes externos envían 0.
20script_query_numberVarUIntClientQUERY_AND_LINE_NUMBERS (v54475)Posición, con índice base 1, de la instrucción en un script con múltiples instrucciones. Los clientes externos envían 0.
21script_line_numberVarUIntClientQUERY_AND_LINE_NUMBERS (v54475)Número de línea, con índice base 1, dentro del script fuente. Los clientes externos envían 0.
22jwt_presentUInt8entre servidoresJWT_IN_INTERSERVER (v54476)0 = sin JWT; 1 = a continuación se incluye un JWT. Los clientes externos sin autenticación JWT envían 0.
23jwtStringentre servidoresJWT_IN_INTERSERVER (v54476), if jwt_present=1token Bearer JWT, solo presente cuando el campo 22 = 1.
24client_agentStringClientCLIENT_AGENT_IN_CLIENT_INFO (v54485)Campo final. Identificador de la herramienta o agente cliente, detectado automáticamente a partir del entorno (p. ej., claude-code, cursor, gemini-cli o la variable de entorno AGENT). Los clientes externos sin ningún agente detectado envían una cadena vacía. Está presente en la ruta normal de Query una vez que la versión negociada es ≥ 54485 (se envía en todas las interfaces, no solo TCP).
Layout dependiente de la interfaz (campos 7–12)Los campos 7–12 anteriores corresponden a la rama TCP. Cuando query_interface (campo 6) no es TCP, estos campos se sustituyen por un layout wire diferente; no son simples omisiones opcionales, por lo que un decodificador debe bifurcarse en función del campo 6.
  • query_interface = 2 (HTTP): en su lugar, se escribe la información de la solicitud HTTP reenviada por el servidor: http_method (UInt8), http_user_agent (String), y después forwarded_for (String, condicionado por X_FORWARDED_FOR_IN_CLIENT_INFO v54443) y http_referer (String, condicionado por REFERER_IN_CLIENT_INFO v54447). No están presentes los campos os_user/client_hostname/client_name/version_*/protocol_version.
  • Cualquier otra interfaz: no se escribe ninguno de los campos TCP (7–12) ni ninguno de los campos HTTP; el flujo continúa directamente con quota_key.
Después de esta rama, el layout vuelve a unirse: quota_key (campo 13) y distributed_depth (campo 14) aparecen en todas las interfaces, y luego version_patch (campo 15) se escribe solo para TCP.Esta rama importa principalmente para el tráfico entre servidores, donde el servidor que inicia reenvía una consulta que originalmente llegó por HTTP. Un decodificador que siempre lea los campos TCP interpretará mal esos paquetes, tratando http_method o http_user_agent como quota_key.
Codificación de OpenTelemetry (campo 16):
[UInt8: has_trace]              0 = no trace data follows, 1 = trace data follows
If has_trace == 1:
  [16 bytes: trace_id]          byte-swapped per-8-bytes
  [8 bytes:  span_id]           byte-swapped
  [String:   trace_state]       W3C trace state
  [UInt8:    trace_flags]       W3C trace flags

Autenticación entre servidores

El campo 4 de consulta (auth_hash) no es el secreto compartido del clúster en la representación wire. Enviar el secreto en bruto haría que la autenticación fallara y además lo expondría. En su lugar, un servidor que actúa como Client entre servidores demuestra que conoce el secreto con un hash SHA-256 con salt:
  1. Entrar en modo entre servidores. El servidor que se conecta lo indica dentro de ClientHello: el campo user es el marcador entre servidores y password está vacío. A continuación, añade dos strings más —el nombre del clúster y una salt de 32 bytes recién generada (encodeSHA256 de un valor aleatorio)— inmediatamente después de los campos user/password, como parte del mismo paquete ClientHello. El servidor lee estas dos strings antes de enviar ServerHello, por lo que un Client debe escribirlas de antemano; esperar primero a ServerHello provoca interbloqueos, porque el servidor queda bloqueado leyéndolas.
  2. Obtener el nonce. ServerHello incluye un nonce UInt64 de 8 bytes cuando se negocia INTERSERVER_SECRET_V2 (v54462).
  3. Calcular el hash. Para cada paquete consulta que no sea InitialQuery, el Client escribe encodeSHA256(salt + nonce + cluster_secret + query + query_id + initial_user + external_roles) en el campo 4: un digest de 32 bytes. (nonce va en su forma de string decimal, presente solo cuando se negocia ≥ v54462; external_roles se añade solo cuando se negocia INTERSERVER_EXTERNALLY_GRANTED_ROLES (v54472).) Para un InitialQuery, o cuando no hay ningún secreto de clúster configurado, el Client escribe en su lugar un string vacío.
  4. Verificar. El servidor lee el campo 4 con un límite de 32 bytes y vuelve a calcular la misma concatenación usando su propia copia del secreto del clúster; la conexión se rechaza si los digests difieren.
Los clientes externos (no entre servidores) nunca entran en este modo y siempre envían un auth_hash vacío.

Configuración

Codificada en línea en la lista de configuraciones del cuerpo de Query (el paquete Query, campo 3). La lista siempre está presente, independientemente de la versión negociada, y termina con una SETTING con key vacía: un único VarUInt 0, sin indicadores ni valor a continuación. Solo la codificación de cada configuración depende de la versión negociada, controlada por SETTINGS_SERIALIZED_AS_STRINGS (v54429). v54429+ (STRINGS_WITH_FLAGS) — cada configuración es el triplete que se muestra aquí:
#CampoTipoRolDescripción
1keyStringuniversalNombre de la configuración. Vacío = fin de la lista.
2flagsVarUIntuniversalIndicadores de bits de metadatos; véase más abajo.
3valueStringuniversalValor de la configuración como cadena
Los campos 2 y 3 no están presentes cuando key está vacía. Pre-54429 (BINARY) — cada configuración es [String key][type-specific binary value]: el campo flags no se escribe, y el valor se codifica en la forma binaria nativa de la configuración (por ejemplo, un entero de ancho fijo o una cadena con prefijo de longitud) en lugar de como una cadena decimal o de texto. La lista sigue terminando con una key vacía. Un client que apunte a una versión negociada inferior a 54429 debe leer y escribir esta forma binaria, no el triplete anterior. (Las configuraciones personalizadas definidas por el usuario son la excepción: siempre llevan flags y un valor de cadena, en ambas codificaciones). El campo flags contiene:
  • 0x01Importante: la configuración afecta a los resultados de la consulta y no debe ser ignorada silenciosamente por pares más antiguos.
  • 0x02Personalizada: una configuración personalizada definida por el usuario.
  • 0x0c — un campo tier de 2 bits, no un indicador independiente: 0x00 = Production, 0x04 = Obsolete, 0x08 = Experimental, 0x0c = Beta. Lea los 2 bits completos (flags & 0x0c) — una comprobación ingenua de flags & 0x04 clasificaría erróneamente Beta (0x0c) como Obsolete.
  • 0x80HotReload (recarga de configuración sin reinicio; definido en el enum de indicadores, presente principalmente en configuraciones de coordinación).

Parámetro

Parámetros de consulta para consultas parametrizadas como SELECT {x:UInt64}. Se codifican de forma idéntica a un SETTING con el indicador Custom (0x02) activado, y terminan con una clave vacía de la misma forma.
#CampoTipoRolDescripción
1keyStringclientNombre del parámetro. Vacío = fin de la lista.
2flagsVarUIntclientSiempre 0x02 (Custom)
3valueStringclientValor del parámetro como cadena. Consulta la nota siguiente sobre las comillas.
El valor del parámetro es la representación SQL del valor, no un literal sin procesar. Los parámetros de tipo cadena deben pasarse ya entre comillas simples (por ejemplo, el valor de {name:String} es 'Alice', no Alice); de lo contrario, el analizador de valores del servidor los rechazará.

Data (tipo de paquete 1 servidor→client, tipo de paquete 2 client→servidor)

En ambas direcciones. Transporta bloques de resultados, datos de INSERT, tablas externas y marcadores de fin de datos. El wire format es simétrico: ambas direcciones incluyen un prefijo table_name antes del Block. Solo cambia el byte del tipo de paquete.
[VarUInt: packet_type]     1 (server→client) or 2 (client→server)
[String:  table_name]      External table name; empty in most cases
[Block]                    See the Native Format spec for the Block layout
CampoTipoRolDescripción
table_nameStringuniversalNombre de la tabla externa. Vacío ("") es lo habitual: para la tabla principal, los resultados de la consulta y el flujo de filas de INSERT. table_name vacío por sí solo no es el marcador de fin de datos (los paquetes normales de filas de INSERT también llevan "").
Cuerpo del bloqueVéase Block & column structure.
El marcador de fin de datos es un paquete cuyo Block está vacío: 0 columnas y 0 filas, independientemente de table_name. El servidor trata un paquete Data del client como terminador solo cuando el bloque decodificado está vacío (block.empty()); un paquete con table_name = "" y un bloque no vacío es un paquete de filas normal, no un terminador. Por tanto, un flujo de filas de INSERT es una secuencia de bloques Data no vacíos seguida de un bloque Data vacío que lo finaliza. Las variantes de bloque y su significado se documentan en Block variants.

Progress (tipo de paquete 3)

Servidor → Cliente. Se envía periódicamente durante la ejecución de la consulta. Todos los campos son VarUInt, y cada paquete contiene incrementos desde el paquete Progress anterior, no totales acumulados. Antes de enviarlo, el server lee sus contadores, los restablece atómicamente a cero y calcula elapsed_ns como la diferencia de tiempo desde el último envío. Por lo tanto, un client debe acumular localmente los paquetes sucesivos para obtener totales acumulados; tratar un paquete como un valor absoluto hace que la visualización del progreso retroceda o cuente de menos cuando llega más de un paquete.
#CampoTipoRolCondiciónDescripción
1rowsVarUIntuniversalsiempreFilas leídas desde el paquete anterior (sumar al total acumulado)
2bytesVarUIntuniversalsiempreBytes leídos desde el paquete anterior (sumar al total acumulado)
3total_rowsVarUIntuniversalsiempreIncremento del total estimado de filas por leer; acumular (puede ser 0 en un paquete determinado)
4total_bytesVarUIntuniversalTOTAL_BYTES_IN_PROGRESS (v54463)Incremento del total estimado de bytes por leer; acumular. Se sitúa ENTRE total_rows y wrote_rows en el wire.
5wrote_rowsVarUIntuniversalWRITE_CLIENT_INFO (v54420)Filas escritas desde el paquete anterior (para INSERT); acumular
6wrote_bytesVarUIntuniversalWRITE_CLIENT_INFO (v54420)Bytes escritos desde el paquete anterior (para INSERT); acumular
7elapsed_nsVarUIntuniversalSERVER_QUERY_TIME_IN_PROGRESS (v54460)Nanosegundos transcurridos desde el paquete anterior (una diferencia, no el tiempo total de la consulta); acumular

ProfileInfo (tipo de paquete 6)

Servidor → client. Se envía una vez por consulta, hacia el final de la ejecución.
#CampoTipoRolCondiciónDescripción
1rowsVarUIntuniversalsiempreTotal de filas procesadas
2blocksVarUIntuniversalsiempreTotal de bloques procesados
3bytesVarUIntuniversalsiempreTotal de bytes procesados
4applied_limitBooluniversalsiempreIndica si se aplicó una cláusula LIMIT
5rows_before_limitVarUIntuniversalsiempreRecuento de filas antes de LIMIT
6obsoleteBooluniversalsiempreByte de compatibilidad obsoleto. El servidor siempre escribe true aquí y el client lo descarta al leerlo; no es un indicador de que se haya calculado “rows_before_limit”. El estado significativo del límite es el campo 4 (applied_limit) junto con el campo 5. Léalo e ignórelo.
7applied_aggregationBooluniversalROWS_BEFORE_AGGREGATION (v54469)Indica si se aplicó GROUP BY
8rows_before_aggregationVarUIntuniversalROWS_BEFORE_AGGREGATION (v54469)Recuento de filas antes de la agregación

Totales (tipo de paquete 7)

Servidor → client. Se envía para consultas con WITH TOTALS. El formato de transmisión es idéntico a Data: una cadena table_name (siempre vacía) seguida de un Block. Solo cambia el byte del tipo de paquete.
[VarUInt: 7]                packet type
[String:  table_name]       always empty
[Block]                     see the Native Format spec

Extremes (tipo de paquete 8)

Servidor → client. Se envía cuando la configuración extremes está habilitada. El formato de transmisión es idéntico a Data. El bloque tiene exactamente 2 filas: la fila 0 contiene el mínimo de cada columna y la fila 1, el máximo.
[VarUInt: 8]                packet type
[String:  table_name]       always empty
[Block]                     num_rows = 2

Log (packet type 10)

Servidor → Cliente. Se envía cuando la consulta tiene una cola de logs activa (la configuración send_logs_level; consulte streaming de logs). El formato del sobre y del cuerpo es el mismo que el de Data. El bloque tiene un num_columns = 8 fijo y un esquema predefinido. Cada línea de log ocupa una fila en las 8 columnas, y un único paquete Log puede contener muchas filas.
[VarUInt: 10]               packet type
[String:  table_name]       always empty
[Block]                     num_columns = 8, num_rows = number of log lines
Las 8 columnas, en este orden exacto:
#NombreTipoDescripción
1event_timeDateTimeMarca temporal del evento (segundos desde la época Unix)
2event_time_microsecondsUInt32Componente de microsegundos
3host_nameStringHostname del servidor que emite el log
4query_idStringID de la consulta a la que pertenece el log
5thread_idUInt64ID del hilo del SO
6priorityInt8Nivel de registro (prioridad de Poco: 1 = Fatal, … 8 = Trace)
7sourceStringNombre del logger
8textStringTexto del mensaje de log

ProfileEvents (tipo de paquete 14)

Servidor → cliente. Contiene contadores de rendimiento por consulta. La envoltura y el formato del cuerpo son los mismos que en Data. El bloque tiene un num_columns = 6 fijo y un esquema predefinido. Cada evento es una fila.
[VarUInt: 14]               packet type
[String:  table_name]       always empty
[Block]                     num_columns = 6, num_rows = number of events
Las 6 columnas:
#NombreTipoDescripción
1host_nameStringHostname del servidor
2current_timeDateTimeMarca de tiempo del evento
3thread_idUInt64ID del hilo
4typeEnum8Tipo de evento: 1 = Incremento (counter), 2 = Gauge. El almacenamiento subyacente es un byte con signo.
5nameStringNombre del evento (p. ej., "Query", "NetworkReceiveBytes")
6valueInt64Valor del contador o lectura del gauge
El tipo de elemento de la columna value no es fijo entre paquetes: los servidores antiguos emiten UInt64 y los más nuevos, Int64. Lee la cadena de tipo de la columna desde la cabecera del bloque en lugar de asumir un tamaño fijo.

TableColumns (tipo de paquete 11)

Server → client, condicionado por COLUMN_DEFAULTS_METADATA (v54410). El server lo envía antes del bloque de esquema de INSERT para transportar metadatos de valores predeterminados de las columnas, pero solo cuando la versión negociada es ≥ 54410 y la configuración input_format_defaults_for_omitted_fields está habilitada. Por debajo de 54410, el paquete no se envía nunca, por lo que un client más antiguo no debe esperarlo: el bloque de esquema Data llega directamente. Un client v54410+ debe estar preparado para cualquiera de estas dos secuencias: un TableColumns opcional y luego el bloque de esquema.
#CampoTipoRolDescripción
1external_tableStringuniversalNombre de la tabla externa. Vacío = tabla principal.
2columns_descriptionStringuniversalDefiniciones textuales de columnas; por ejemplo, "id Int32, name String DEFAULT ''". Es texto libre: analízalo como una cadena.
Cuerpo comprimido a partir de v54481Con una versión negociada ≥ 54481 (COMPRESSED_LOGS_PROFILE_EVENTS_COLUMNS), el server escribe ambos campos a través de la misma ruta de salida con compresión opcional, de modo que, cuando la consulta tiene compression = true, todo el cuerpo de TableColumns (external_table + columns_description) va dentro del frame de compresión; el client lo lee a través del flujo descomprimido correspondiente. Cuando la consulta no tiene compresión, el cuerpo se transmite sin comprimir, exactamente como muestra la tabla anterior. Esto es importante para las respuestas de esquema de INSERT: un client que cambie el manejo de la compresión para Log y ProfileEvents, pero no para TableColumns, interpretará mal la respuesta cuando la compresión de la consulta esté habilitada.

TimezoneUpdate (tipo de paquete 17)

Server → Client, controlado por TIMEZONE_UPDATES (v54464). Se envía exactamente en un lugar: el inicializador de la table function input (una consulta de la forma INSERT INTO <table> SELECT ... FROM input('<structure>'), que transmite filas desde el client). Justo después de que el servidor envía el bloque Data del esquema de entrada (consulta la fase INSERT), emite TimezoneUpdate con la session_timezone actual del contexto de la consulta para que el client procese las filas que está a punto de enviar con la misma zona horaria. El servidor no emite este paquete para cambios arbitrarios de SET session_timezone a mitad de consulta, ni para indicarle al client cómo debe dar formato a bloques de resultados posteriores.
#FieldTypeRoleDescription
1timezoneStringuniversalLa nueva zona horaria predeterminada de la sesión (p. ej., "UTC", "Europe/Berlin").
El paquete llega una sola vez, inmediatamente después del bloque del esquema de entrada y antes de que el client empiece a enviar bloques de filas. Un decodificador que ignore TimezoneUpdate DEBE igualmente consumir el String final para mantener alineado el wire.

Autenticación SSH de desafío-respuesta (tipos de paquete 11, 12, 18)

Controlada por SSH_AUTHENTICATION (v54466) y disponible solo mediante activación explícita. Una conexión entra en el flujo SSH cuando ClientHello envía user = " SSH KEY AUTHENTICATION " + <real_user> (con los espacios inicial y final) y password = "". El servidor lee el prefijo, lo elimina para recuperar el usuario real y cambia al modo de desafío-respuesta.
PacketCodeDirectionBody
SSHChallengeRequest11Client → Server(sin cuerpo)
SSHChallenge18Server → ClientString challenge — bytes aleatorios; un componente de la cadena que se firma (véase más abajo)
SSHChallengeResponse12Client → ServerString signature — firma SSH sobre la concatenación definida a continuación, no sobre el desafío en bruto
Este flujo se ejecuta en lugar de la autenticación por contraseña, y el intercambio de desafío-respuesta ocurre antes de ServerHello: el servidor aplaza su respuesta Hello hasta que la autenticación se complete correctamente:
  1. El cliente envía ClientHello con el prefijo marcador SSH y una contraseña vacía.
  2. El cliente envía SSHChallengeRequest (paquete 11). El servidor todavía no ha enviado ServerHello: primero procesa la autenticación y queda bloqueado aquí, esperando este paquete.
  3. El servidor responde con SSHChallenge, que contiene bytes aleatorios (paquete 18).
  4. El cliente construye la cadena que debe firmar y firma esa, no el desafío en bruto; luego envía SSHChallengeResponse (paquete 12) con la firma. El mensaje firmado es la concatenación byte a byte, sin separadores, de cuatro partes en este orden exacto:
    to_sign = decimal(protocol_version) + default_database + user + challenge
    
    PartSource
    decimal(protocol_version)La protocol version del cliente como una cadena ASCII decimal (por ejemplo, "54466") — el número de versión como cadena, no como VarUInt ni como entero de ancho fijo. El servidor valida con la misma protocol version que recibió en ClientHello.
    default_databaseEl campo database de ClientHello (cadena vacía si no hay ninguna).
    userEl nombre del usuario real con el prefijo marcador " SSH KEY AUTHENTICATION " eliminado — el mismo nombre que el servidor recupera tras eliminar el prefijo.
    challengeLos bytes en bruto de challenge del paquete SSHChallenge.
  5. El servidor verifica la firma con la public key registrada del usuario, reconstruyendo la misma cadena decimal(protocol_version) + default_database + user + challenge. Si la validación tiene éxito, envía ServerHello —la misma respuesta que en el flujo con contraseña— y el handshake continúa con normalidad (Addendum, etc.); si falla, devuelve una Exception y termina la connection. Un client que firme solo los bytes del desafío en bruto no superará la autenticación.
Esto es lo contrario del handshake de autenticación por contraseña, en el que ServerHello sigue inmediatamente a ClientHello. Con la autenticación SSH, ServerHello se retiene hasta que se verifica la firma, por lo que el challenge-response de SSH se intercala en el handshake antes de que aparezca cualquier ServerHello.
Los clientes externos que no usan autenticación SSH nunca ven los paquetes 11, 12 o 18; no aparecen en el wire a menos que el usuario lo habilite explícitamente mediante el prefijo del nombre de usuario.

Referencia de tipos de paquete

Cliente → Servidor

CodeNameFormato del cuerpoDescripción
0HelloClientHelloInicio del handshake
1QueryQuerySolicitud de ejecución de consulta
2DataDataBloque de datos (datos de INSERT, tablas externas, marcador de fin de datos)
3Cancel(sin cuerpo)Cancelar una consulta en ejecución
4PingPingComprobación de actividad
5TablesStatusRequestno especificadoComprobación del estado de las tablas
6KeepAliveno especificadoKeepalive de la conexión
7Scalarno especificadoBloque de datos escalar
8IgnoredPartUUIDsno especificadoPartes que se excluirán de la consulta
9ReadTaskResponseno especificadoRespuesta de lectura del clúster S3
10MergeTreeReadTaskResponseno especificadoRespuesta de tarea de lectura paralela
11SSHChallengeRequestautenticación SSHSolicitud de desafío de autenticación SSH
12SSHChallengeResponseautenticación SSHRespuesta al desafío de autenticación SSH
13QueryPlanno especificadoPlan de consulta

Servidor → Cliente

CodeNameFormato del cuerpoDescripción
0HelloServerHelloRespuesta del handshake
1DataDataBloque de datos del resultado
2ExceptionExceptionError
3ProgressProgressProgreso de ejecución de la consulta
4PongPongRespuesta de liveness
5EndOfStream(sin cuerpo)Consulta completada
6ProfileInfoProfileInfoDatos de profiling posteriores a la ejecución
7TotalsTotalsFila de GROUP BY WITH TOTALS
8ExtremesExtremesValores mín./máx. (bloque de 2 filas)
9TablesStatusResponseno especificadoRespuesta de estado de la tabla
10LogLogLíneas de registro de ejecución de la consulta
11TableColumnsTableColumnsDescripciones de columnas para valores predeterminados
12PartUUIDsno especificadoID únicos de partes
13ReadTaskRequestno especificadoSolicitud de tarea de lectura del clúster
14ProfileEventsProfileEventsContadores de rendimiento
15MergeTreeAllRangesAnnouncementno especificadoInicialización de lectura en paralelo
16MergeTreeReadTaskRequestno especificadoAsignación de tarea de lectura en paralelo
17TimezoneUpdateTimezoneUpdateActualización de la zona horaria del servidor
18SSHChallengeAutenticación SSHDesafío de autenticación SSH

Configuración

Esta sección abarca los parámetros ajustables que definen las conexiones del protocolo nativo: Los valores predeterminados que se muestran a continuación reflejan una versión reciente del servidor; pueden variar según la versión y el despliegue.

Ajustes de la capa de transporte

Opciones de socket

OptionDefaultSideDescription
TCP_NODELAYactivadoambosAlgoritmo de Nagle desactivado. Los paquetes pequeños se envían inmediatamente.
SO_KEEPALIVEactivado (client), valor predeterminado del SO (servidor)asimétricoSondeos TCP keepalive a nivel del kernel. El client lo habilita explícitamente cuando tcp_keep_alive_timeout > 0. El servidor hereda el valor predeterminado del SO.
SO_RCVBUF / SO_SNDBUFvalores predeterminados del SOTamaños del búfer del socket. El protocolo no los ajusta.

Tiempos de espera

AjustePredeterminadoUnidadLadoDescripción
connect_timeout10segundosclientTiempo de espera para establecer la conexión TCP inicial.
handshake_timeout_ms10000milisegundosclientTiempo de espera para recibir ServerHello durante el handshake.
send_timeout300segundosambosSi no se pueden escribir bytes dentro de este intervalo, la conexión genera una excepción.
receive_timeout300segundosambosSi no se pueden leer bytes dentro de este intervalo, la conexión genera una excepción.
tcp_keep_alive_timeout290segundosclientTiempo de inactividad antes de que el SO envíe la primera sonda TCP keepalive.
receive_data_timeout_ms2000milisegundosclientTiempo de espera para recibir el primer paquete Data de una réplica.
connect_timeout_with_failover_ms1000milisegundosclientTiempo de espera de conexión por intento al recorrer las réplicas.
connect_timeout_with_failover_secure_ms1000milisegundosclientTiempo de espera de conexión por intento al recorrer las réplicas mediante TLS.
hedged_connection_timeout_ms50milisegundosclientTiempo de espera de conexión por intento para solicitudes especulativas.
poll_interval10segundosserverGranularidad del bucle de comprobación de conexiones inactivas y apagado del server.
Los tiempos de espera se anidan así:
tcp_keep_alive_timeout (290s)
      < receive_timeout (300s)
      < idle_connection_timeout (3600s)
      < tcp_close_connection_after_queries_seconds (0 = unlimited by default)
El keepalive del sistema operativo se activa primero y puede detectar silenciosamente peers caídos a nivel del kernel. El tiempo de espera de recepción de la aplicación es la siguiente línea de defensa. El tiempo de espera de inactividad es el último recurso, y elimina las conexiones que llevan mucho tiempo sin usarse.

Límites de conexión

ConfiguraciónPredeterminadoUnidadLadoDescripción
max_connections4096cantidadservidorNúmero máximo de conexiones TCP concurrentes.
idle_connection_timeout3600segundosservidorTiempo máximo que una conexión inactiva puede permanecer abierta.
tcp_close_connection_after_queries_num0 (ilimitado)cantidadservidorNúmero máximo de consultas por conexión antes de forzar su cierre.
tcp_close_connection_after_queries_seconds0 (ilimitado)segundosservidorTiempo de vida total máximo de la conexión, independientemente de la actividad.
Una conexión que ejecuta consultas con regularidad puede permanecer activa indefinidamente. Solo las conexiones inactivas se cierran al cabo de una hora, y no existe un tiempo de vida máximo predeterminado.

Ajustes de la capa de aplicación

Estos ajustes se envían con cada consulta en la lista de ajustes del paquete Query. Cambian lo que el servidor envía por la red o cómo se estructura.

Compresión

ConfiguraciónPredeterminadoUnidadDescripción
network_compression_method"LZ4"cadenaCódec de compresión utilizado cuando el campo compression del paquete Query está activado. Valores: "LZ4", "LZ4HC", "ZSTD", "NONE".
network_zstd_compression_level11–15Nivel de ZSTD cuando network_compression_method == "ZSTD".
El campo compression del paquete Query (campo 6) activa o desactiva la compresión; estos ajustes seleccionan qué códec se usa cuando está activada.

Streaming de logs

SettingDefaultUnitDescription
send_logs_level"fatal"stringNivel mínimo de logs. Valores: "none", "fatal", "error", "warning", "information", "debug", "trace".
send_logs_source_regexp""stringFiltro Regex sobre el origen del logger. Vacío = se aceptan todos los orígenes.
Establecer send_logs_level en cualquier valor distinto de "none" hace que el servidor emita paquetes Log durante la ejecución de la consulta.

Reporte de Progress

ConfiguraciónValor predeterminadoUnidadDescripción
interactive_delay100000microsegundosIntervalo mínimo objetivo entre paquetes Progress consecutivos.
Este es un mínimo objetivo, no un máximo estricto: el servidor puede enviar paquetes Progress con menor frecuencia cuando la consulta no genera trabajo con suficiente rapidez.

Envolvente del resultado

ConfiguraciónPredeterminadoUnidadDescripción
extremesfalseboolCuando es true, el servidor envía un paquete Extremes con valores mínimos y máximos por columna.
max_result_rows0 (ilimitado)cantidadLímite de filas transmitidas. El comportamiento se controla con result_overflow_mode.
max_result_bytes0 (ilimitado)bytes sin comprimirLímite del volumen de bytes sin comprimir. El comportamiento se controla con result_overflow_mode.
result_overflow_mode"throw"string"throw" finaliza el flujo con Exception; "break" envía resultados parciales seguidos de EndOfStream.

INSERT asíncrono

ConfiguraciónPredeterminadoUnidadDescripción
async_inserttrueboolSi es true, los datos de INSERT se ponen en cola en el servidor y se agrupan en lotes.
wait_for_async_inserttrueboolSi es true (con async_insert activado), el servidor retiene la respuesta hasta que se vacían los datos en cola.
wait_for_async_insert_timeout120secondsTiempo máximo que el servidor espera a que se vacíen los datos antes de devolver la respuesta.

Trazabilidad distribuida

ConfiguraciónPredeterminadoUnidadDescripción
opentelemetry_start_trace_probability0.0probabilidad 0–1Probabilidad en el servidor de adjuntar el contexto de OpenTelemetry a la telemetría de la respuesta.

Ajustes fuera del alcance

A veces estos ajustes se confunden con ajustes a nivel de protocolo, pero controlan la ejecución de SQL, el almacenamiento o el uso de CPU, en lugar del comportamiento en el wire. Una implementación del protocolo no necesita tratarlos de forma especial.
  • max_threads — paralelismo dentro de la ejecución de la consulta.
  • max_memory_usage — límite de memoria por consulta.
  • max_block_size, preferred_block_size_bytes — dimensionamiento interno de bloques durante el procesamiento de consultas; los bloques en el wire son independientes de estos.
  • compile_expressions — compilación JIT; solo CPU.
  • async_insert_max_data_size — búfer de cola del lado del servidor.
  • Todos los ajustes input_format_* y output_format_* excepto la familia input_format_native_* / output_format_native_* — los que no son native seleccionan o ajustan otros formatos (por ejemplo, sobre HTTP) y no cambian los bloques Data del protocolo nativo.
Los ajustes *_native_* son la excepción: cambian los bytes dentro de los bloques Data del TCP nativo, por lo que una implementación del protocolo debe tenerlos en cuenta. output_format_native_encode_types_in_binary_format cambia el campo type de la columna de una cadena de texto a una codificación binaria de tipos, output_format_native_write_json_as_string emite las columnas JSON como String, y output_format_native_use_flattened_dynamic_and_json_serialization selecciona el layout FLATTENED de Dynamic/JSON. Como afectan al cuerpo del bloque y no a la envoltura del paquete, se especifican en la especificación Native Format; consulta column wire layout y versioned types.

Glosario

Cancel — un paquete iniciado por el client (tipo 3) que aborta una consulta en ejecución. No se detalla en esta página. Marcador de fin de datos del client — un paquete Data vacío (0 columnas, 0 filas) que el client envía para cerrar un flujo de entrada. Su posición varía según el tipo de consulta:
  • Consulta normal (SELECT, etc.): se envía después del paquete Query y de cualquier paquete Data de tablas externas para indicar que “no hay más datos externos”. El server comienza entonces la ejecución.
  • INSERT: el client no envía un marcador previo al esquema. El server envía primero el bloque de esquema, el client transmite sus blocks Data de filas y solo después envía el paquete Data vacío para terminar el flujo de filas. Si se enviara un marcador vacío antes del bloque de esquema, se interpretaría como un final inmediato de las filas y los datos se perderían.
Feature — un cambio en el formato wire introducido en una versión específica del protocol. Está activa cuando la versión negociada es igual o superior a la versión de la feature. Consulta control de versiones y puertas de feature. Inter-server — una etiqueta de rol para un field que solo tiene sentido en distributed queries de server a server. Los clients externos escriben un default value (normalmente una cadena vacía, 0 o false). Versión negociadamin(client_version, server_version), calculada durante el handshake. Determina qué features están activas durante el lifetime de la connection. Packet — un mensaje wire: un código de tipo de paquete VarUInt seguido de un body cuyo formato depende del tipo. Consulta envoltura de paquete. Código de tipo de paquete — el VarUInt inicial de un paquete que identifica su formato. Actualmente están asignados los valores 0–18. Consulta la referencia de tipos de paquetes. Flujo de respuesta — la secuencia de paquetes que el server emite durante una consulta. Su longitud es abierta y termina con exactamente un EndOfStream (éxito) o Exception (error). Consulta la fase de consulta. Schema block — el header block (un Block con columnas pero 0 filas) que el server envía durante la fase de INSERT para indicar las shapes de columna esperadas antes de que el client envíe datos. Lista de Settings — una secuencia de tuples (key, flags, value) en el body de Query, terminada por una key vacía. Lleva la configuración por consulta de la capa de aplicación. Consulta Setting. Stage — un field VarUInt del paquete Query (field 5) que controla hasta dónde ejecuta la consulta el server. Los clients externos suelen enviar 2 (Complete); las distributed queries y los planes de consulta serializados usan los valores más altos. Consulta el field 5 de Query para ver el conjunto completo de valores wire. Terminator — un paquete que pone fin a un flujo. La respuesta de Query termina con EndOfStream (éxito) o Exception (error). El flujo de entrada del client termina con el marcador Data vacío.
Last modified on June 25, 2026