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.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
| Propiedad | Valor |
|---|---|
| Transporte | TCP, opcionalmente protegido con TLS |
| Orden de bytes | little-endian para enteros de ancho fijo |
| Codificación | Binaria y posicional (sin etiquetas de campo excepto en BlockInfo) |
| Modelo de conexión | Con estado, una consulta a la vez, sin multiplexación |
| Control de versiones | Se negocia en el handshake; las funcionalidades individuales se habilitan según la versión |
| Formato de datos | El Formato nativo para todos los datos tabulares |
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 sí respeta la cláusula FORMAT; HTTP queda fuera del alcance de esta explicación.)
Seguridad
Seguridad de transporte (TLS)
Autenticación
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
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
Controles de funcionalidad
Tabla de funcionalidades
| Funcionalidad | Versión | Afecta a | Impacto a nivel wire |
|---|---|---|---|
| BLOCK_INFO | all | Block | Añade el prefijo BlockInfo (is_overflows, bucket_number) a cada Block. |
| CLIENT_INFO | 54032 | Query | Añade el bloque ClientInfo al cuerpo de Query. |
| TIMEZONE | 54058 | ServerHello | Añade el campo timezone a ServerHello. |
| QUOTA_KEY_IN_CLIENT_INFO | 54060 | ClientInfo | Añade el campo quota_key a ClientInfo. |
| DISPLAY_NAME | 54372 | ServerHello | Añade el campo display_name a ServerHello. |
| VERSION_PATCH | 54401 | ServerHello, ClientInfo | Añade el campo version_patch a ambos. |
| SERVER_LOGS | 54406 | Log | El servidor emite paquetes Log cuando se configura send_logs_level. |
| COLUMN_DEFAULTS_METADATA | 54410 | TableColumns | El 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_INFO | 54420 | Progress | Añ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_STRINGS | 54429 | Query (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_SECRET | 54441 | Query | Añ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_TELEMETRY | 54442 | ClientInfo | Añade el trace context de OpenTelemetry a ClientInfo. |
| DISTRIBUTED_DEPTH | 54448 | ClientInfo | Añade el campo distributed_depth a ClientInfo. |
| INITIAL_QUERY_START_TIME | 54449 | ClientInfo | Añade el campo initial_time (Int64, de ancho fijo). |
| PROFILE_EVENTS | 54451 | ProfileEvents | El servidor emite paquetes ProfileEvents durante la ejecución de la consulta. |
| PARALLEL_REPLICAS | 54453 | ClientInfo | Añade campos de coordinación de réplicas paralelas a ClientInfo. |
| CUSTOM_SERIALIZATION | 54454 | Block (Column) | Añade el byte has_custom_serialization después de la cadena de tipo de cada columna. |
| ADDENDUM | 54458 | Handshake | El client envía un addendum (quota_key) después del intercambio de handshake. |
| PARAMETERS | 54459 | Query | Añade la lista de parámetros al cuerpo de Query. |
| SERVER_QUERY_TIME_IN_PROGRESS | 54460 | Progress | Añade el campo elapsed_ns a Progress. |
| PASSWORD_COMPLEXITY_RULES | 54461 | ServerHello | Añade a ServerHello una lista de patrones regex de políticas de contraseñas y mensajes legibles para humanos. |
| INTERSERVER_SECRET_V2 | 54462 | ServerHello | Añ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_PROGRESS | 54463 | Progress | Añade el campo total_bytes_to_read (VarUInt) a Progress, entre total_rows y wrote_rows. |
| TIMEZONE_UPDATES | 54464 | TimezoneUpdate | Añ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_SERIALIZATION | 54465 | Block (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_AUTHENTICATION | 54466 | Auth flow | Añ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_CHECK | 54467 | TablesStatusResponse | Añ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_TABLE | 54468 | system tables | El 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_AGGREGATION | 54469 | ProfileInfo | Añade applied_aggregation (Bool) y rows_before_aggregation (VarUInt) a ProfileInfo, en ese orden, al final. |
| CHUNKED_PROTOCOL | 54470 | Connection framing | El 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_PROTOCOL | 54471 | ServerHello, Addendum | Ambos 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_ROLES | 54472 | Query | Añ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_SERIALIZATION | 54473 | Column body | El 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_SETTINGS | 54474 | ServerHello | El 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_NUMBERS | 54475 | ClientInfo | Añ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_INTERSERVER | 54476 | ClientInfo | Añ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_SERIALIZATION | 54477 | ServerHello, QueryPlan packet | ServerHello 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_MARSHALLING | 54478 | Block (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_PROTOCOL | 54479 | ServerHello | Añ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_AGGREGATION | 54480 | BlockInfo | Añ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_COLUMNS | 54481 | Log, ProfileEvents, TableColumns | El 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_SERIALIZATION | 54482 | Block (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_SERIALIZATION | 54483 | Block (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_INSERT | 54484 | Progress (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_INFO | 54485 | ClientInfo | Añ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
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+)
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:
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/notchunkedson estrictos: ese lado requiere exactamente ese modo.- Las variantes
_optionalson flexibles: aceptan el modo que elija el otro lado.
| Preferencia del servidor | Preferencia del Client | Acordado |
|---|---|---|
*_optional | cualquiera | seguir al CLIENT (su starts_with("chunked")) |
| cualquiera | *_optional | seguir al SERVER |
chunked estricto | chunked estricto | chunked |
notchunked estricto | notchunked estricto | notchunked |
| desacuerdo estricto | desacuerdo estricto | error de protocolo — la conexión DEBE cerrarse |
Ciclo de vida de la conexión
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
HANDSHAKE → READY → READING_RESPONSE → READY — con el bucle de Ping/Pong y todas las transiciones de error convergiendo en el único estado terminal Terminated.
| Estado | Descripción |
|---|---|
HANDSHAKE | Estado 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. |
READY | Inactivo. 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_RESPONSE | Se 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). |
| Terminated | Ya no se puede usar. El Client debe abrir una nueva conexión TCP y reiniciar el handshake. |
Fase de handshake
-
El Client envía
ClientHellocon la versión máxima del protocolo que admite. -
El Client lee la respuesta y actúa según el tipo de paquete:
Tipo de paquete Acción Hello(0)Decodifica ServerHello. Calculanegotiated_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 otro Violación del protocolo. Termina la conexión. -
Si
negotiated_version ≥ 54458(la funcionalidadADDENDUM), el Client envía unAddendum. Esta decisión se basa en la versión negociada, no en la versión declarada del Client.
READY; ante cualquier error, se termina.
Fase de Ping
READY, el flujo es:
-
El Client envía
Ping. -
El Client lee la respuesta:
Tipo de paquete Acción Pong(4)Se confirma que sigue activo. Volver a READY.Exception(2)Decodificar Exceptiony devolverla como error.cualquier otro valor Violación del protocolo.
Fase de consulta
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.
-
El Client envía
Querycon unquery_idúnico (normalmente, un UUID). -
El Client envía cualquier tabla externa y luego el marcador
Datavacío. El paqueteDatavacío tienetable_name = "",num_columns = 0,num_rows = 0. El servidor no empieza a ejecutar la consulta hasta que recibe este marcador. -
El Client pasa a
READING_RESPONSEy vacía su búfer de escritura. -
El Client lee los paquetes de respuesta en un bucle y los procesa según su tipo:
Tipo de paquete Acción Data(1)Decodifica el bloque. El primer Dataes la cabecera del esquema; los posteriores son bloques de resultados (acumúlalos); un bloque vacío es un marcador de límite.num_rows == 0no 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 else Inesperado durante la fase de consulta. Termina la conexión.
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
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:
- El Client envía
Queryconbodyestablecido en el SQL de INSERT. - 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
consultadeINSERTse 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. - 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.
- El Client envía uno o varios bloques de datos. Para cada bloque, escribe
VarUInt(ClientPacket::Data = 2), luegoString("")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. - El Client envía el terminador de fin de entrada: un paquete Data con un Block vacío (0 columnas, 0 filas).
- El Client consume el flujo de respuesta hasta
EndOfStream(éxito) oException(fallo).
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
Type usa:
VarUInt— entero sin signo de longitud variable (consulta VarUInt).String— bytes con prefijoVarUInt(consulta String).UInt8,Int32, etcétera — enteros little-endian de ancho fijo.Bool— un único byte,0x00o0x01.
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.
ClientHello (tipo de paquete 0)
| # | Campo | Tipo | Rol | Descripción |
|---|---|---|---|---|
| 1 | client_name | String | universal | Identificador del client (p. ej., "clickhouse-client") |
| 2 | version_major | VarUInt | universal | Versión principal del client |
| 3 | version_minor | VarUInt | universal | Versión secundaria del client |
| 4 | protocol_version | VarUInt | universal | Versión máxima del protocolo que admite el client |
| 5 | database | String | universal | Nombre de la base de datos predeterminada |
| 6 | user | String | universal | Nombre de usuario para la autenticación |
| 7 | password | String | universal | Contraseña (en texto no cifrado) |
ServerHello (tipo de paquete 0)
| # | Campo | Tipo | Rol | Condición | Descripción |
|---|---|---|---|---|---|
| 1 | server_name | String | universal | always | Identificador del servidor |
| 2 | version_major | VarUInt | universal | always | Versión principal del servidor |
| 3 | version_minor | VarUInt | universal | always | Versión secundaria del servidor |
| 4 | protocol_version | VarUInt | universal | always | Versión del protocolo del servidor |
| 4a | parallel_replicas_protocol_version | VarUInt | universal | VERSIONED_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. |
| 5 | timezone | String | universal | TIMEZONE (v54058) | Zona horaria del servidor (p. ej., "UTC") |
| 6 | display_name | String | universal | DISPLAY_NAME (v54372) | Nombre del servidor legible para humanos |
| 7 | version_patch | VarUInt | universal | VERSION_PATCH (v54401) | Versión de parche del servidor |
| 8 | proto_send_chunked_srv | String | universal | CHUNKED_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. |
| 9 | proto_recv_chunked_srv | String | universal | CHUNKED_PROTOCOL (v54470) | Fragmentación entrante preferida del servidor. Mismo conjunto de valores que el campo 8. |
| 10 | password_complexity_rules | Rule[] | universal | PASSWORD_COMPLEXITY_RULES (v54461) | Política de contraseñas del servidor. VarUInt count seguido de count × Rule. Véase más abajo. |
| 11 | nonce | UInt64 | inter-server | INTERSERVER_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. |
| 12 | server_settings | Setting[] | universal | SERVER_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. |
| 13 | query_plan_serialization_version | VarUInt | universal | QUERY_PLAN_SERIALIZATION (v54477) | Versión de serialización del plan de consulta compatible con el servidor. Los Clients externos la decodifican y la ignoran. |
| 14 | cluster_function_protocol_version | VarUInt | universal | VERSIONED_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. |
password_complexity_rules:
| # | Campo | Tipo | Descripción |
|---|---|---|---|
| 1 | pattern | String | Patrón de expresión regular que debe cumplir una contraseña válida. |
| 2 | message | String | Explicación legible para humanos que se muestra cuando una contraseña no cumple esta regla. |
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)
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.
| # | Field | Type | Role | Condition | Description |
|---|---|---|---|---|---|
| 1 | quota_key | String | universal | siempre | Clave 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. |
| 2 | proto_send_chunked | String | universal | CHUNKED_PROTOCOL (v54470) | Fragmentación saliente negociada del Client: "chunked" o "notchunked". Se calcula con respecto a proto_recv_chunked_srv de ServerHello. |
| 3 | proto_recv_chunked | String | universal | CHUNKED_PROTOCOL (v54470) | Fragmentación entrante negociada del Client. Se calcula con respecto a proto_send_chunked_srv. |
| 4 | parallel_replicas_protocol_version | VarUInt | universal | VERSIONED_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. |
Ping (tipo de paquete 4)
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)
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)
| # | Campo | Tipo | Rol | Descripción |
|---|---|---|---|---|
| 1 | code | Int32 | universal | Código de error |
| 2 | name | String | universal | Clase de excepción (p. ej., "DB::Exception") |
| 3 | message | String | universal | Mensaje de error legible para humanos |
| 4 | stack_trace | String | universal | Stack trace del servidor |
| 5 | has_nested (obsoleto) | Bool | universal | Byte de compatibilidad obsoleto. El server siempre lo escribe como false |
Consulta (tipo de paquete 1)
| # | Campo | Tipo | Rol | Condición | Descripción |
|---|---|---|---|---|---|
| 1 | query_id | String | universal | siempre | Identificador único de consulta (UUID) |
| 2 | client_info | ClientInfo | universal | CLIENT_INFO (v54032) | Consulte ClientInfo |
| 3 | settings | Setting[] | universal | siempre | Consulte 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. |
| 3a | external_roles | String | universal | INTERSERVER_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. |
| 4 | auth_hash | String | entre servidores | INTERSERVER_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. |
| 5 | stage | VarUInt | universal | siempre | Etapa 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. |
| 6 | compression | VarUInt | universal | siempre | 0 = deshabilitado, 1 = habilitado |
| 7 | query_body | String | universal | siempre | Texto SQL |
| 8 | parameters | Parameter[] | client | PARAMETERS (v54459) | Consulte Parameter. Terminado por una clave vacía. |
ClientInfo (incluido en consulta)
CLIENT_INFO (v54032). (Algunos campos dentro de ClientInfo están condicionados por versiones posteriores, como se indica más abajo en cada campo.)
| # | Campo | Tipo | Rol | Condición | Descripción |
|---|---|---|---|---|---|
| 1 | query_kind | UInt8 | universal | siempre | 0 = NoQuery, 1 = InitialQuery, 2 = SecondaryQuery. Los clientes externos envían 1. |
| 2 | initial_user | String | universal | siempre | Usuario que inició la consulta |
| 3 | initial_query_id | String | universal | siempre | ID de la consulta original |
| 4 | initial_address | String | universal | siempre | Dirección del socket del cliente de origen en formato host:port |
| 5 | initial_time | Int64 | Client | INITIAL_QUERY_START_TIME (v54449) | Hora de inicio de la consulta (microsegundos). Ancho fijo de 8 bytes, no VarUInt |
| 6 | query_interface | UInt8 | universal | siempre | 1 = TCP, 2 = HTTP |
| 7 | os_user | String | Client | si interface = TCP | username del SO |
| 8 | client_hostname | String | Client | si interface = TCP | hostname de la máquina cliente |
| 9 | client_name | String | Client | si interface = TCP | Nombre de la aplicación cliente |
| 10 | version_major | VarUInt | universal | si interface = TCP | Versión principal del Client |
| 11 | version_minor | VarUInt | universal | si interface = TCP | Versión secundaria del Client |
| 12 | protocol_version | VarUInt | universal | si interface = TCP | La 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. |
| 13 | quota_key | String | universal | QUOTA_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. |
| 14 | distributed_depth | VarUInt | entre servidores | DISTRIBUTED_DEPTH (v54448) | Profundidad de anidamiento de la consulta Distributed. Los clientes externos envían 0. |
| 15 | version_patch | VarUInt | universal | VERSION_PATCH (v54401), solo TCP | Versión de parche del cliente |
| 16 | open_telemetry | (abajo) | Client | OPEN_TELEMETRY (v54442) | Contexto de trace. Los clientes sin tracing envían 0. |
| 17 | collaborate_with_initiator | VarUInt | entre servidores | PARALLEL_REPLICAS (v54453) | Bool como VarUInt. Los clientes externos envían 0. |
| 18 | count_participating_replicas | VarUInt | entre servidores | PARALLEL_REPLICAS (v54453) | Los clientes externos envían 0. |
| 19 | number_of_current_replica | VarUInt | entre servidores | PARALLEL_REPLICAS (v54453) | Los clientes externos envían 0. |
| 20 | script_query_number | VarUInt | Client | QUERY_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. |
| 21 | script_line_number | VarUInt | Client | QUERY_AND_LINE_NUMBERS (v54475) | Número de línea, con índice base 1, dentro del script fuente. Los clientes externos envían 0. |
| 22 | jwt_present | UInt8 | entre servidores | JWT_IN_INTERSERVER (v54476) | 0 = sin JWT; 1 = a continuación se incluye un JWT. Los clientes externos sin autenticación JWT envían 0. |
| 23 | jwt | String | entre servidores | JWT_IN_INTERSERVER (v54476), if jwt_present=1 | token Bearer JWT, solo presente cuando el campo 22 = 1. |
| 24 | client_agent | String | Client | CLIENT_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ésforwarded_for(String, condicionado porX_FORWARDED_FOR_IN_CLIENT_INFOv54443) yhttp_referer(String, condicionado porREFERER_IN_CLIENT_INFOv54447). No están presentes los camposos_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.
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.Autenticación entre servidores
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:
- Entrar en modo entre servidores. El servidor que se conecta lo indica dentro de
ClientHello: el campouseres el marcador entre servidores ypasswordestá vacío. A continuación, añade dos strings más —el nombre del clúster y unasaltde 32 bytes recién generada (encodeSHA256de un valor aleatorio)— inmediatamente después de los camposuser/password, como parte del mismo paqueteClientHello. El servidor lee estas dos strings antes de enviarServerHello, por lo que un Client debe escribirlas de antemano; esperar primero aServerHelloprovoca interbloqueos, porque el servidor queda bloqueado leyéndolas. - Obtener el nonce.
ServerHelloincluye un nonceUInt64de 8 bytes cuando se negociaINTERSERVER_SECRET_V2(v54462). - Calcular el hash. Para cada paquete consulta que no sea
InitialQuery, el Client escribeencodeSHA256(salt + nonce + cluster_secret + query + query_id + initial_user + external_roles)en el campo 4: un digest de 32 bytes. (nonceva en su forma de string decimal, presente solo cuando se negocia ≥ v54462;external_rolesse añade solo cuando se negociaINTERSERVER_EXTERNALLY_GRANTED_ROLES(v54472).) Para unInitialQuery, o cuando no hay ningún secreto de clúster configurado, el Client escribe en su lugar un string vacío. - 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.
auth_hash vacío.
Configuración
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í:
| # | Campo | Tipo | Rol | Descripción |
|---|---|---|---|---|
| 1 | key | String | universal | Nombre de la configuración. Vacío = fin de la lista. |
| 2 | flags | VarUInt | universal | Indicadores de bits de metadatos; véase más abajo. |
| 3 | value | String | universal | Valor de la configuración como cadena |
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:
0x01— Importante: la configuración afecta a los resultados de la consulta y no debe ser ignorada silenciosamente por pares más antiguos.0x02— Personalizada: 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 deflags & 0x04clasificaría erróneamente Beta (0x0c) como Obsolete.0x80— HotReload (recarga de configuración sin reinicio; definido en el enum de indicadores, presente principalmente en configuraciones de coordinación).
Parámetro
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.
| # | Campo | Tipo | Rol | Descripción |
|---|---|---|---|---|
| 1 | key | String | client | Nombre del parámetro. Vacío = fin de la lista. |
| 2 | flags | VarUInt | client | Siempre 0x02 (Custom) |
| 3 | value | String | client | Valor 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)
table_name antes del Block. Solo cambia el byte del tipo de paquete.
| Campo | Tipo | Rol | Descripción |
|---|---|---|---|
| table_name | String | universal | Nombre 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 bloque | — | — | Véase Block & column structure. |
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)
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.
| # | Campo | Tipo | Rol | Condición | Descripción |
|---|---|---|---|---|---|
| 1 | rows | VarUInt | universal | siempre | Filas leídas desde el paquete anterior (sumar al total acumulado) |
| 2 | bytes | VarUInt | universal | siempre | Bytes leídos desde el paquete anterior (sumar al total acumulado) |
| 3 | total_rows | VarUInt | universal | siempre | Incremento del total estimado de filas por leer; acumular (puede ser 0 en un paquete determinado) |
| 4 | total_bytes | VarUInt | universal | TOTAL_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. |
| 5 | wrote_rows | VarUInt | universal | WRITE_CLIENT_INFO (v54420) | Filas escritas desde el paquete anterior (para INSERT); acumular |
| 6 | wrote_bytes | VarUInt | universal | WRITE_CLIENT_INFO (v54420) | Bytes escritos desde el paquete anterior (para INSERT); acumular |
| 7 | elapsed_ns | VarUInt | universal | SERVER_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)
| # | Campo | Tipo | Rol | Condición | Descripción |
|---|---|---|---|---|---|
| 1 | rows | VarUInt | universal | siempre | Total de filas procesadas |
| 2 | blocks | VarUInt | universal | siempre | Total de bloques procesados |
| 3 | bytes | VarUInt | universal | siempre | Total de bytes procesados |
| 4 | applied_limit | Bool | universal | siempre | Indica si se aplicó una cláusula LIMIT |
| 5 | rows_before_limit | VarUInt | universal | siempre | Recuento de filas antes de LIMIT |
| 6 | obsolete | Bool | universal | siempre | Byte 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. |
| 7 | applied_aggregation | Bool | universal | ROWS_BEFORE_AGGREGATION (v54469) | Indica si se aplicó GROUP BY |
| 8 | rows_before_aggregation | VarUInt | universal | ROWS_BEFORE_AGGREGATION (v54469) | Recuento de filas antes de la agregación |
Totales (tipo de paquete 7)
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.
Extremes (tipo de paquete 8)
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.
Log (packet type 10)
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.
| # | Nombre | Tipo | Descripción |
|---|---|---|---|
| 1 | event_time | DateTime | Marca temporal del evento (segundos desde la época Unix) |
| 2 | event_time_microseconds | UInt32 | Componente de microsegundos |
| 3 | host_name | String | Hostname del servidor que emite el log |
| 4 | query_id | String | ID de la consulta a la que pertenece el log |
| 5 | thread_id | UInt64 | ID del hilo del SO |
| 6 | priority | Int8 | Nivel de registro (prioridad de Poco: 1 = Fatal, … 8 = Trace) |
| 7 | source | String | Nombre del logger |
| 8 | text | String | Texto del mensaje de log |
ProfileEvents (tipo de paquete 14)
num_columns = 6 fijo y un esquema predefinido. Cada evento es una fila.
| # | Nombre | Tipo | Descripción |
|---|---|---|---|
| 1 | host_name | String | Hostname del servidor |
| 2 | current_time | DateTime | Marca de tiempo del evento |
| 3 | thread_id | UInt64 | ID del hilo |
| 4 | type | Enum8 | Tipo de evento: 1 = Incremento (counter), 2 = Gauge. El almacenamiento subyacente es un byte con signo. |
| 5 | name | String | Nombre del evento (p. ej., "Query", "NetworkReceiveBytes") |
| 6 | value | Int64 | Valor 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)
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.
| # | Campo | Tipo | Rol | Descripción |
|---|---|---|---|---|
| 1 | external_table | String | universal | Nombre de la tabla externa. Vacío = tabla principal. |
| 2 | columns_description | String | universal | Definiciones 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)
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.
| # | Field | Type | Role | Description |
|---|---|---|---|---|
| 1 | timezone | String | universal | La nueva zona horaria predeterminada de la sesión (p. ej., "UTC", "Europe/Berlin"). |
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)
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.
| Packet | Code | Direction | Body |
|---|---|---|---|
| SSHChallengeRequest | 11 | Client → Server | (sin cuerpo) |
| SSHChallenge | 18 | Server → Client | String challenge — bytes aleatorios; un componente de la cadena que se firma (véase más abajo) |
| SSHChallengeResponse | 12 | Client → Server | String signature — firma SSH sobre la concatenación definida a continuación, no sobre el desafío en bruto |
- El cliente envía ClientHello con el prefijo marcador SSH y una contraseña vacía.
-
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. -
El servidor responde con
SSHChallenge, que contiene bytes aleatorios (paquete 18). -
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:Part Source 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ó enClientHello.default_databaseEl campo databasedeClientHello(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 challengedel paqueteSSHChallenge. -
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íaServerHello—la misma respuesta que en el flujo con contraseña— y el handshake continúa con normalidad (Addendum, etc.); si falla, devuelve unaExceptiony 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.
Referencia de tipos de paquete
Cliente → Servidor
| Code | Name | Formato del cuerpo | Descripción |
|---|---|---|---|
| 0 | Hello | ClientHello | Inicio del handshake |
| 1 | Query | Query | Solicitud de ejecución de consulta |
| 2 | Data | Data | Bloque de datos (datos de INSERT, tablas externas, marcador de fin de datos) |
| 3 | Cancel | (sin cuerpo) | Cancelar una consulta en ejecución |
| 4 | Ping | Ping | Comprobación de actividad |
| 5 | TablesStatusRequest | no especificado | Comprobación del estado de las tablas |
| 6 | KeepAlive | no especificado | Keepalive de la conexión |
| 7 | Scalar | no especificado | Bloque de datos escalar |
| 8 | IgnoredPartUUIDs | no especificado | Partes que se excluirán de la consulta |
| 9 | ReadTaskResponse | no especificado | Respuesta de lectura del clúster S3 |
| 10 | MergeTreeReadTaskResponse | no especificado | Respuesta de tarea de lectura paralela |
| 11 | SSHChallengeRequest | autenticación SSH | Solicitud de desafío de autenticación SSH |
| 12 | SSHChallengeResponse | autenticación SSH | Respuesta al desafío de autenticación SSH |
| 13 | QueryPlan | no especificado | Plan de consulta |
Servidor → Cliente
| Code | Name | Formato del cuerpo | Descripción |
|---|---|---|---|
| 0 | Hello | ServerHello | Respuesta del handshake |
| 1 | Data | Data | Bloque de datos del resultado |
| 2 | Exception | Exception | Error |
| 3 | Progress | Progress | Progreso de ejecución de la consulta |
| 4 | Pong | Pong | Respuesta de liveness |
| 5 | EndOfStream | (sin cuerpo) | Consulta completada |
| 6 | ProfileInfo | ProfileInfo | Datos de profiling posteriores a la ejecución |
| 7 | Totals | Totals | Fila de GROUP BY WITH TOTALS |
| 8 | Extremes | Extremes | Valores mín./máx. (bloque de 2 filas) |
| 9 | TablesStatusResponse | no especificado | Respuesta de estado de la tabla |
| 10 | Log | Log | Líneas de registro de ejecución de la consulta |
| 11 | TableColumns | TableColumns | Descripciones de columnas para valores predeterminados |
| 12 | PartUUIDs | no especificado | ID únicos de partes |
| 13 | ReadTaskRequest | no especificado | Solicitud de tarea de lectura del clúster |
| 14 | ProfileEvents | ProfileEvents | Contadores de rendimiento |
| 15 | MergeTreeAllRangesAnnouncement | no especificado | Inicialización de lectura en paralelo |
| 16 | MergeTreeReadTaskRequest | no especificado | Asignación de tarea de lectura en paralelo |
| 17 | TimezoneUpdate | TimezoneUpdate | Actualización de la zona horaria del servidor |
| 18 | SSHChallenge | Autenticación SSH | Desafío de autenticación SSH |
Configuración
- ajuste de la capa de transporte — opciones de socket TCP y tiempos de espera, que afectan al comportamiento de la propia conexión TCP.
- ajuste de la capa de aplicación — parámetros ajustables por consulta incluidos en la lista de settings del paquete Query, que afectan a lo que el servidor envía por la red o a cómo se estructura.
- ajuste fuera del alcance — settings que a menudo se confunden con la configuración del protocolo, pero que en realidad controlan la ejecución de SQL o el almacenamiento.
Ajustes de la capa de transporte
Opciones de socket
| Option | Default | Side | Description |
|---|---|---|---|
TCP_NODELAY | activado | ambos | Algoritmo de Nagle desactivado. Los paquetes pequeños se envían inmediatamente. |
SO_KEEPALIVE | activado (client), valor predeterminado del SO (servidor) | asimétrico | Sondeos 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_SNDBUF | valores predeterminados del SO | — | Tamaños del búfer del socket. El protocolo no los ajusta. |
Tiempos de espera
| Ajuste | Predeterminado | Unidad | Lado | Descripción |
|---|---|---|---|---|
connect_timeout | 10 | segundos | client | Tiempo de espera para establecer la conexión TCP inicial. |
handshake_timeout_ms | 10000 | milisegundos | client | Tiempo de espera para recibir ServerHello durante el handshake. |
send_timeout | 300 | segundos | ambos | Si no se pueden escribir bytes dentro de este intervalo, la conexión genera una excepción. |
receive_timeout | 300 | segundos | ambos | Si no se pueden leer bytes dentro de este intervalo, la conexión genera una excepción. |
tcp_keep_alive_timeout | 290 | segundos | client | Tiempo de inactividad antes de que el SO envíe la primera sonda TCP keepalive. |
receive_data_timeout_ms | 2000 | milisegundos | client | Tiempo de espera para recibir el primer paquete Data de una réplica. |
connect_timeout_with_failover_ms | 1000 | milisegundos | client | Tiempo de espera de conexión por intento al recorrer las réplicas. |
connect_timeout_with_failover_secure_ms | 1000 | milisegundos | client | Tiempo de espera de conexión por intento al recorrer las réplicas mediante TLS. |
hedged_connection_timeout_ms | 50 | milisegundos | client | Tiempo de espera de conexión por intento para solicitudes especulativas. |
poll_interval | 10 | segundos | server | Granularidad del bucle de comprobación de conexiones inactivas y apagado del server. |
Límites de conexión
| Configuración | Predeterminado | Unidad | Lado | Descripción |
|---|---|---|---|---|
max_connections | 4096 | cantidad | servidor | Número máximo de conexiones TCP concurrentes. |
idle_connection_timeout | 3600 | segundos | servidor | Tiempo máximo que una conexión inactiva puede permanecer abierta. |
tcp_close_connection_after_queries_num | 0 (ilimitado) | cantidad | servidor | Número máximo de consultas por conexión antes de forzar su cierre. |
tcp_close_connection_after_queries_seconds | 0 (ilimitado) | segundos | servidor | Tiempo de vida total máximo de la conexión, independientemente de la actividad. |
Ajustes de la capa de aplicación
Compresión
| Configuración | Predeterminado | Unidad | Descripción |
|---|---|---|---|
network_compression_method | "LZ4" | cadena | Códec de compresión utilizado cuando el campo compression del paquete Query está activado. Valores: "LZ4", "LZ4HC", "ZSTD", "NONE". |
network_zstd_compression_level | 1 | 1–15 | Nivel de ZSTD cuando network_compression_method == "ZSTD". |
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
| Setting | Default | Unit | Description |
|---|---|---|---|
send_logs_level | "fatal" | string | Nivel mínimo de logs. Valores: "none", "fatal", "error", "warning", "information", "debug", "trace". |
send_logs_source_regexp | "" | string | Filtro Regex sobre el origen del logger. Vacío = se aceptan todos los orígenes. |
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ón | Valor predeterminado | Unidad | Descripción |
|---|---|---|---|
interactive_delay | 100000 | microsegundos | Intervalo mínimo objetivo entre paquetes Progress consecutivos. |
Envolvente del resultado
| Configuración | Predeterminado | Unidad | Descripción |
|---|---|---|---|
extremes | false | bool | Cuando es true, el servidor envía un paquete Extremes con valores mínimos y máximos por columna. |
max_result_rows | 0 (ilimitado) | cantidad | Límite de filas transmitidas. El comportamiento se controla con result_overflow_mode. |
max_result_bytes | 0 (ilimitado) | bytes sin comprimir | Lí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ón | Predeterminado | Unidad | Descripción |
|---|---|---|---|
async_insert | true | bool | Si es true, los datos de INSERT se ponen en cola en el servidor y se agrupan en lotes. |
wait_for_async_insert | true | bool | Si es true (con async_insert activado), el servidor retiene la respuesta hasta que se vacían los datos en cola. |
wait_for_async_insert_timeout | 120 | seconds | Tiempo máximo que el servidor espera a que se vacíen los datos antes de devolver la respuesta. |
Trazabilidad distribuida
| Configuración | Predeterminado | Unidad | Descripción |
|---|---|---|---|
opentelemetry_start_trace_probability | 0.0 | probabilidad 0–1 | Probabilidad en el servidor de adjuntar el contexto de OpenTelemetry a la telemetría de la respuesta. |
Ajustes fuera del alcance
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_*youtput_format_*excepto la familiainput_format_native_*/output_format_native_*— los que no sonnativeseleccionan o ajustan otros formatos (por ejemplo, sobre HTTP) y no cambian los bloquesDatadel protocolo nativo.
*_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
- 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.
min(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.