Skip to main content
Формат фрейминга мультиплексирует различные части ответа на запрос в одном потоке: фрагменты данных, итоги и экстремумы, пакеты прогресса, события профилирования (метрики) и серверные журналы — всё, что поддерживает собственный протокол. Это обеспечивает расширенный обмен данными по HTTP-протоколу. Форматы фрейминга не зависят от форматов вывода: они инкапсулируют байты, создаваемые любым форматом вывода, разделяя и при необходимости кодируя эти фрагменты байтов. Конкатенация полезных нагрузок всех пакетов data, totals и extremes в точности соответствует тому, что сформировал бы формат вывода без фрейминга. Вспомогательные пакеты (прогресс, журналы, события профилирования, исключения) представляются в формате JSON. Фрейминг также может расширять возможности формата вывода — это единственное намеренное исключение из указанного выше правила. Семейство форматов JSONCompactEachRow не включает итоги и экстремумы в обычный вывод, поскольку их строки невозможно отличить от обычных строк данных. При использовании формата фрейминга тип пакета позволяет их различать, поэтому эти форматы помещают строки итогов и экстремумов (в обычном синтаксисе строк) в пакеты totals и extremes. Для этих форматов конкатенация полезных нагрузок только пакетов data в точности соответствует тому, что формат вывода сформировал бы без фрейминга, а пакеты totals и extremes содержат дополнительные строки, отсутствующие в выводе без фрейминга. Поэтому клиент, восстанавливающий вывод без фрейминга из такого потока, должен объединять только полезные нагрузки data. Формат фрейминга выбирается настройкой уровня запроса framing_output_format. В настоящее время он применяется к HTTP-протоколу и игнорируется другими интерфейсами. Серверные журналы включаются в поток в виде пакетов, если задана настройка send_logs_level. События профилирования включаются, если включена настройка send_profile_events (по умолчанию). Пакеты прогресса и событий профилирования отправляются не чаще одного раза в interactive_delay микросекунд. Успешный поток завершается финальным пакетом progress, содержащим итоговые счётчики (result_rows, result_bytes, memory_usage), подобно финальному пакету прогресса собственного протокола. Эти счётчики становятся известны только после завершения запроса, поэтому ни один предыдущий пакет progress их не содержит. Финальный пакет progress записывается после завершающих пакетов log и profile_events, сформированных при журналировании завершения запроса (например, записи в журнале «пиковое потребление памяти»), поэтому он действительно является последним пакетом потока. При сбое последним пакетом становится пакет exception, а пакет progress с итоговыми счётчиками вообще не записывается — он служит маркером успешного завершения потока — даже если сбой произошёл после завершения самого запроса и итоговые счётчики уже были известны (например, при сбое записи в журнал запросов). Поскольку этот завершающий участок потока записывается после регистрации записи QueryFinish в system.query_log, события профилирования сетевой отправки запроса (NetworkSendBytes, NetworkSendElapsedMicroseconds) не включают отправку завершающих пакетов и закрытие ответа, а также, если ответ буферизуется (http_response_buffer_size или wait_end_of_query), отправку буферизованного тела ответа, которое передаётся только после завершения запроса. Это соответствует собственному протоколу, который также отправляет завершающие журналы и события профилирования после записи в журнале запросов. Всё, что запрос включает только через собственное предложение SETTINGS — формат фрейминга, send_logs_level или send_profile_events, — неизвестно до разбора запроса, поэтому соответствующие журналы и события профилирования фиксируются только с момента выполнения запроса. Журналы и события профилирования этапов разбора, планирования и анализа фиксируются, только если настройка задана в сеансе или URL. В частности, запрос, завершающийся сбоем на этапе анализа (до выполнения конвейера) — например, из-за ссылки на неизвестную таблицу, — и включающий send_logs_level только в своём предложении SETTINGS, передаёт только пакет exception, но не журналы этапа анализа. Чтобы фиксировать их, задайте send_logs_level в сеансе или URL. То же ограничение, связанное с поздним обнаружением, действует и для send_logs_source_regexp: очередь журналов фильтрует записи по источнику в момент захвата каждой из них, поэтому регулярное выражение, заданное только в предложении SETTINGS самого запроса, начинает действовать лишь с момента выполнения запроса. Пакеты log этапов разбора, планирования и анализа фильтруются значением настройки, заданным для сеанса или в URL; если там оно не задано, фильтрация не выполняется. Поэтому они могут содержать источники, не соответствующие регулярному выражению на уровне запроса. И наоборот, записи, отброшенные более узким регулярным выражением на уровне сеанса или URL, безвозвратно теряются и не восстанавливаются более широким выражением на уровне запроса. Задайте send_logs_source_regexp для сеанса или в URL, чтобы фильтровать весь жизненный цикл запроса. Если во время выполнения запроса возникает исключение, оно отправляется в виде пакета exception (последнего пакета потока) независимо от настройки http_write_exception_in_output_format, поэтому клиент всегда может разобрать ответ как поток пакетов. После регистрации исключения формат вывода больше не добавляет байты полезной нагрузки: запрос, завершающийся сбоем до формирования какого-либо вывода, вообще не передаёт пакет data (даже пустой каркас документа формата), а запрос, завершающийся сбоем в середине потока, оставляет объединённую полезную нагрузку усечённой в точке сбоя, без суффикса формата — полезная нагрузка завершившегося сбоем запроса не должна выглядеть как полноценный документ. Есть одно исключение: если запись самого пакета завершается сбоем после частичной записи (например, соединение разрывается после того, как некоторые байты пакета уже достигли клиента), кадрирование аварийно завершается, а поток прекращается без финального пакета exception. Частично записанный пакет никогда не отправляется повторно, поскольку это добавило бы дубликат после усечённых байтов и повредило поток. В этой ситуации клиент получает усечённый ответ и разорванное HTTP-соединение вместо корректно сформированного завершающего пакета. То же правило применяется при сбое во время закрытия самого потока ответа (сброса буферизованных результатов, завершения HTTP-сжатия, закрытия сокета): к этому моменту часть или весь поток успешного ответа уже переданы по сети, поэтому к нему ничего не добавляется — ни пакет exception, ни общий блок HTTP-ошибки, — и клиент получает усечённый ответ и разорванное соединение. Оно также применяется, когда сбой происходит при доставке самого исключения: если запись завершающего пакета exception завершается сбоем (например, при выгрузке завершающих журналов) после того, как была сформирована какая-либо часть потока пакетов — независимо от того, была ли она уже передана или всё ещё находится в серверных буферах ответа (http_response_buffer_size), — поток аналогично завершается без каких-либо добавлений, поэтому обычное тело HTTP-ошибки никогда не смешивается с частичным потоком пакетов. Сбой при записи строковых полей вспомогательных пакетов log, profile_events и exception также считается частично записанным пакетом, включая сбой при записи последних байтов такой строки: поток тогда заканчивается этим усечённым пакетом и вообще не содержит терминатора — ни пакета exception, ни финального пакета progress со счётчиками, — поэтому клиент, которому требуется терминатор, обнаружит сбой, даже если сам запрос выполнен успешно. Формат фрейминга также применяется к запросам, не создающим поток результатов: успешной команде INSERT, DDL-запросу или любому другому запросу без вывода. Такой ответ не содержит пакетов data, но всё равно задаёт для ответа Content-Type формата фрейминга и передаёт пакеты progress, log и profile_events, как в собственном протоколе. Поток заканчивается финальным пакетом progress, содержащим итоговые счётчики (например, result_rows и result_bytes с числом записанных строк для INSERT). Поскольку полезная нагрузка не форматируется, формат вывода для таких запросов не имеет значения и не влияет на кадрированный поток.

