Learning Platform
Глоссарий Troubleshooting
Урок 16.02 · 30 мин
Продвинутый
JSON typeVariantDynamicsub-columnJSONExtractGA 25.3semi-structured data

JSON type (GA 25.3), Variant и Dynamic типы

ClickHouse 25.3 вводит нативный тип JSON в статус GA (Generally Available). Это кардинальное изменение в подходе к хранению полуструктурированных данных: вместо сериализации всего JSON в одну строку каждый уникальный путь хранится в отдельном sub-column с собственной компрессией.

WARNING

Тип JSON стал GA (Generally Available) в ClickHouse 25.3. В более ранних версиях (включая экспериментальный JSON тип в 22.6+) существовал несовместимый API. Используйте нативный JSON только на ClickHouse 25.3+. ClickHouse 26.3 LTS поставляется с GA JSON type.

Связанная тема: архитектура типов данных ClickHouse подробно рассматривается в Модуль 02 курса — теоретическая основа для понимания компрессии и выбора типов.


JSON type: sub-column хранение

Нативный JSON type хранит каждый уникальный JSON-путь как отдельный sub-column. Каждый sub-column имеет собственное сжатие и статистику — аналогично обычным колонкам MergeTree.

-- Создание таблицы с JSON колонкой (GA 25.3+)
CREATE TABLE user_events
(
    id         UInt64,
    event_time DateTime,
    payload    JSON
)
ENGINE = MergeTree()
ORDER BY (event_time, id);

-- INSERT: обычный JSON
INSERT INTO user_events VALUES
(1, now(), '{"action": "click", "page": "/home", "user_agent": "Mozilla/5.0"}'),
(2, now(), '{"action": "purchase", "amount": 99.99, "items": ["book", "pen"]}');

-- Запрос по JSON пути (оператор ^)
SELECT
    id,
    payload.action,           -- доступ к sub-column по пути
    payload.amount,
    payload.page
FROM user_events
WHERE payload.action = 'purchase';

Архитектура sub-column хранения:

JSON type: sub-column хранение
JSON Column (payload)JSON колонка — логический контейнер. При INSERT ClickHouse анализирует JSON-документы и извлекает уникальные пути. Каждый путь становится отдельным sub-column.
разбивается на sub-columns по уникальным путям
Sub-columns (payload.action, payload.amount, ...)Sub-columns: каждый уникальный JSON-путь (payload.action, payload.amount, payload.page) хранится отдельно. Тип sub-column выводится автоматически или задаётся явно. NULL хранится эффективно — не занимает место для отсутствующих путей.
сжатие по колонкам
Compressed Files (per sub-column)Compressed files: каждый sub-column сжимается независимо с оптимальным алгоритмом. Числовые пути (amount) сжимаются как числа, строковые (action) — как строки с LZ4/ZSTD. Это обеспечивает лучшую компрессию, чем сериализация всего JSON в String.

Преимущества над String + JSONExtract:

  • Компрессия: каждый путь сжимается оптимально для своего типа
  • Производительность: доступ к payload.action читает только один sub-column
  • Типизация: автоматическое определение типов (или явное задание)
  • Векторизация: чтение по путям полностью векторизовано

Variant type: смешанные типы в одной колонке

Variant(T1, T2, ...) — тип для хранения нескольких разных типов данных в одной колонке. Аналог union types из статически типизированных языков.

-- Колонка value может хранить UInt64, String или Float64
CREATE TABLE mixed_metrics
(
    id     UInt64,
    name   String,
    value  Variant(UInt64, String, Float64)
)
ENGINE = MergeTree()
ORDER BY id;

INSERT INTO mixed_metrics VALUES
(1, 'count',   42::Variant(UInt64, String, Float64)),
(2, 'label',   'active'::Variant(UInt64, String, Float64)),
(3, 'ratio',   0.75::Variant(UInt64, String, Float64));

-- Проверка типа и извлечение значения
SELECT
    id,
    name,
    variantType(value),                          -- тип хранимого значения
    value::UInt64 AS value_uint,                 -- каст к конкретному типу
    value::String AS value_str
