INSERT, la télémétrie d’exécution et les signaux d’erreur. C’est le protocole sous-jacent au client en ligne de commande, au client C++ et à la plupart des drivers natifs tiers.
Cette page couvre le protocole lui-même : tramage des paquets, machine à états de la connexion, négociation de version et corps de chaque message autre que Block. Les octets à l’intérieur des paquets de la famille Data (le Block, ses colonnes et les encodages propres à chaque type) relèvent d’un sujet distinct, documenté dans la spécification Native Format.
Spécification complémentaireCette page est l’un des deux volets de cet ensemble et est publiée avec la spécification complémentaire Native Format. Les deux spécifications se répartissent clairement les rôles : cette page couvre la couche des paquets et du transport ; la spécification Native Format couvre les octets à l’intérieur des paquets de la famille
Data.BlockInfo, donc un seul octet mal placé désynchronise tout ce qui suit. Il est avec état, et chaque connexion TCP traite une requête à la fois — il n’y a pas de multiplexage. Les entiers à largeur fixe sont en little-endian.
Vue d’ensemble
| Propriété | Valeur |
|---|---|
| Transport | TCP, éventuellement encapsulé dans TLS |
| Ordre des octets | Little-endian pour les entiers à largeur fixe |
| Encodage | Binaire et positionnel (sans balises de champ, sauf dans BlockInfo) |
| Modèle de connexion | Avec état, une requête à la fois, sans multiplexage |
| Versionnement | Négocié lors du handshake ; certaines fonctionnalités dépendent de la version |
| Format de données | Le Native Format pour toutes les données tabulaires |
VarUInt, suivi d’un corps dont la structure dépend de ce code et de la version du protocole négociée.
Une connexion se déroule en trois phases — un handshake initial unique, puis un nombre quelconque d’échanges Ping ou Query, puis la fermeture :
Le protocole TCP natif transporte toujours des données tabulaires au format Native, quelle que soit la clause FORMAT dans la requête SQL. Le reformatage en RowBinary, CSV, JSON, etc. relève du client et s’effectue après le décodage des blocs Native. (L’interface HTTP suit un chemin de code différent qui, lui, respecte bien la clause FORMAT ; HTTP n’est pas traité ici.)
Sécurité
Sécurité de transport (TLS)
Authentification
ClientHello. Les champs user et password sont transmis sous forme de chaînes en clair ; la protection des credentials en transit repose donc sur le chiffrement au niveau du transport (TLS).
L’authentification SSH par challenge-response est disponible à partir de la version 54466 du protocole — voir Authentification SSH par challenge-response.
Secret inter-serveurs
Query contient un auth_hash SHA-256 de 32 octets dans le champ 4 de Query, calculé à partir d’un salt, d’un nonce, du secret configuré et de la requête, puis recalculé et comparé par le serveur destinataire. Ce mécanisme est conditionné par la fonctionnalité INTERSERVER_SECRET (v54441). Les clients externes envoient toujours ici une chaîne vide. Voir Authentification inter-serveurs.
Gestion des versions et feature gates
Négociation de la version
Feature gates
Tableau des fonctionnalités
| Fonctionnalité | Version | Affecte | Impact sur le format binaire |
|---|---|---|---|
| BLOCK_INFO | all | Block | Ajoute le préfixe BlockInfo (is_overflows, bucket_number) à chaque Block. |
| CLIENT_INFO | 54032 | Query | Ajoute le bloc ClientInfo au corps de Query. |
| TIMEZONE | 54058 | ServerHello | Ajoute le champ timezone à ServerHello. |
| QUOTA_KEY_IN_CLIENT_INFO | 54060 | ClientInfo | Ajoute le champ quota_key à ClientInfo. |
| DISPLAY_NAME | 54372 | ServerHello | Ajoute le champ display_name à ServerHello. |
| VERSION_PATCH | 54401 | ServerHello, ClientInfo | Ajoute le champ version_patch aux deux. |
| SERVER_LOGS | 54406 | Log | Le serveur émet des paquets Log lorsque send_logs_level est défini. |
| COLUMN_DEFAULTS_METADATA | 54410 | TableColumns | Le serveur peut envoyer le paquet TableColumns (type 11) avec les métadonnées des valeurs par défaut des colonnes avant le bloc de schéma d’entrée/INSERT. Envoyé uniquement lorsque la version négociée ≥ 54410 et que input_format_defaults_for_omitted_fields est activé. En dessous de cette version, le paquet n’est jamais envoyé ; les clients ne doivent pas l’attendre. |
| WRITE_CLIENT_INFO | 54420 | Progress | Ajoute wrote_rows et wrote_bytes à Progress. (Malgré son nom, cela ne contrôle pas le bloc ClientInfo — c’est le rôle de CLIENT_INFO (v54032).) |
| SETTINGS_SERIALIZED_AS_STRINGS | 54429 | Query (encodage des settings) | Modifie la manière dont la liste des settings, toujours présente, est encodée ; ne contrôle pas si les settings sont envoyés. v54429+ écrit chaque setting sous la forme (name, flags, value-as-string) ; les pairs plus anciens écrivent (name, type-specific-binary-value) sans indicateurs. Voir Setting. |
| INTERSERVER_SECRET | 54441 | Query | Ajoute à Query le champ inter-serveur auth_hash — un SHA-256 salé du secret du cluster, et non le secret brut. Les clients externes envoient une chaîne vide. Voir Inter-server authentication. |
| OPEN_TELEMETRY | 54442 | ClientInfo | Ajoute le trace context OpenTelemetry à ClientInfo. |
| DISTRIBUTED_DEPTH | 54448 | ClientInfo | Ajoute le champ distributed_depth à ClientInfo. |
| INITIAL_QUERY_START_TIME | 54449 | ClientInfo | Ajoute le champ initial_time (Int64, largeur fixe). |
| PROFILE_EVENTS | 54451 | ProfileEvents | Le serveur émet des paquets ProfileEvents pendant l’exécution de la requête. |
| PARALLEL_REPLICAS | 54453 | ClientInfo | Ajoute à ClientInfo des champs de coordination des replicas parallèles. |
| CUSTOM_SERIALIZATION | 54454 | Block (Column) | Ajoute l’octet has_custom_serialization après la type string de chaque colonne. |
| ADDENDUM | 54458 | Handshake | Le client envoie un addendum (quota_key) après l’échange de handshake. |
| PARAMETERS | 54459 | Query | Ajoute la liste des paramètres au corps de Query. |
| SERVER_QUERY_TIME_IN_PROGRESS | 54460 | Progress | Ajoute le champ elapsed_ns à Progress. |
| PASSWORD_COMPLEXITY_RULES | 54461 | ServerHello | Ajoute à ServerHello une liste de motifs regex de politique de mot de passe et de messages lisibles par l’utilisateur. |
| INTERSERVER_SECRET_V2 | 54462 | ServerHello | Ajoute à ServerHello un nonce UInt64 de 8 octets. Utilisé pour la signature des requêtes inter-serveurs ; les clients externes le décodent et l’ignorent. |
| TOTAL_BYTES_IN_PROGRESS | 54463 | Progress | Ajoute à Progress le champ total_bytes_to_read (VarUInt), entre total_rows et wrote_rows. |
| TIMEZONE_UPDATES | 54464 | TimezoneUpdate | Ajoute le paquet serveur TimezoneUpdate (type 17). Corps : un seul String contenant le timezone de la session. Envoyé uniquement par l’initialiseur de la table function input, juste après le bloc de schéma d’entrée, afin que le client analyse les lignes qu’il envoie avec la session_timezone du serveur. Voir TimezoneUpdate. |
| SPARSE_SERIALIZATION | 54465 | Block (Column) | Le serveur peut définir has_custom_serialization = 1 et émettre une colonne encodée en sparse. Wire format : type sur 1 octet (0x01 = SPARSE), puis flux de décalages VarUInt terminé par EOG, puis les valeurs non par défaut encodées de manière dense dans le type interne. Voir kind_stack and sparse encoding. |
| SSH_AUTHENTICATION | 54466 | Flux d’authentification | Ajoute l’authentification SSH par challenge-response. Opt-in : le client envoie un user de la forme " SSH KEY AUTHENTICATION " + <real_user> avec un mot de passe vide pour la déclencher. Voir SSH challenge-response authentication. |
| TABLE_READ_ONLY_CHECK | 54467 | TablesStatusResponse | Ajoute un indicateur is_readonly à la ligne de chaque table dans TablesStatusResponse. Les clients externes qui n’émettent pas TablesStatusRequest ne voient aucun changement du wire format. |
| SYSTEM_KEYWORDS_TABLE | 54468 | tables système | Le serveur renseigne system.keywords afin que le clickhouse-client canonique puisse autocompléter les keywords. Aucun changement du wire format du protocole natif. |
| ROWS_BEFORE_AGGREGATION | 54469 | ProfileInfo | Ajoute applied_aggregation (Bool) et rows_before_aggregation (VarUInt) à ProfileInfo, dans cet ordre à la fin. |
| CHUNKED_PROTOCOL | 54470 | Tramage de connexion | Le tramage par fragments de chaque paquet encapsule chaque corps de paquet. Négocié dans Addendum. ServerHello contient la préférence du serveur pour chaque direction ; Addendum contient le choix final du client. Voir chunked framing. |
| VERSIONED_PARALLEL_REPLICAS_PROTOCOL | 54471 | ServerHello, Addendum | Les deux côtés échangent une version VarUInt du protocole de coordination des répliques parallèles. Le champ de ServerHello est placé immédiatement après protocol_version (avant timezone). Le champ d’Addendum est ajouté après les chaînes du protocole fragmenté. Valeur actuelle : 7 (DBMS_PARALLEL_REPLICAS_PROTOCOL_VERSION). |
| INTERSERVER_EXTERNALLY_GRANTED_ROLES | 54472 | Query | Ajoute un champ String external_roles au corps de Query, entre le terminateur des settings et le hachage du secret inter-server. Les clients externes envoient une liste de rôles vide (un seul octet 0x00, c.-à-d. VarUInt 0 dans une enveloppe String). |
| V2_DYNAMIC_AND_JSON_SERIALIZATION | 54473 | Column body | Le serveur peut émettre la sérialisation V2 pour les types de colonnes Dynamic et JSON — cela détermine la version de state_prefix utilisée. Voir versioned types. |
| SERVER_SETTINGS | 54474 | ServerHello | Le serveur diffuse ses server settings non par défaut sous forme de liste à la fin de ServerHello, après nonce. Format : triplets (key, flags, value) terminés par une clé vide — identique à la settings list du Query packet. |
| QUERY_AND_LINE_NUMBERS | 54475 | ClientInfo | Ajoute script_query_number (VarUInt) et script_line_number (VarUInt) à la fin de ClientInfo. Utilisé par clickhouse-client pour attribuer les erreurs dans les scripts multi-instructions ; les clients externes envoient 0, 0. |
| JWT_IN_INTERSERVER | 54476 | ClientInfo | Ajoute un indicateur de présence JWT de type UInt8 ainsi qu’un String jwt optionnel à la fin de ClientInfo. Les clients externes (sans JWT) envoient l’octet 0x00. (Écrit DBMS_MIN_REVISON_WITH_JWT_IN_INTERSERVER en C++ — notez la faute de frappe dans le nom de la constante.) |
| QUERY_PLAN_SERIALIZATION | 54477 | ServerHello, QueryPlan packet | ServerHello ajoute VarUInt query_plan_serialization_version après les server settings. Introduit également ClientPacket::QueryPlan (code 13) pour le transport inter-server de plans de requête préconstruits — les clients externes ne l’envoient jamais. |
| PARALLEL_BLOCK_MARSHALLING | 54478 | Block (Column) | Le serveur peut encapsuler les colonnes dans ColumnBLOB (compressé inline) pour le traitement parallèle. Dépend du fait que la requête ait la compression activée ET rows > 1 ; sinon, le format binaire habituel des colonnes s’applique. Les clients qui n’activent jamais la compression sur les Query packets sortants ne voient aucun changement sur le wire. |
| VERSIONED_CLUSTER_FUNCTION_PROTOCOL | 54479 | ServerHello | Ajoute VarUInt cluster_function_protocol_version à la fin de ServerHello. Utilisé pour les table functions *Cluster (s3Cluster, etc.). Les clients externes le décodent et l’ignorent. |
| OUT_OF_ORDER_BUCKETS_IN_AGGREGATION | 54480 | BlockInfo | Ajoute le champ 3 (out_of_order_buckets: Vec<Int32>) au flux de BlockInfo balisé par champs. Décodé comme [VarUInt count][Int32]*count. Les clients externes ne l’émettent pas eux-mêmes ; le décodeur lit toute liste non vide envoyée par le serveur. |
| COMPRESSED_LOGS_PROFILE_EVENTS_COLUMNS | 54481 | Log, ProfileEvents, TableColumns | Le serveur peut encapsuler les corps des paquets Log, ProfileEvents et TableColumns dans la trame de compression. À cette version, les trois corps passent par le même chemin de sortie éventuellement compressé, qui ne devient une véritable trame de compression que lorsque la requête a compression = true. Les clients qui n’activent jamais la compression sur les Query packets sortants ne voient aucun changement sur le wire. |
| REPLICATED_SERIALIZATION | 54482 | Block (Column) | Le serveur peut émettre des colonnes avec kind_stack 0x04 = REPLICATED — une forme compacte de type dictionnaire pour les valeurs répétées — voir kind_stack and sparse encoding. En dessous de cette version, l’émetteur développait ces colonnes avant l’envoi. Décodé via recherche d’index (elements[indexes[i]] par row) ; types feuille et types internes Nullable/Array/Tuple/Map/Nested/LowCardinality pris en charge. |
| NULLABLE_SPARSE_SERIALIZATION | 54483 | Block (Column) | Compose la sérialisation sparse avec Nullable(T). En dessous de cette version, l’émetteur développait sparse pour les colonnes Nullable avant l’envoi ; à partir de v54483, les données sur le wire sont sparse-over-Nullable. Voir kind_stack and sparse encoding. |
| PROGRESS_IN_ASYNC_INSERT | 54484 | Progress (INSERT) | Lors d’un INSERT asynchrone (async_insert = 1), une fois l’insert flushé, le serveur envoie un paquet Progress supplémentaire, puis les ProfileEvents de l’insert, avant EndOfStream. Dépend de la version négociée ≥ 54484 ; en dessous, le serveur omet ce Progress final. Le format binaire de Progress est inchangé — seule son émission est nouvelle. En pratique, l’incrément transporte le temps écoulé ; les compteurs de rows écrites sont signalés via les ProfileEvents associés. Un client qui draine déjà les paquets Progress entrelacés n’a besoin d’aucun changement de format, seulement de tolérer un paquet supplémentaire. |
| CLIENT_AGENT_IN_CLIENT_INFO | 54485 | ClientInfo | Ajoute un String client_agent final à ClientInfo. Le client canonique détecte automatiquement un identifiant d’agent dans son environnement (par exemple claude-code, cursor, gemini-cli ou la valeur de la variable AGENT) ; un client externe pour lequel rien n’est détecté envoie une chaîne vide. Obligatoire dès lors que la version négociée est ≥ 54485 — l’omettre désynchronise le reste du Query packet. |
Enveloppe de paquet
VarUInt, et non un octet de largeur fixe. Pour les valeurs inférieures à 128, un VarUInt produit le même octet, mais les implémentations doivent utiliser l’encodage VarUInt afin de rester compatibles si de futurs types de paquets devaient atteindre 128 ou plus.
La référence des messages documente uniquement la charge utile de chaque paquet — les octets situés après le code du type de paquet. La numérotation des champs commence à 1 avec le premier champ de la charge utile.
Tramage par fragments (v54470+)
CHUNKED_PROTOCOL est négociée (voir le handshake), chaque paquet sur le wire est tramé par fragments. Ce tramage est propre à chaque sens : client→serveur et serveur→client sont négociés séparément et peuvent au final utiliser des modes différents (avec fragments ou sans encapsulation).
Format binaire par paquet :
VarUInt se trouve dans le flux découpé en fragments : c’est le premier octet de la charge utile du paquet (le premier octet du premier fragment), et non un octet distinct envoyé avant le tramage. La charge utile fragmentée de chaque paquet correspond à l’intégralité de [VarUInt packet_type_code][message body] de l’enveloppe de paquet. Un client qui laisse le type de paquet en dehors du flux découpé en fragments amène le pair à lire cet octet de type comme le premier octet de la taille de fragment u32, ce qui désynchronise la connexion.
Un même paquet peut être réparti sur plusieurs fragments si le tampon d’écriture se remplit en plein milieu du paquet ; la coupure peut se produire n’importe où, y compris au milieu du VarUInt du type de paquet. Le lecteur concatène les charges utiles des fragments et traite le zéro final sur 4 octets comme une limite de paquet transparente — il le consomme, mais ne le transmet pas à ce qui lit les corps de paquet.
Les paquets sans corps restent enveloppés : un paquet d’un seul octet comme Ping ou Pong devient [u32 size = 1][0x04][u32 0] une fois le découpage en fragments négocié. Toute mention ailleurs sur cette page d’un « octet unique sur le réseau » correspond à la forme antérieure au découpage en fragments.
Négociation. ServerHello et Addendum transportent chacun deux champs String, un par direction, avec des valeurs tirées de {"chunked", "notchunked", "chunked_optional", "notchunked_optional"} :
chunked/notchunkedsont stricts : ce côté exige exactement ce mode.- Les variantes
_optionalsont souples : elles acceptent le mode choisi par l’autre côté.
| Préférence serveur | Préférence client | Valeur retenue |
|---|---|---|
*_optional | n’importe laquelle | suivre le CLIENT (son starts_with("chunked")) |
| n’importe laquelle | *_optional | suivre le SERVEUR |
chunked strict | chunked strict | chunked |
notchunked strict | notchunked strict | notchunked |
| incompatibilité stricte | incompatibilité stricte | erreur de protocole — la connexion DOIT être interrompue |
ClientHello → ServerHello (préférences du serveur) → Addendum (valeurs négociées du client). Le basculement vers le tramage s’applique à chaque octet envoyé après l’écriture de l’Addendum. L’Addendum lui-même, le ClientHello et le ServerHello sont toujours envoyés sans tramage.
Cycle de vie de la connexion
HANDSHAKE, READY, READING_RESPONSE, ou terminée. Comme le protocole ne prend pas en charge le multiplexage, un client qui envoie une nouvelle requête avant d’avoir entièrement lu la réponse précédente entrelace les octets dans le flux de données transmis et corrompt le flux.
États
HANDSHAKE → READY → READING_RESPONSE → READY — avec la boucle Ping/Pong sur elle-même, et toutes les transitions d’échec convergent vers l’unique sink Terminated.
| State | Description |
|---|---|
HANDSHAKE | État initial après l’ouverture de la connexion TCP. Seuls les messages de handshake sont valides. Passe à READY en cas de succès ou se termine en cas d’échec. |
READY | Au repos. Le client peut envoyer Ping, Query, ou fermer la connexion. La connexion peut rester en READY indéfiniment (sous réserve de idle_connection_timeout, voir les limites de connexion). |
READING_RESPONSE | État atteint lorsque le client envoie une Query. Le client doit vider intégralement le flux de réponse du serveur avant de revenir à READY. Le seul paquet client→serveur autorisé ici est Cancel (non décrit sur cette page). |
| Terminated | N’est plus utilisable. Le client doit ouvrir une nouvelle connexion TCP et relancer le handshake. |
Phase de handshake
-
Le client envoie
ClientHelloavec la version maximale du protocole qu’il prend en charge. -
Le client lit la réponse et la traite selon le type de paquet :
Type de paquet Action Hello(0)Décode ServerHello. Calculenegotiated_version = min(client_ver, server_ver). Passe à l’étape 3.Exception(2)Décode Exception. Le retourne comme erreur et termine la connexion.anything else Violation du protocole. Termine la connexion. -
Si
negotiated_version ≥ 54458(la fonctionnalitéADDENDUM), le client envoie unAddendum. Cette décision repose sur la version négociée, et non sur la version déclarée du client.
READY ; en cas d’erreur, elle est interrompue.
Phase Ping
READY, le flux est :
-
Le client envoie
Ping. -
Le client lit la réponse :
Type de paquet Action Pong(4)Disponibilité confirmée. Revenir à READY.Exception(2)Décoder Exceptionet la renvoyer en erreur.tout autre cas Violation du protocole.
Phase de requête
EndOfStream ou une Exception.
À partir de READY, le flux est :
En cas d’erreur à n’importe quelle étape, le serveur envoie une Exception au lieu de EndOfStream, ce qui met fin à la requête.
-
Le client envoie
Queryavec unquery_idunique (généralement un UUID). -
Le client envoie les tables externes éventuelles, puis le marqueur Data vide. Le paquet Data vide a
table_name = "",num_columns = 0,num_rows = 0. Le serveur ne commence pas à exécuter la requête tant qu’il n’a pas reçu ce marqueur. -
Le client passe à
READING_RESPONSEet vide son tampon d’écriture. -
Le client lit les paquets de réponse en boucle et les traite selon leur type :
Type de paquet Action Data(1)Décode le bloc. Le premier paquet Data est l’en-tête de schéma ; les suivants sont des blocs de résultat (à accumuler) ; un bloc vide est un marqueur de délimitation. num_rows == 0n’est pas la fin de la requête.Progress(3)Métriques d’exécution. Chaque paquet est un incrément par rapport au précédent — à accumuler localement. EndOfStream(5)Requête terminée. Quitter la boucle et revenir à READY.ProfileInfo(6)Données de profiling post-exécution. Totals(7)Bloc des totaux d’agrégation (même format binaire que Data). Extremes(8)Bloc des valeurs min/max (même format binaire que Data). Log(10)Ligne du journal du serveur. TableColumns(11)Métadonnées des valeurs par défaut des colonnes. ProfileEvents(14)Compteurs de performance. Exception(2)Décode et renvoie comme erreur. Quitter la boucle et revenir à READY.tout autre type Inattendu pendant la phase de requête. Mettre fin à la connexion.
EndOfStream ou d’une Exception gérée, la connexion revient à READY. Une violation du protocole ou une erreur d’E/S y met fin.
Le cas
num_rows == 0 piège souvent les nouvelles implémentations. Un bloc de zéro ligne est un marqueur de délimitation ou un en-tête de schéma, pas un signal de fin de flux. Seuls EndOfStream ou Exception mettent fin à la réponse.Phase INSERT
INSERT ; le serveur répond avec un bloc de schéma décrivant la table cible ; le client envoie des paquets Data contenant les lignes, puis le marqueur Data vide ; le serveur termine avec EndOfStream ou Exception.
À partir de READY, la requête SQL est un INSERT de la forme INSERT INTO <table> [(<cols>)] VALUES — sans littéral VALUES (...) intégré à la requête, puisque les données des lignes transitent via des paquets Data. Le flux :
- Le client envoie
Queryavecbodycontenant la requête SQL INSERT. - Le client envoie les éventuelles tables externes (cas rare pour INSERT). Contrairement à la phase de requête, il n’envoie pas ici de marqueur Data vide. Le paquet
QuerydeINSERTest envoyé avec des données en attente ; le bloc vide de fin de données est donc reporté à l’étape 5. L’envoyer avant le bloc de schéma amènerait le serveur à l’interpréter comme la fin du flux de lignes, à terminer l’INSERT sans aucune ligne, puis à analyser le premier vrai paquet de lignes comme un paquet de niveau supérieur parasite. - Le client consomme les paquets de métadonnées (TableColumns, Progress, ProfileInfo, Log, ProfileEvents) jusqu’à lire le paquet Data de schéma — un Block avec 0 ligne, mais la structure complète des colonnes (noms et types). Le bloc de schéma fait foi : les lignes envoyées ensuite par le client doivent correspondre à cette structure de colonnes.
- Le client envoie un ou plusieurs blocs de données. Pour chaque bloc, il écrit
VarUInt(ClientPacket::Data = 2), puisString("")pour le nom vide de la table externe, puis le Block. Les types de colonnes doivent correspondre, par position, à ceux des colonnes du bloc de schéma. - Le client envoie le terminateur de fin d’entrée : un paquet Data avec un Block vide (0 colonne, 0 ligne).
- Le client consomme le flux de réponse jusqu’à
EndOfStream(succès) ouException(échec).
async_insert = 1, le serveur place les lignes en file d’attente et les écrit dans le cadre d’un lot. À la version négociée ≥ 54484 (PROGRESS_IN_ASYNC_INSERT), une fois l’écriture terminée, le serveur émet un paquet Progress supplémentaire, immédiatement suivi des ProfileEvents de l’INSERT, puis de EndOfStream. En dessous de 54484, le serveur omet ce Progress final. Ce paquet est un Progress ordinaire ; comme le serveur réinitialise le pipeline de requête avant d’y intégrer les compteurs d’écriture, l’incrément ne contient en pratique que le temps écoulé, et les statistiques sur les lignes et octets écrits parviennent au client via les ProfileEvents associés. Un client qui consomme déjà les paquets Progress entrelacés à l’étape 6 n’a qu’à accepter un paquet de plus.
La connexion revient à l’état READY à la réception de EndOfStream ou d’une Exception gérée. Les violations du protocole et les erreurs d’E/S y mettent fin.
Référence des messages
Type utilise :
VarUInt— entier non signé à longueur variable (voir VarUInt).String— octets préfixés par un VarUInt (voir String).UInt8,Int32, etc. — entiers little-endian à largeur fixe.Bool— un seul octet,0x00ou0x01.
Role indique qui utilise chaque champ :
- client — défini par les clients externes.
- inter-serveur — n’a de sens que pour la communication de serveur à serveur ; les clients externes écrivent une valeur par défaut.
- universal — utilisé par les deux.
ClientHello (type de paquet 0)
| # | Champ | Type | Rôle | Description |
|---|---|---|---|---|
| 1 | client_name | String | universel | Identifiant du client (p. ex., "clickhouse-client") |
| 2 | version_major | VarUInt | universel | Version majeure du client |
| 3 | version_minor | VarUInt | universel | Version mineure du client |
| 4 | protocol_version | VarUInt | universel | Version maximale du protocole prise en charge par le client |
| 5 | database | String | universel | Nom de la base de données par défaut |
| 6 | user | String | universel | Nom d’utilisateur pour l’authentification |
| 7 | password | String | universel | Mot de passe (en texte brut) |
ServerHello (type de paquet 0)
| # | Champ | Type | Rôle | Condition | Description |
|---|---|---|---|---|---|
| 1 | server_name | String | universel | toujours | Identifiant du serveur |
| 2 | version_major | VarUInt | universel | toujours | Version majeure du serveur |
| 3 | version_minor | VarUInt | universel | toujours | Version mineure du serveur |
| 4 | protocol_version | VarUInt | universel | toujours | Version du protocole du serveur |
| 4a | parallel_replicas_protocol_version | VarUInt | universel | VERSIONED_PARALLEL_REPLICAS_PROTOCOL (v54471) | Version du protocole de coordination des répliques parallèles du serveur. Position sur le wire : immédiatement après protocol_version, avant timezone. Valeur actuelle : 7. |
| 5 | timezone | String | universel | TIMEZONE (v54058) | Fuseau horaire du serveur (par ex. : "UTC") |
| 6 | display_name | String | universel | DISPLAY_NAME (v54372) | Nom du serveur lisible par l’humain |
| 7 | version_patch | VarUInt | universel | VERSION_PATCH (v54401) | Version corrective du serveur |
| 8 | proto_send_chunked_srv | String | universel | CHUNKED_PROTOCOL (v54470) | Mode de fragmentation sortante préféré du serveur. L’une des valeurs suivantes : "chunked", "notchunked", "chunked_optional", "notchunked_optional". Voir tramage par fragments. Se trouve AVANT password_complexity_rules sur le wire même si sa version d’activation est plus élevée. |
| 9 | proto_recv_chunked_srv | String | universel | CHUNKED_PROTOCOL (v54470) | Mode de fragmentation entrante préféré du serveur. Même ensemble de valeurs que le champ 8. |
| 10 | password_complexity_rules | Rule[] | universel | PASSWORD_COMPLEXITY_RULES (v54461) | Politique de mot de passe du serveur. VarUInt count suivi de count × Rule. Voir ci-dessous. |
| 11 | nonce | UInt64 | inter-serveur | INTERSERVER_SECRET_V2 (v54462) | Nonce aléatoire LE de 8 octets. Le schéma de signature des query inter-serveurs du serveur l’utilise. Les clients externes DOIVENT le décoder (pour conserver l’alignement du flux) et DEVRAIENT ignorer sa valeur. |
| 12 | server_settings | Setting[] | universel | SERVER_SETTINGS (v54474) | Diffusion des settings non par défaut du serveur. Format : zéro, un ou plusieurs triplets (String key, VarUInt flags, String value), terminés par une clé vide. Identique à la liste des settings du paquet Query. |
| 13 | query_plan_serialization_version | VarUInt | universel | QUERY_PLAN_SERIALIZATION (v54477) | Version de sérialisation du plan de query prise en charge par le serveur. Les clients externes la décodent puis l’ignorent. |
| 14 | cluster_function_protocol_version | VarUInt | universel | VERSIONED_CLUSTER_FUNCTION_PROTOCOL (v54479) | Version du protocole de fonction de table *Cluster du serveur. Les clients externes la décodent puis l’ignorent. |
password_complexity_rules :
| # | Champ | Type | Description |
|---|---|---|---|
| 1 | pattern | String | Expression régulière à laquelle un mot de passe conforme doit correspondre. |
| 2 | message | String | Explication lisible par l’humain affichée lorsqu’un mot de passe ne respecte pas cette règle. |
Pour borner l’utilisation des ressources face à un serveur hostile ou mal configuré, plafonnez la valeur décodée de
count à 256 entrées et limitez chaque String pattern et message à 4096 octets. Une valeur count de 0 (aucune paire ensuite) est le cas le plus courant sur les serveurs sans politique de mot de passe configurée.Addendum (sans type de paquet)
ADDENDUM (v54458). Envoyé immédiatement après la fin du handshake. Il ne s’agit pas d’un type de paquet distinct — les champs sont transmis bruts sur le wire, sans octet de préfixe de type de paquet.
| # | Champ | Type | Rôle | Condition | Description |
|---|---|---|---|---|---|
| 1 | quota_key | String | universel | toujours | Clé de quota de ressource pour les quotas à clé côté serveur. Les clients qui n’utilisent pas de quota à clé envoient une chaîne vide. |
| 2 | proto_send_chunked | String | universel | CHUNKED_PROTOCOL (v54470) | Fragmentation sortante négociée du client : "chunked" ou "notchunked". Calculée à partir de proto_recv_chunked_srv de ServerHello. |
| 3 | proto_recv_chunked | String | universel | CHUNKED_PROTOCOL (v54470) | Fragmentation entrante négociée du client. Calculée à partir de proto_send_chunked_srv. |
| 4 | parallel_replicas_protocol_version | VarUInt | universel | VERSIONED_PARALLEL_REPLICAS_PROTOCOL (v54471) | Version du protocole de coordination des répliques parallèles prise en charge par le client. Les clients externes ne participant pas aux requêtes distribuées DEVRAIENT tout de même envoyer une version valide (actuellement 7) afin que la vérification de compatibilité du serveur réussisse. |
Ping (type de paquet 4)
0x04 avant le tramage par fragments ; lorsque le découpage en fragments est négocié, l’octet devient la charge utile d’un fragment d’un octet (voir tramage par fragments).
Pong (type de paquet 4)
0x04 avant le tramage par fragments ; lorsque le découpage en blocs est négocié, cet octet devient la charge utile d’un octet d’un bloc (voir le tramage par fragments).
Exception (type de paquet 2)
| # | Champ | Type | Rôle | Description |
|---|---|---|---|---|
| 1 | code | Int32 | universel | Code d’erreur |
| 2 | name | String | universel | Classe d’exception (par ex. "DB::Exception") |
| 3 | message | String | universel | Message d’erreur lisible par un humain |
| 4 | stack_trace | String | universel | Stack trace côté serveur |
| 5 | has_nested (obsolète) | Bool | universel | Octet de compatibilité obsolète. Toujours écrit sous la forme false par le serveur |
Query (type de paquet 1)
| # | Champ | Type | Rôle | Condition | Description |
|---|---|---|---|---|---|
| 1 | query_id | String | universel | toujours | Identifiant unique de la requête (UUID) |
| 2 | client_info | ClientInfo | universel | CLIENT_INFO (v54032) | Voir ClientInfo |
| 3 | settings | Setting[] | universel | toujours | Voir Setting. Toujours présent (terminé par une clé vide) ; seul l’encodage propre à chaque setting dépend de la version — voir la note sur l’encodage dans Setting. Un client ne doit pas omettre ce champ pour les versions négociées inférieures à 54429. |
| 3a | external_roles | String | universel | INTERSERVER_EXTERNALLY_GRANTED_ROLES (v54472) | Liste sérialisée des noms de rôles accordés par une source externe. Liste vide = octet 0x00 (VarUInt 0) encapsulé dans une String ([VarUInt 1][0x00] sur le wire). Les clients externes envoient toujours une liste vide. |
| 4 | auth_hash | String | inter-serveur | INTERSERVER_SECRET (v54441) | Hash d’authentification inter-serveur — pas la valeur brute du secret de cluster. Voir Inter-server authentication ci-dessous. Les clients externes (et toute InitialQuery) envoient une chaîne vide. |
| 5 | stage | VarUInt | universel | toujours | Étape du traitement de la requête. 0 = FetchColumns, 1 = WithMergeableState, 2 = Complete, 3 = WithMergeableStateAfterAggregation, 4 = WithMergeableStateAfterAggregationAndLimit, 7 = QueryPlan. Les valeurs 3/4 apparaissent dans les requêtes distribuées ; 7 accompagne un plan de requête sérialisé. Les clients externes envoient normalement 2. |
| 6 | compression | VarUInt | universel | toujours | 0 = désactivé, 1 = activé |
| 7 | query_body | String | universel | toujours | Texte SQL |
| 8 | parameters | Parameter[] | client | PARAMETERS (v54459) | Voir Parameter. Terminé par une clé vide. |
ClientInfo (intégré à Query)
CLIENT_INFO (v54032). (Certains champs de ClientInfo sont conditionnés par des versions ultérieures, comme indiqué champ par champ ci-dessous.)
| # | Champ | Type | Rôle | Condition | Description |
|---|---|---|---|---|---|
| 1 | query_kind | UInt8 | universel | toujours | 0 = NoQuery, 1 = InitialQuery, 2 = SecondaryQuery. Les clients externes envoient 1. |
| 2 | initial_user | String | universel | toujours | Utilisateur ayant initié la requête |
| 3 | initial_query_id | String | universel | toujours | ID de la requête d’origine |
| 4 | initial_address | String | universel | toujours | Adresse du socket du client à l’origine, au format host:port |
| 5 | initial_time | Int64 | client | INITIAL_QUERY_START_TIME (v54449) | Heure de début de la requête (en microsecondes). Encodage sur 8 octets à largeur fixe, et non en VarUInt |
| 6 | query_interface | UInt8 | universel | toujours | 1 = TCP, 2 = HTTP |
| 7 | os_user | String | client | si interface = TCP | Nom d’utilisateur de l’OS |
| 8 | client_hostname | String | client | si interface = TCP | Nom d’hôte de la machine cliente |
| 9 | client_name | String | client | si interface = TCP | Nom de l’application cliente |
| 10 | version_major | VarUInt | universel | si interface = TCP | Version majeure du client |
| 11 | version_minor | VarUInt | universel | si interface = TCP | Version mineure du client |
| 12 | protocol_version | VarUInt | universel | si interface = TCP | Version propre du protocole TCP du client d’origine (DBMS_TCP_PROTOCOL_VERSION), et non la version négociée. La révision du pair détermine uniquement quels champs sont présents ; cette valeur correspond à la version intégrée à la compilation de l’initiateur. Ainsi, lorsqu’un client plus récent communique avec un serveur plus ancien, elle peut être supérieure à la révision négociée ou à celle du serveur. |
| 13 | quota_key | String | universel | QUOTA_KEY_IN_CLIENT_INFO (v54060) | Clé de quota de ressources pour les quotas à clé côté serveur. Les clients qui n’utilisent pas de quota à clé envoient une chaîne vide. |
| 14 | distributed_depth | VarUInt | inter-server | DISTRIBUTED_DEPTH (v54448) | Profondeur d’imbrication de la requête distribuée. Les clients externes envoient 0. |
| 15 | version_patch | VarUInt | universel | VERSION_PATCH (v54401), TCP uniquement | Version de correctif du client |
| 16 | open_telemetry | (ci-dessous) | client | OPEN_TELEMETRY (v54442) | Contexte de trace. Les clients sans tracing envoient 0. |
| 17 | collaborate_with_initiator | VarUInt | inter-server | PARALLEL_REPLICAS (v54453) | Bool encodé en VarUInt. Les clients externes envoient 0. |
| 18 | count_participating_replicas | VarUInt | inter-server | PARALLEL_REPLICAS (v54453) | Les clients externes envoient 0. |
| 19 | number_of_current_replica | VarUInt | inter-server | PARALLEL_REPLICAS (v54453) | Les clients externes envoient 0. |
| 20 | script_query_number | VarUInt | client | QUERY_AND_LINE_NUMBERS (v54475) | Position de l’instruction dans un script multi-instruction, indexée à partir de 1. Les clients externes envoient 0. |
| 21 | script_line_number | VarUInt | client | QUERY_AND_LINE_NUMBERS (v54475) | Numéro de ligne dans le script source, indexé à partir de 1. Les clients externes envoient 0. |
| 22 | jwt_present | UInt8 | inter-server | JWT_IN_INTERSERVER (v54476) | 0 = aucun JWT ; 1 = un JWT suit. Les clients externes sans authentification JWT envoient 0. |
| 23 | jwt | String | inter-server | JWT_IN_INTERSERVER (v54476), si jwt_present=1 | Bearer token JWT, présent uniquement lorsque le champ 22 = 1. |
| 24 | client_agent | String | client | CLIENT_AGENT_IN_CLIENT_INFO (v54485) | Champ final. Identifiant de l’outil/agent client, détecté automatiquement à partir de l’environnement (par ex. claude-code, cursor, gemini-cli ou la variable d’environnement AGENT). Les clients externes sans agent détecté envoient une chaîne vide. Présent sur le chemin Query normal dès que la version négociée est ≥ 54485 (envoyé sur toutes les interfaces, pas uniquement TCP). |
Disposition dépendante de l’interface (champs 7–12)Les champs 7 à 12 ci-dessus correspondent à la branche TCP. Lorsque
query_interface (champ 6) n’est pas TCP, ces champs sont remplacés par une autre disposition sur le wire — il ne s’agit pas simplement de champs omis de façon facultative ; un décodeur doit donc bifurquer selon la valeur du champ 6.query_interface = 2(HTTP) : les informations de requête HTTP relayée par le server sont écrites à la place —http_method(UInt8),http_user_agent(String), puisforwarded_for(String, conditionné parX_FORWARDED_FOR_IN_CLIENT_INFOv54443) ethttp_referer(String, conditionné parREFERER_IN_CLIENT_INFOv54447). Les champsos_user/client_hostname/client_name/version_*/protocol_versionne sont pas présents.- Toute autre interface : aucun des champs TCP (7–12) ni aucun des champs HTTP n’est écrit ; le stream continue directement avec
quota_key.
quota_key (champ 13) et distributed_depth (champ 14) suivent pour toutes les interfaces, puis version_patch (champ 15) n’est écrit que pour TCP.Cette bifurcation concerne surtout le trafic inter-serveurs, où le server initiateur relaie une query arrivée initialement via HTTP. Un décodeur qui lit systématiquement les champs TCP interprétera mal ces paquets — en traitant http_method ou http_user_agent comme quota_key.Authentification inter-serveur
auth_hash) n’est pas le secret partagé du cluster transmis sur le réseau. Envoyer le secret brut ferait à la fois échouer l’authentification et le divulguerait. À la place, un serveur agissant comme client inter-serveur prouve qu’il connaît le secret à l’aide d’un hachage SHA-256 salé :
- Entrer en mode inter-serveur. Le serveur qui se connecte l’indique dans
ClientHello: le champusercontient le marqueur inter-serveur etpasswordest vide. Il ajoute ensuite deux chaînes supplémentaires — le nom du cluster et unsaltde 32 octets nouvellement généré (encodeSHA256d’une valeur aléatoire) — immédiatement après les champsuser/password, dans le même paquetClientHello. Le serveur lit ces deux chaînes avant d’envoyerServerHello, donc un client doit les écrire d’emblée ; attendreServerHellod’abord provoque un interblocage, car le serveur reste bloqué à les lire. - Obtenir le nonce.
ServerHellocontient un nonceUInt64de 8 octets lorsqueINTERSERVER_SECRET_V2(v54462) est négocié. - Calculer le hachage. Pour chaque paquet Query autre que
InitialQuery, le client écritencodeSHA256(salt + nonce + cluster_secret + query + query_id + initial_user + external_roles)dans le champ 4 — un condensat de 32 octets. (nonceest sous forme de chaîne décimale et n’est présent que si une version ≥ v54462 a été négociée ;external_rolesn’est ajouté que lorsqueINTERSERVER_EXTERNALLY_GRANTED_ROLES(v54472) est négocié.) Pour unInitialQuery, ou lorsqu’aucun secret de cluster n’est configuré, le client écrit à la place une chaîne vide. - Vérifier. Le serveur lit le champ 4 avec une limite de 32 octets et recalcule la même concaténation à l’aide de sa propre copie du secret du cluster ; la connexion est rejetée si les condensats diffèrent.
auth_hash vide.
Paramètre
VarUInt 0, sans indicateurs ni valeur après. Seul l’encodage de chaque paramètre dépend de la version négociée, selon SETTINGS_SERIALIZED_AS_STRINGS (v54429).
v54429+ (STRINGS_WITH_FLAGS) — chaque paramètre est le triplet présenté ici :
| # | Champ | Type | Rôle | Description |
|---|---|---|---|---|
| 1 | key | String | universel | Nom du paramètre. Vide = fin de la liste. |
| 2 | flags | VarUInt | universel | Indicateurs de bits de métadonnées ; voir ci-dessous. |
| 3 | value | String | universel | Valeur du paramètre sous forme de chaîne |
key est vide.
Avant 54429 (BINARY) — chaque paramètre est [String key][type-specific binary value] : le champ flags n’est pas écrit, et la valeur est encodée dans la forme binaire native du paramètre (par exemple, un entier de largeur fixe ou une chaîne préfixée par sa longueur) plutôt que comme chaîne décimale/texte. La liste se termine toujours par un key vide. Un client ciblant une version négociée inférieure à 54429 doit lire et écrire cette forme binaire, et non le triplet ci-dessus. (Les paramètres personnalisés définis par l’utilisateur font exception : ils comportent toujours flags et une valeur de chaîne, dans les deux encodages.)
Le champ flags regroupe :
0x01— Important : le paramètre affecte les résultats de la requête et ne doit pas être ignoré silencieusement par des pairs plus anciens.0x02— Custom : un paramètre personnalisé défini par l’utilisateur.0x0c— un champ de tier sur 2 bits, et non un indicateur indépendant :0x00= Production,0x04= Obsolete,0x08= Experimental,0x0c= Beta. Lisez bien les 2 bits (flags & 0x0c) — un test naïfflags & 0x04classerait à tort Beta (0x0c) comme Obsolete.0x80— HotReload (rechargement de la config sans redémarrage ; défini dans l’enum des indicateurs, rencontré principalement pour les paramètres de coordination).
Paramètre
SELECT {x:UInt64}. Ils sont encodés de façon identique à un Setting avec l’indicateur Custom (0x02) activé, et se terminent de la même manière par une clé vide.
| # | Champ | Type | Rôle | Description |
|---|---|---|---|---|
| 1 | key | String | client | Nom du paramètre. Vide = fin de la liste. |
| 2 | flags | VarUInt | client | Toujours 0x02 (Custom) |
| 3 | value | String | client | Valeur du paramètre sous forme de chaîne. Voir la note ci-dessous concernant les guillemets. |
La valeur du paramètre est la représentation SQL de la valeur, et non un littéral brut. Les paramètres de type chaîne doivent être transmis déjà entre guillemets simples (par exemple, la valeur de
{name:String} est 'Alice', et non Alice) ; sinon, l’analyseur de valeurs du serveur les rejette.Data (type de paquet 1 serveur→client, type de paquet 2 client→serveur)
table_name est inclus avant le Block. Seul l’octet du type de paquet diffère.
| Champ | Type | Rôle | Description |
|---|---|---|---|
| table_name | String | universel | Nom de la table externe. La valeur vide ("") est le cas le plus courant — pour la table principale, le résultat de la requête et le flux de lignes INSERT. Un table_name vide à lui seul n’est pas le marqueur de fin des données (les paquets de lignes INSERT normaux contiennent eux aussi ""). |
| Corps du bloc | — | — | Voir Structure des blocs et des colonnes. |
0 colonnes et 0 lignes — quelle que soit la valeur de table_name. Le serveur traite un paquet client Data comme terminateur uniquement lorsque le bloc décodé est vide (block.empty()) ; un paquet avec table_name = "" et un bloc non vide est un paquet de lignes ordinaire, pas un terminateur. Ainsi, un flux de lignes INSERT est une séquence de blocs Data non vides suivie d’un bloc Data vide qui y met fin.
Les variantes de bloc et leur signification sont décrites dans Variantes de bloc.
Progress (type de paquet 3)
Progress précédent, et non des totaux cumulés. Avant l’envoi, le serveur lit ses compteurs et les réinitialise atomiquement à zéro, puis calcule elapsed_ns comme le temps écoulé depuis le dernier envoi. Un client doit donc accumuler localement les paquets successifs pour obtenir des totaux cumulés — traiter un paquet comme une valeur absolue fait reculer l’affichage de progression ou provoque un sous-comptage dès que plusieurs paquets arrivent.
| # | Champ | Type | Rôle | Condition | Description |
|---|---|---|---|---|---|
| 1 | rows | VarUInt | universel | toujours | Lignes lues depuis le paquet précédent (à ajouter au total cumulé) |
| 2 | bytes | VarUInt | universel | toujours | Octets lus depuis le paquet précédent (à ajouter au total cumulé) |
| 3 | total_rows | VarUInt | universel | toujours | Incrément du nombre total estimé de lignes à lire ; à cumuler (peut valoir 0 dans un paquet donné) |
| 4 | total_bytes | VarUInt | universel | TOTAL_BYTES_IN_PROGRESS (v54463) | Incrément du nombre total estimé d’octets à lire ; à cumuler. Se situe ENTRE total_rows et wrote_rows dans l’encodage binaire. |
| 5 | wrote_rows | VarUInt | universel | WRITE_CLIENT_INFO (v54420) | Lignes écrites depuis le paquet précédent (pour INSERT) ; à cumuler |
| 6 | wrote_bytes | VarUInt | universel | WRITE_CLIENT_INFO (v54420) | Octets écrits depuis le paquet précédent (pour INSERT) ; à cumuler |
| 7 | elapsed_ns | VarUInt | universel | SERVER_QUERY_TIME_IN_PROGRESS (v54460) | Nanosecondes écoulées depuis le paquet précédent (un delta, pas le temps total de la requête) ; à cumuler |
ProfileInfo (type de paquet 6)
| # | Champ | Type | Rôle | Condition | Description |
|---|---|---|---|---|---|
| 1 | rows | VarUInt | universel | toujours | Nombre total de lignes traitées |
| 2 | blocks | VarUInt | universel | toujours | Nombre total de blocs traités |
| 3 | bytes | VarUInt | universel | toujours | Nombre total d’octets traités |
| 4 | applied_limit | Bool | universel | toujours | Indique si une clause LIMIT a été appliquée |
| 5 | rows_before_limit | VarUInt | universel | toujours | Nombre de lignes avant LIMIT |
| 6 | obsolete | Bool | universel | toujours | Octet de compatibilité obsolète. Le serveur écrit toujours true ici et le client l’ignore à la lecture ; ce n’est pas un indicateur signifiant que « rows_before_limit a été calculé ». L’état significatif de la limite correspond au champ 4 (applied_limit) conjointement au champ 5. À lire puis à ignorer. |
| 7 | applied_aggregation | Bool | universel | ROWS_BEFORE_AGGREGATION (v54469) | Indique si GROUP BY a été appliqué |
| 8 | rows_before_aggregation | VarUInt | universel | ROWS_BEFORE_AGGREGATION (v54469) | Nombre de lignes avant agrégation |
Totaux (type de paquet 7)
WITH TOTALS. Le format binaire est identique à Data : une chaîne table_name (toujours vide), suivie d’un bloc. Seul l’octet indiquant le type de paquet diffère.
Extremes (type de paquet 8)
extremes est activé. Le format binaire de transmission est identique à Data. Le bloc contient exactement 2 lignes : la ligne 0 contient le minimum de chaque colonne, la ligne 1 contient le maximum.
Log (type de paquet 10)
send_logs_level ; voir la transmission des logs en continu).
Même format d’enveloppe et de corps que Data. Le bloc a un num_columns = 8 fixe et un schéma prédéfini. Chaque ligne de log constitue une ligne sur les 8 colonnes, et un paquet Log peut contenir de nombreuses lignes.
| # | Nom | Type | Description |
|---|---|---|---|
| 1 | event_time | DateTime | Horodatage de l’événement (secondes depuis l’époque Unix) |
| 2 | event_time_microseconds | UInt32 | Composante en microsecondes |
| 3 | host_name | String | Nom d’hôte du serveur qui émet le log |
| 4 | query_id | String | ID de la requête à laquelle le log appartient |
| 5 | thread_id | UInt64 | ID du thread du système d’exploitation |
| 6 | priority | Int8 | Niveau de log (priorité Poco : 1 = Fatal, … 8 = Trace) |
| 7 | source | String | Nom du logger |
| 8 | text | String | Texte du message de log |
ProfileEvents (type de paquet 14)
num_columns = 6 fixe et un schéma prédéfini. Chaque événement constitue une ligne.
| # | Nom | Type | Description |
|---|---|---|---|
| 1 | host_name | String | Nom d’hôte du serveur |
| 2 | current_time | DateTime | Horodatage de l’événement |
| 3 | thread_id | UInt64 | ID du thread |
| 4 | type | Enum8 | Type d’événement : 1 = incrément (compteur), 2 = jauge. Le stockage sous-jacent utilise un octet signé. |
| 5 | name | String | Nom de l’événement (p. ex., "Query", "NetworkReceiveBytes") |
| 6 | value | Int64 | Valeur du compteur ou mesure de la jauge |
Le type d’élément de la colonne
value n’est pas fixe d’un paquet à l’autre — les anciens serveurs émettent UInt64, les plus récents Int64. Lisez la chaîne de type de la colonne dans l’en-tête du bloc plutôt que de supposer une taille donnée.TableColumns (type de paquet 11)
COLUMN_DEFAULTS_METADATA (v54410). Le serveur l’envoie avant le bloc de schéma INSERT pour transmettre les métadonnées des valeurs par défaut des colonnes, mais uniquement lorsque la version négociée est ≥ 54410 et que le paramètre input_format_defaults_for_omitted_fields est activé. En dessous de 54410, le paquet n’est jamais envoyé ; un ancien client ne doit donc pas l’attendre — le bloc de schéma Data arrive directement. Un client v54410+ doit être prêt à l’un ou l’autre ordre : un TableColumns facultatif, puis le bloc de schéma.
| # | Champ | Type | Rôle | Description |
|---|---|---|---|---|
| 1 | external_table | String | universal | Nom de la table externe. Vide = table principale. |
| 2 | columns_description | String | universal | Définitions textuelles des colonnes, par ex. "id Int32, name String DEFAULT ''". Texte libre — à analyser comme une chaîne. |
Corps compressé à partir de v54481+À partir d’une version négociée ≥ 54481 (
COMPRESSED_LOGS_PROFILE_EVENTS_COLUMNS), le serveur écrit les deux champs via le même flux de sortie avec compression éventuelle ; lorsque la requête a compression = true, l’intégralité du corps TableColumns (external_table + columns_description) se trouve dans la trame de compression, et le client la lit via le flux décompressé correspondant. Lorsque la requête n’utilise pas de compression, le corps est transmis sans compression, exactement comme indiqué dans le tableau ci-dessus. C’est important pour les réponses de schéma INSERT : un client qui gère la compression pour Log et ProfileEvents, mais pas pour TableColumns, lira mal la réponse lorsque la compression de la requête est activée.TimezoneUpdate (type de paquet 17)
TIMEZONE_UPDATES (v54464). Envoyé à un seul endroit : l’initialiseur de la table function input (une query de la forme INSERT INTO <table> SELECT ... FROM input('<structure>'), qui transmet des lignes depuis le client). Juste après que le serveur a envoyé le bloc Data du schéma d’entrée (voir la phase INSERT), il émet TimezoneUpdate avec la valeur courante de session_timezone du contexte de la query, afin que le client interprète les lignes qu’il s’apprête à envoyer avec le même fuseau horaire. Le serveur n’émet pas ce paquet pour des changements arbitraires de SET session_timezone au milieu d’une query, ni pour indiquer au client comment formater les blocs de résultats envoyés ensuite.
| # | Champ | Type | Rôle | Description |
|---|---|---|---|---|
| 1 | timezone | String | universel | Le nouveau fuseau horaire par défaut de la session (par ex. "UTC", "Europe/Berlin"). |
TimezoneUpdate DOIT quand même consommer le String final pour conserver l’alignement sur le wire.
Authentification SSH par défi-réponse (types de paquets 11, 12 et 18)
SSH_AUTHENTICATION (v54466) et disponible uniquement sur activation explicite. Une connexion entre dans le flux SSH lorsque ClientHello envoie user = " SSH KEY AUTHENTICATION " + <real_user> (avec les espaces au début et à la fin) et password = "". Le serveur lit le préfixe, le supprime pour retrouver l’utilisateur réel, puis bascule vers le mécanisme de défi-réponse.
| Packet | Code | Direction | Body |
|---|---|---|---|
| SSHChallengeRequest | 11 | Client → Server | (aucun corps) |
| SSHChallenge | 18 | Server → Client | String challenge — octets aléatoires ; l’un des composants de la chaîne signée (voir ci-dessous) |
| SSHChallengeResponse | 12 | Client → Server | String signature — signature SSH sur la concaténation définie ci-dessous, et non sur le défi brut |
ServerHello — le serveur diffère sa réponse Hello jusqu’à ce que l’authentification réussisse :
-
Le client envoie
ClientHelloavec le préfixe marqueur SSH et un mot de passe vide. -
Le client envoie
SSHChallengeRequest(paquet 11). Le serveur n’a pas encore envoyéServerHello— il traite d’abord l’authentification et reste bloqué ici en attendant ce paquet. -
Le serveur répond avec
SSHChallenge, contenant des octets aléatoires (paquet 18). -
Le client construit la chaîne à signer et signe celle-ci, et non le défi brut, puis envoie
SSHChallengeResponse(paquet 12) avec la signature. Le message signé est la concaténation, octet par octet et sans séparateurs, de quatre parties dans cet ordre exact :Part Source decimal(protocol_version)La version du protocole du client sous forme de chaîne ASCII décimale (par ex. "54466") — le numéro de version sous forme de chaîne, et non d’un VarUInt ou d’un entier à largeur fixe. Le serveur valide à l’aide de la même version du protocole que celle reçue dansClientHello.default_databaseLe champ databasedeClientHello(chaîne vide s’il n’y en a pas).userLe nom de l’utilisateur réel une fois le préfixe marqueur " SSH KEY AUTHENTICATION "supprimé — le même nom que le serveur retrouve après suppression du préfixe.challengeLes octets bruts de challengeprovenant du paquetSSHChallenge. -
Le serveur vérifie la signature à l’aide de la clé publique enregistrée de l’utilisateur, en reconstruisant la même chaîne
decimal(protocol_version) + default_database + user + challenge. En cas de succès, il envoieServerHello— la même réponse que dans le flux par mot de passe — et la négociation initiale se poursuit normalement (Addendum, etc.) ; en cas d’échec, il renvoie uneExceptionet met fin à la connexion. Un client qui signe uniquement les octets bruts du défi échouera à l’authentification.
C’est l’inverse de la négociation initiale par mot de passe, où
ServerHello suit immédiatement ClientHello. Avec l’authentification SSH, ServerHello n’est envoyé qu’après vérification de la signature, de sorte que le challenge-réponse SSH s’intercale dans la négociation initiale avant tout ServerHello.Référence des types de paquets
Client → serveur
| Code | Nom | Format du corps | Description |
|---|---|---|---|
| 0 | Hello | ClientHello | Initialisation de la négociation |
| 1 | Query | Query | Demande d’exécution de requête |
| 2 | Data | Data | Bloc de données (données INSERT, tables externes, marqueur de fin des données) |
| 3 | Cancel | (aucun corps) | Annuler la requête en cours |
| 4 | Ping | Ping | Vérification de disponibilité |
| 5 | TablesStatusRequest | non spécifié | Vérification de l’état des tables |
| 6 | KeepAlive | non spécifié | Maintien de la connexion active |
| 7 | Scalar | non spécifié | Bloc de données scalaire |
| 8 | IgnoredPartUUIDs | non spécifié | Parts à exclure de la requête |
| 9 | ReadTaskResponse | non spécifié | Réponse de lecture du cluster S3 |
| 10 | MergeTreeReadTaskResponse | non spécifié | Réponse de tâche de lecture parallèle |
| 11 | SSHChallengeRequest | Authentification SSH | Demande de challenge d’authentification SSH |
| 12 | SSHChallengeResponse | Authentification SSH | Réponse au challenge d’authentification SSH |
| 13 | QueryPlan | non spécifié | Plan de requête |
Serveur → Client
| Code | Nom | Format du corps | Description |
|---|---|---|---|
| 0 | Hello | ServerHello | Réponse de négociation initiale |
| 1 | Data | Data | Bloc de données de résultat |
| 2 | Exception | Exception | Erreur |
| 3 | Progress | Progress | Progression de l’exécution de la requête |
| 4 | Pong | Pong | Réponse de vérification de disponibilité |
| 5 | EndOfStream | (aucun corps) | Exécution de la requête terminée |
| 6 | ProfileInfo | ProfileInfo | Données de profilage post-exécution |
| 7 | Totals | Totals | Ligne GROUP BY WITH TOTALS |
| 8 | Extremes | Extremes | Valeurs min/max (bloc de 2 lignes) |
| 9 | TablesStatusResponse | non spécifié | Réponse d’état des tables |
| 10 | Log | Log | Lignes de journal d’exécution de la requête |
| 11 | TableColumns | TableColumns | Descriptions de colonnes pour les valeurs par défaut |
| 12 | PartUUIDs | non spécifié | ID uniques de parts |
| 13 | ReadTaskRequest | non spécifié | Demande de tâche de lecture du cluster |
| 14 | ProfileEvents | ProfileEvents | Compteurs de performances |
| 15 | MergeTreeAllRangesAnnouncement | non spécifié | Initialisation de la lecture parallèle |
| 16 | MergeTreeReadTaskRequest | non spécifié | Attribution de tâche de lecture parallèle |
| 17 | TimezoneUpdate | TimezoneUpdate | Mise à jour du fuseau horaire du serveur |
| 18 | SSHChallenge | SSH auth | Challenge d’authentification SSH |
Configuration
- Paramètres de la couche de transport — options de socket TCP et délais d’expiration, qui affectent le comportement de la connexion TCP elle-même.
- Paramètres de la couche applicative — paramètres réglables par requête transmis dans la liste des paramètres du paquet Query, qui influent sur ce que le serveur envoie sur le réseau ou sur la façon dont ces données sont encapsulées.
- Paramètres hors périmètre — paramètres souvent confondus avec les paramètres du protocole, mais qui contrôlent en réalité l’exécution SQL ou le stockage.
Paramètres de la couche transport
Options de socket
| Option | Par défaut | Côté | Description |
|---|---|---|---|
TCP_NODELAY | activé | les deux | Algorithme de Nagle désactivé. Les petits paquets sont envoyés immédiatement. |
SO_KEEPALIVE | activé (client), valeur par défaut de l’OS (serveur) | asymétrique | Sondes TCP keepalive au niveau du noyau. Le client l’active explicitement lorsque tcp_keep_alive_timeout > 0. Le serveur hérite de la valeur par défaut de l’OS. |
SO_RCVBUF / SO_SNDBUF | valeurs par défaut de l’OS | — | Taille des tampons de socket. Non ajustée par le protocole. |
Délais d’attente
| Setting | Default | Unit | Side | Description |
|---|---|---|---|---|
connect_timeout | 10 | secondes | client | Délai d’attente pour établir la connexion TCP initiale. |
handshake_timeout_ms | 10000 | millisecondes | client | Délai d’attente pour recevoir ServerHello pendant la négociation initiale. |
send_timeout | 300 | secondes | les deux | Si aucun octet ne peut être écrit pendant cet intervalle, la connexion lève une exception. |
receive_timeout | 300 | secondes | les deux | Si aucun octet ne peut être lu pendant cet intervalle, la connexion lève une exception. |
tcp_keep_alive_timeout | 290 | secondes | client | Durée d’inactivité avant que l’OS n’envoie la première sonde TCP keepalive. |
receive_data_timeout_ms | 2000 | millisecondes | client | Délai d’attente pour recevoir le premier paquet Data d’une réplique. |
connect_timeout_with_failover_ms | 1000 | millisecondes | client | Délai d’attente de connexion par tentative lors du parcours des répliques. |
connect_timeout_with_failover_secure_ms | 1000 | millisecondes | client | Délai d’attente de connexion par tentative lors du parcours des répliques via TLS. |
hedged_connection_timeout_ms | 50 | millisecondes | client | Délai d’attente de connexion par tentative pour les requêtes hedgées. |
poll_interval | 10 | secondes | serveur | Granularité de la boucle de vérification des connexions inactives et de l’arrêt du serveur. |
Limites de connexion
| Paramètre | Défaut | Unité | Côté | Description |
|---|---|---|---|---|
max_connections | 4096 | nombre | serveur | Nombre maximal de connexions TCP concurrentes. |
idle_connection_timeout | 3600 | secondes | serveur | Durée maximale pendant laquelle une connexion inactive peut rester ouverte. |
tcp_close_connection_after_queries_num | 0 (illimité) | nombre | serveur | Nombre maximal de requêtes par connexion avant fermeture forcée. |
tcp_close_connection_after_queries_seconds | 0 (illimité) | secondes | serveur | Durée de vie totale maximale d’une connexion, quelle que soit l’activité. |
Paramètres de la couche applicative
Compression
| Paramètre | Par défaut | Unité | Description |
|---|---|---|---|
network_compression_method | "LZ4" | chaîne | Codec de compression utilisé lorsque l’indicateur compression du paquet Query est activé. Valeurs : "LZ4", "LZ4HC", "ZSTD", "NONE". |
network_zstd_compression_level | 1 | 1–15 | Niveau ZSTD lorsque network_compression_method == "ZSTD". |
compression du paquet Query (champ 6) active ou désactive la compression ; ces paramètres sélectionnent le codec utilisé lorsqu’elle est activée.
Transmission des logs
| Paramètre | Par défaut | Unité | Description |
|---|---|---|---|
send_logs_level | "fatal" | string | Niveau de log minimum. Valeurs : "none", "fatal", "error", "warning", "information", "debug", "trace". |
send_logs_source_regexp | "" | string | Filtre Regex appliqué à la source du logger. Vide = toutes les sources sont acceptées. |
send_logs_level est défini sur une valeur autre que "none", le serveur émet des paquets Log pendant l’exécution de la requête.
Rapport de progression
| Paramètre | Valeur par défaut | Unité | Description |
|---|---|---|---|
interactive_delay | 100000 | microsecondes | Intervalle minimal visé entre deux paquets Progress consécutifs. |
Enveloppe de résultat
| Paramètre | Par défaut | Unité | Description |
|---|---|---|---|
extremes | false | bool | Lorsque la valeur est true, le serveur envoie un paquet Extremes avec les valeurs min/max pour chaque colonne. |
max_result_rows | 0 (illimité) | nombre | Limite du nombre de lignes transmises. Le comportement est contrôlé par result_overflow_mode. |
max_result_bytes | 0 (illimité) | octets non compressés | Limite du volume d’octets non compressés. Le comportement est contrôlé par result_overflow_mode. |
result_overflow_mode | "throw" | string | "throw" met fin au flux avec Exception ; "break" envoie des résultats partiels suivis de EndOfStream. |
INSERT asynchrone
| Setting | Default | Unit | Description |
|---|---|---|---|
async_insert | true | bool | Si true, les données INSERT sont mises en file d’attente côté serveur, puis regroupées par lots. |
wait_for_async_insert | true | bool | Si true (avec async_insert activé), le serveur ne renvoie la réponse qu’une fois les données en file d’attente écrites sur le stockage. |
wait_for_async_insert_timeout | 120 | secondes | Durée maximale pendant laquelle le serveur attend un flush avant de renvoyer la réponse. |
Traçage distribué
| Paramètre | Par défaut | Unité | Description |
|---|---|---|---|
opentelemetry_start_trace_probability | 0.0 | probabilité de 0 à 1 | Probabilité, côté serveur, d’associer le contexte OpenTelemetry à la télémétrie de réponse. |
Paramètres hors du périmètre
max_threads— parallélisme lors de l’exécution de la requête.max_memory_usage— limite de mémoire par requête.max_block_size,preferred_block_size_bytes— dimensionnement interne des blocs pendant le query processing ; les blocs transmis sur le réseau en sont indépendants.compile_expressions— compilation JIT ; CPU uniquement.async_insert_max_data_size— buffer de file d’attente côté serveur.- Tous les paramètres
input_format_*etoutput_format_*à l’exception de la familleinput_format_native_*/output_format_native_*— les paramètres non-nativesélectionnent ou ajustent d’autres formats (par exemple via HTTP) et ne modifient pas les blocsDatadu protocole natif.
*_native_* font exception : ils modifient les octets à l’intérieur des blocs Data en native TCP ; une implémentation du protocole doit donc en tenir compte. output_format_native_encode_types_in_binary_format fait passer le champ type de la colonne d’une chaîne textuelle à un encodage binaire du type, output_format_native_write_json_as_string émet les colonnes JSON comme une String, et output_format_native_use_flattened_dynamic_and_json_serialization sélectionne le layout FLATTENED Dynamic/JSON. Comme ces paramètres affectent le body du bloc plutôt que le packet envelope, ils sont décrits dans la spécification Native Format — voir column wire layout et types versionnés.
Glossaire
- Requête normale (
SELECT, etc.) : envoyé après le paquet Query et les éventuels paquets Data de table externe pour signaler « plus de données externes ». Le serveur commence alors l’exécution. INSERT: le client n’envoie pas de marqueur avant le schéma. Le serveur envoie d’abord le bloc de schéma, le client transmet ensuite ses blocs Data de lignes, puis envoie seulement après le paquet Data vide pour terminer le flux de lignes. Envoyer un marqueur vide avant le bloc de schéma serait interprété comme une fin immédiate des lignes et entraînerait la perte des données.
0 ou false).
Version négociée — min(client_version, server_version), calculé pendant la négociation initiale. Détermine quelles fonctionnalités sont actives pendant toute la durée de vie de la connexion.
Packet — un message sur le réseau : un code de type de paquet VarUInt suivi d’un body dont le format dépend du type. Voir packet envelope.
Code de type de paquet — le VarUInt initial d’un paquet qui identifie son format. Les valeurs 0–18 sont actuellement attribuées. Voir la référence des types de paquet.
Flux de réponse — la séquence de paquets émise par le serveur pendant une requête. Sa longueur est ouverte et il se termine par exactement un EndOfStream (succès) ou une Exception (échec). Voir la phase de requête.
bloc de schéma — le header block (un Block avec des colonnes mais 0 ligne) que le serveur envoie pendant la phase INSERT pour annoncer les formes de colonnes attendues avant que le client n’envoie les données.
Settings list — une séquence de tuples (key, flags, value) dans le body de Query, terminée par une key vide. Transporte une configuration de couche applicative propre à chaque requête. Voir Setting.
Stage — un champ VarUInt du paquet Query (champ 5) qui contrôle jusqu’où le serveur exécute la requête. Les clients externes envoient généralement 2 (Complete) ; les requêtes distribuées et les query plans sérialisés utilisent des valeurs plus élevées. Voir le champ 5 de Query pour l’ensemble complet des valeurs sur le réseau.
Terminator — un paquet qui termine un flux. La réponse à Query se termine par EndOfStream (succès) ou Exception (échec). Le flux d’entrée du client se termine par le marqueur Data vide.