Доступные форматы фрейминга

None

Используется по умолчанию. Прозрачно передаёт всё применимое (данные, итоги, экстремальные значения, прогресс) в выходной формат и игнорирует всё неприменимое (метрики, журналы). Поэтому всё работает так же, как по умолчанию, включая форматы, которые сами поддерживают отображение прогресса, например JSONEachRowWithProgress.

EventStream

Упаковывает пакеты в события, отправляемые сервером, и задаёт для Content-Type ответа значение text/event-stream; charset=UTF-8; payload=base64. Каждый пакет отправляется как событие с именем, соответствующим типу пакета: data, totals, extremes, progress, log, profile_events, exception. Пакеты прогресса и другие вспомогательные пакеты отправляются в формате JSON. События, отправляемые сервером, — это текстовый протокол, в котором переводы строк (включая возврат каретки, \r) используются как разделители полей. Поэтому байты, сформированные форматом вывода, не встраиваются дословно: блок форматированных данных кодируется в Base64 в одном поле data:, которое при декодировании содержит полностью отформатированную полезную нагрузку со всеми переводами строк. Именно это означает параметр payload=base64 в Content-Type. Конкатенация декодированных полезных нагрузок пакетов data, totals и extremes в точности совпадает с тем, что формат вывода сформировал бы без кадрирования, байт в байт, для любого формата вывода: текстового, бинарного (Native, RowBinary) или необработанной сквозной передачи (RawBLOB, TSVRaw). Вспомогательные JSON-пакеты (progress, log, profile_events, exception) никогда не кодируются: они записываются как одно поле data: с JSON без переводов строк. Форматы вывода *WithProgress (JSONEachRowWithProgress, JSONCompactEachRowWithProgress) записывают прогресс как встроенные строки, являющиеся частью собственного вывода. Формат фрейминга вместо этого передаёт прогресс отдельными пакетами progress, поэтому он несовместим с этими форматами вывода и отклоняет их. Используйте базовый формат вывода (например, JSONEachRow) с кадрированием либо кадрирование None с форматом *WithProgress.
EventStream использует HTTP-протокол и генерирует исключение, если он неприменим.

