Проектирование вашей схемы
Хотя вывод схемы можно использовать для установления начальной схемы для данных JSON и запроса файлов JSON на месте, например, в S3, пользователи должны стремиться установить оптимизированную версионированную схему для своих данных. Мы обсуждаем рекомендуемый подход к моделированию JSON-структур ниже.
Статический и динамический JSON
Основная задача при определении схемы для JSON заключается в определении подходящего типа для значения каждого ключа. Мы рекомендуем пользователям рекурсивно применять следующие правила к каждому ключу в иерархии JSON для определения соответствующего типа.
- Примитивные типы - Если значение ключа является примитивным типом, независимо от того, является ли оно частью подпункта или находится на корневом уровне, убедитесь, что вы выбрали его тип в соответствии с общими лучшими практиками проектирования схемы и правилами оптимизации типов. Массивы примитивов, такие как
phone_numbers
ниже, могут быть смоделированы какArray(<type>)
, например,Array(String)
. - Статический против динамического - Если значение ключа является сложным объектом, т.е. либо объектом, либо массивом объектов, определите, подвержено ли оно изменениям. Объекты, у которых редко появляются новые ключи, и добавление нового ключа можно предсказать и обработать с помощью изменения схемы через
ALTER TABLE ADD COLUMN
, можно считать статическими. Это включает объекты, в которых только подмножество ключей может быть представлено в некоторых документах JSON. Объекты, в которых часто добавляются новые ключи и/или они непредсказуемы, следует считать динамическими. Исключение здесь составляют структуры с сотнями или тысячами подпунктов, которые могут считаться динамическими в целях удобства.
Чтобы выяснить, является ли значение статическим или динамическим, смотрите соответствующие разделы Обработка статических объектов и Обработка динамических объектов ниже.
Важно: Указанные выше правила должны применяться рекурсивно. Если значение ключа определяется как динамическое, дальнейшая оценка не требуется, и можно следовать рекомендациям в Обработка динамических объектов. Если объект статический, продолжайте оценивать подпункты до тех пор, пока либо значения ключей не станут примитивными, либо не встретятся динамические ключи.
Чтобы проиллюстрировать эти правила, мы используем следующий пример JSON, представляющий личность:
Применяя эти правила:
- Корневые ключи
name
,username
,email
,website
могут быть представлены как типString
. Колонкаphone_numbers
является массивом примитивов типаArray(String)
, сdob
иid
типаDate
иUInt32
соответственно. - Новые ключи не будут добавлены в объект
address
(только новые объекты адресов), и его можно считать статическим. Если мы рекурсивно проанализируем, все подколонки могут считаться примитивами (и типаString
), кромеgeo
. Это также статическая структура с двумя колонкамиFloat32
,lat
иlon
. - Колонка
tags
является динамической. Мы предполагаем, что новые произвольные теги могут быть добавлены в этот объект любого типа и структуры. - Объект
company
является статическим и всегда будет содержать не более 3 заданных ключей. Подключиname
иcatchPhrase
имеют типString
. Ключlabels
является динамическим. Мы предполагаем, что новые произвольные теги могут быть добавлены в этот объект. Значения всегда будут парами ключ-значение типа строка.
Структуры с сотнями или тысячами статических ключей могут считаться динамическими, так как редко бывает реалистично статично объявить колонки для них. Однако, где это возможно, пропустите пути, которые не нужны, чтобы сэкономить как место для хранения, так и накладные расходы на вывод.
Обработка статических структур
Мы рекомендуем обрабатывать статические структуры с помощью именованных кортежей, т.е. Tuple
. Массивы объектов могут храниться с использованием массивов кортежей, т.е. Array(Tuple)
. Внутри самих кортежей колонки и их соответствующие типы должны определяться с использованием тех же правил. Это может привести к вложенным кортежам для представления вложенных объектов, как показано ниже.
Чтобы проиллюстрировать это, мы используем предыдущий пример JSON с личностью, пропуская динамические объекты:
Схема для этой таблицы показана ниже:
Обратите внимание, как колонка company
определена как Tuple(catchPhrase String, name String)
. Ключ address
использует Array(Tuple)
, с вложенным Tuple
для представления колонки geo
.
JSON можно вставить в эту таблицу в текущей структуре:
В нашем приведенном выше примере у нас минимальные данные, но, как показано ниже, мы можем запрашивать кортежные колонки по их именам, разделенным точками.
Обратите внимание, как колонка address.street
возвращается как массив. Чтобы запросить конкретный объект внутри массива по позиции, индекс массива следует указать после имени колонки. Например, чтобы получить улицу из первого адреса:
Подколонки также могут использоваться в ключах сортировки из 24.12
:
Обработка значений по умолчанию
Даже если объекты JSON структурированы, они часто являются разреженными, с тем, что представлены только подмножества известных ключей. К счастью, тип Tuple
не требует, чтобы все колонки были в полезной нагрузке JSON. Если ключи не указаны, будут использоваться значения по умолчанию.
Рассмотрим нашу прежнюю таблицу people
и следующий разреженный JSON, пропускающий ключи suite
, geo
, phone_numbers
и catchPhrase
.
Мы можем увидеть, что эта строка может быть успешно вставлена:
Запрашивая эту одну строку, мы можем увидеть, что для колонок (включая под-объекты), которые были пропущены, используются значения по умолчанию:
Если пользователи необходимо различать, является ли значение пустым и не предоставленным, можно использовать тип Nullable. Это должно быть избегнуто, если это абсолютно не требуется, так как это негативно скажется на хранении и производительности запросов для этих колонок.
Обработка новых колонок
Хотя структурированный подход является самым простым, когда ключи JSON статичны, этот подход все же может быть использован, если изменения в схеме могут быть запланированы, т.е. новые ключи известны заранее, и схема может быть изменена соответственно.
Обратите внимание, что ClickHouse по умолчанию будет игнорировать ключи JSON, которые присутствуют в полезной нагрузке и отсутствуют в схеме. Рассмотрим следующую измененную JSON-полезную нагрузку с добавлением ключа nickname
:
Эти JSON могут быть успешно вставлены с игнорированием ключа nickname
:
Колонки могут быть добавлены к схеме с помощью команды ALTER TABLE ADD COLUMN
. Значение по умолчанию может быть указано через DEFAULT
, которое будет использовано, если не будет указано во время последующих вставок. Строки, для которых это значение не присутствует (так как они были вставлены до его создания), также вернут это значение по умолчанию. Если значение по умолчанию не указано, будет использовано значение по умолчанию для типа.
Например:
Обработка полу-структурированных/динамических структур
Если данные JSON имеют полу-структурированный вид, где ключи могут добавляться динамически и/или иметь несколько типов, рекомендуется использовать тип JSON
.
Более конкретно, используйте тип JSON, когда ваши данные:
- Имеют непредсказуемые ключи, которые могут меняться со временем.
- Содержат значения с различными типами (например, путь может иногда содержать строку, иногда число).
- Требуют гибкости схемы, где строгая типизация нецелесообразна.
- У вас есть сотни или даже тысячи путей, которые статичны, но просто нереалистично объявлять явно. Это бывает редко.
Рассмотрим наш предыдущий JSON с личностью, где объект company.labels
был определен как динамический.
Предположим, что company.labels
содержит произвольные ключи. Кроме того, тип для любого ключа в этой структуре может быть непостоянным между строками. Например:
Учитывая динамическую природу колонки company.labels
между объектами, касающиеся ключей и типов, у нас есть несколько вариантов для моделирования этих данных:
- Единая колонка JSON - представляет всю схему как единую колонку
JSON
, позволяя всем структурам быть динамическими. - Целевая колонка JSON - используйте тип
JSON
только для колонкиcompany.labels
, сохраняя структурированную схему, используемую выше, для всех остальных колонок.
Хотя первый подход не соответствует предыдущей методологии, подход с единой колонкой JSON полезен для прототипирования и задач по обработке данных.
Для производственных развертываний ClickHouse в масштабе мы рекомендуем быть конкретными со структурой и использовать тип JSON для целевых динамических подструктур, где это возможно.
Строгая схема имеет ряд преимуществ:
- Валидация данных – обеспечение строгой схемы избегает риска взрыва колонн, кроме специфических структур.
- Избегает риска взрыва колонн - Хотя тип JSON масштабируется до потенциально тысяч колонн, где подпункты хранятся как отдельные колонки, это может привести к взрыву файлов колонн, когда создается чрезмерное количество файлов колонн, что влияет на производительность. Для смягчения этого подлежащий Динамический тип, используемый JSON, предлагает параметр
max_dynamic_paths
, который ограничивает количество уникальных путей, хранящихся как отдельные файлы колонн. Как только порог достигается, дополнительные пути хранятся в общем файле колонн с использованием компактного закодированного формата, поддерживая производительность и эффективность хранения, при этом поддерживая гибкий прием данных. Однако доступ к этому общему файлу колонн не так оптимален. Однако стоит отметить, что колонка JSON может использоваться с подсказками типов. "Подсказанные" колонки будут обеспечивать такую же производительность, как и выделенные колонки. - Проще проанализировать пути и типы - Хотя тип JSON поддерживает функции интроспекции для определения типов и путей, которые были выведены, статические структуры могут быть проще для исследования, например, с помощью
DESCRIBE
.
Единая колонка JSON
Этот подход полезен для прототипирования и задач по обработке данных. Для производства старайтесь использовать JSON
только для динамических подструктур, где это необходимо.
Единую колонку JSON можно оптимизировать, пропуская (не сохраняя) пути JSON, которые не требуются, и используя подсказки типов. Подсказки типов позволяют пользователю явно определить тип для подпункта, избегая тем самым вывода и обработки косвенности во время выполнения запроса. Это может быть использовано для обеспечения такой же производительности, как если бы была использована явная схема. См. "Использование подсказок типов и пропуск путей" для дальнейших подробностей.
Схема для единой колонки JSON здесь проста:
Мы предоставляем подсказку типа для колонки username
в определении JSON, поскольку мы используем ее в ключе сортировки/первичном ключе. Это помогает ClickHouse понимать, что эта колонка не будет нулевой и гарантирует, что он знает, какую подколонку username
использовать (так как их может быть несколько для каждого типа, поэтому это иначе не однозначно).
Вставка строк в вышеупомянутую таблицу может быть выполнена с использованием формата JSONAsObject
:
Мы можем определить выведенные подпункты и их типы, используя функции интроспекции. Например:
Для получения полного списка функций интроспекции смотрите "Функции интроспекции".
Подпути можно получить с помощью нотации .
например:
Обратите внимание, что колонки, отсутствующие в строках, возвращаются как NULL
.
Кроме того, для путей с одинаковым типом создается отдельная подколонка. Например, существует подколонка для company.labels.type
как String
, так и Array(Nullable(String))
. Хотя обе будут возвращены, когда это возможно, мы можем нацелиться на конкретные подколонки, используя синтаксис .:
:
Для того, чтобы вернуть вложенные под-объекты, требуется ^
. Это выбор дизайна, чтобы избежать чтения большого числа колонн — если не запрашивается явно. Объекты, доступные без ^
, вернут NULL
, как показано ниже:
Целевая колонка JSON
Хотя это полезно при прототипировании и решении задач в области инженерии данных, мы рекомендуем использовать явную схему в производственных системах, где это возможно.
Наш предыдущий пример можно смоделировать с помощью одной колонки JSON
для колонки company.labels
.
Мы можем вставить данные в эту таблицу, используя формат JSONEachRow
:
Функции интроспекции могут быть использованы для определения выведенных путей и типов для колонки company.labels
.
Использование подсказок типов и пропуска путей
Подсказки типов позволяют нам указывать тип для пути и его подколонки, предотвращая ненужный вывод типов. Рассмотрим следующий пример, где мы указываем типы для ключей JSON dissolved
, employees
и founded
внутри колонки JSON company.labels
.
Обратите внимание, как теперь эти колонки имеют наши явные типы:
Дополнительно мы можем пропускать пути в JSON, которые не хотим сохранять, с помощью параметров SKIP
и SKIP REGEXP
, чтобы минимизировать объем хранимых данных и избежать ненужного вывода на не нужных путях. Например, предположим, что мы используем одну колонку JSON для данных выше. Мы можем пропустить пути address
и company
:
Обратите внимание, как наши колонки были исключены из данных:
Оптимизация производительности с помощью подсказок типов
Подсказки типов предлагают больше, чем просто способ избежать ненужного вывода типов — они полностью устраняют косвенные обращения к памяти и процессору, а также позволяют указывать оптимальные примитивные типы. JSON пути с подсказками типов всегда хранятся так же, как традиционные колонки, обходя необходимость в столбцах-дискриминаторах или динамическом разрешении во время выполнения запросов.
Это значит, что при хорошо определенных подсказках типов, вложенные JSON ключи достигают той же производительности и эффективности, как если бы они изначально моделировались как колонки верхнего уровня.
В результате, для наборов данных, которые в основном последовательны, но все же выигрывают от гибкости JSON, подсказки типов предоставляют удобный способ сохранить производительность без необходимости перестраивать свою схему или конвейер приема данных.
Настройка динамических путей
ClickHouse хранит каждый JSON путь как подколонку в истинной колонной раскладке, что позволяет получить те же преимущества производительности, которые наблюдаются с традиционными колонками, такие как сжатие, обработка с ускорением SIMD и минимальный ввод-вывод диска. Каждая уникальная комбинация пути и типа в ваших JSON данных может стать своим собственным файловым столбцом на диске.

