Skip to main content
Собственный протокол — это бинарный протокол с установлением соединения, по которому клиенты и серверы ClickHouse взаимодействуют по TCP. Он передаёт SQL-запросы, данные результатов, полезную нагрузку INSERT, телеметрию выполнения и сообщения об ошибках. Именно этот протокол используется клиентом командной строки, C++ и большинством сторонних нативных драйверов. На этой странице рассматривается сам протокол: кадрирование пакетов, машина состояний соединения, согласование версий и тело каждого сообщения, кроме Block. Байты внутри пакетов семейства Data (то есть Block, его столбцы и кодировки отдельных типов) — отдельная тема, описанная в спецификации Native Format.
Сопутствующая спецификацияЭта страница — одна из двух частей пары и публикуется вместе с сопутствующей спецификацией Native Format. Эти две спецификации чётко разделяют задачи: эта страница описывает пакетный и транспортный уровни, а спецификация Native Format — байты внутри пакетов семейства Data.
Для всего протокола справедливы несколько свойств. Протокол бинарный и позиционный: тегов полей нет, кроме BlockInfo, поэтому один смещённый байт нарушает синхронизацию всего, что идёт дальше. Он работает с сохранением состояния, и каждое TCP-соединение обрабатывает только один запрос за раз — мультиплексирования нет. Для целых чисел фиксированной ширины используется порядок байтов little-endian.

Обзор

Каждое передаваемое по сети сообщение начинается с кода типа пакета VarUInt, за которым следует тело, структура которого зависит от этого кода и согласованной версии протокола. Соединение проходит через три фазы — однократное рукопожатие, затем любое количество обменов Ping или Query, после чего соединение закрывается: Протокол нативного TCP всегда передаёт табличные данные в формате Native, независимо от наличия предложения FORMAT в SQL. Повторное форматирование в RowBinary, CSV, JSON и так далее — задача клиента; оно выполняется после декодирования блоков Native. (HTTP-интерфейс использует другой кодовый путь и действительно учитывает предложение FORMAT; HTTP здесь не рассматривается.)

Безопасность

Защита транспортного уровня (TLS)

TLS работает на транспортном уровне, ниже протокола. Когда TLS включён, шифруется весь TCP-трафик, а сообщения протокола остаются побайтно идентичными независимо от того, используется TLS или нет.

Аутентификация

Аутентификация происходит во время рукопожатия, в сообщении ClientHello. Поля user и password передаются как строки в открытом виде, поэтому учетные данные при передаче защищаются шифрованием на транспортном уровне (TLS). Аутентификация SSH по схеме challenge-response доступна начиная с версии протокола 54466 — см. Аутентификация SSH по схеме challenge-response.

Межсерверный секрет

Для выполнения распределённого запроса серверы аутентифицируют друг друга, подтверждая знание общего секрета, — без передачи самого секрета по сети. Каждый Query содержит 32-байтный SHA-256 auth_hash в поле 4 Query, вычисленный на основе salt, nonce, настроенного секрета и запроса; принимающий сервер вычисляет его заново и сравнивает. Это контролируется возможностью INTERSERVER_SECRET (v54441). Внешние клиенты всегда отправляют здесь пустую строку. См. Межсерверная аутентификация.

Версионирование и флаги возможностей

Согласование версии

И клиент, и сервер сообщают максимальную поддерживаемую версию протокола при рукопожатии. Согласованная версия — меньшая из двух:
Каждое последующее сообщение использует согласованную версию, чтобы определить, какие поля присутствуют в двоичном представлении.

Флаги возможностей

Возможность определяется версией протокола, в которой она появилась, и считается активной, если согласованная версия больше или равна этому номеру.
Когда возможность активна, её поля обязательно должны присутствовать в бинарном представлении данных. Протокол строго позиционный, поэтому пропуск поля, управляемого флагом возможности, нарушает поток байтов для всех последующих полей.

Таблица возможностей

Оболочка пакета

Каждое сообщение в передаваемом двоичном формате имеет одинаковую внешнюю структуру в обоих направлениях:
Полные таблицы типов пакетов приведены в справочнике по типам пакетов. Тип пакета — это VarUInt, а не байт фиксированной длины. Для значений меньше 128 VarUInt даёт тот же однобайтный результат, но реализации должны использовать кодирование VarUInt, чтобы сохранить совместимость, если в будущем появятся типы пакетов со значением 128 и выше. В справочнике по сообщениям описывается только тело каждого пакета — байты после кода типа пакета. Нумерация полей начинается с 1, где первое поле — это первое поле тела.