JSONEachPacketBase64 и JSONEachPacketString

Каждый пакет представляет собой объект JSON в отдельной строке (JSON, разделённый символами новой строки, application/x-ndjson) и содержит информацию о пакете. Байты, сформированные форматом вывода, помещаются в поле data: в кодировке base64 в JSONEachPacketBase64 (подходит для двоичных форматов вывода) или как JSON-строка в JSONEachPacketString. Эти два варианта по-разному кодируют поле data, поэтому различаются по Content-Type ответа, как и EventStream: JSONEachPacketBase64 устанавливает application/x-ndjson; charset=UTF-8; payload=base64, а JSONEachPacketStringapplication/x-ndjson; payload=string. Таким образом, клиент может определить только по метаданным ответа, нужно ли декодировать поле data из base64. charset=UTF-8 гарантируется только для JSONEachPacketBase64, поскольку лишь кодирование base64 обеспечивает корректность UTF-8 для всего потока независимо от байтов полезной нагрузки — см. ниже. Поскольку JSONEachPacketString помещает байты полезной нагрузки в JSON-строку, он предназначен для форматов вывода, формирующих корректный текст UTF-8. Столбцы String и FixedString могут содержать произвольные байты, поэтому текстовые форматы вывода, такие как JSONEachRow, TSV или CSV, могут выводить для таких значений некорректный UTF-8 — так же, как собственный формат JSONEachRow ClickHouse при значении по умолчанию output_format_json_validate_utf8 = 0. В этом случае итоговая JSON-строка, а следовательно, и весь поток NDJSON, не обязательно будут корректным UTF-8. JSONEachPacketString не проверяет и не перекодирует полезную нагрузку; для точной побайтовой передачи произвольных байтов используйте JSONEachPacketBase64. Форматы вывода, которые заведомо создают байты не в UTF-8, JSONEachPacketString сразу отклоняет с ошибкой, до выполнения запроса: двоичные форматы (Native, RowBinary), сквозные форматы без обработки (RawBLOB, TSVRaw), форматы, записывающие в вывод из заголовка запроса имя столбца, имя типа данных или имя элемента Tuple, не закодированные в UTF-8, а также конфигурации, в которых литералы, задаваемые настройками, сериализации записывают дословно и которые не являются корректным UTF-8: настройки format_csv_delimiter, format_tsv_null_representation / format_csv_null_representation и bool_true_representation / bool_false_representation.
При использовании JSONEachPacketBase64 тот же пакет данных выглядит так:

Типы пакетов

В отличие от полезных нагрузок data, totals и extremes (см. приведённые выше примечания о точном соответствии байтов), для строковых полей вспомогательных пакетов (query_id, text и source в log, name в profile_events и сообщения exception) не предусмотрено экранирование с помощью base64. Некоторые из них (например, query_id, получаемый из запроса) могут содержать произвольные байты. Эти поля всегда преобразуются в допустимый UTF-8: недопустимые последовательности заменяются символом замены (U+FFFD), поэтому вспомогательные пакеты всегда представляют собой допустимый JSON. Одновременная обработка нескольких запросов пока не реализована, но дизайн это допускает: каждый пакет можно дополнить информацией об индексе запроса среди нескольких запросов.
Последнее изменение 27 августа 2026 г.