Описание
Полная официальная спецификация форматаNative доступна здесь, а сопутствующая спецификация протокола Native — TCP-протокола, по которому он передается, — доступна здесь.
Обе спецификации были сгенерированы LLM на основе исходного кода ClickHouse. Код остается основным источником истины: если спецификация и код расходятся, верен код.
Native — самый эффективный формат в ClickHouse, поскольку он действительно является «столбцовым»:
он не преобразует столбцы в строки.
В этом формате данные записываются и читаются блоками в бинарном формате.
Для каждого блока последовательно записываются количество строк, количество столбцов, имена и типы столбцов, а также части столбцов в блоке.
Этот формат используется в нативном интерфейсе для взаимодействия между серверами, в клиенте командной строки и в клиентах на C++.
Формат передачи данных для типов данных
Данные передаются в столбцовом формате: каждый столбец передаётся отдельно, а все его значения отправляются вместе как единый массив. Каждый столбец в блоке содержит заголовок, аналогичный RowBinaryWithNamesAndTypes.При использовании нативного бинарного протокола TCP (или когда конечная точка HTTP получает
?client_protocol_version=<n>),
структура BlockInfo записывается перед количеством столбцов и строк. В примерах этого раздела используется
обычный HTTP-интерфейс без версии протокола, поэтому BlockInfo не записывается.Структура блока
Следующий запрос возвращает два столбца,number и str, из трёх строк:
Несколько блоков
Однако во многих случаях данные не помещаются в один блок, и ClickHouse отправляет их в виде нескольких блоков. Рассмотрим следующий запрос, который извлекает две строки при уменьшенном размере блока, чтобы принудительно разбить данные так, что в каждом блоке будет по одной строке:Простые типы данных
Формат передачи данных отдельного значения одного из простых типов данных аналогиченRowBinary/RowBinaryWithNamesAndTypes.
Полный список типов, соответствующих этому описанию:
- (U)Int8, (U)Int16, (U)Int32, (U)Int64, (U)Int128, (U)Int256
- Float32, Float64
- Bool
- String
- FixedString(N)
- Date
- Date32
- DateTime
- DateTime64
- IPv4
- IPv6
- UUID
Сложные типы данных
Кодирование следующих типов отличается от кодирования вRowBinary и RowBinaryWithNamesAndTypes.
- Nullable
- LowCardinality
- Array
- Map
- Variant
- Dynamic
- JSON
Nullable
В форматеNative перед самими данными для столбца с типом Nullable записывается количество байтов, равное числу строк в блоке. Каждый из этих байтов указывает, равно ли значение NULL или нет. Например, в этом запросе каждое нечётное число будет NULL:
Nullable(String) это работает аналогично. Индикатор NULL всегда задаётся байтом маски nullable —
значение маски 0x01 означает, что строка равна NULL независимо от содержимого строки. Для строк со значением NULL
базовая строка хранится как пустая строка (длина LEB128 0). Обратите внимание, что пустая строка с не-NULL
значением также имеет длину LEB128 0, поэтому эти два случая различаются только байтом маски. Например, следующий запрос:
LowCardinality
В отличие от RowBinary, гдеLowCardinality прозрачен, формат Native использует словарное столбцовое кодирование. Столбец кодируется как префикс версии, затем словарь уникальных значений и массив целочисленных индексов этого словаря.
Столбец можно определить как
LowCardinality(Nullable(T)), но определить его как Nullable(LowCardinality(T)) нельзя — это всегда приводит к ошибке сервера.UInt64(LE) со значением 1, который записывается один раз для каждого столбца. Затем для каждого блока записывается следующее:
UInt64(LE)— битовое полеIndexesSerializationType. Биты 0–7 кодируют ширину индекса (0 = UInt8, 1 = UInt16, 2 = UInt32, 3 = UInt64). Бит 8 (NeedGlobalDictionaryBit) никогда не устанавливается в формате Native (сервер генерирует исключение, если он встречается). Бит 9 указывает на наличие дополнительных ключей словаря. Бит 10 указывает, что словарь нужно сбросить.UInt64(LE)— количество ключей словаря, после которого сами ключи массово сериализуются с использованием кодирования внутреннего типа.UInt64(LE)— количество строк, после которого значения индексов массово сериализуются с использованием соответствующей разрядности UInt.
String, 0 для числовых типов). Для LowCardinality(Nullable(T)) индекс 0 соответствует NULL, а ключи сериализуются без обёртки Nullable.
Например, LowCardinality(String) с 5 строками ['foo', 'bar', 'baz', 'foo', 'bar']:
LowCardinality(Nullable(String)) индекс 0 — NULL:
Array
В отличие от RowBinary, где каждому массиву предшествует число элементов в кодировке LEB128, формат Native кодирует массивы как два столбцовых подпотока:- N накопительных смещений
UInt64(little-endian, по 8 байт каждое). Строкаiсодержитoffset[i] - offset[i-1]элементов, при этомoffset[-1]неявно считается равным 0. - Все вложенные элементы из всех строк, сериализованные подряд в один непрерывный блок.
Array(UInt32) с 3 строками [[0, 10], [1, 11], [2, 12]]:
Array(String) с 4 строками [[], ['0'], ['0','1'], ['0','1','2']]:
Map
Map(K, V) кодируется как Array(Tuple(K, V)) — сначала идут смещения массива, затем все ключи, а потом все значения. Это отличается от RowBinary, где ключи и значения чередуются в каждой записи.
Например, Map(String, UInt64) с 3 строками [{'a':0,'b':10}, {'a':1,'b':11}, {'a':2,'b':12}]:
Variant
В отличие от RowBinary, где каждая строка содержит собственный байт дискриминатора, за которым сразу следует встроенное значение, формат Native отделяет дискриминаторы от данных. СтолбецVariant кодируется следующим образом:
- Префикс режима дискриминаторов
UInt64(LE)(0= BASIC,1= COMPACT). Вывод в формате Native обычно использует BASIC (0); режим COMPACT может встречаться при чтении данных, сохраненных с включеннымuse_compact_variant_discriminators_serialization. - N дискриминаторов
UInt8, по одному на строку. - Данные каждого типа варианта в виде отдельного столбца с массовыми данными, содержащего только соответствующие строки, в порядке дискриминаторов.
Variant(String, UInt32) с 5 строками [0::UInt32, 'hello', NULL, 3::UInt32, 'hello'] (в отсортированном списке: String = 0, UInt32 = 1):
Dynamic
В отличие от RowBinary, где каждое значение является самоописывающимся (префикс типа + значение), формат Native сериализуетDynamic как префикс структуры, за которым следует столбец Variant.
Префикс структуры содержит версию сериализации UInt64(LE), затем количество динамических типов (в формате VarUInt), а затем имена типов в виде строк. В версии V1 количество типов для совместимости записывается дважды. Следующие за ним данные представляют собой столбец Variant, список типов которого включает динамические типы и внутренний тип SharedVariant, отсортированные по алфавиту.
Например, Dynamic с 5 строками [0::UInt32, 'hello', NULL, 3::UInt32, 'hello']:
JSON
В отличие от RowBinary, где каждая строка содержит имена путей и значения и потому является самоописывающейся, формат Native сериализуетJSON в столбцовой структуре. Схема кодирования сложна и зависит от версии: она включает префикс структуры с версией сериализации, имена динамических путей и структуру общих данных, после чего следуют типизированные пути (каждый в виде отдельного столбца), динамические пути (каждый как столбец Dynamic) и общие данные для overflow-путей.
Для более простой совместимости можно использовать настройку output_format_native_write_json_as_string=1, которая сериализует JSON-столбцы как обычные текстовые строки JSON (по одному String на строку).