Native
| Входной | Выходной | Псевдоним |
|---|---|---|
| ✔ | ✔ |
Описание
Формат Native является самым эффективным форматом ClickHouse, поскольку он по-настоящему «столбцовый»
и не преобразует столбцы в строки.
В этом формате данные записываются и читаются блоками в двоичном виде. Для каждого блока последовательно записываются количество строк, количество столбцов, имена и типы столбцов, а также части столбцов, входящие в этот блок.
Этот формат используется во встроенном интерфейсе для взаимодействия между серверами, в клиенте командной строки и в C++-клиентах.
Вы можете использовать этот формат для быстрого создания дампов, которые могут быть прочитаны только СУБД ClickHouse. Работать с этим форматом напрямую может быть не слишком практично.
Проводной формат типов данных
Данные передаются по сети в столбцовом формате: каждый столбец отправляется отдельно, а все его значения передаются вместе как единый массив.
Каждый столбец в блоке содержит заголовок, аналогичный RowBinaryWithNamesAndTypes.
При использовании нативного двоичного протокола TCP (или когда HTTP-конечная точка получает ?client_protocol_version=<n>)
структура BlockInfo записывается перед количеством столбцов и строк. В примерах этого раздела используется
обычный HTTP-интерфейс без версии протокола, поэтому BlockInfo опускается.
Структура блока
Следующий запрос возвращает два столбца, number и str, в трёх строках:
Выходные данные помещаются в один блок ClickHouse и выглядят так:
Несколько блоков
Однако во многих случаях данные не помещаются в один блок, и 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".
Сложные типы данных
Кодирование следующих типов отличается от 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.
Словарь всегда содержит значение по умолчанию с индексом 0 (например, пустую строку для 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 дискриминанты отделены от данных.
Как и в RowBinary, типы в определении всегда сортируются по алфавиту, а дискриминант — это индекс в этом отсортированном списке. 0xFF (255) обозначает NULL.
Столбец 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) и общие данные для путей, не поместившихся в основной структуре.
Для более простой совместимости рассмотрите использование настройки output_format_native_write_json_as_string=1, которая сериализует JSON-столбцы как обычные текстовые строки JSON (по одной String на строку).