Фрагментное кадрирование (v54470+)

Когда возможность CHUNKED_PROTOCOL согласована (см. этап рукопожатия), каждый пакет при передаче оборачивается во фрагментное кадрирование. Такое оборачивание выполняется отдельно для каждого направления: client→server и server→client согласуются независимо и в итоге могут работать в разных режимах (с фрагментным кадрированием или без него). Структура данных в канале для каждого пакета:
Структура данных в wire-формате для каждого фрагмента:
Тип пакета 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 — гибкие: они принимают любой режим, который выберет другая сторона.
Согласованное значение для каждого направления вычисляется попарно: На стороне клиента предпочтение SEND клиента согласуется с предпочтением RECV сервера, и наоборот. Время применения. Строки согласования передаются без кадрирования: ClientHello → ServerHello (предпочтения сервера) → Addendum (согласованные значения клиента). Переключение на кадрирование применяется ко всем байтам, отправленным после того, как Addendum сброшен на диск. Сам Addendum, ClientHello и ServerHello всегда передаются без кадрирования.

Жизненный цикл соединения

В любой момент соединение находится ровно в одном из четырёх состояний: HANDSHAKE, READY, READING_RESPONSE или завершено. Поскольку протокол не поддерживает мультиплексирование, клиент, который отправляет новый запрос, не дочитав предыдущий ответ до конца, перемешивает байты в потоке передачи данных и повреждает поток.

Состояния

Штатный сценарий идёт прямо вниз — HANDSHAKE → READY → READING_RESPONSE → READY — с самопетлёй Ping/Pong, а все ветви сбоев сходятся в единственное конечное состояние Terminated.

Фаза рукопожатия

На этом этапе выполняются аутентификация и согласование версии протокола. Это происходит ровно один раз для каждого соединения — прежде всего остального. TCP-соединение только что установлено, и никакими сообщениями стороны ещё не обменивались. Последовательность:
  1. Клиент отправляет ClientHello, указав максимальную поддерживаемую версию протокола.
  2. Клиент читает ответ и обрабатывает его в зависимости от типа пакета:
  3. Если negotiated_version ≥ 54458 (возможность ADDENDUM), клиент отправляет Addendum. Это решение принимается на основе согласованной версии, а не версии, объявленной клиентом.
В случае успеха соединение переходит в состояние READY; при любой ошибке оно завершается.

Фаза Ping

Проверка работоспособности на уровне приложения, не зависящая от TCP keepalive. Успешный обмен Ping/Pong подтверждает, что TCP-соединение активно в обоих направлениях и сервер отвечает. Ping не хранит состояние и не связан ни с каким запросом, поэтому несколько последовательных Ping независимы. Начиная с READY, последовательность такова:
  1. Клиент отправляет Ping.
  2. Клиент читает ответ:

Фаза запроса

Клиент отправляет SQL-оператор; сервер потоково возвращает блоки результатов и телеметрию выполнения. Ответ представляет собой последовательность пакетов, которая завершается ровно одним EndOfStream или Exception. Начиная с READY, последовательность выглядит так: При ошибке на любом этапе сервер отправляет Exception вместо EndOfStream, что приводит к завершению запроса.
  1. Клиент отправляет Query с уникальным query_id (обычно UUID).
  2. Клиент отправляет все внешние таблицы, затем пустой маркер Data. У пустого пакета Data: table_name = "", num_columns = 0, num_rows = 0. Сервер не начинает выполнять запрос, пока не получит этот маркер.
  3. Клиент переходит в READING_RESPONSE и сбрасывает буфер записи.
  4. Клиент в цикле читает пакеты ответа и обрабатывает их по типу:
При EndOfStream или обработанном Exception connection возвращается в READY. Нарушение protocol или ошибка I/O приводит к её завершению.
Случай num_rows == 0 часто вызывает путаницу в новых реализациях. Block с нулём строк — это маркер границы или заголовок схемы, а не признак конца потока. Ответ завершается только при EndOfStream или Exception.

Фаза INSERT

