Перейти к основному содержанию
Хотя автоматическое определение схемы можно использовать для построения начальной схемы для JSON-данных и выполнения запросов к JSON-файлам с данными напрямую, например в S3, вам следует стремиться к созданию оптимизированной версионируемой схемы для ваших данных. Ниже мы рассмотрим рекомендуемый подход к моделированию JSON-структур.

Статический и динамический JSON

Основная задача при определении схемы для JSON — подобрать подходящий тип для значения каждого ключа. Мы рекомендуем рекурсивно применять следующие правила к каждому ключу в иерархии JSON, чтобы определить для него подходящий тип.
  1. Примитивные типы - Если значение ключа имеет примитивный тип, независимо от того, находится ли оно во вложенном объекте или в корне, убедитесь, что выбираете его тип в соответствии с общими рекомендациями по проектированию схемы и правилами оптимизации типов. Массивы примитивов, такие как phone_numbers ниже, можно моделировать как Array(<type>), например Array(String).
  2. Статический или динамический - Если значение ключа представляет собой сложный объект, то есть объект или массив объектов, определите, может ли оно меняться. Объекты, в которых новые ключи появляются редко, а добавление нового ключа можно предвидеть и обработать изменением схемы через ALTER TABLE ADD COLUMN, можно считать статическими. Сюда относятся и объекты, в которых в некоторых JSON-документах может присутствовать только часть ключей. Объекты, в которые новые ключи добавляются часто и/или непредсказуемо, следует считать динамическими. Исключение составляют структуры с сотнями или тысячами вложенных ключей — для удобства их тоже можно считать динамическими.
Чтобы определить, является ли значение статическим или динамическим, см. соответствующие разделы Обработка статических объектов и Обработка динамических объектов ниже.

Важно: Приведенные выше правила следует применять рекурсивно. Если значение ключа определяется как динамическое, дальнейшая оценка не требуется, и можно следовать рекомендациям из раздела Обработка динамических объектов. Если объект статический, продолжайте оценивать вложенные ключи, пока не дойдете до примитивных значений или не встретите динамические ключи. Чтобы проиллюстрировать эти правила, используем следующий пример JSON, описывающий человека:
Применяя эти правила:
  • Корневые ключи name, username, email, website можно представить типом String. Столбец phone_numbers — это примитив Array типа Array(String), а dob и id имеют типы Date и UInt32 соответственно.
  • В объект address новые ключи добавляться не будут (только новые объекты адресов), поэтому его можно считать статическим. Если рекурсивно развернуть его, все подстолбцы можно считать примитивами (и типа String), кроме geo. Это также статическая структура с двумя столбцами типа Float32: lat и lon.
  • Столбец tags является динамическим. Мы предполагаем, что в этот объект можно добавлять новые произвольные теги любого типа и структуры.
  • Объект company является статическим и всегда будет содержать не более 3 указанных ключей. Вложенные ключи name и catchPhrase имеют тип String. Ключ labels является динамическим. Мы предполагаем, что в этот объект можно добавлять новые произвольные теги. Значения всегда будут представлять собой пары ключ-значение строкового типа.
Структуры с сотнями или тысячами статических ключей можно считать динамическими, поскольку на практике для них редко удается статически объявить столбцы. Однако там, где это возможно, пропускайте ненужные пути, чтобы сократить затраты как на хранение, так и на определение типов.

Обработка статических структур

Мы рекомендуем обрабатывать статические структуры с помощью именованных кортежей, то есть Tuple. Массивы объектов можно хранить с помощью массивов кортежей, то есть Array(Tuple). В самих кортежах столбцы и соответствующие им типы следует определять по тем же правилам. В результате могут получаться вложенные Tuple для представления вложенных объектов, как показано ниже. Чтобы проиллюстрировать это, мы используем приведённый ранее JSON-пример с person, опуская динамические объекты:
Ниже показана схема этой таблицы:
Обратите внимание, что столбец company имеет тип Tuple(catchPhrase String, name String). Для ключа address используется Array(Tuple) с вложенным Tuple для представления столбца geo. JSON можно вставлять в эту таблицу в её текущей структуре:
В приведённом выше примере данных немного, но, как показано ниже, мы можем обращаться в запросах к столбцам кортежа по их именам, разделённым точками.
Обратите внимание, что столбец address.street возвращается как Array. Чтобы обратиться к конкретному объекту внутри массива по его позиции, после имени столбца нужно указать смещение в массиве. Например, чтобы получить улицу из первого адреса:
Подстолбцы также можно использовать в ключах сортировки, начиная с 24.12:

Обработка значений по умолчанию

Даже если объекты JSON имеют структуру, они часто бывают разреженными: в них передаётся лишь часть известных ключей. К счастью, тип Tuple не требует наличия всех столбцов в полезной нагрузке JSON. Если какие-то из них не указаны, будут использованы значения по умолчанию. Рассмотрим таблицу people из предыдущего примера и следующий разреженный JSON, в котором отсутствуют ключи suite, geo, phone_numbers и catchPhrase.
Ниже видно, что эту строку можно успешно вставить:
Из запроса к этой единственной строке видно, что для столбцов (включая вложенные объекты), которые не были указаны, используются значения по умолчанию:
Различие между пустым значением и nullЕсли вам нужно различать пустое значение и отсутствие значения, можно использовать тип Nullable. Однако этого следует избегать, если только это не является абсолютно необходимым, поскольку это отрицательно скажется на хранении и производительности запросов для этих столбцов.

Обработка новых столбцов

Хотя структурированный подход проще всего применять, когда ключи JSON статичны, его по-прежнему можно использовать, если изменения в схеме можно спланировать, то есть если новые ключи известны заранее и схему можно соответствующим образом изменить. Обратите внимание, что ClickHouse по умолчанию игнорирует ключи JSON, переданные в полезной нагрузке, если они отсутствуют в схеме. Рассмотрим следующий изменённый JSON с добавлением ключа nickname:
Этот JSON можно успешно вставить, проигнорировав ключ nickname:
Столбцы можно добавлять в схему с помощью команды ALTER TABLE ADD COLUMN. Значение по умолчанию можно задать с помощью конструкции DEFAULT; оно будет использоваться, если его не указать при последующих вставках. Для строк, в которых это значение отсутствует (поскольку они были вставлены до его создания), также будет возвращаться это значение по умолчанию. Если значение DEFAULT не указано, будет использоваться значение по умолчанию для этого типа. Например:

Обработка полуструктурированных/динамических структур

Если JSON-данные являются полуструктурированными, то есть ключи могут динамически добавляться и/или иметь несколько типов, рекомендуется тип JSON. Точнее, используйте тип JSON, если ваши данные:
  • Содержат непредсказуемые ключи, которые со временем могут меняться.
  • Содержат значения разных типов (например, путь иногда может содержать строку, а иногда число).
  • Требуют гибкости схемы, когда строгая типизация непрактична.
  • У вас есть сотни или даже тысячи путей, которые статичны, но явно объявлять их просто нереалистично. Это, как правило, встречается редко.
Рассмотрим предыдущий пример JSON с person, где объект company.labels был определён как динамический. Предположим, что company.labels содержит произвольные ключи. Кроме того, тип значения любого ключа в этой структуре может различаться от строки к строке. Например:
Учитывая динамический характер столбца company.labels в разных объектах с точки зрения ключей и типов, у нас есть несколько вариантов моделирования этих данных:
  • Один JSON-столбец - представляет всю схему в виде одного столбца JSON, позволяя всем вложенным структурам оставаться динамическими.
  • Целевой JSON-столбец - использовать тип JSON только для столбца company.labels, сохраняя для всех остальных столбцов структурированную схему, описанную выше.
Хотя первый подход не соответствует предыдущей методологии, подход с одним JSON-столбцом полезен для прототипирования и задач дата-инжиниринга. Для production-развертываний ClickHouse в большом масштабе мы рекомендуем явно задавать структуру и по возможности использовать тип JSON для отдельных динамических подструктур. Строгая схема имеет ряд преимуществ:
  • Валидация данных – использование строгой схемы позволяет избежать риска разрастания столбцов за пределами явно заданных структур.
  • Позволяет избежать риска разрастания столбцов - Хотя тип JSON может масштабироваться до тысяч столбцов, где подстолбцы хранятся как отдельные столбцы, это может привести к лавинообразному росту числа файлов столбцов, что негативно сказывается на производительности. Чтобы уменьшить этот риск, базовый тип Dynamic, используемый в JSON, поддерживает параметр max_dynamic_paths, который ограничивает число уникальных путей, сохраняемых как отдельные файлы столбцов. После достижения этого порога дополнительные пути сохраняются в общем файле столбца в компактном закодированном формате, что позволяет сохранить производительность и эффективность хранения, одновременно поддерживая гибкую ингестию данных. Однако доступ к этому общему файлу столбца менее производителен. При этом JSON-столбец можно использовать с подсказками типов. Столбцы с подсказками обеспечивают ту же производительность, что и отдельные столбцы.
  • Более простая интроспекция путей и типов - Хотя тип JSON поддерживает функции интроспекции для определения выведенных типов и путей, статические структуры бывает проще исследовать, например с помощью DESCRIBE.

