INSERT, телеметрию выполнения и сообщения об ошибках. Именно этот протокол используется клиентом командной строки, C++ и большинством сторонних нативных драйверов.
На этой странице рассматривается сам протокол: кадрирование пакетов, машина состояний соединения, согласование версий и тело каждого сообщения, кроме Block. Байты внутри пакетов семейства Data (то есть Block, его столбцы и кодировки отдельных типов) — отдельная тема, описанная в спецификации Native Format.
Сопутствующая спецификацияЭта страница — одна из двух частей пары и публикуется вместе с сопутствующей спецификацией Native Format. Эти две спецификации чётко разделяют задачи: эта страница описывает пакетный и транспортный уровни, а спецификация Native Format — байты внутри пакетов семейства
Data.BlockInfo, поэтому один смещённый байт нарушает синхронизацию всего, что идёт дальше. Он работает с сохранением состояния, и каждое TCP-соединение обрабатывает только один запрос за раз — мультиплексирования нет. Для целых чисел фиксированной ширины используется порядок байтов little-endian.
Обзор
| Свойство | Значение |
|---|---|
| Транспорт | TCP, при необходимости с TLS |
| Порядок байтов | Little-endian для целых чисел фиксированной ширины |
| Кодирование | Бинарное и позиционное (без тегов полей, кроме BlockInfo) |
| Модель соединения | С сохранением состояния, по одному запросу за раз, без мультиплексирования |
| Версионирование | Согласуется при рукопожатии; отдельные возможности зависят от версии |
| Формат данных | Native Format для всех табличных данных |
VarUInt, за которым следует тело, структура которого зависит от этого кода и согласованной версии протокола.
Соединение проходит через три фазы — однократное рукопожатие, затем любое количество обменов Ping или Query, после чего соединение закрывается:
Протокол нативного TCP всегда передаёт табличные данные в формате Native, независимо от наличия предложения FORMAT в SQL. Повторное форматирование в RowBinary, CSV, JSON и так далее — задача клиента; оно выполняется после декодирования блоков Native. (HTTP-интерфейс использует другой кодовый путь и действительно учитывает предложение FORMAT; HTTP здесь не рассматривается.)
Безопасность
Защита транспортного уровня (TLS)
Аутентификация
ClientHello. Поля user и password передаются как строки в открытом виде, поэтому учетные данные при передаче защищаются шифрованием на транспортном уровне (TLS).
Аутентификация SSH по схеме challenge-response доступна начиная с версии протокола 54466 — см. Аутентификация SSH по схеме challenge-response.
Межсерверный секрет
auth_hash в поле 4 Query, вычисленный на основе salt, nonce, настроенного секрета и запроса; принимающий сервер вычисляет его заново и сравнивает. Это контролируется возможностью INTERSERVER_SECRET (v54441). Внешние клиенты всегда отправляют здесь пустую строку. См. Межсерверная аутентификация.
Версионирование и флаги возможностей
Согласование версии
Флаги возможностей
Таблица возможностей
| Возможность | Версия | Влияет на | Влияние на бинарный формат |
|---|---|---|---|
| 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. Текущее значение: 7 (DBMS_PARALLEL_REPLICAS_PROTOCOL_VERSION). |
| 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 и т. д.). Внешние клиенты декодируют и игнорируют его. |
| 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 будет десинхронизирована. |
Оболочка пакета
VarUInt, а не байт фиксированной длины. Для значений меньше 128 VarUInt даёт тот же однобайтный результат, но реализации должны использовать кодирование VarUInt, чтобы сохранить совместимость, если в будущем появятся типы пакетов со значением 128 и выше.
В справочнике по сообщениям описывается только тело каждого пакета — байты после кода типа пакета. Нумерация полей начинается с 1, где первое поле — это первое поле тела.
Фрагментное кадрирование (v54470+)
CHUNKED_PROTOCOL согласована (см. этап рукопожатия), каждый пакет при передаче оборачивается во фрагментное кадрирование. Такое оборачивание выполняется отдельно для каждого направления: client→server и server→client согласуются независимо и в итоге могут работать в разных режимах (с фрагментным кадрированием или без него).
Структура данных в канале для каждого пакета:
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 | ошибка протокола — соединение НЕОБХОДИМО закрыть |
Addendum сброшен на диск. Сам Addendum, ClientHello и ServerHello всегда передаются без кадрирования.
Жизненный цикл соединения
HANDSHAKE, READY, READING_RESPONSE или завершено. Поскольку протокол не поддерживает мультиплексирование, клиент, который отправляет новый запрос, не дочитав предыдущий ответ до конца, перемешивает байты в потоке передачи данных и повреждает поток.
Состояния
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-соединение и заново выполнить рукопожатие. |
Фаза рукопожатия
-
Клиент отправляет
ClientHello, указав максимальную поддерживаемую версию протокола. -
Клиент читает ответ и обрабатывает его в зависимости от типа пакета:
Тип пакета Действие Hello(0)Декодировать ServerHello. Вычислитьnegotiated_version = min(client_ver, server_ver). Перейти к шагу 3.Exception(2)Декодировать Exception. Вернуть ошибку и завершить соединение.anything else Нарушение протокола. Завершить соединение. -
Если
negotiated_version ≥ 54458(возможностьADDENDUM), клиент отправляетAddendum. Это решение принимается на основе согласованной версии, а не версии, объявленной клиентом.
READY; при любой ошибке оно завершается.
Фаза Ping
запросом, поэтому несколько последовательных Ping независимы.
Начиная с READY, последовательность такова:
-
Клиент отправляет
Ping. -
Клиент читает ответ:
Тип пакета Действие Pong(4)Работоспособность подтверждена. Возврат в READY.Exception(2)Декодировать Exceptionи вернуть как ошибку.любой другой Нарушение протокола.
Фаза запроса
EndOfStream или Exception.
Начиная с READY, последовательность выглядит так:
При ошибке на любом этапе сервер отправляет 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 приводит к её завершению.
Случай
num_rows == 0 часто вызывает путаницу в новых реализациях. Block с нулём строк — это маркер границы или заголовок схемы, а не признак конца потока. Ответ завершается только при EndOfStream или Exception.Фаза INSERT
INSERT; сервер отвечает блоком схемы, описывающим целевую таблицу; клиент потоково передаёт пакеты Data со строками, а затем пустой маркер Data; сервер завершает обмен сообщением EndOfStream или Exception.
Начиная с READY, SQL-запрос имеет вид INSERT INTO <table> [(<cols>)] VALUES — без встроенного литерала VALUES (...), поскольку данные строк передаются через пакеты Data. Поток:
- Клиент отправляет
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. Нарушения протокола и ошибки ввода-вывода приводят к его завершению.
Справочник сообщений
Type используются:
VarUInt— беззнаковое целое число переменной длины (см. VarUInt).String— байты с префиксомVarUInt(см. String).UInt8,Int32и так далее — целые числа фиксированной длины в little-endian формате.Bool— один байт,0x00или0x01.
Role показывает, кто использует каждое поле:
- client — задаётся внешними клиентами.
- inter-server — имеет значение только при обмене между серверами; внешние клиенты записывают значение по умолчанию.
- universal — используется в обоих случаях.
ClientHello (тип пакета 0)
| # | Поле | Тип | Роль | Описание |
|---|---|---|---|---|
| 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 | Имя пользователя для аутентификации |
| 7 | password | String | universal | Пароль (в открытом виде) |
ServerHello (тип пакета 0)
| # | 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. Текущее значение: 7. |
| 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 на сервере. Внешние клиенты декодируют и игнорируют. |
password_complexity_rules:
| # | Field | Type | Description |
|---|---|---|---|
| 1 | pattern | String | Шаблон регулярного выражения, которому должен соответствовать пароль. |
| 2 | message | String | Человекочитаемое пояснение, показываемое, если пароль не проходит это правило. |
Чтобы ограничить использование ресурсов при работе с враждебным или неверно настроенным сервером, ограничьте декодированное значение
count 256 элементами, а каждую строку pattern и message — 4096 байтами. Значение count, равное 0 (без последующих пар), — обычный случай для серверов, у которых политика паролей не настроена.Дополнение (без типа пакета)
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) | Поддерживаемая клиентом версия протокола координации параллельных реплик. Внешние клиенты, не участвующие в распределённых запросах, всё равно должны отправлять допустимую версию (сейчас 7), чтобы проверка совместимости на стороне сервера проходила успешно. |
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)
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 | Адрес сокета исходного клиента в формате host: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). |
Структура, зависящая от интерфейса (поля 7–12)Поля 7–12 выше относятся к ветке TCP. Когда
query_interface (поле 6) не TCP, эти поля заменяются другой структурой в wire-формате — это не просто необязательные пропуски, поэтому декодер должен выполнять ветвление по полю 6.query_interface = 2(HTTP): вместо них записывается информация о HTTP-запросе, пересланном сервером, —http_method(UInt8),http_user_agent(String), затемforwarded_for(String, при наличииX_FORWARDED_FOR_IN_CLIENT_INFOv54443) иhttp_referer(String, при наличииREFERER_IN_CLIENT_INFOv54447). Поляos_user/client_hostname/client_name/version_*/protocol_versionотсутствуют.- Любой другой интерфейс: ни поля TCP (7–12), ни поля HTTP не записываются; поток сразу продолжается с
quota_key.
quota_key (поле 13) и distributed_depth (поле 14) следуют для всех интерфейсов, затем version_patch (поле 15) записывается только для TCP.Эта ветка важна главным образом для межсерверного трафика, когда инициирующий сервер пересылает запрос, который изначально пришел по HTTP. Декодер, который всегда читает поля TCP, будет неверно интерпретировать такие пакеты — принимая http_method или http_user_agent за quota_key.Межсерверная аутентификация
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)в поле 4 — 32-байтный дайджест. (nonce— это его десятичное строковое представление, присутствующее только при согласовании ≥ v54462;external_rolesдобавляется только при согласованииINTERSERVER_EXTERNALLY_GRANTED_ROLES(v54472).) ДляInitialQuery, а также если секрет кластера не настроен, клиент вместо этого записывает пустую строку. - Проверьте. Сервер читает поле 4 с ограничением в 32 байта и заново вычисляет ту же конкатенацию, используя свою собственную копию секрета кластера; соединение отклоняется, если дайджесты не совпадают.
auth_hash.
Параметр
key — одним VarUInt 0, без последующих flags или value. Только кодирование каждого отдельного параметра зависит от согласованной версии и определяется SETTINGS_SERIALIZED_AS_STRINGS (v54429).
v54429+ (STRINGS_WITH_FLAGS) — каждый параметр представляет собой тройку, показанную ниже:
| # | Поле | Тип | Роль | Описание |
|---|---|---|---|---|
| 1 | key | String | универсальная | Имя параметра. Пустое значение = конец списка. |
| 2 | flags | VarUInt | универсальная | Битовые флаги метаданных; см. ниже. |
| 3 | value | String | универсальная | Значение параметра в виде строки |
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 | Значение параметра в строковом виде. См. примечание ниже о кавычках. |
Значение параметра — это SQL-представление значения, а не сырой литерал. Параметры строкового типа должны передаваться уже заключёнными в одинарные кавычки (например, значение для
{name:String} — 'Alice', а не Alice); в противном случае парсер значений на стороне сервера их отклонит.Данные (пакет типа 1 server→client, пакет типа 2 client→server)
table_name. Отличается только байт типа пакета.
| Поле | Type | Role | Описание |
|---|---|---|---|
| table_name | String | universal | Имя внешней таблицы. Пустое значение ("") — обычный случай для основной таблицы, результатов запроса и потока строк INSERT. Само по себе пустое table_name не является маркером конца данных (обычные пакеты строк INSERT тоже содержат ""). |
| Тело блока | — | — | См. Структура блока и столбца. |
0 столбцов и 0 строк, независимо от table_name. Сервер считает клиентский пакет Data терминатором только тогда, когда декодированный блок пуст (block.empty()); пакет с table_name = "" и непустым блоком — это обычный пакет строк, а не терминатор. Таким образом, поток строк INSERT — это последовательность непустых блоков Data, за которой следует один пустой блок Data, завершающий поток.
Варианты блоков и их значение описаны в разделе Варианты блоков.
Прогресс (тип пакета 3)
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 (всегда пустая), за которой следует блок. Отличается только байт типа пакета.
Extremes (тип пакета 8)
extremes. Формат передачи данных идентичен данным. Блок содержит ровно 2 строки: строка 0 содержит минимум по каждому столбцу, строка 1 — максимум.
Log (тип пакета 10)
send_logs_level; см. потоковую передачу логов).
Формат обёртки и тела такой же, как у данных. Блок имеет фиксированное значение num_columns = 8 и предопределённую схему. Каждая строка лога соответствует одной строке по всем 8 столбцам, и один пакет Log может содержать много строк.
| # | 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) |
| 7 | source | String | Имя логгера |
| 8 | text | String | Текст сообщения лога |
ProfileEvents (тип пакета 14)
num_columns = 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 |
Тип элемента столбца
value различается между пакетами: старые серверы выдают UInt64, новые — Int64. Считывайте строку типа столбца из заголовка блока, а не предполагая фиксированную разрядность.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 ''". Произвольный текст — разбирайте как строку. |
Сжатое тело в v54481+При согласованной версии ≥ 54481 (
COMPRESSED_LOGS_PROFILE_EVENTS_COLUMNS) сервер записывает оба поля через один и тот же выходной поток с необязательным сжатием, поэтому, если у запроса compression = true, всё тело TableColumns (external_table + columns_description) находится внутри фрейма сжатия; клиент считывает его через соответствующий распакованный поток. Если для запроса сжатие не используется, тело передаётся несжатым ровно в том виде, как показано в таблице выше. Это важно для ответов со схемой INSERT: клиент, который переключает обработку сжатия для Log и ProfileEvents, но не для TableColumns, будет неверно читать ответ, если для запроса включено сжатие.TimezoneUpdate (пакет типа 17)
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-подпись от конкатенации, определённой ниже, а не от сырого челленджа |
- Клиент отправляет ClientHello с SSH-префиксом-маркером и пустым паролем.
-
Клиент отправляет
SSHChallengeRequest(пакет 11). Сервер ещё не отправил ServerHello — он сначала обрабатывает аутентификацию и останавливается здесь в ожидании этого пакета. -
Сервер отвечает пакетом
SSHChallengeсо случайными байтами (пакет 18). -
Клиент формирует строку для подписи и подписывает её, а не сырой челлендж, затем отправляет
SSHChallengeResponse(пакет 12) с подписью. Подписываемое сообщение — это побайтовая конкатенация без разделителей четырёх частей в следующем точном порядке:Part 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 и т. д.); в случае неудачи сервер возвращаетИсключениеи завершает соединение. Клиент, который подписывает только сырые байты челленджа, не пройдёт аутентификацию.
Это обратный вариант рукопожатия с паролем, при котором сразу после ClientHello следует ServerHello. При SSH-аутентификации ServerHello задерживается до проверки подписи, поэтому механизм SSH challenge-response встраивается в рукопожатие до появления какого-либо ServerHello.
Справочник по типам пакетов
Клиент → Сервер
| Код | Имя | Формат тела | Описание |
|---|---|---|---|
| 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 | не указано | План запроса |
Сервер → Клиент
| Код | Имя | Формат тела | Описание |
|---|---|---|---|
| 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 | секунды | сервер | Интервал цикла проверки бездействующих соединений и завершения работы сервера. |
Ограничения соединений
| Параметр | По умолчанию | Единица | Сторона | Описание |
|---|---|---|---|---|
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". |
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 к телеметрии ответа. |
Настройки вне области рассмотрения
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 — см. структуру столбца в передаваемых данных и версируемые типы.
Глоссарий
- Обычный запрос (
SELECTи т. п.): отправляется после пакета Query и всех пакетов данных внешних таблиц, чтобы сообщить: «внешних данных больше нет». После этого server начинает выполнение. INSERT: клиент не отправляет маркер до схемы. Сначала server отправляет блок схемы, затем клиент передаёт свои блоки данных со строками и только после этого отправляет пустой пакет данных, чтобы завершить поток строк. Если отправить пустой маркер до блока схемы, он будет прочитан как немедленное завершение строк, и данные будут потеряны.
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 (успех) или Исключение (ошибка). Входной поток клиента завершается пустым маркером данных.