Фаза INSERT — это фаза запроса с двумя дополнительными обменами. Клиент отправляет оператор INSERT; сервер отвечает блоком схемы, описывающим целевую таблицу; клиент потоково передаёт пакеты Data со строками, а затем пустой маркер Data; сервер завершает обмен сообщением EndOfStream или Exception. Начиная с READY, SQL-запрос имеет вид INSERT INTO <table> [(<cols>)] VALUES — без встроенного литерала VALUES (...), поскольку данные строк передаются через пакеты Data. Поток:
  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-соединения.

ServerHello (тип пакета 0)

Server → Client. Ответ на ClientHello при успешной аутентификации. Rule — элемент password_complexity_rules: Этот список отражает конфигурацию политики паролей, заданную оператором сервера, и носит исключительно рекомендательный характер — сервер не применяет эти правила во время рукопожатия. Клиент, предоставляющий возможность смены или установки пароля, может использовать эти правила, чтобы выявлять ошибки до отправки на сервер пароля, не соответствующего требованиям.
Чтобы ограничить использование ресурсов при работе с враждебным или неверно настроенным сервером, ограничьте декодированное значение count 256 элементами, а каждую строку pattern и message — 4096 байтами. Значение count, равное 0 (без последующих пар), — обычный случай для серверов, у которых политика паролей не настроена.

Дополнение (без типа пакета)

Client → Server, доступно при ADDENDUM (v54458). Отправляется сразу после завершения обмена рукопожатия. Это не отдельный тип пакета — поля передаются в wire в сыром виде, без байтового префикса типа пакета. Переключение на фрагментированное кадрирование вступает в силу после сброса этого Дополнения на диск — само Дополнение передаётся без кадрирования.

Ping (тип пакета 4)

Клиент → сервер. Без тела — до разбиения на фрагменты пакет представляет собой один байт 0x04; при согласованном разбиении на фрагменты этот байт становится однобайтовой полезной нагрузкой фрагмента (см. кадрирование с фрагментацией).

Pong (тип пакета 4)

Сервер → Клиент. Без тела — пакет представляет собой один байт 0x04 до кадрирования с фрагментацией; если согласована фрагментация, этот байт становится однобайтной полезной нагрузкой фрагмента (см. кадрирование с фрагментацией).

Исключение (тип пакета 2)

Сервер → Клиент. Отправляется при возникновении ошибки на любом этапе.

Query (тип пакета 1)

Клиент → Сервер.

ClientInfo (встроено в Query)

Клиент → Сервер, встроено в тело Query (поле 2). Поддерживается начиная с CLIENT_INFO (v54032). (Некоторые поля внутри ClientInfo поддерживаются только в более поздних версиях — это указано ниже для каждого поля.)
Структура, зависящая от интерфейса (поля 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_INFO v54443) и http_referer (String, при наличии REFERER_IN_CLIENT_INFO v54447). Поля 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.
Кодирование OpenTelemetry (поле 16):

Межсерверная аутентификация

Поле 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) в поле 4 — 32-байтный дайджест. (nonce — это его десятичное строковое представление, присутствующее только при согласовании ≥ v54462; external_roles добавляется только при согласовании INTERSERVER_EXTERNALLY_GRANTED_ROLES (v54472).) Для 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) — каждый параметр представляет собой тройку, показанную ниже: Поля 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), и таким же образом завершаются пустым ключом.
Значение параметра — это SQL-представление значения, а не сырой литерал. Параметры строкового типа должны передаваться уже заключёнными в одинарные кавычки (например, значение для {name:String}'Alice', а не Alice); в противном случае парсер значений на стороне сервера их отклонит.

Данные (пакет типа 1 server→client, пакет типа 2 client→server)

В обоих направлениях. Содержит блоки результатов, данные для INSERT, внешние таблицы и маркеры окончания данных. Формат передачи данных симметричен — в обоих направлениях перед Block идёт префикс table_name. Отличается только байт типа пакета.
Маркер конца данных — это пакет, у которого Block пуст: 0 столбцов и 0 строк, независимо от table_name. Сервер считает клиентский пакет Data терминатором только тогда, когда декодированный блок пуст (block.empty()); пакет с table_name = "" и непустым блоком — это обычный пакет строк, а не терминатор. Таким образом, поток строк INSERT — это последовательность непустых блоков Data, за которой следует один пустой блок Data, завершающий поток. Варианты блоков и их значение описаны в разделе Варианты блоков.