FROM mixed_metrics;

Случаи применения Variant:

  • Метрики, где значение может быть числом, строкой или булевым
  • Конфигурационные параметры с разными типами значений
  • Полуструктурированные данные с известным набором возможных типов

Dynamic type: полностью динамические данные

Dynamic — наиболее гибкий тип: хранит произвольные типы без предварительного объявления. Подходит для данных с непредсказуемой схемой.

-- Dynamic: тип определяется при INSERT
CREATE TABLE schema_free_data
(
    id      UInt64,
    key     String,
    val     Dynamic
)
ENGINE = MergeTree()
ORDER BY id;

INSERT INTO schema_free_data VALUES
(1, 'temperature', 23.5),
(2, 'status',      'online'),
(3, 'count',       1000),
(4, 'tags',        ['a', 'b', 'c']);

-- Запрос с автоматическим выводом типа
SELECT id, key, val, dynamicType(val) AS detected_type
FROM schema_free_data;

Сравнение JSON, Variant, Dynamic:

ТипКогда использоватьОграничения
JSONПолуструктурированные документы (JSON API, logs)GA с 25.3
Variant(T1,T2,...)Колонка с известным набором типовНужно объявить типы
DynamicПолностью неизвестная схемаМеньше оптимизаций при запросах

Миграция из String + JSONExtract

Если в текущей таблице JSON хранится как String с доступом через JSONExtract* функции, миграция на нативный JSON type — двухшаговый процесс.

-- Шаг 1: Создать новую таблицу с JSON колонкой
CREATE TABLE user_events_new
(
    id         UInt64,
    event_time DateTime,
    payload    JSON          -- нативный JSON type (GA 25.3)
)
ENGINE = MergeTree()
ORDER BY (event_time, id);

-- Шаг 2: Перенести данные с кастом
INSERT INTO user_events_new
SELECT
    id,
    event_time,
    payload::JSON            -- каст String -> JSON
FROM user_events_old;

-- После верификации — атомарный swap (требует Atomic DB engine)
EXCHANGE TABLES user_events AND user_events_new;
DROP TABLE user_events_new;

Совместимость JSONExtract функций с нативным JSON type (25.3+):

-- JSONExtract* работают с нативным JSON type в 25.3+
SELECT
    JSONExtractString(payload, 'action') AS action_extract,  -- JSONExtract из JSON type
    payload.action AS action_subcolumn                        -- прямой доступ (быстрее)
FROM user_events
WHERE event_time >= today();
TIP

При миграции предпочитайте прямой доступ через точку (payload.action) над JSONExtractString(payload, 'action'). Прямой доступ к sub-column быстрее — читает один sub-column вместо разбора всего JSON.


Ключевые выводы

  1. JSON type GA в 25.3 — кардинально иная архитектура. Sub-column хранение: каждый уникальный JSON-путь в отдельном сжатом файле. Несовместим с экспериментальным JSON из 22.6.
  2. Компрессия и производительность лучше String. Числовые пути сжимаются как числа, строковые — как строки. Запрос по одному пути читает только один sub-column.
  3. Variant(T1,T2,...) для известных типов-вариантов. Объявите возможные типы заранее — получите оптимизации при запросах.
  4. Dynamic для полностью неизвестной схемы. Самый гибкий, но с наименьшими оптимизациями. Используйте как последний вариант.
  5. Миграция через INSERT SELECT + EXCHANGE TABLES. Каст payload::JSON конвертирует String в JSON type при переносе данных. JSONExtract* функции совместимы с нативным JSON в 25.3+.
JSON: формат, кодирование и ограничения для аналитики Стратегии моделирования JSON: sparse vs dense, flatten vs native

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 3. Разработчик хочет использовать нативный тип JSON в ClickHouse для хранения полуструктурированных данных API. На каком минимальном релизе ClickHouse тип JSON достиг статуса GA (Generally Available) и безопасен для production?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 7