Собственный протокол — это бинарный протокол с установлением соединения, по которому клиенты и серверы ClickHouse взаимодействуют по TCP. Он передаёт SQL-запросы, данные результатов, полезную нагрузку INSERT, телеметрию выполнения и сообщения об ошибках. Именно этот протокол используется клиентом командной строки, C++ и большинством сторонних нативных драйверов.
На этой странице рассматривается сам протокол: кадрирование пакетов, машина состояний соединения, согласование версий и тело каждого сообщения, кроме Block. Байты внутри пакетов семейства Data (то есть Block, его столбцы и кодировки отдельных типов) — отдельная тема, описанная в спецификации Native Format.
Для всего протокола справедливы несколько свойств. Протокол бинарный и позиционный: тегов полей нет, кроме BlockInfo, поэтому один смещённый байт нарушает синхронизацию всего, что идёт дальше. Он работает с сохранением состояния, и каждое TCP-соединение обрабатывает только один запрос за раз — мультиплексирования нет. Для целых чисел фиксированной ширины используется порядок байтов little-endian.
Обзор
| Свойство | Значение |
|---|---|
| Транспорт | TCP, при необходимости с TLS |
| Порядок байтов | Little-endian для целых чисел фиксированной ширины |
| Кодирование | Бинарное и позиционное (без тегов полей, кроме BlockInfo) |
| Модель соединения | С сохранением состояния, по одному запросу за раз, без мультиплексирования |
| Версионирование | Согласуется при рукопожатии; отдельные возможности зависят от версии |
| Формат данных | Native Format для всех табличных данных |
Каждое передаваемое по сети сообщение начинается с кода типа пакета VarUInt, за которым следует тело, структура которого зависит от этого кода и согласованной версии протокола.
Соединение проходит через три фазы — однократное рукопожатие, затем любое количество обменов Ping или Query, после чего соединение закрывается:
sequenceDiagram
autonumber
participant C as Client
participant S as Server
C->>S: TCP connect
rect rgb(220, 235, 255)
Note over C,S: Handshake
C->>S: ClientHello (name, version, db, user, password)
S->>C: ServerHello (server_name, version, [timezone, display_name, ...])
Note over C,S: negotiated_version = min(client, server)
opt negotiated_version ≥ 54458
C->>S: Addendum (quota_key)
end
end
rect rgb(220, 245, 225)
Note over C,S: Query phase
C->>S: Query packet (ClientInfo, settings, params, SQL)
C->>S: External-table Data packets (0 or more)
C->>S: Empty Data marker — the "go" signal
loop until EndOfStream or Exception
S->>C: Data / Progress / Log / ProfileInfo / Totals / ...
end
S->>C: EndOfStream
end
Протокол нативного TCP всегда передаёт табличные данные в формате Native, независимо от наличия предложения FORMAT в SQL. Повторное форматирование в RowBinary, CSV, JSON и так далее — задача клиента; оно выполняется после декодирования блоков Native. (HTTP-интерфейс использует другой кодовый путь и действительно учитывает предложение FORMAT; HTTP здесь не рассматривается.)
Безопасность
Защита транспортного уровня (TLS)
TLS работает на транспортном уровне, ниже протокола. Когда TLS включён, шифруется весь TCP-трафик, а сообщения протокола остаются побайтно идентичными независимо от того, используется TLS или нет.
Аутентификация
Аутентификация происходит во время рукопожатия, в сообщении ClientHello. Поля user и password передаются как строки в открытом виде, поэтому учетные данные при передаче защищаются шифрованием на транспортном уровне (TLS).
Пустое поле user сервер интерпретирует как настроенного пользователя сеанса по умолчанию: настройку сервера default_session_user (значение по умолчанию — default), которая может быть переопределена для каждого listener в разделе protocols. Если для сервера настроен пустой пользователь сеанса по умолчанию или версия сервера ниже 26.8, пустое поле user отклоняется с исключением. Обратите внимание, что clickhouse-client никогда не отправляет пустое имя пользователя: на стороне клиента он подставляет default.
Аутентификация SSH по схеме challenge-response доступна начиная с версии протокола 54466 — см. Аутентификация SSH по схеме challenge-response.
Межсерверный секрет
Для выполнения распределённого запроса серверы аутентифицируют друг друга, подтверждая знание общего секрета, — без передачи самого секрета по сети. Каждый Query содержит 32-байтный SHA-256 auth_hash в поле 4 Query, вычисленный на основе salt, nonce, настроенного секрета и запроса; принимающий сервер вычисляет его заново и сравнивает. Это контролируется возможностью INTERSERVER_SECRET (v54441). Внешние клиенты всегда отправляют здесь пустую строку. См. Межсерверная аутентификация.
Версионирование и флаги возможностей
Согласование версии
И клиент, и сервер сообщают максимальную поддерживаемую версию протокола при рукопожатии. Согласованная версия — меньшая из двух:
negotiated_version = min(client_version, server_version)Каждое последующее сообщение использует согласованную версию, чтобы определить, какие поля присутствуют в двоичном представлении.
Флаги возможностей
Возможность определяется версией протокола, в которой она появилась, и считается активной, если согласованная версия больше или равна этому номеру.
Таблица возможностей
| Возможность | Версия | Влияет на | Влияние на бинарный формат |
|---|---|---|---|
| BLOCK_INFO | all | Block | Добавляет префикс BlockInfo (is_overflows, bucket_number) к каждому Block. |
| CLIENT_INFO | 54032 | Query | Добавляет блок ClientInfo в тело Query. |
| TIMEZONE | 54058 | ServerHello | Добавляет поле timezone в ServerHello. |
| QUOTA_KEY_IN_CLIENT_INFO | 54060 | ClientInfo | Добавляет поле quota_key в ClientInfo. |
| DISPLAY_NAME | 54372 | ServerHello | Добавляет поле display_name в ServerHello. |
| VERSION_PATCH | 54401 | ServerHello, ClientInfo | Добавляет поле version_patch в оба пакета. |
| SERVER_LOGS | 54406 | Log | Сервер отправляет пакеты Log, если задан send_logs_level. |
| COLUMN_DEFAULTS_METADATA | 54410 | TableColumns | Сервер может отправить пакет TableColumns (тип 11) с метаданными значений столбцов по умолчанию перед блоком схемы INSERT/входных данных. Отправляется только если согласованная версия ≥ 54410 и включён input_format_defaults_for_omitted_fields. Для более ранних версий пакет никогда не отправляется; клиенты не должны его ожидать. |
| WRITE_CLIENT_INFO | 54420 | Progress | Добавляет wrote_rows и wrote_bytes в Progress. (Несмотря на название, это не управляет блоком ClientInfo — за него отвечает CLIENT_INFO (v54032).) |
| SETTINGS_SERIALIZED_AS_STRINGS | 54429 | Query (кодирование settings) | Меняет способ кодирования всегда присутствующего списка settings; не определяет, будут ли settings отправлены. В v54429+ каждый setting записывается как (name, flags, value-as-string); более старые узлы записывают (name, type-specific-binary-value) без flags. См. Setting. |
| INTERSERVER_SECRET | 54441 | Query | Добавляет в Query межсерверное поле auth_hash — salted SHA-256 от секрета кластера, а не сам секрет. Внешние клиенты отправляют пустую строку. См. Inter-server authentication. |
| OPEN_TELEMETRY | 54442 | ClientInfo | Добавляет trace context OpenTelemetry в ClientInfo. |
| DISTRIBUTED_DEPTH | 54448 | ClientInfo | Добавляет поле distributed_depth в ClientInfo. |
| INITIAL_QUERY_START_TIME | 54449 | ClientInfo | Добавляет поле initial_time (Int64, фиксированной ширины). |
| PROFILE_EVENTS | 54451 | ProfileEvents | Сервер отправляет пакеты ProfileEvents во время выполнения запроса. |
| PARALLEL_REPLICAS | 54453 | ClientInfo | Добавляет в ClientInfo поля координации параллельных реплик. |
| CUSTOM_SERIALIZATION | 54454 | Block (Column) | Добавляет байт has_custom_serialization после строки типа каждого столбца. |
| ADDENDUM | 54458 | Handshake | Клиент отправляет addendum (quota_key) после обмена рукопожатием. |
| PARAMETERS | 54459 | Query | Добавляет список параметров в тело Query. |
| SERVER_QUERY_TIME_IN_PROGRESS | 54460 | Progress | Добавляет поле elapsed_ns в Progress. |
| PASSWORD_COMPLEXITY_RULES | 54461 | ServerHello | Добавляет в ServerHello список regex-шаблонов политики паролей и человекочитаемых сообщений. |
| INTERSERVER_SECRET_V2 | 54462 | ServerHello | Добавляет в ServerHello 8-байтовый UInt64 nonce. Используется для подписи межсерверных запросов; внешние клиенты декодируют его и игнорируют. |
| TOTAL_BYTES_IN_PROGRESS | 54463 | Progress | Добавляет поле total_bytes_to_read (VarUInt) в Progress, между total_rows и wrote_rows. |
| TIMEZONE_UPDATES | 54464 | TimezoneUpdate | Добавляет серверный пакет TimezoneUpdate (тип 17). Тело: один String, содержащий часовой пояс сеанса. Отправляется только инициализатором table function input, сразу после блока входной схемы, чтобы клиент разбирал отправляемые строки с session_timezone сервера. См. TimezoneUpdate. |
| SPARSE_SERIALIZATION | 54465 | Block (Column) | Сервер может установить has_custom_serialization = 1 и отправить столбец в разреженном кодировании. Формат передачи данных: 1-байтовый kind (0x01 = SPARSE), затем поток смещений VarUInt, завершённый EOG, затем не-default значения, плотно закодированные во внутреннем типе. См. kind_stack and sparse encoding. |
| SSH_AUTHENTICATION | 54466 | Auth flow | Добавляет SSH challenge-response authentication. Включается явно: клиент отправляет user в виде " SSH KEY AUTHENTICATION " + <real_user> с пустым паролем, чтобы активировать её. См. SSH challenge-response authentication. |
| TABLE_READ_ONLY_CHECK | 54467 | TablesStatusResponse | Добавляет флаг is_readonly в строку каждой таблицы в TablesStatusResponse. Внешние клиенты, которые не отправляют TablesStatusRequest, не увидят изменений в формате передачи данных. |
| SYSTEM_KEYWORDS_TABLE | 54468 | system tables | Сервер заполняет system.keywords, чтобы стандартный clickhouse-client мог автодополнять ключевые слова. В native protocol изменений формата передачи данных нет. |
| ROWS_BEFORE_AGGREGATION | 54469 | ProfileInfo | Добавляет в ProfileInfo applied_aggregation (Bool) и rows_before_aggregation (VarUInt) именно в таком порядке, в конце. |
| CHUNKED_PROTOCOL | 54470 | Фрейминг соединения | Пофрагментный фрейминг пакетов оборачивает каждое тело пакета. Согласуется в Addendum. ServerHello содержит предпочтение сервера для каждого направления; Addendum содержит окончательный выбор клиента. См. chunked framing. |
| VERSIONED_PARALLEL_REPLICAS_PROTOCOL | 54471 | ServerHello, Addendum | Обе стороны обмениваются версией протокола координации parallel-replicas в виде VarUInt. Поле в ServerHello располагается сразу после protocol_version (перед timezone). Поле в Addendum добавляется после строк chunked-protocol. Текущее значение: 8 (DBMS_PARALLEL_REPLICAS_PROTOCOL_VERSION). Версия 8 добавляет MergeTreeAllRangesAnnouncementResponse (пакет client 14): когда согласованная версия parallel-replicas равна ≥ 8, инициатор отвечает на каждое announcement follower в режиме не-Default авторитетным списком parts для этого потока, и follower ждёт его перед отправкой запросов на чтение. Ниже 8 announcement работает по принципу fire-and-forget. |
| INTERSERVER_EXTERNALLY_GRANTED_ROLES | 54472 | Query | Добавляет поле String external_roles в тело Query, между терминатором settings и хешем interserver-secret. Внешние клиенты отправляют пустой список ролей (один байт 0x00, то есть VarUInt 0 внутри оболочки String). |
| V2_DYNAMIC_AND_JSON_SERIALIZATION | 54473 | Column body | Server может использовать сериализацию V2 для типов столбцов Dynamic и JSON — это определяет, какую версию state_prefix они используют. См. versioned types. |
| SERVER_SETTINGS | 54474 | ServerHello | Server передаёт свои настройки сервера, отличающиеся от значений по умолчанию, в виде списка в конце ServerHello, после nonce. Формат: тройки (key, flags, value), завершаемые пустым key — так же, как список settings в пакете Query. |
| QUERY_AND_LINE_NUMBERS | 54475 | ClientInfo | Добавляет script_query_number (VarUInt) и script_line_number (VarUInt) в конец ClientInfo. Используется clickhouse-client для привязки ошибок в многооператорных скриптах; внешние клиенты отправляют 0, 0. |
| JWT_IN_INTERSERVER | 54476 | ClientInfo | Добавляет признак наличия JWT типа UInt8 и необязательный String jwt в конец ClientInfo. Внешние клиенты (без JWT) отправляют байт 0x00. (В C++ записано как DBMS_MIN_REVISON_WITH_JWT_IN_INTERSERVER — обратите внимание на опечатку в имени константы.) |
| QUERY_PLAN_SERIALIZATION | 54477 | ServerHello, QueryPlan packet | ServerHello добавляет VarUInt query_plan_serialization_version после настроек сервера. Также вводится ClientPacket::QueryPlan (код 13) для межсерверной передачи pre-built query plans — внешние клиенты его никогда не отправляют. |
| PARALLEL_BLOCK_MARSHALLING | 54478 | Block (Column) | Server может оборачивать столбцы в ColumnBLOB (со встроенным сжатием) для параллельной обработки. Используется только если для запроса включено сжатие и rows > 1; в противном случае применяется обычный формат передачи данных столбцов. Клиенты, которые никогда не включают сжатие для исходящих пакетов Query, не увидят изменений в формате передачи данных. |
| VERSIONED_CLUSTER_FUNCTION_PROTOCOL | 54479 | ServerHello | Добавляет VarUInt cluster_function_protocol_version в конец ServerHello. Используется для table function *Cluster (s3Cluster и т. д.). Текущее значение: 8 (DBMS_CLUSTER_PROCESSING_PROTOCOL_VERSION); версия 7 зарезервирована для функции из приватного репозитория (уплотнение Iceberg), а 8 добавляет необязательный read_source_index в межсерверную полезную нагрузку задачи чтения кластера (тело ReadTaskResponse, которое здесь остаётся неуточнённым — см. ниже). Внешние клиенты декодируют его и игнорируют. |
| OUT_OF_ORDER_BUCKETS_IN_AGGREGATION | 54480 | BlockInfo | Добавляет поле 3 (out_of_order_buckets: Vec<Int32>) в поток BlockInfo с тегами полей. Декодируется как [VarUInt count][Int32]*count. Внешние клиенты сами это не отправляют; декодер читает любой непустой список, который отправляет Server. |
| COMPRESSED_LOGS_PROFILE_EVENTS_COLUMNS | 54481 | Log, ProfileEvents, TableColumns | Server может оборачивать тела пакетов Log, ProfileEvents и TableColumns в compression frame. В этой версии все три тела передаются по одному и тому же выходному пути с необязательным сжатием, которое становится полноценным compression frame только при compression = true в запросе. Клиенты, которые никогда не включают сжатие для исходящих пакетов Query, не увидят изменений в формате передачи данных. |
| REPLICATED_SERIALIZATION | 54482 | Block (Column) | Server может выдавать столбцы с kind_stack 0x04 = REPLICATED — компактной формой в стиле словаря для повторяющихся значений — см. kind_stack and sparse encoding. Ниже этой версии writer разворачивал такие столбцы перед отправкой. Декодирование выполняется через поиск по индексу (elements[indexes[i]] для каждой строки); поддерживаются leaf types, а также внутренние типы Nullable/Array/Tuple/Map/Nested/LowCardinality. |
| NULLABLE_SPARSE_SERIALIZATION | 54483 | Block (Column) | Комбинирует разреженную сериализацию с Nullable(T). Ниже этой версии writer разворачивал sparse для столбцов с типом Nullable перед отправкой; начиная с v54483+ данные в формате передачи данных представлены как sparse-over-Nullable. См. kind_stack and sparse encoding. |
| PROGRESS_IN_ASYNC_INSERT | 54484 | Progress (INSERT) | При асинхронной вставке INSERT (async_insert = 1) после сброса вставки Server отправляет дополнительный пакет Progress, затем ProfileEvents этой вставки, перед EndOfStream. Используется только при согласованной версии ≥ 54484; ниже неё Server не отправляет этот завершающий Progress. Формат передачи данных Progress не меняется — новшество только в самой отправке. На практике приращение содержит прошедшее время; счётчики записанных строк передаются через сопутствующий ProfileEvents. Клиенту, который уже читает чередующиеся Progress, не нужно менять формат — достаточно допустить ещё один пакет. |
| CLIENT_AGENT_IN_CLIENT_INFO | 54485 | ClientInfo | Добавляет в конец ClientInfo поле String client_agent. Канонический клиент автоматически определяет идентификатор agent из окружения (например, claude-code, cursor, gemini-cli или значение переменной AGENT); внешний клиент, если ничего не обнаружено, отправляет пустую строку. Обязательно при согласованной версии ≥ 54485 — если его опустить, оставшаяся часть пакета Query будет десинхронизирована. |
| INTERNAL_QUERY_FLAG | 54486 | ClientInfo | Добавляет в конец ClientInfo UInt8 is_internal. Значение 1 — для внутреннего запроса сервера (не инициированного пользователем); оно передаётся в удалённые запросы, чтобы их строки в system.query_log помечались как внутренние; внешние клиенты отправляют 0. Обязательно при согласованной версии ≥ 54486 — если его опустить, оставшаяся часть пакета Query будет десинхронизирована. |
| INTERSERVER_CURRENT_ROLES | 54488 | ClientInfo | Добавляет в конец ClientInfo необязательный список String current_roles ([UInt8 present], затем, если 1, [VarUInt count][String]*count), а для межсерверных запросов расширяет auth_hash, чтобы он включал сериализованный список. Передаёт имена активных (включённых) ролей инициатора, чтобы вторичный узел применял политики строк так же, вместо возврата к ролям пользователя по умолчанию. Заполняется только для межсерверных запросов и только при push_external_roles_in_interserver_queries = 1; внешние клиенты отправляют 0 (отсутствует). Обязательно при согласованной версии ≥ 54488 — если опустить байт наличия, оставшаяся часть пакета Query будет десинхронизирована. |
| CURRENT_AGGREGATION_VARIANT_SELECTION_METHOD | 54489 | Агрегация (двухуровневые бакеты) | Метод агрегации для одного ключа String определяется параметром enable_packed_string_keys_in_aggregation. Узел с ревизией ниже этой всегда использует упакованный метод и не знает этот параметр, поэтому при обмене двухуровневыми бакетами один и тот же ключ оказался бы в разных бакетах. Для такого узла инициатор действует безопасно: обнуляет пороги двухуровневой агрегации и сам повторно распределяет его одноуровневые блоки по бакетам. Сравнение выполняется с ревизией узла, а не с его версией, поскольку метод может измениться в рамках одного релиза. В native protocol изменений формата передачи данных нет. |
| HTTP_HANDLER_IN_CLIENT_INFO | 54490 | ClientInfo | Добавляет http_handler_name и http_request_url (оба типа String) в ветвь HTTP ClientInfo сразу после http_referer. Они содержат имя соответствующего определённого в SQL HTTP-обработчика и URL запроса, чтобы currentHandler/currentRequestURL и столбцы http_handler_name/http_request_url в system.query_log оставались заполненными на удалённых шардах для распределённых запросов обработчика. Записываются только при query_interface = HTTP (пустые строки для HTTP-запроса без обработчика); обязательны при согласованной версии ≥ 54490 — если их опустить, оставшаяся часть пакета Query будет десинхронизирована. |
| QUANTILE_DETERMINISTIC_SKIP_DEGREE | 54491 | Block (Column) | Повышает версию состояния AggregateFunction(quantileDeterministic, ...) (и вариантов quantiles/median) с 0 до 1, добавляя завершающий UInt8 skip_degree к каждому сериализованному состоянию. Изменяется только полезная нагрузка состояния этой агрегатной функции; фрейминг столбца не затрагивается, а все остальные агрегатные функции сохраняют собственные версии. Ниже этой ревизии writer выдаёт состояния версии 0, поэтому более ранний узел не затрагивается. |
| STRING_WITH_SIZE_STREAM_SERIALIZATION | 54492 | Block (Column) | Столбцы String переходят от схемы с префиксом длины для каждого значения к отдельному потоку накопительных байтовых смещений (такая же схема смещений используется в Array), передаваемому как есть: [UInt64 × num_rows offsets][data blob]. Зависит от согласованной ревизии; ниже 54492 writer использует префиксы длины для каждого значения. Формат передачи данных не содержит маркера для каждого столбца — обе стороны определяют это только по ревизии. См. String column layout. |
Оболочка пакета
Каждое сообщение в передаваемом двоичном формате имеет одинаковую внешнюю структуру в обоих направлениях:
[VarUInt: packet_type_code] always encoded as VarUInt
[message body] format depends on packet_type_codeПолные таблицы типов пакетов приведены в справочнике по типам пакетов.
Тип пакета — это VarUInt, а не байт фиксированной длины. Для значений меньше 128 VarUInt даёт тот же однобайтный результат, но реализации должны использовать кодирование VarUInt, чтобы сохранить совместимость, если в будущем появятся типы пакетов со значением 128 и выше.
В справочнике по сообщениям описывается только тело каждого пакета — байты после кода типа пакета. Нумерация полей начинается с 1, где первое поле — это первое поле тела.
Фрагментное кадрирование (v54470+)
Когда возможность CHUNKED_PROTOCOL согласована (см. этап рукопожатия), каждый пакет при передаче оборачивается во фрагментное кадрирование. Такое оборачивание выполняется отдельно для каждого направления: client→server и server→client согласуются независимо и в итоге могут работать в разных режимах (с фрагментным кадрированием или без него).
Структура данных в канале для каждого пакета:
<chunk>... one or more chunks; their payloads concatenated form the whole packet
[u32 LE = 0] zero-size terminator marking end of packetСтруктура данных в wire-формате для каждого фрагмента:
[u32 LE: chunk_size] chunk_size in [1, UINT32_MAX]
[chunk_size bytes] packet bytes (see note below)Тип пакета VarUInt находится внутри потока, разбитого на фрагменты: это первый байт полезной нагрузки пакета (первый байт первого фрагмента), а не отдельный байт, отправляемый перед кадрированием. Полезная нагрузка фрагментов каждого пакета представляет собой полный [VarUInt packet_type_code][message body] из конверта пакета. Если клиент оставляет тип пакета вне потока, разбитого на фрагменты, другая сторона считывает этот байт типа как первый байт размера фрагмента u32, что приводит к рассинхронизации соединения.
Один пакет может быть разделен на несколько фрагментов, если буфер пишущей стороны заполняется посреди пакета; разбиение может произойти в любом месте, в том числе внутри VarUInt типа пакета. Читатель объединяет полезные нагрузки фрагментов и рассматривает завершающий 4-байтовый ноль как прозрачную границу пакета — он считывает его, но не передает тому, что читает тела пакетов.
Пакеты без тела все равно оборачиваются: однобайтовый пакет, такой как Ping или Pong, после согласования разбиения на фрагменты становится [u32 size = 1][0x04][u32 0]. Любое описание «один байт в байтовом потоке» в других местах этой страницы относится к форме до разбиения на фрагменты.
Согласование. ServerHello и Addendum содержат по два поля String, по одному для каждого направления, со значениями из {"chunked", "notchunked", "chunked_optional", "notchunked_optional"}:
chunked/notchunked— строгие: эта сторона требует именно этот режим.- Варианты
_optional— гибкие: они принимают любой режим, который выберет другая сторона.
Согласованное значение для каждого направления вычисляется попарно:
| Предпочтение сервера | Предпочтение клиента | Согласованное значение |
|---|---|---|
*_optional |
что угодно | следовать CLIENT (его starts_with("chunked")) |
| что угодно | *_optional |
следовать SERVER |
chunked strict |
chunked strict |
chunked |
notchunked strict |
notchunked strict |
notchunked |
| несовпадение strict | несовпадение strict | ошибка протокола — соединение НЕОБХОДИМО закрыть |
На стороне клиента предпочтение SEND клиента согласуется с предпочтением RECV сервера, и наоборот.
Время применения. Строки согласования передаются без кадрирования: ClientHello → ServerHello (предпочтения сервера) → Addendum (согласованные значения клиента). Переключение на кадрирование применяется ко всем байтам, отправленным после того, как Addendum сброшен на диск. Сам Addendum, ClientHello и ServerHello всегда передаются без кадрирования.
Жизненный цикл соединения
В любой момент соединение находится ровно в одном из четырёх состояний: HANDSHAKE, READY, READING_RESPONSE или завершено. Поскольку протокол не поддерживает мультиплексирование, клиент, который отправляет новый запрос, не дочитав предыдущий ответ до конца, перемешивает байты в потоке передачи данных и повреждает поток.
Состояния
stateDiagram-v2
[*] --> HANDSHAKE: TCP connect
HANDSHAKE --> READY: handshake ok
READY --> READING_RESPONSE: send Query
READING_RESPONSE --> READY: EndOfStream / Exception
READY --> READY: Ping / Pong
HANDSHAKE --> Terminated: handshake error
READING_RESPONSE --> Terminated: protocol violation / I/O error
READY --> Terminated: close
Terminated --> [*]
Штатный сценарий идёт прямо вниз — HANDSHAKE → READY → READING_RESPONSE → READY — с самопетлёй Ping/Pong, а все ветви сбоев сходятся в единственное конечное состояние Terminated.
| State | Description |
|---|---|
HANDSHAKE |
Начальное состояние после открытия TCP-соединения. Допустимы только сообщения рукопожатия. При успехе выполняется переход в READY, при сбое соединение завершается. |
READY |
Бездействует. Клиент может отправить Ping, запрос или закрыть соединение. Соединение может оставаться в READY сколь угодно долго (с учётом idle_connection_timeout, см. ограничения соединения). |
READING_RESPONSE |
В это состояние клиент переходит после отправки запроса. Клиент должен полностью вычитать поток ответа сервера, прежде чем вернуться в READY. Единственный допустимый здесь пакет client→server — Cancel (на этой странице не описан). |
| Terminated | Больше не используется. Клиент должен открыть новое TCP-соединение и заново выполнить рукопожатие. |
Фаза рукопожатия
На этом этапе выполняются аутентификация и согласование версии протокола. Это происходит ровно один раз для каждого соединения — прежде всего остального.
TCP-соединение только что установлено, и никакими сообщениями стороны ещё не обменивались. Последовательность:
sequenceDiagram
autonumber
participant C as Client
participant S as Server
C->>S: ClientHello
alt success
S->>C: ServerHello
else error
S->>C: Exception — connection terminates
end
Note over C,S: negotiated_version = min(client, server)
opt negotiated_version ≥ 54458
C->>S: Addendum
end
-
Клиент отправляет
ClientHello, указав максимальную поддерживаемую версию протокола. -
Клиент читает ответ и обрабатывает его в зависимости от типа пакета:
Тип пакета Действие Hello(0)Декодировать ServerHello. Вычислитьnegotiated_version = min(client_ver, server_ver). Перейти к шагу 3.Exception(2)Декодировать Exception. Вернуть ошибку и завершить соединение.anything else Нарушение протокола. Завершить соединение. -
Если
negotiated_version ≥ 54458(возможностьADDENDUM), клиент отправляетAddendum. Это решение принимается на основе согласованной версии, а не версии, объявленной клиентом.
В случае успеха соединение переходит в состояние READY; при любой ошибке оно завершается.
Фаза Ping
Проверка работоспособности на уровне приложения, не зависящая от TCP keepalive. Успешный обмен Ping/Pong подтверждает, что TCP-соединение активно в обоих направлениях и сервер отвечает. Ping не хранит состояние и не связан ни с каким запросом, поэтому несколько последовательных Ping независимы.
Начиная с READY, последовательность такова:
sequenceDiagram
autonumber
participant C as Client
participant S as Server
C->>S: Ping (0x04)
alt responsive
S->>C: Pong (0x04)
else error
S->>C: Exception
end
-
Клиент отправляет
Ping. -
Клиент читает ответ:
Тип пакета Действие Pong(4)Работоспособность подтверждена. Возврат в READY.Exception(2)Декодировать Exceptionи вернуть как ошибку.любой другой Нарушение протокола.
Фаза запроса
Клиент отправляет SQL-оператор; сервер потоково возвращает блоки результатов и телеметрию выполнения. Ответ представляет собой последовательность пакетов, которая завершается ровно одним EndOfStream или Exception.
Начиная с READY, последовательность выглядит так:
sequenceDiagram
autonumber
participant C as Client
participant S as Server
C->>S: Query message
C->>S: External-table Data packets (0 or more)
C->>S: Empty Data marker — end-of-client-data (required)
S->>C: Data — header block (N cols, 0 rows)
loop until EndOfStream or Exception
S->>C: Progress / Log (interleaved)
S->>C: Data — result block (N cols, M rows)
S->>C: Totals / Extremes (aggregation queries)
S->>C: ProfileInfo / ProfileEvents (profiling)
end
S->>C: Data — empty block (boundary marker)
S->>C: Progress — final updates
S->>C: EndOfStream — authoritative end of query
При ошибке на любом этапе сервер отправляет Exception вместо EndOfStream, что приводит к завершению запроса.
-
Клиент отправляет
Queryс уникальнымquery_id(обычно UUID). -
Клиент отправляет все внешние таблицы, затем пустой маркер
Data. У пустого пакетаData:table_name = "",num_columns = 0,num_rows = 0. Сервер не начинает выполнять запрос, пока не получит этот маркер. -
Клиент переходит в
READING_RESPONSEи сбрасывает буфер записи. -
Клиент в цикле читает пакеты ответа и обрабатывает их по типу:
Тип пакета Действие Data(1)Декодировать block. Первый Data— это заголовок схемы; последующие — блоки результата (их нужно накапливать); пустой block — маркер границы.num_rows == 0не означает конец запроса.Progress(3)Метрики выполнения. Каждый пакет — приращение относительно предыдущего, поэтому их нужно накапливать локально. EndOfStream(5)Запрос завершён. Выйти из цикла и вернуться в READY.ProfileInfo(6)Данные профилирования после выполнения. Totals(7)Блок итогов агрегации (тот же формат передачи данных, что и у Data).Extremes(8)Блок минимальных/максимальных значений (тот же формат передачи данных, что и у Data).Log(10)Строка server log. TableColumns(11)Метаданные значений по умолчанию для столбцов. ProfileEvents(14)Счётчики производительности. Exception(2)Декодировать и вернуть как ошибку. Выйти из цикла и вернуться в READY.anything else Неожиданное состояние на этапе запроса. Завершить connection.
При EndOfStream или обработанном Exception connection возвращается в READY. Нарушение protocol или ошибка I/O приводит к её завершению.
Фаза INSERT
Фаза INSERT — это фаза запроса с двумя дополнительными обменами. Клиент отправляет оператор INSERT; сервер отвечает блоком схемы, описывающим целевую таблицу; клиент потоково передаёт пакеты Data со строками, а затем пустой маркер Data; сервер завершает обмен сообщением EndOfStream или Exception.
Начиная с READY, SQL-запрос имеет вид INSERT INTO <table> [(<cols>)] VALUES — без встроенного литерала VALUES (...), поскольку данные строк передаются через пакеты Data. Поток:
sequenceDiagram
autonumber
participant C as Client
participant S as Server
C->>S: Query packet (INSERT body)
C->>S: External-table Data packets (0 or more)
Note over C,S: No empty Data marker here —<br/>it would end the row stream before it starts
opt metadata before schema
S->>C: TableColumns / Progress / ...
end
S->>C: Data packet — schema block (columns, 0 rows)
Note over C,S: Schema block is the contract:<br/>rows sent next must match these column shapes
loop one or more blocks
C->>S: Data packet (rows N)
end
C->>S: Data packet — empty block (0 rows), end-of-input terminator
loop until EndOfStream or Exception
S->>C: Progress / ProfileInfo / Log / ProfileEvents
end
opt async_insert = 1 and negotiated_version ≥ 54484
S->>C: trailing Progress, then insert ProfileEvents
end
S->>C: EndOfStream
- Клиент отправляет
Query, где вbodyуказан SQL-запросINSERT. - Клиент отправляет все внешние таблицы (для
INSERTэто редкость). В отличие от фазы запроса, здесь он не отправляет пустой маркерData. ПакетINSERTQueryотправляется вместе с ожидающими данными, поэтому пустой завершающий блок данных откладывается до шага 5; если отправить его до блока схемы, сервер воспримет его как конец потока строк, завершитINSERTбез строк, а затем разберёт первый реальный пакет строк как лишний пакет верхнего уровня. - Клиент считывает пакеты метаданных (TableColumns, Progress, ProfileInfo, Log, ProfileEvents), пока не получит пакет
Dataсо схемой —Blockс 0 строк, но с полной структурой столбцов (имена и типы). Блок схемы — это контракт: строки, которые клиент отправит дальше, должны соответствовать этим структурам столбцов. - Клиент отправляет блоки данных. Для каждого блока он записывает
VarUInt(ClientPacket::Data = 2), затемString("")для пустого имени внешней таблицы, а затем самBlock. Типы столбцов должны соответствовать столбцам блока схемы по позиции. - Клиент отправляет завершающий маркер конца ввода: пакет
Dataс пустымBlock(0 столбцов, 0 строк). - Клиент считывает поток ответа до
EndOfStream(успех) илиException(ошибка).
Асинхронный INSERT (v54484+). Когда запрос содержит async_insert = 1, сервер ставит строки в очередь и сбрасывает их в хранилище как часть батча. При согласованной версии ≥ 54484 (PROGRESS_IN_ASYNC_INSERT) после завершения сброса сервер отправляет дополнительный пакет Progress, сразу после которого идут ProfileEvents этой вставки, а затем EndOfStream. Ниже 54484 сервер пропускает этот завершающий Progress. Это обычный пакет Progress; поскольку сервер сбрасывает конвейер запроса перед добавлением счётчиков записи, на практике это приращение содержит только затраченное время, а статистика по записанным строкам и байтам поступает клиенту через сопутствующие ProfileEvents. Клиенту, который уже считывает чередующиеся пакеты Progress на шаге 6, достаточно просто принять ещё один пакет.
Соединение возвращается в состояние READY при EndOfStream или обработанном Exception. Нарушения протокола и ошибки ввода-вывода приводят к его завершению.
Справочник сообщений
Поля перечислены в порядке следования в wire-формате. В столбце Type используются:
VarUInt— беззнаковое целое число переменной длины (см. VarUInt).String— байты с префиксомVarUInt(см. String).UInt8,Int32и так далее — целые числа фиксированной длины в little-endian формате.Bool— один байт,0x00или0x01.
Столбец Role показывает, кто использует каждое поле:
- client — задаётся внешними клиентами.
- inter-server — имеет значение только при обмене между серверами; внешние клиенты записывают значение по умолчанию.
- universal — используется в обоих случаях.
В этих таблицах описано только тело каждого пакета, после кода типа пакета.
ClientHello (тип пакета 0)
Клиент → Сервер. Первое сообщение после установления TCP-соединения.
| # | Поле | Тип | Роль | Описание |
|---|---|---|---|---|
| 1 | client_name | String | universal | Идентификатор клиента (например, "clickhouse-client") |
| 2 | version_major | VarUInt | universal | Мажорная версия клиента |
| 3 | version_minor | VarUInt | universal | Минорная версия клиента |
| 4 | protocol_version | VarUInt | universal | Максимальная версия протокола, поддерживаемая клиентом |
| 5 | database | String | universal | Имя базы данных по умолчанию |
| 6 | user | String | universal | Имя пользователя для аутентификации. Пустое значение означает пользователя сеанса сервера по умолчанию (настройка сервера default_session_user; не поддерживается серверами версий ниже 26.8). См. Аутентификация. |
| 7 | password | String | universal | Пароль (в открытом виде) |
ServerHello (тип пакета 0)
Server → Client. Ответ на ClientHello при успешной аутентификации.
| # | Field | Type | Role | Condition | Description |
|---|---|---|---|---|---|
| 1 | server_name | String | universal | always | Идентификатор сервера |
| 2 | version_major | VarUInt | universal | always | Мажорная версия сервера |
| 3 | version_minor | VarUInt | universal | always | Минорная версия сервера |
| 4 | protocol_version | VarUInt | universal | always | Версия протокола сервера |
| 4a | parallel_replicas_protocol_version | VarUInt | universal | VERSIONED_PARALLEL_REPLICAS_PROTOCOL (v54471) | Версия протокола координации параллельных реплик сервера. Позиция в wire-представлении: сразу после protocol_version, перед timezone. Текущее значение: 8. |
| 5 | timezone | String | universal | TIMEZONE (v54058) | Часовой пояс сервера (например, "UTC") |
| 6 | display_name | String | universal | DISPLAY_NAME (v54372) | Человекочитаемое имя сервера |
| 7 | version_patch | VarUInt | universal | VERSION_PATCH (v54401) | Патч-версия сервера |
| 8 | proto_send_chunked_srv | String | universal | CHUNKED_PROTOCOL (v54470) | Предпочтительный исходящий режим разбиения на фрагменты на стороне сервера. Одно из значений: "chunked", "notchunked", "chunked_optional", "notchunked_optional". См. кадрирование с фрагментацией. В wire-представлении находится ПЕРЕД password_complexity_rules, хотя его version gate выше. |
| 9 | proto_recv_chunked_srv | String | universal | CHUNKED_PROTOCOL (v54470) | Предпочтительный входящий режим разбиения на фрагменты на стороне сервера. Тот же набор значений, что и у поля 8. |
| 10 | password_complexity_rules | Rule[] | universal | PASSWORD_COMPLEXITY_RULES (v54461) | Политика сложности паролей на сервере. VarUInt count, затем count × Rule. См. ниже. |
| 11 | nonce | UInt64 | inter-server | INTERSERVER_SECRET_V2 (v54462) | 8-байтовый случайный nonce в формате LE. Он используется в межсерверной схеме подписи запросов. Внешние клиенты ОБЯЗАНЫ декодировать его (чтобы сохранить выравнивание потока) и ДОЛЖНЫ игнорировать его значение. |
| 12 | server_settings | Setting[] | universal | SERVER_SETTINGS (v54474) | Передаваемые сервером настройки, отличающиеся от значений по умолчанию. Формат: ноль или более троек (String key, VarUInt flags, String value), завершённых пустым ключом. То же, что и список настроек в пакете Query. |
| 13 | query_plan_serialization_version | VarUInt | universal | QUERY_PLAN_SERIALIZATION (v54477) | Поддерживаемая сервером версия сериализации плана запроса. Внешние клиенты декодируют и игнорируют. |
| 14 | cluster_function_protocol_version | VarUInt | universal | VERSIONED_CLUSTER_FUNCTION_PROTOCOL (v54479) | Версия протокола табличной функции *Cluster на сервере. Текущее значение: 8. Это значение управляет дополнительными полями в полезной нагрузке межсерверной задачи чтения кластера (в остальном неуточнённом теле ReadTaskResponse); версия 7 зарезервирована для возможности приватного репозитория (Iceberg compaction), а 8 добавляет необязательный read_source_index. Внешние клиенты не участвуют в чтении кластера — они декодируют это поле и игнорируют. |
Rule — элемент password_complexity_rules:
| # | Field | Type | Description |
|---|---|---|---|
| 1 | pattern | String | Шаблон регулярного выражения, которому должен соответствовать пароль. |
| 2 | message | String | Человекочитаемое пояснение, показываемое, если пароль не проходит это правило. |
Этот список отражает конфигурацию политики паролей, заданную оператором сервера, и носит исключительно рекомендательный характер — сервер не применяет эти правила во время рукопожатия. Клиент, предоставляющий возможность смены или установки пароля, может использовать эти правила, чтобы выявлять ошибки до отправки на сервер пароля, не соответствующего требованиям.
Дополнение (без типа пакета)
Client → Server, доступно при ADDENDUM (v54458). Отправляется сразу после завершения обмена рукопожатия. Это не отдельный тип пакета — поля передаются в wire в сыром виде, без байтового префикса типа пакета.
| # | Field | Type | Role | Condition | Description |
|---|---|---|---|---|---|
| 1 | quota_key | String | universal | always | Ключ ресурсной квоты для серверных квот с ключом. Клиенты, не использующие квоту с ключом, отправляют пустую строку. |
| 2 | proto_send_chunked | String | universal | CHUNKED_PROTOCOL (v54470) | Согласованный исходящий режим разбиения на фрагменты на стороне клиента: "chunked" или "notchunked". Вычисляется на основе proto_recv_chunked_srv из ServerHello. |
| 3 | proto_recv_chunked | String | universal | CHUNKED_PROTOCOL (v54470) | Согласованный входящий режим разбиения на фрагменты на стороне клиента. Вычисляется на основе proto_send_chunked_srv. |
| 4 | parallel_replicas_protocol_version | VarUInt | universal | VERSIONED_PARALLEL_REPLICAS_PROTOCOL (v54471) | Поддерживаемая клиентом версия протокола координации параллельных реплик. Внешние клиенты, не участвующие в распределённых запросах, всё равно должны отправлять допустимую версию (сейчас 8), чтобы проверка совместимости на стороне сервера проходила успешно. |
Переключение на фрагментированное кадрирование вступает в силу после сброса этого Дополнения на диск — само Дополнение передаётся без кадрирования.
Ping (тип пакета 4)
Клиент → сервер. Без тела — до разбиения на фрагменты пакет представляет собой один байт 0x04; при согласованном разбиении на фрагменты этот байт становится однобайтовой полезной нагрузкой фрагмента (см. кадрирование с фрагментацией).
Pong (тип пакета 4)
Сервер → Клиент. Без тела — пакет представляет собой один байт 0x04 до кадрирования с фрагментацией; если согласована фрагментация, этот байт становится однобайтной полезной нагрузкой фрагмента (см. кадрирование с фрагментацией).
Исключение (тип пакета 2)
Сервер → Клиент. Отправляется при возникновении ошибки на любом этапе.
| # | Поле | Тип | Роль | Описание |
|---|---|---|---|---|
| 1 | code | Int32 | universal | Код ошибки |
| 2 | name | String | universal | Класс исключения (например, "DB::Exception") |
| 3 | message | String | universal | Читаемое человеком сообщение об ошибке |
| 4 | stack_trace | String | universal | Трассировка стека на стороне сервера |
| 5 | has_nested (устарело) | Bool | universal | Устаревший байт совместимости. Сервер всегда записывает false |
Query (тип пакета 1)
Клиент → Сервер.
| # | Поле | Тип | Роль | Условие | Описание |
|---|---|---|---|---|---|
| 1 | query_id | String | универсальная | всегда | Уникальный идентификатор запроса (UUID) |
| 2 | client_info | ClientInfo | универсальная | CLIENT_INFO (v54032) | См. ClientInfo |
| 3 | settings | Setting[] | универсальная | всегда | См. Setting. Присутствует всегда (завершается пустым ключом); по версии ограничено только кодирование отдельных настроек — см. примечание о кодировании в Setting. Клиент не должен опускать это поле для согласованных версий ниже 54429. |
| 3a | external_roles | String | универсальная | INTERSERVER_EXTERNALLY_GRANTED_ROLES (v54472) | Сериализованный список имён ролей, выданных извне. Пустой список = байт 0x00 (VarUInt 0), обёрнутый в String ([VarUInt 1][0x00] в wire-формате). Внешние клиенты всегда отправляют пустое значение. |
| 4 | auth_hash | String | межсерверная | INTERSERVER_SECRET (v54441) | Хеш межсерверной аутентификации — не исходный секрет кластера. См. Inter-server authentication ниже. Внешние клиенты (и любой InitialQuery) отправляют пустую строку. |
| 5 | stage | VarUInt | универсальная | всегда | Этап обработки запроса. 0 = FetchColumns, 1 = WithMergeableState, 2 = Complete, 3 = WithMergeableStateAfterAggregation, 4 = WithMergeableStateAfterAggregationAndLimit, 7 = QueryPlan. Значения 3/4 встречаются в распределённых запросах; 7 сопровождает сериализованный план запроса. Внешние клиенты обычно отправляют 2. |
| 6 | compression | VarUInt | универсальная | всегда | 0 = отключено, 1 = включено |
| 7 | query_body | String | универсальная | всегда | Текст SQL |
| 8 | parameters | Parameter[] | клиентская | PARAMETERS (v54459) | См. Parameter. Завершается пустым ключом. |
ClientInfo (встроено в Query)
Клиент → Сервер, встроено в тело Query (поле 2). Поддерживается начиная с CLIENT_INFO (v54032). (Некоторые поля внутри ClientInfo поддерживаются только в более поздних версиях — это указано ниже для каждого поля.)
| # | Поле | Тип | Роль | Условие | Описание |
|---|---|---|---|---|---|
| 1 | query_kind | UInt8 | universal | always | 0 = NoQuery, 1 = InitialQuery, 2 = SecondaryQuery. Внешние клиенты отправляют 1. |
| 2 | initial_user | String | universal | always | Пользователь, инициировавший запрос |
| 3 | initial_query_id | String | universal | always | Исходный ID запроса |
| 4 | initial_address | String | universal | always | Адрес сокета исходного клиента. Сервер никогда не разрешает это значение (без поиска hostname или имени сервиса). Для SECONDARY_QUERY (где значение сохраняется и используется, например, в system.query_log и при межсерверной аутентификации) допустимый формат — IPv4 a.b.c.d:port или IPv6 в квадратных скобках [addr]:port, где хост должен быть IP-литералом, а порт — десятичным числом в диапазоне 0..65535; другие формы (например, localhost:9000, host:http, :9000 или путь к UNIX-сокету, такой как /tmp/ch.sock) отклоняются с ошибкой INCORRECT_DATA. Для INITIAL_QUERY сервер перезаписывает это поле реальным адресом другой стороны, поэтому допускается любое значение (значение, не являющееся обычным ip:port, заменяется на значение по умолчанию 0.0.0.0:0). Внешние клиенты должны отправлять собственный ip:port. |
| 5 | initial_time | Int64 | client | INITIAL_QUERY_START_TIME (v54449) | Время начала запроса (в микросекундах). Фиксированная длина — 8 байт, не VarUInt |
| 6 | query_interface | UInt8 | universal | always | 1 = TCP, 2 = HTTP |
| 7 | os_user | String | client | if interface = TCP | Имя пользователя ОС |
| 8 | client_hostname | String | client | if interface = TCP | Имя хоста клиентской машины |
| 9 | client_name | String | client | if interface = TCP | Имя клиентского приложения |
| 10 | version_major | VarUInt | universal | if interface = TCP | Мажорная версия клиента |
| 11 | version_minor | VarUInt | universal | if interface = TCP | Минорная версия клиента |
| 12 | protocol_version | VarUInt | universal | if interface = TCP | Собственная версия TCP-протокола исходного клиента (DBMS_TCP_PROTOCOL_VERSION), не согласованная версия. Ревизия другой стороны определяет только то, какие поля присутствуют; это значение — версия, встроенная у инициатора при компиляции, поэтому у более нового клиента, подключающегося к более старому серверу, оно может быть выше, чем согласованная ревизия или ревизия сервера. |
| 13 | quota_key | String | universal | QUOTA_KEY_IN_CLIENT_INFO (v54060) | Ключ квоты ресурсов для квот с ключом на стороне сервера. Клиенты, не использующие квоту с ключом, отправляют пустую строку. |
| 14 | distributed_depth | VarUInt | inter-server | DISTRIBUTED_DEPTH (v54448) | Глубина вложенности Distributed-запроса. Внешние клиенты отправляют 0. |
| 15 | version_patch | VarUInt | universal | VERSION_PATCH (v54401), TCP only | Патч-версия клиента |
| 16 | open_telemetry | (below) | client | OPEN_TELEMETRY (v54442) | Контекст трассировки. Клиенты без трассировки отправляют 0. |
| 17 | collaborate_with_initiator | VarUInt | inter-server | PARALLEL_REPLICAS (v54453) | Bool в виде VarUInt. Внешние клиенты отправляют 0. |
| 18 | count_participating_replicas | VarUInt | inter-server | PARALLEL_REPLICAS (v54453) | Внешние клиенты отправляют 0. |
| 19 | number_of_current_replica | VarUInt | inter-server | PARALLEL_REPLICAS (v54453) | Внешние клиенты отправляют 0. |
| 20 | script_query_number | VarUInt | client | QUERY_AND_LINE_NUMBERS (v54475) | Позиция оператора в скрипте из нескольких операторов, начиная с 1. Внешние клиенты отправляют 0. |
| 21 | script_line_number | VarUInt | client | QUERY_AND_LINE_NUMBERS (v54475) | Номер строки в исходном скрипте, начиная с 1. Внешние клиенты отправляют 0. |
| 22 | jwt_present | UInt8 | inter-server | JWT_IN_INTERSERVER (v54476) | 0 = JWT отсутствует; 1 = далее следует JWT. Внешние клиенты без JWT-аутентификации отправляют 0. |
| 23 | jwt | String | inter-server | JWT_IN_INTERSERVER (v54476), if jwt_present=1 | JWT Bearer-токен; присутствует только если поле 22 = 1. |
| 24 | client_agent | String | client | CLIENT_AGENT_IN_CLIENT_INFO (v54485) | Завершающее поле. Идентификатор клиентского инструмента/агента, автоматически определяемый из окружения (например, claude-code, cursor, gemini-cli или переменной окружения AGENT). Внешние клиенты без обнаруженного агента отправляют пустую строку. Присутствует в обычном пути Query, если согласованная версия ≥ 54485 (отправляется через все интерфейсы, не только TCP). |
| 25 | is_internal | UInt8 | client | INTERNAL_QUERY_FLAG (v54486) | Завершающее поле. 1 для внутреннего запроса сервера (не инициированного пользователем), передается удаленным запросам, чтобы помечать их как внутренние в system.query_log; не связано с query_kind (поле 1). Внешние клиенты отправляют 0. Присутствует, если согласованная версия ≥ 54486 (отправляется через все интерфейсы, не только TCP). |
| 26 | current_roles | UInt8 [+ список String] | межсерверная | INTERSERVER_CURRENT_ROLES (v54488) | Завершающее поле. [UInt8 present], затем при present = 1 — список [VarUInt count][String]*count имен активных (включенных) ролей инициатора. Позволяет вторичному узлу применять политики строк в контексте ролей инициатора, а не ролей пользователя по умолчанию. Заполняется только для межсерверных запросов и только при push_external_roles_in_interserver_queries = 1; в противном случае, а также для внешних клиентов, present = 0. Байт присутствия обязателен, если согласованная версия ≥ 54488 (отправляется через все интерфейсы, не только TCP). Для межсерверных запросов сериализованный список также включается в auth_hash (см. Межсерверная аутентификация). |
Кодирование OpenTelemetry (поле 16):
[UInt8: has_trace] 0 = no trace data follows, 1 = trace data follows
If has_trace == 1:
[16 bytes: trace_id] byte-swapped per-8-bytes
[8 bytes: span_id] byte-swapped
[String: trace_state] W3C trace state
[UInt8: trace_flags] W3C trace flagsМежсерверная аутентификация
Поле 4 в Query (auth_hash) не является общим секретом кластера в передаваемом по сети виде. Отправка самого секрета и не пройдет аутентификацию, и раскроет его. Вместо этого сервер, выступающий как межсерверный клиент, доказывает знание секрета с помощью SHA-256-хеша с salt:
- Войдите в межсерверный режим. Подключающийся сервер сигнализирует об этом в
ClientHello: полеuserслужит маркером межсерверного режима, аpasswordпусто. Затем он добавляет еще две строки — имя кластера и заново сгенерированный 32-байтныйsalt(encodeSHA256от случайного значения) — сразу после полейuser/password, как часть того же пакетаClientHello. Сервер читает эти две строки до отправкиServerHello, поэтому клиент должен записать их сразу; если сначала ждатьServerHello, возникнет взаимная блокировка, потому что сервер будет заблокирован на их чтении. - Получите nonce.
ServerHelloсодержит 8-байтныйUInt64nonce, когда согласованINTERSERVER_SECRET_V2(v54462). - Вычислите hash. Для каждого пакета Query, кроме
InitialQuery, клиент записываетencodeSHA256(salt + nonce + cluster_secret + query + query_id + initial_user + external_roles + current_roles)в поле 4 — 32-байтный дайджест. (nonce— это его десятичное строковое представление, присутствующее только при согласовании ≥ v54462;external_rolesдобавляется только при согласованииINTERSERVER_EXTERNALLY_GRANTED_ROLES(v54472);current_roles— это сериализованный список имен ролей —[VarUInt count][String]*count, без байта наличия, — добавляемый только при согласованииINTERSERVER_CURRENT_ROLES(v54488) и наличии списка.) ДляInitialQuery, а также если секрет кластера не настроен, клиент вместо этого записывает пустую строку. - Проверьте. Сервер читает поле 4 с ограничением в 32 байта и заново вычисляет ту же конкатенацию, используя свою собственную копию секрета кластера; соединение отклоняется, если дайджесты не совпадают.
Внешние (не межсерверные) клиенты никогда не входят в этот режим и всегда отправляют пустой auth_hash.
Параметр
Кодируется прямо в списке settings в теле Query (пакет Query, поле 3). Список всегда присутствует независимо от согласованной версии и завершается элементом Setting с пустым key — одним VarUInt 0, без последующих flags или value. Только кодирование каждого отдельного параметра зависит от согласованной версии и определяется SETTINGS_SERIALIZED_AS_STRINGS (v54429).
v54429+ (STRINGS_WITH_FLAGS) — каждый параметр представляет собой тройку, показанную ниже:
| # | Поле | Тип | Роль | Описание |
|---|---|---|---|---|
| 1 | key | String | универсальная | Имя параметра. Пустое значение = конец списка. |
| 2 | flags | VarUInt | универсальная | Битовые флаги метаданных; см. ниже. |
| 3 | value | String | универсальная | Значение параметра в виде строки |
Поля 2 и 3 отсутствуют, если key пуст.
До 54429 (BINARY) — каждый параметр имеет вид [String key][type-specific binary value]: поле flags не записывается, а значение кодируется в собственном бинарном формате параметра (например, как целое фиксированной ширины или строка с префиксом длины), а не как десятичная или текстовая строка. Список по-прежнему завершается пустым key. Клиент, работающий с согласованной версией ниже 54429, должен читать и записывать именно эту бинарную форму, а не тройку выше. (Исключение — пользовательские custom settings: в обоих форматах кодирования они всегда содержат flags и строковое значение.)
Поле flags включает:
0x01— Important: параметр влияет на результаты запроса и не должен молча игнорироваться более старыми peer.0x02— Custom: пользовательская custom setting.0x0c— 2-битное поле уровня, а не отдельный флаг:0x00= продакшн,0x04= устаревший,0x08= экспериментальный,0x0c= бета. Считывайте все 2 бита (flags & 0x0c) — простая проверкаflags & 0x04ошибочно классифицирует Beta (0x0c) как Obsolete.0x80— HotReload (перезагрузка config без рестарта; определён в enum флагов, в основном встречается для настроек coordination).
Параметр
Параметры запроса для параметризованных запросов, таких как SELECT {x:UInt64}. Кодируются так же, как настройка с установленным флагом Custom (0x02), и таким же образом завершаются пустым ключом.
| # | Поле | Тип | Роль | Описание |
|---|---|---|---|---|
| 1 | key | String | client | Имя параметра. Пустое значение = конец списка. |
| 2 | flags | VarUInt | client | Всегда 0x02 (Custom) |
| 3 | value | String | client | Значение параметра в строковом виде. См. примечание ниже о кавычках. |
Данные (пакет типа 1 server→client, пакет типа 2 client→server)
В обоих направлениях. Содержит блоки результатов, данные для INSERT, внешние таблицы и маркеры окончания данных.
Формат передачи данных симметричен — в обоих направлениях перед Block идёт префикс table_name. Отличается только байт типа пакета.
[VarUInt: packet_type] 1 (server→client) or 2 (client→server)
[String: table_name] External table name; empty in most cases
[Block] See the Native Format spec for the Block layout| Поле | Type | Role | Описание |
|---|---|---|---|
| table_name | String | universal | Имя внешней таблицы. Пустое значение ("") — обычный случай для основной таблицы, результатов запроса и потока строк INSERT. Само по себе пустое table_name не является маркером конца данных (обычные пакеты строк INSERT тоже содержат ""). |
| Тело блока | — | — | См. Структура блока и столбца. |
Маркер конца данных — это пакет, у которого Block пуст: 0 столбцов и 0 строк, независимо от table_name. Сервер считает клиентский пакет Data терминатором только тогда, когда декодированный блок пуст (block.empty()); пакет с table_name = "" и непустым блоком — это обычный пакет строк, а не терминатор. Таким образом, поток строк INSERT — это последовательность непустых блоков Data, за которой следует один пустой блок Data, завершающий поток.
Варианты блоков и их значение описаны в разделе Варианты блоков.
Прогресс (тип пакета 3)
Сервер → клиент. Отправляется периодически во время выполнения запроса. Все поля имеют тип VarUInt, и каждый пакет содержит приращения относительно предыдущего пакета Progress, а не накопленные итоги. Перед отправкой сервер считывает свои счётчики и атомарно сбрасывает их в ноль, а elapsed_ns вычисляет как разницу во времени с момента предыдущей отправки. Поэтому клиент должен накапливать последовательные пакеты локально, чтобы получить накопленные итоги — если трактовать пакет как абсолютное значение, отображение прогресса начнёт откатываться назад или занижать значения, как только придёт более одного пакета.
| # | Поле | Тип | Роль | Условие | Описание |
|---|---|---|---|---|---|
| 1 | rows | VarUInt | universal | always | Строки, прочитанные с момента предыдущего пакета (добавляйте к накопленному итогу) |
| 2 | bytes | VarUInt | universal | always | Байты, прочитанные с момента предыдущего пакета (добавляйте к накопленному итогу) |
| 3 | total_rows | VarUInt | universal | always | Приращение к оценке общего числа строк для чтения; накапливайте (в конкретном пакете может быть 0) |
| 4 | total_bytes | VarUInt | universal | TOTAL_BYTES_IN_PROGRESS (v54463) | Приращение к оценке общего числа байтов для чтения; накапливайте. В wire-формате находится МЕЖДУ total_rows и wrote_rows. |
| 5 | wrote_rows | VarUInt | universal | WRITE_CLIENT_INFO (v54420) | Строки, записанные с момента предыдущего пакета (для INSERT); накапливайте |
| 6 | wrote_bytes | VarUInt | universal | WRITE_CLIENT_INFO (v54420) | Байты, записанные с момента предыдущего пакета (для INSERT); накапливайте |
| 7 | elapsed_ns | VarUInt | universal | SERVER_QUERY_TIME_IN_PROGRESS (v54460) | Наносекунды, прошедшие с момента предыдущего пакета (дельта, а не общее время запроса); накапливайте |
ProfileInfo (тип пакета 6)
Сервер → Клиент. Отправляется один раз для каждого запроса, ближе к завершению выполнения.
| # | Поле | Тип | Роль | Условие | Описание |
|---|---|---|---|---|---|
| 1 | rows | VarUInt | universal | always | Общее число обработанных строк |
| 2 | blocks | VarUInt | universal | always | Общее число обработанных блоков |
| 3 | bytes | VarUInt | universal | always | Общее число обработанных байт |
| 4 | applied_limit | Bool | universal | always | Был ли применён LIMIT |
| 5 | rows_before_limit | VarUInt | universal | always | Число строк до LIMIT |
| 6 | obsolete | Bool | universal | always | Устаревший байт совместимости. Сервер всегда записывает здесь true, а клиент отбрасывает его при чтении; это не флаг «rows_before_limit был вычислен». Фактическое состояние LIMIT определяется полем 4 (applied_limit) вместе с полем 5. Считайте и игнорируйте. |
| 7 | applied_aggregation | Bool | universal | ROWS_BEFORE_AGGREGATION (v54469) | Был ли применён GROUP BY |
| 8 | rows_before_aggregation | VarUInt | universal | ROWS_BEFORE_AGGREGATION (v54469) | Число строк до агрегации |
Итоги (тип пакета 7)
Сервер → клиент. Отправляется для запросов с WITH TOTALS. Формат передачи данных идентичен данным: строка table_name (всегда пустая), за которой следует блок. Отличается только байт типа пакета.
[VarUInt: 7] packet type
[String: table_name] always empty
[Block] see the Native Format specExtremes (тип пакета 8)
Сервер → Клиент. Отправляется, когда включён параметр extremes. Формат передачи данных идентичен данным. Блок содержит ровно 2 строки: строка 0 содержит минимум по каждому столбцу, строка 1 — максимум.
[VarUInt: 8] packet type
[String: table_name] always empty
[Block] num_rows = 2Log (тип пакета 10)
Сервер → клиент. Отправляется, когда у запроса есть активная очередь логов (параметр send_logs_level; см. потоковую передачу журналов).
Формат обёртки и тела такой же, как у данных. Блок имеет фиксированное значение num_columns = 8 и предопределённую схему. Каждая строка журнала соответствует одной строке по всем 8 столбцам, и один пакет Log может содержать много строк.
[VarUInt: 10] packet type
[String: table_name] always empty
[Block] num_columns = 8, num_rows = number of log lines8 столбцов в этом точном порядке:
| # | Name | Type | Description |
|---|---|---|---|
| 1 | event_time | DateTime | Временная метка события (секунды с начала эпохи) |
| 2 | event_time_microseconds | UInt32 | Микросекундная составляющая |
| 3 | host_name | String | Имя хоста сервера, записывающего лог |
| 4 | query_id | String | Query ID, к которому относится лог |
| 5 | thread_id | UInt64 | Идентификатор потока ОС |
| 6 | priority | Int8 | Уровень логирования (приоритет Poco: 1 = Fatal, … 8 = Trace, 9 = Test) |
| 7 | source | String | Имя логгера |
| 8 | text | String | Текст сообщения лога |
ProfileEvents (тип пакета 14)
Сервер → клиент. Передаёт счётчики производительности для каждого запроса.
Тот же формат оболочки и тела, что и у данных. Блок имеет фиксированное значение num_columns = 6 и предопределённую схему. Каждое событие соответствует одной строке.
[VarUInt: 14] packet type
[String: table_name] always empty
[Block] num_columns = 6, num_rows = number of events6 столбцов:
| # | Имя | Тип | Описание |
|---|---|---|---|
| 1 | host_name | String | Имя хоста сервера |
| 2 | current_time | DateTime | Временная метка события |
| 3 | thread_id | UInt64 | Идентификатор потока |
| 4 | type | Enum8 | Тип события: 1 = Increment (counter), 2 = Gauge. В нижележащем хранилище используется один знаковый байт. |
| 5 | name | String | Имя события (например, "Query", "NetworkReceiveBytes") |
| 6 | value | Int64 | Значение Counter или значение Gauge |
TableColumns (тип пакета 11)
Сервер → клиент, доступно при COLUMN_DEFAULTS_METADATA (v54410). Сервер отправляет этот пакет перед блоком схемы INSERT, чтобы передать метаданные значений по умолчанию для столбцов, но только если согласованная версия ≥ 54410 и включена настройка input_format_defaults_for_omitted_fields. Для версий ниже 54410 пакет не отправляется никогда, поэтому более старый клиент не должен его ожидать — блок схемы Data приходит сразу. Клиент v54410+ должен быть готов к одному из двух вариантов: либо необязательный TableColumns, затем блок схемы, либо сразу блок схемы.
| # | Field | Type | Role | Description |
|---|---|---|---|---|
| 1 | external_table | String | universal | Имя внешней таблицы. Пустое значение = основная таблица. |
| 2 | columns_description | String | universal | Текстовые определения столбцов, например "id Int32, name String DEFAULT ''". Произвольный текст — разбирайте как строку. |
TimezoneUpdate (пакет типа 17)
Server → Client, используется при TIMEZONE_UPDATES (v54464). Отправляется ровно в одном месте: при инициализации табличной функции input (запрос вида INSERT INTO <table> SELECT ... FROM input('<structure>'), который потоково передаёт строки от клиента). Сразу после того, как сервер отправляет Data-блок входной схемы (см. фазу INSERT), он отправляет TimezoneUpdate с текущим значением session_timezone из контекста запроса, чтобы клиент разбирал строки, которые собирается отправить, в том же часовом поясе. Сервер не отправляет этот пакет при произвольных изменениях SET session_timezone посреди запроса и не использует его, чтобы сообщить клиенту, как форматировать последующие блоки результатов.
| # | Поле | Тип | Роль | Описание |
|---|---|---|---|---|
| 1 | timezone | String | universal | Новый часовой пояс сеанса по умолчанию (например, "UTC", "Europe/Berlin"). |
Пакет приходит один раз: сразу после блока входной схемы и до того, как клиент начнёт отправлять блоки строк. Декодер, который игнорирует TimezoneUpdate, ВСЁ РАВНО ДОЛЖЕН прочитать завершающий String, чтобы сохранить выравнивание в потоке.
Аутентификация SSH по схеме челлендж-ответ (типы пакетов 11, 12, 18)
Управляется флагом SSH_AUTHENTICATION (v54466) и включается только явным образом. Соединение переходит в SSH-сценарий, когда ClientHello отправляет user = " SSH KEY AUTHENTICATION " + <real_user> (с пробелами в начале и конце) и password = "". Сервер считывает префикс, удаляет его, чтобы восстановить реальное имя пользователя, и переключается на схему челлендж-ответ.
| Packet | Code | Direction | Body |
|---|---|---|---|
| SSHChallengeRequest | 11 | Client → Server | (без тела) |
| SSHChallenge | 18 | Server → Client | String challenge — случайные байты; один из компонентов строки, которая подписывается (см. ниже) |
| SSHChallengeResponse | 12 | Client → Server | String signature — SSH-подпись от конкатенации, определённой ниже, а не от сырого челленджа |
Этот сценарий используется вместо аутентификации по паролю, а обмен по схеме челлендж-ответ происходит до ServerHello — сервер откладывает ответ Hello, пока аутентификация не завершится успешно:
-
Клиент отправляет ClientHello с SSH-префиксом-маркером и пустым паролем.
-
Клиент отправляет
SSHChallengeRequest(пакет 11). Сервер ещё не отправил ServerHello — он сначала обрабатывает аутентификацию и останавливается здесь в ожидании этого пакета. -
Сервер отвечает пакетом
SSHChallengeсо случайными байтами (пакет 18). -
Клиент формирует строку для подписи и подписывает её, а не сырой челлендж, затем отправляет
SSHChallengeResponse(пакет 12) с подписью. Подписываемое сообщение — это побайтовая конкатенация без разделителей четырёх частей в следующем точном порядке:to_sign = decimal(protocol_version) + default_database + user + challengePart Source decimal(protocol_version)Версия протокола клиента в виде десятичной ASCII-строки (например, "54466") — номер версии именно как строка, а не как VarUInt или целое число фиксированной ширины. Сервер проверяет подпись, используя ту же версию протокола, которую получил вClientHello.default_databaseПоле databaseизClientHello(пустая строка, если не указано).userРеальное имя пользователя без префикса-маркера " SSH KEY AUTHENTICATION "— то же имя, которое сервер восстанавливает после удаления префикса.challengeСырые байты challengeиз пакетаSSHChallenge. -
Сервер проверяет подпись по зарегистрированному открытому ключу пользователя, восстанавливая ту же строку
decimal(protocol_version) + default_database + user + challenge. В случае успеха он отправляетServerHello— тот же ответ, что и при аутентификации по паролю, — и рукопожатие продолжается в обычном режиме (Addendum и т. д.); в случае неудачи сервер возвращаетИсключениеи завершает соединение. Клиент, который подписывает только сырые байты челленджа, не пройдёт аутентификацию.
sequenceDiagram
autonumber
participant C as Client
participant S as Server
C->>S: ClientHello (SSH marker user, empty password)
C->>S: SSHChallengeRequest (11)
Note over C,S: Server has NOT sent ServerHello yet —<br/>it authenticates first and blocks here
S->>C: SSHChallenge (18) — random bytes to sign
C->>S: SSHChallengeResponse (12) — signature over version+db+user+challenge
alt signature verifies
S->>C: ServerHello — handshake continues normally
else verification fails
S->>C: Exception — connection terminates
end
Внешние клиенты, не использующие SSH-аутентификацию, никогда не видят пакеты 11, 12 или 18 — они не передаются по сети, если только пользователь явно не включит их через префикс имени пользователя.
MergeTreeAllRangesAnnouncementResponse (тип пакета 14)
Client → Server, только для взаимодействия между серверами. Используется только при parallel_replicas_protocol_version ≥ 8 (см. VERSIONED_PARALLEL_REPLICAS_PROTOCOL). Внешние клиенты этот пакет не отправляют.
Когда согласованная версия parallel-replicas ≥ 8, меняется цикл запросов и ответов инициатора для MergeTreeAllRangesAnnouncement, отправляемого follower'ом (тип пакета 15, направление server→client):
- Follower открывает свой конвейер чтения и отправляет
MergeTreeAllRangesAnnouncementинициатору. - Только если
modeв объявлении отличается отDefault(WithOrder = 1илиReverseOrder = 2, оба используются для параллельного чтения с сохранением порядка), инициатор отвечает пакетомMergeTreeAllRangesAnnouncementResponse. Приmode = Default = 0инициатор ничего не отправляет, а follower не ждёт ответа — в режимеDefaultдиапазоны выдаются вместе с каждымMergeTreeReadTaskRequest, и предварительный список частей не нужен. - Follower ожидает ответ (если он должен прийти) перед отправкой своего первого
MergeTreeReadTaskRequest(пакет сервера16— отправляется в направлении follower→initiator; в ответ инициатор отправляетMergeTreeReadTaskResponse, пакет клиента10), и использует возвращённый список частей, чтобы при построении источников оставить только те части, которые принадлежат его потоку#split_i.
Для версий ниже 8 объявление работает по принципу fire-and-forget независимо от режима, а follower строит источники по всем локально известным частям (legacy behaviour).
Тело
| # | Поле | Тип | Описание |
|---|---|---|---|
| 1 | version | Int64 (little-endian) | Версия протокола parallel replicas у отправителя. Равна DBMS_PARALLEL_REPLICAS_PROTOCOL_VERSION (сейчас 8), если TCP-ревизия получателя ≥ DBMS_MIN_REVISION_WITH_VERSIONED_PARALLEL_REPLICAS_PROTOCOL (54471); в противном случае используется DBMS_MIN_SUPPORTED_PARALLEL_REPLICAS_PROTOCOL_VERSION (3). Получатель отклоняет любое значение ниже DBMS_MIN_SUPPORTED_PARALLEL_REPLICAS_PROTOCOL_VERSION. |
| 2 | parts | RangesInDataPartsDescription | Канонический набор частей, который координатор зарегистрировал для потока этого анонса. Пустой список означает, что такого потока у координатора нет (например, ведомый узел анонсировал больше сплитов, чем создал инициатор); пул ведомого узла для этого потока сразу помечается как завершённый. |
| 3 | stream_id | String | Повторяет stream_id анонса, на который отвечает этот response (имя таблицы плюс суффикс #split_i, когда используется топология со сплитами). |
Тело RangesInDataPartsDescription
| # | Поле | Тип | Описание |
|---|---|---|---|
| 1 | count | VarUInt | Количество дескрипторов частей, которые следуют далее. Декодер отклоняет значения выше 100'000'000'000 как некорректные. |
| 2 | parts | RangesInDataPartDescription, повторяется count раз |
Дескрипторы в порядке регистрации у координатора. |
Тело RangesInDataPartDescription
| # | Поле | Тип | Gate | Описание |
|---|---|---|---|---|
| 1 | info | MergeTreePartInfo | universal | Идентификатор части (партиция, диапазон блоков, уровень, мутация). |
| 2 | ranges | MarkRanges | universal | Диапазоны marks в info, которые может обслуживать этот поток. Пустой список означает, что часть зарегистрирована, но в данный момент для неё не назначена работа. |
| 3 | rows | VarUInt | universal | Общее количество строк, охватываемых ranges. |
| 4 | projection_name | String | DBMS_PARALLEL_REPLICAS_MIN_VERSION_WITH_PROJECTION (PR v5) |
Пусто для строк основной части; иначе — имя проекции. |
| 5 | min_marks_per_task | VarUInt | DBMS_PARALLEL_REPLICAS_MIN_VERSION_WITH_MIN_MARKS_PER_TASK (PR v6) |
Минимальное количество marks, которое пул follower должен объединять в одну задачу чтения для этой части. |
Тело MergeTreePartInfo
| # | Поле | Тип | Описание |
|---|---|---|---|
| 1 | version | Int64 (little-endian) | Всегда DBMS_MERGE_TREE_PART_INFO_VERSION (1). Декодер отклоняет любое другое значение. |
| 2 | partition_id | String | Идентификатор партиции (например, "all" для таблиц без партиционирования или строковое представление значения кортежного выражения ключа партиции). |
| 3 | min_block | Int64 (little-endian) | Первый номер блока в диапазоне блоков этой части. |
| 4 | max_block | Int64 (little-endian) | Последний номер блока в диапазоне блоков этой части (включительно). |
| 5 | level | UInt32 (little-endian) | Уровень слияния. |
| 6 | mutation | Int64 (little-endian) | Версия мутации, в результате которой была создана эта часть (0 для части без мутации). |
| 7 | use_legacy_max_level | Bool (text) | Кодируется одним байтом ASCII ('1' или '0') — исторический флаг совместимости для формата имени части. |
Тело MarkRanges
| # | Поле | Тип | Описание |
|---|---|---|---|
| 1 | size | UInt64 (little-endian) | Число пар диапазонов mark, приведённых далее. Примечание: little-endian, фиксированная ширина, не VarUInt. |
| 2 | ranges | size повторений (UInt64 begin, UInt64 end), каждое в little-endian |
Полуоткрытые интервалы mark [begin, end). |
Справочник по типам пакетов
Клиент → Сервер
| Код | Имя | Формат тела | Описание |
|---|---|---|---|
| 0 | Hello | ClientHello | Инициация рукопожатия |
| 1 | Query | Query | Запрос на выполнение |
| 2 | данные | данные | Блок данных (данные INSERT, внешние таблицы, маркер конца данных) |
| 3 | Cancel | (без тела) | Отмена выполняющегося запроса |
| 4 | Ping | Ping | Проверка работоспособности |
| 5 | TablesStatusRequest | не указано | Проверка состояния таблиц |
| 6 | KeepAlive | не указано | Поддержание соединения |
| 7 | Scalar | не указано | Скалярный блок данных |
| 8 | IgnoredPartUUIDs | не указано | Части, исключаемые из запроса |
| 9 | ReadTaskResponse | не указано | Ответ на чтение из кластера S3 |
| 10 | MergeTreeReadTaskResponse | не указано | Ответ на задачу параллельного чтения |
| 11 | SSHChallengeRequest | SSH-аутентификация | Запрос челленджа SSH-аутентификации |
| 12 | SSHChallengeResponse | SSH-аутентификация | Ответ на челлендж SSH-аутентификации |
| 13 | QueryPlan | не указано | План запроса |
| 14 | MergeTreeAllRangesAnnouncementResponse | MergeTreeAllRangesAnnouncementResponse | Ответ инициатора на MergeTreeAllRangesAnnouncement от последователя (используется только при parallel_replicas_protocol_version ≥ 8 — см. VERSIONED_PARALLEL_REPLICAS_PROTOCOL). Только для межсерверного взаимодействия — внешние клиенты его не отправляют. |
Сервер → Клиент
| Код | Имя | Формат тела | Описание |
|---|---|---|---|
| 0 | Hello | ServerHello | Ответ на рукопожатие |
| 1 | данные | данные | Блок данных результата |
| 2 | Исключение | Исключение | Ошибка |
| 3 | Прогресс | Прогресс | Прогресс выполнения запроса |
| 4 | Pong | Pong | Ответ на проверку работоспособности |
| 5 | EndOfStream | (без тела) | Запрос завершён |
| 6 | ProfileInfo | ProfileInfo | Данные профилирования после выполнения |
| 7 | Totals | Totals | Строка GROUP BY WITH TOTALS |
| 8 | Extremes | Extremes | Минимальные/максимальные значения (блок из 2 строк) |
| 9 | TablesStatusResponse | не указано | Ответ со статусом таблиц |
| 10 | Log | Log | Строки журнала выполнения запроса |
| 11 | TableColumns | TableColumns | Описания столбцов для значений по умолчанию |
| 12 | PartUUIDs | не указано | Уникальные идентификаторы частей |
| 13 | ReadTaskRequest | не указано | Запрос задания на чтение в кластере |
| 14 | ProfileEvents | ProfileEvents | Счётчики производительности |
| 15 | MergeTreeAllRangesAnnouncement | не указано | Инициализация параллельного чтения |
| 16 | MergeTreeReadTaskRequest | не указано | Назначение задания параллельного чтения |
| 17 | TimezoneUpdate | TimezoneUpdate | Обновление часового пояса сервера |
| 18 | SSHChallenge | SSH-аутентификация | Челлендж SSH-аутентификации |
Конфигурация
В этом разделе рассматриваются параметры, определяющие работу соединений по собственному протоколу:
- Настройки транспортного уровня — параметры TCP-сокета и тайм-ауты, влияющие на поведение самого TCP-соединения.
- Настройки прикладного уровня — параметры на уровне запроса, передаваемые в списке настроек пакета Query, которые влияют на то, что сервер отправляет по сети и как эти данные структурируются.
- Настройки вне области рассмотрения — настройки, которые часто путают с настройками протокола, но которые на самом деле управляют выполнением SQL или хранилищем.
Приведённые ниже значения по умолчанию соответствуют одному из недавних выпусков сервера; они могут различаться в разных версиях и развертываниях.
Настройки транспортного уровня
Параметры сокета
| Параметр | По умолчанию | Сторона | Описание |
|---|---|---|---|
TCP_NODELAY |
on | обе | Алгоритм Нейгла отключён. Небольшие пакеты отправляются немедленно. |
SO_KEEPALIVE |
on (client), по умолчанию в ОС (server) | асимметрично | Проверки TCP keepalive на уровне ядра. Клиент явно включает этот параметр, когда tcp_keep_alive_timeout > 0. Сервер наследует значение по умолчанию из ОС. |
SO_RCVBUF / SO_SNDBUF |
значения ОС по умолчанию | — | Размеры буферов сокета. Протокол их не настраивает. |
Тайм-ауты
| Параметр | По умолчанию | Единица | Сторона | Описание |
|---|---|---|---|---|
connect_timeout |
10 | секунды | клиент | Тайм-аут установки исходного TCP-соединения. |
handshake_timeout_ms |
10000 | миллисекунды | клиент | Тайм-аут получения ServerHello во время рукопожатия. |
send_timeout |
300 | секунды | обе | Если в течение этого интервала не удаётся записать ни одного байта, соединение генерирует исключение. |
receive_timeout |
300 | секунды | обе | Если в течение этого интервала не удаётся прочитать ни одного байта, соединение генерирует исключение. |
tcp_keep_alive_timeout |
290 | секунды | клиент | Время бездействия до отправки ОС первого TCP keepalive-зонда. |
receive_data_timeout_ms |
2000 | миллисекунды | клиент | Тайм-аут получения первого пакета Data от реплики. |
connect_timeout_with_failover_ms |
1000 | миллисекунды | клиент | Тайм-аут подключения на одну попытку при переборе реплик. |
connect_timeout_with_failover_secure_ms |
1000 | миллисекунды | клиент | Тайм-аут подключения на одну попытку при переборе реплик по TLS. |
hedged_connection_timeout_ms |
50 | миллисекунды | клиент | Тайм-аут подключения на одну попытку для hedged-запросов. |
poll_interval |
10 | секунды | сервер | Интервал цикла проверки бездействующих соединений и завершения работы сервера. |
Эти тайм-ауты вложены следующим образом:
tcp_keep_alive_timeout (290s)
< receive_timeout (300s)
< idle_connection_timeout (3600s)
< tcp_close_connection_after_queries_seconds (0 = unlimited by default)Сначала срабатывает механизм keepalive ОС и может незаметно для приложения обнаружить недоступные узлы на уровне ядра. Следующий рубеж защиты — тайм-аут на получение данных приложением. Тайм-аут бездействия — крайняя мера, закрывающая соединения, которые долго не использовались.
Ограничения соединений
| Параметр | По умолчанию | Единица | Сторона | Описание |
|---|---|---|---|---|
max_connections |
4096 | count | server | Максимальное количество одновременных TCP-соединений. |
idle_connection_timeout |
3600 | seconds | server | Максимальное время, в течение которого бездействующее соединение может оставаться открытым. |
tcp_close_connection_after_queries_num |
0 (без ограничений) | count | server | Максимальное количество запросов на одно соединение до принудительного закрытия. |
tcp_close_connection_after_queries_seconds |
0 (без ограничений) | seconds | server | Максимальное общее время жизни соединения независимо от активности. |
Соединение, которое регулярно выполняет запросы, может существовать неограниченно долго. Через час закрываются только бездействующие соединения, а максимальное время жизни по умолчанию не ограничено.
Настройки прикладного уровня
Эти настройки передаются с каждым запросом в списке настроек пакета запроса. Они влияют на то, какие данные сервер отправляет по сети и как они разбиваются на кадры.
Сжатие
| Настройка | По умолчанию | Единица | Описание |
|---|---|---|---|
network_compression_method |
"LZ4" |
строка | Кодек сжатия, используемый, когда установлен флаг compression пакета Query. Значения: "LZ4", "LZ4HC", "ZSTD", "NONE". |
network_zstd_compression_level |
1 | 1–15 | Уровень ZSTD, если network_compression_method == "ZSTD". |
Флаг compression в пакете Query (поле 6) включает и отключает сжатие; эти настройки определяют, какой кодек используется, когда сжатие включено.
Потоковая передача журналов
| Настройка | По умолчанию | Единица | Описание |
|---|---|---|---|
send_logs_level |
"fatal" |
string | Минимальный уровень журналирования. Значения: "none", "fatal", "error", "warning", "information", "debug", "trace", "test". |
send_logs_source_regexp |
"" |
string | Regex-фильтр по источнику логгера. Пустое значение = проходят все источники. |
Если задать для send_logs_level любое значение, кроме "none", сервер будет отправлять пакеты Log во время выполнения запроса.
Отчёт о прогрессе
| Параметр | По умолчанию | Единица | Описание |
|---|---|---|---|
interactive_delay |
100000 | микросекунды | Целевой минимальный интервал между последовательными пакетами Прогресс. |
Это целевой минимум, а не жёсткий максимум: сервер может отправлять пакеты Прогресс реже, если запрос выполняется недостаточно быстро.
Оболочка результата
| Параметр | По умолчанию | Единица измерения | Описание |
|---|---|---|---|
extremes |
false | bool | Если true, сервер отправляет пакет Extremes с минимальным и максимальным значениями для каждого столбца. |
max_result_rows |
0 (без ограничений) | count | Ограничение на количество передаваемых строк. Поведение задаётся параметром result_overflow_mode. |
max_result_bytes |
0 (без ограничений) | uncompressed bytes | Ограничение на объём передаваемых несжатых данных в байтах. Поведение задаётся параметром result_overflow_mode. |
result_overflow_mode |
"throw" |
string | "throw" завершает поток с Исключением; "break" отправляет частичный результат, после чего следует EndOfStream. |
Асинхронная вставка
| Параметр | По умолчанию | Единица | Описание |
|---|---|---|---|
async_insert |
true | bool | Если задано значение true, данные INSERT ставятся в очередь на стороне сервера и обрабатываются батчами. |
wait_for_async_insert |
true | bool | Если задано значение true (при включённом async_insert), сервер удерживает ответ, пока данные из очереди не будут сброшены на диск. |
wait_for_async_insert_timeout |
120 | секунды | Максимальное время, в течение которого сервер ждёт сброса на диск перед возвратом ответа. |
Распределённая трассировка
| Параметр | По умолчанию | Единица измерения | Описание |
|---|---|---|---|
opentelemetry_start_trace_probability |
0.0 | вероятность 0–1 | Вероятность на стороне сервера прикрепить контекст OpenTelemetry к телеметрии ответа. |
Настройки вне области рассмотрения
Эти настройки иногда ошибочно принимают за настройки уровня протокола, но на самом деле они управляют выполнением SQL, хранилищем или использованием CPU, а не поведением данных при передаче. Реализации протокола не требуется обрабатывать их как-то особенно.
max_threads— параллелизм при выполнении запроса.max_memory_usage— ограничение памяти на запрос.max_block_size,preferred_block_size_bytes— внутренний сайзинг блоков при обработке запроса; блоки на уровне передачи от них не зависят.compile_expressions— JIT-компиляция; влияет только на CPU.async_insert_max_data_size— буфер очереди на стороне сервера.- Все настройки
input_format_*иoutput_format_*, кроме семействаinput_format_native_*/output_format_native_*, — варианты, отличные отnative, выбирают или настраивают другие форматы (например, по HTTP) и не изменяют блокиDataсобственного протокола.
Настройки *_native_* — исключение: они меняют байты внутри блоков Data нативного TCP, поэтому реализация протокола должна это учитывать. output_format_native_encode_types_in_binary_format переключает поле type столбца с текстовой строки на двоичное кодирование типа, output_format_native_write_json_as_string выводит столбцы JSON как String, а output_format_native_use_flattened_dynamic_and_json_serialization выбирает FLATTENED-структуру Dynamic/JSON. Поскольку они влияют на тело блока, а не на оболочку пакета, они описаны в спецификации Native Format — см. структуру столбца в передаваемых данных и версируемые типы.
Глоссарий
Cancel — пакет (type 3), инициируемый клиентом и прерывающий выполняющийся запрос. Подробно на этой странице не описан.
Маркер конца клиентских данных — пустой пакет данных (0 столбцов, 0 строк), который клиент отправляет, чтобы закрыть входной поток. Его положение зависит от типа запроса:
- Обычный запрос (
SELECTи т. п.): отправляется после пакета Query и всех пакетов данных внешних таблиц, чтобы сообщить: «внешних данных больше нет». После этого server начинает выполнение. INSERT: клиент не отправляет маркер до схемы. Сначала server отправляет блок схемы, затем клиент передаёт свои блоки данных со строками и только после этого отправляет пустой пакет данных, чтобы завершить поток строк. Если отправить пустой маркер до блока схемы, он будет прочитан как немедленное завершение строк, и данные будут потеряны.
Возможность — изменение формата wire, добавленное в определённой версии protocol. Активна, если согласованная version равна версии этой возможности или выше. См. versioning and feature gates.
Inter-server — метка роли для поля, которое имеет смысл только в server-to-server distributed queries. Внешние клиенты записывают значение по умолчанию (обычно пустую строку, 0 или false).
Согласованная version — min(client_version, server_version), вычисляется во время рукопожатия. Определяет, какие возможности активны на всём протяжении lifetime connection.
Пакет — wire-сообщение: код типа пакета VarUInt, за которым следует body, формат которого зависит от типа. См. packet envelope.
Код типа пакета — начальный VarUInt пакета, который определяет его формат. В настоящее время назначены значения 0–18. См. packet type reference.
Поток ответов — последовательность пакетов, которые server отправляет во время выполнения запроса. Имеет произвольную длину и завершается ровно одним EndOfStream (успех) или Исключение (ошибка). См. query phase.
Блок схемы — заголовочный блок (Block со столбцами, но 0 строк), который server отправляет во время фазы INSERT, чтобы объявить ожидаемую структуру столбцов до того, как клиент отправит данные.
Список Settings — последовательность Tuple (key, flags, value) в body Query, завершающаяся пустым key. Содержит конфигурацию прикладного уровня для конкретного запроса. См. Setting.
Stage — поле VarUInt в пакете Query (field 5), управляющее тем, насколько далеко server выполняет запрос. Внешние клиенты обычно отправляют 2 (Complete); distributed queries и сериализованные query plan используют более высокие значения. Полный набор значений wire см. в field 5 Query.
Терминатор — пакет, который завершает поток. Ответ на Query завершается пакетом EndOfStream (успех) или Исключение (ошибка). Входной поток клиента завершается пустым маркером данных.