Прогресс (тип пакета 3)

Сервер → клиент. Отправляется периодически во время выполнения запроса. Все поля имеют тип VarUInt, и каждый пакет содержит приращения относительно предыдущего пакета Progress, а не накопленные итоги. Перед отправкой сервер считывает свои счётчики и атомарно сбрасывает их в ноль, а elapsed_ns вычисляет как разницу во времени с момента предыдущей отправки. Поэтому клиент должен накапливать последовательные пакеты локально, чтобы получить накопленные итоги — если трактовать пакет как абсолютное значение, отображение прогресса начнёт откатываться назад или занижать значения, как только придёт более одного пакета.

ProfileInfo (тип пакета 6)

Сервер → Клиент. Отправляется один раз для каждого запроса, ближе к завершению выполнения.

Итоги (тип пакета 7)

Сервер → клиент. Отправляется для запросов с WITH TOTALS. Формат передачи данных идентичен данным: строка table_name (всегда пустая), за которой следует блок. Отличается только байт типа пакета.

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

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

Log (тип пакета 10)

Сервер → клиент. Отправляется, когда у запроса есть активная очередь логов (параметр send_logs_level; см. потоковую передачу логов). Формат обёртки и тела такой же, как у данных. Блок имеет фиксированное значение num_columns = 8 и предопределённую схему. Каждая строка лога соответствует одной строке по всем 8 столбцам, и один пакет Log может содержать много строк.
8 столбцов в этом точном порядке:

ProfileEvents (тип пакета 14)

Сервер → клиент. Передаёт счётчики производительности для каждого запроса. Тот же формат оболочки и тела, что и у данных. Блок имеет фиксированное значение num_columns = 6 и предопределённую схему. Каждое событие соответствует одной строке.
6 столбцов:
Тип элемента столбца value различается между пакетами: старые серверы выдают UInt64, новые — Int64. Считывайте строку типа столбца из заголовка блока, а не предполагая фиксированную разрядность.

TableColumns (тип пакета 11)

Сервер → клиент, доступно при COLUMN_DEFAULTS_METADATA (v54410). Сервер отправляет этот пакет перед блоком схемы INSERT, чтобы передать метаданные значений по умолчанию для столбцов, но только если согласованная версия ≥ 54410 и включена настройка input_format_defaults_for_omitted_fields. Для версий ниже 54410 пакет не отправляется никогда, поэтому более старый клиент не должен его ожидать — блок схемы Data приходит сразу. Клиент v54410+ должен быть готов к одному из двух вариантов: либо необязательный TableColumns, затем блок схемы, либо сразу блок схемы.
Сжатое тело в v54481+При согласованной версии ≥ 54481 (COMPRESSED_LOGS_PROFILE_EVENTS_COLUMNS) сервер записывает оба поля через один и тот же выходной поток с необязательным сжатием, поэтому, если у запроса compression = true, всё тело TableColumns (external_table + columns_description) находится внутри фрейма сжатия; клиент считывает его через соответствующий распакованный поток. Если для запроса сжатие не используется, тело передаётся несжатым ровно в том виде, как показано в таблице выше. Это важно для ответов со схемой INSERT: клиент, который переключает обработку сжатия для Log и ProfileEvents, но не для TableColumns, будет неверно читать ответ, если для запроса включено сжатие.

TimezoneUpdate (пакет типа 17)

Server → Client, используется при TIMEZONE_UPDATES (v54464). Отправляется ровно в одном месте: при инициализации табличной функции input (запрос вида INSERT INTO <table> SELECT ... FROM input('<structure>'), который потоково передаёт строки от клиента). Сразу после того, как сервер отправляет Data-блок входной схемы (см. фазу INSERT), он отправляет TimezoneUpdate с текущим значением session_timezone из контекста запроса, чтобы клиент разбирал строки, которые собирается отправить, в том же часовом поясе. Сервер не отправляет этот пакет при произвольных изменениях SET session_timezone посреди запроса и не использует его, чтобы сообщить клиенту, как форматировать последующие блоки результатов. Пакет приходит один раз: сразу после блока входной схемы и до того, как клиент начнёт отправлять блоки строк. Декодер, который игнорирует TimezoneUpdate, ВСЁ РАВНО ДОЛЖЕН прочитать завершающий String, чтобы сохранить выравнивание в потоке.

