JSON type (GA 25.3), Variant и Dynamic типы
ClickHouse 25.3 вводит нативный тип JSON в статус GA (Generally Available). Это кардинальное изменение в подходе к хранению полуструктурированных данных: вместо сериализации всего JSON в одну строку каждый уникальный путь хранится в отдельном sub-column с собственной компрессией.
Тип 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 хранения:
Преимущества над 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();
При миграции предпочитайте прямой доступ через точку (payload.action) над JSONExtractString(payload, 'action'). Прямой доступ к sub-column быстрее — читает один sub-column вместо разбора всего JSON.
Ключевые выводы
- JSON type GA в 25.3 — кардинально иная архитектура. Sub-column хранение: каждый уникальный JSON-путь в отдельном сжатом файле. Несовместим с экспериментальным JSON из 22.6.
- Компрессия и производительность лучше
String. Числовые пути сжимаются как числа, строковые — как строки. Запрос по одному пути читает только один sub-column. Variant(T1,T2,...)для известных типов-вариантов. Объявите возможные типы заранее — получите оптимизации при запросах.Dynamicдля полностью неизвестной схемы. Самый гибкий, но с наименьшими оптимизациями. Используйте как последний вариант.- Миграция через INSERT SELECT + EXCHANGE TABLES. Каст
payload::JSONконвертирует String в JSON type при переносе данных. JSONExtract* функции совместимы с нативным JSON в 25.3+.