Например, когда два JSON пути вставляются с различающимися типами, ClickHouse хранит значения каждого конкретного типа в отдельных подколонках. Эти подколонки могут быть доступны независимо, минимизируя ненужный ввод-вывод. Обратите внимание, что при запросе колонки с несколькими типами, ее значения все равно возвращаются как единый колонный ответ.
Кроме того, используя смещения, ClickHouse обеспечивает, чтобы эти подколонки оставались плотными, без хранения значений по умолчанию для отсутствующих JSON путей. Этот подход максимизирует сжатие и дополнительно снижает ввод-вывод.

Однако в сценариях с высокой кардинальностью или сильно изменчивыми структурами JSON — такими как телеметрические конвейеры, логи или хранилища функций машинного обучения — это поведение может привести к взрыву файлов столбцов. Каждый новый уникальный JSON путь приводит к созданию нового файла столбца, и каждый вариант типа под этим путем результирует в дополнительном файлов столбце. Хотя это оптимально для производительности чтения, это создает операционные проблемы: исчерпание дескрипторов файлов, увеличение использования памяти и замедленные слияния из-за большого количества мелких файлов.
Чтобы смягчить это, ClickHouse вводит концепцию подколонки переполнения: как только количество уникальных JSON путей превышает порог, дополнительные пути хранятся в одном общем файле с использованием компактного закодированного формата. Этот файл все еще подлежит запросам, но не имеет тех же характеристик производительности, что и специальные подколонки.

Этот порог контролируется параметром max_dynamic_paths
в объявлении типа JSON.
Избегайте установки этого параметра слишком высоким — большие значения увеличивают потребление ресурсов и снижают эффективность. В качестве правила, держите его ниже 10,000. Для рабочих нагрузок с высокодинамичными структурами используйте подсказки типов и параметры SKIP
, чтобы ограничить то, что сохраняется.
Для заинтересованных пользователей в реализации этого нового типа столбца мы рекомендуем ознакомиться с нашей детальной статьей "Новый мощный тип данных JSON для ClickHouse".