Аутентификация SSH по схеме челлендж-ответ (типы пакетов 11, 12, 18)

Управляется флагом SSH_AUTHENTICATION (v54466) и включается только явным образом. Соединение переходит в SSH-сценарий, когда ClientHello отправляет user = " SSH KEY AUTHENTICATION " + <real_user> (с пробелами в начале и конце) и password = "". Сервер считывает префикс, удаляет его, чтобы восстановить реальное имя пользователя, и переключается на схему челлендж-ответ. Этот сценарий используется вместо аутентификации по паролю, а обмен по схеме челлендж-ответ происходит до ServerHello — сервер откладывает ответ Hello, пока аутентификация не завершится успешно:
  1. Клиент отправляет ClientHello с SSH-префиксом-маркером и пустым паролем.
  2. Клиент отправляет SSHChallengeRequest (пакет 11). Сервер ещё не отправил ServerHello — он сначала обрабатывает аутентификацию и останавливается здесь в ожидании этого пакета.
  3. Сервер отвечает пакетом SSHChallenge со случайными байтами (пакет 18).
  4. Клиент формирует строку для подписи и подписывает её, а не сырой челлендж, затем отправляет SSHChallengeResponse (пакет 12) с подписью. Подписываемое сообщение — это побайтовая конкатенация без разделителей четырёх частей в следующем точном порядке:
  5. Сервер проверяет подпись по зарегистрированному открытому ключу пользователя, восстанавливая ту же строку decimal(protocol_version) + default_database + user + challenge. В случае успеха он отправляет ServerHello — тот же ответ, что и при аутентификации по паролю, — и рукопожатие продолжается в обычном режиме (Addendum и т. д.); в случае неудачи сервер возвращает Исключение и завершает соединение. Клиент, который подписывает только сырые байты челленджа, не пройдёт аутентификацию.
Это обратный вариант рукопожатия с паролем, при котором сразу после ClientHello следует ServerHello. При SSH-аутентификации ServerHello задерживается до проверки подписи, поэтому механизм SSH challenge-response встраивается в рукопожатие до появления какого-либо ServerHello.
Внешние клиенты, не использующие SSH-аутентификацию, никогда не видят пакеты 11, 12 или 18 — они не передаются по сети, если только пользователь явно не включит их через префикс имени пользователя.

Справочник по типам пакетов

Клиент → Сервер

Сервер → Клиент

Конфигурация

В этом разделе рассматриваются параметры, определяющие работу соединений по собственному протоколу: Приведённые ниже значения по умолчанию соответствуют одному из недавних выпусков сервера; они могут различаться в разных версиях и развертываниях.

Настройки транспортного уровня

Параметры сокета

Тайм-ауты

Эти тайм-ауты вложены следующим образом:
Сначала срабатывает механизм keepalive ОС и может незаметно для приложения обнаружить недоступные узлы на уровне ядра. Следующий рубеж защиты — тайм-аут на получение данных приложением. Тайм-аут бездействия — крайняя мера, закрывающая соединения, которые долго не использовались.

Ограничения соединений

Соединение, которое регулярно выполняет запросы, может существовать неограниченно долго. Через час закрываются только бездействующие соединения, а максимальное время жизни по умолчанию не ограничено.

Настройки прикладного уровня

Эти настройки передаются с каждым запросом в списке настроек пакета запроса. Они влияют на то, какие данные сервер отправляет по сети и как они разбиваются на кадры.

Сжатие

Флаг compression в пакете Query (поле 6) включает и отключает сжатие; эти настройки определяют, какой кодек используется, когда сжатие включено.

Потоковая передача журналов

Если задать для send_logs_level любое значение, кроме "none", сервер будет отправлять пакеты Log во время выполнения запроса.

Отчёт о прогрессе

Это целевой минимум, а не жёсткий максимум: сервер может отправлять пакеты Прогресс реже, если запрос выполняется недостаточно быстро.

Оболочка результата

Асинхронная вставка

Распределённая трассировка

Настройки вне области рассмотрения

Эти настройки иногда ошибочно принимают за настройки уровня протокола, но на самом деле они управляют выполнением 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 (успех) или Исключение (ошибка). Входной поток клиента завершается пустым маркером данных.
Last modified on June 25, 2026