Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

Собственный протокол

Собственный протокол — это бинарный протокол с установлением соединения, по которому клиенты и серверы 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
  1. Клиент отправляет ClientHello, указав максимальную поддерживаемую версию протокола.

  2. Клиент читает ответ и обрабатывает его в зависимости от типа пакета:

    Тип пакета Действие
    Hello (0) Декодировать ServerHello. Вычислить negotiated_version = min(client_ver, server_ver). Перейти к шагу 3.
    Exception (2) Декодировать Exception. Вернуть ошибку и завершить соединение.
    anything else Нарушение протокола. Завершить соединение.
  3. Если 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
  1. Клиент отправляет Ping.

  2. Клиент читает ответ:

    Тип пакета Действие
    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, что приводит к завершению запроса.

  1. Клиент отправляет Query с уникальным query_id (обычно UUID).

  2. Клиент отправляет все внешние таблицы, затем пустой маркер Data. У пустого пакета Data: table_name = "", num_columns = 0, num_rows = 0. Сервер не начинает выполнять запрос, пока не получит этот маркер.

  3. Клиент переходит в READING_RESPONSE и сбрасывает буфер записи.

  4. Клиент в цикле читает пакеты ответа и обрабатывает их по типу:

    Тип пакета Действие
    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
  1. Клиент отправляет Query, где в body указан SQL-запрос INSERT.
  2. Клиент отправляет все внешние таблицы (для INSERT это редкость). В отличие от фазы запроса, здесь он не отправляет пустой маркер Data. Пакет INSERT Query отправляется вместе с ожидающими данными, поэтому пустой завершающий блок данных откладывается до шага 5; если отправить его до блока схемы, сервер воспримет его как конец потока строк, завершит INSERT без строк, а затем разберёт первый реальный пакет строк как лишний пакет верхнего уровня.
  3. Клиент считывает пакеты метаданных (TableColumns, Progress, ProfileInfo, Log, ProfileEvents), пока не получит пакет Data со схемой — Block с 0 строк, но с полной структурой столбцов (имена и типы). Блок схемы — это контракт: строки, которые клиент отправит дальше, должны соответствовать этим структурам столбцов.
  4. Клиент отправляет блоки данных. Для каждого блока он записывает VarUInt(ClientPacket::Data = 2), затем String("") для пустого имени внешней таблицы, а затем сам Block. Типы столбцов должны соответствовать столбцам блока схемы по позиции.
  5. Клиент отправляет завершающий маркер конца ввода: пакет Data с пустым Block (0 столбцов, 0 строк).
  6. Клиент считывает поток ответа до 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:

  1. Войдите в межсерверный режим. Подключающийся сервер сигнализирует об этом в ClientHello: поле user служит маркером межсерверного режима, а password пусто. Затем он добавляет еще две строки — имя кластера и заново сгенерированный 32-байтный salt (encodeSHA256 от случайного значения) — сразу после полей user/password, как часть того же пакета ClientHello. Сервер читает эти две строки до отправки ServerHello, поэтому клиент должен записать их сразу; если сначала ждать ServerHello, возникнет взаимная блокировка, потому что сервер будет заблокирован на их чтении.
  2. Получите nonce. ServerHello содержит 8-байтный UInt64 nonce, когда согласован INTERSERVER_SECRET_V2 (v54462).
  3. Вычислите 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. Проверьте. Сервер читает поле 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 включает:

  • 0x01Important: параметр влияет на результаты запроса и не должен молча игнорироваться более старыми peer.
  • 0x02Custom: пользовательская custom setting.
  • 0x0c2-битное поле уровня, а не отдельный флаг: 0x00 = продакшн, 0x04 = устаревший, 0x08 = экспериментальный, 0x0c = бета. Считывайте все 2 бита (flags & 0x0c) — простая проверка flags & 0x04 ошибочно классифицирует Beta (0x0c) как Obsolete.
  • 0x80HotReload (перезагрузка 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 spec

Extremes (тип пакета 8)

Сервер → Клиент. Отправляется, когда включён параметр extremes. Формат передачи данных идентичен данным. Блок содержит ровно 2 строки: строка 0 содержит минимум по каждому столбцу, строка 1 — максимум.

[VarUInt: 8]                packet type
[String:  table_name]       always empty
[Block]                     num_rows = 2

Log (тип пакета 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 lines

8 столбцов в этом точном порядке:

# 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 events

6 столбцов:

# Имя Тип Описание
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, пока аутентификация не завершится успешно:

  1. Клиент отправляет ClientHello с SSH-префиксом-маркером и пустым паролем.

  2. Клиент отправляет SSHChallengeRequest (пакет 11). Сервер ещё не отправил ServerHello — он сначала обрабатывает аутентификацию и останавливается здесь в ожидании этого пакета.

  3. Сервер отвечает пакетом SSHChallenge со случайными байтами (пакет 18).

  4. Клиент формирует строку для подписи и подписывает её, а не сырой челлендж, затем отправляет SSHChallengeResponse (пакет 12) с подписью. Подписываемое сообщение — это побайтовая конкатенация без разделителей четырёх частей в следующем точном порядке:

    to_sign = decimal(protocol_version) + default_database + user + challenge
    Part Source
    decimal(protocol_version) Версия протокола клиента в виде десятичной ASCII-строки (например, "54466") — номер версии именно как строка, а не как VarUInt или целое число фиксированной ширины. Сервер проверяет подпись, используя ту же версию протокола, которую получил в ClientHello.
    default_database Поле database из ClientHello (пустая строка, если не указано).
    user Реальное имя пользователя без префикса-маркера " SSH KEY AUTHENTICATION " — то же имя, которое сервер восстанавливает после удаления префикса.
    challenge Сырые байты challenge из пакета SSHChallenge.
  5. Сервер проверяет подпись по зарегистрированному открытому ключу пользователя, восстанавливая ту же строку 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):

  1. Follower открывает свой конвейер чтения и отправляет MergeTreeAllRangesAnnouncement инициатору.
  2. Только если mode в объявлении отличается от Default (WithOrder = 1 или ReverseOrder = 2, оба используются для параллельного чтения с сохранением порядка), инициатор отвечает пакетом MergeTreeAllRangesAnnouncementResponse. При mode = Default = 0 инициатор ничего не отправляет, а follower не ждёт ответа — в режиме Default диапазоны выдаются вместе с каждым MergeTreeReadTaskRequest, и предварительный список частей не нужен.
  3. 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_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).

Согласованная versionmin(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 (успех) или Исключение (ошибка). Входной поток клиента завершается пустым маркером данных.

Navigation