Один JSON-столбец

Этот подход полезен для прототипирования и задач дата-инжиниринга. Для продакшена старайтесь использовать JSON только для динамических вложенных структур там, где это действительно необходимо.
Соображения по производительностиОдин JSON-столбец можно оптимизировать, пропуская (не сохраняя) JSON-пути, которые не нужны, и используя подсказки типов. Подсказки типов позволяют пользователю явно задавать тип для подстолбца, тем самым избегая вывода типов и дополнительной косвенной обработки во время выполнения запроса. Это позволяет добиться той же производительности, как если бы использовалась явная схема. Подробнее см. в разделе “Использование подсказок типов и пропуск путей”.
Схема для одного JSON-столбца здесь проста:
Мы указываем подсказку типа для столбца username в определении JSON, поскольку используем его в ключе сортировки/первичном ключе. Это помогает ClickHouse понять, что этот столбец не будет содержать NULL, и определить, какой подстолбец 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 and SKIP REGEXP, чтобы сократить объём хранилища и избежать лишнего вывода типов для ненужных путей. Например, предположим, что для приведённых выше данных используется один JSON-столбец. Мы можем пропустить пути address и company:”
Обратите внимание, что эти столбцы исключены из данных:

Оптимизация производительности с помощью подсказок типов

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

Настройка динамических путей

ClickHouse хранит каждый JSON-путь как подстолбец в по-настоящему столбцовой структуре, обеспечивая те же преимущества по производительности, что и у традиционных столбцов, — такие как сжатие, обработка с ускорением SIMD и минимальный дисковый I/O. Каждая уникальная комбинация пути и типа в ваших JSON-данных может стать отдельным файлом столбца на диске. Например, когда в два JSON-пути вставляются значения разных типов, ClickHouse хранит значения каждого конкретного типа в отдельных подстолбцах. К этим подстолбцам можно обращаться независимо, что сводит к минимуму лишний I/O. Обратите внимание, что при запросе столбца с несколькими типами его значения по-прежнему возвращаются как единый столбцовый ответ. Кроме того, используя смещения, ClickHouse гарантирует, что эти подстолбцы остаются плотными, без хранения значений по умолчанию для отсутствующих JSON-путей. Такой подход максимизирует сжатие и дополнительно сокращает I/O. Однако в сценариях с JSON-структурами высокой кардинальности или сильно изменяющейся структурой — например, в конвейерах телеметрии, журналах или хранилищах признаков для машинного обучения — такое поведение может привести к взрывному росту числа файлов столбцов. Каждый новый уникальный JSON-путь приводит к созданию нового файла столбца, а каждый вариант типа в рамках этого пути — к созданию дополнительного файла столбца. Хотя это оптимально для производительности чтения, возникают эксплуатационные сложности: исчерпание дескрипторов файлов, повышенное использование памяти и более медленные слияния из-за большого количества мелких файлов. Чтобы смягчить эту проблему, ClickHouse вводит понятие overflow-подстолбца: как только число различных JSON-путей превышает порог, дополнительные пути сохраняются в одном общем файле в компактном кодированном формате. Этот файл по-прежнему можно запрашивать, но он не обладает теми же характеристиками производительности, что и выделенные подстолбцы. Этот порог задаётся параметром max_dynamic_paths в объявлении типа JSON.
Не задавайте этому параметру слишком большое значение — большие значения увеличивают потребление ресурсов и снижают эффективность. Как правило, значение должно быть меньше 10 000. Для рабочих нагрузок с сильно меняющейся структурой используйте подсказки типов и параметры SKIP, чтобы ограничить объём сохраняемых данных. Пользователям, которым интересна реализация этого нового типа столбца, рекомендуем прочитать нашу подробную статью в блоге “A New Powerful JSON Data Type for ClickHouse”.
Последнее изменение 29 июня 2026 г.