Тип данных VARIANT
Кратко
VARIANTв Apache Doris — это тип столбца для JSON, структура которого меняется со временем. При записи Apache Doris разбирает каждый документ, определяет тип для каждого пути JSON и сохраняет часто используемые пути как отдельные подстолбцы с колоночным хранением. По умолчанию на столбец VARIANT приходится до 2048 подстолбцов. Фильтрация и агрегация поv['user_id']затем читают один столбец вместо разбора всего документа. VARIANT стал общедоступным (GA) в Apache Doris 2.1.0 (март 2024 года); при его разработке использовались идеи Snowflake и ClickHouse.

Зачем использовать тип данных VARIANT в Apache Doris?
Тип данных VARIANT в Apache Doris избавляет от привычного компромисса между гибкостью хранения JSON и скоростью запросов к колоночному хранилищу. Долгое время хранение JSON в системе OLAP требовало уступок.
- Столбец
STRINGилиJSONBсохраняет гибкость, но каждый запрос, обращающийся к пути, разбирает весь документ. УсловиеWHERE payload['action'] = 'opened'на миллиарде строк приводит к чтению каждого байта всех значений payload, даже еслиaction— всего лишь одно короткое поле в объёмном документе. - Статические столбцы работают быстро, но только при стабильной схеме. Когда в следующем спринте источники данных добавят поля, придётся снова выполнять миграции через
ALTER TABLEили использовать вспомогательный столбец JSON.
VARIANT в Apache Doris устраняет этот компромисс. Вы объявляете один столбец, записываете в него JSON, а Apache Doris автоматически переносит часто используемые пути в полноценное колоночное хранилище. Запросы остаются на SQL, а структура таблицы на диске мало отличается от той, которую вы спроектировали бы вручную.
Что такое тип данных VARIANT в Apache Doris?
Тип данных VARIANT в Apache Doris — это полуструктурированный тип столбца, реализованный на основе DataTypeVariant и ColumnVariant (ранее ColumnObject, созданного по образцу реализации ClickHouse). VARIANT принимает любые допустимые значения JSON: скаляры, одномерные массивы и вложенные объекты. По мере поступления строк компонент записи строит префиксное дерево путей, определяет тип для каждого пути и записывает каждый путь как отдельный столбец внутри сегмента с постраничным кодированием и индексированием. Вы запрашиваете JSON, а на диске для каждого пути хранится отдельный подстолбец.
Основные термины
Subcolumn: независимый столбец на диске, сформированный из пути JSON. У каждого подстолбца есть собственное кодирование, сжатие, zone map и bloom filter, как и у статического столбца.Subcolumnization: процесс выделения часто используемых путей JSON в подстолбцы при записи. Управляется параметромvariant_max_subcolumns_count, значение по умолчанию —2048.Schema Template: необязательная конструкцияVARIANT<'path' : TYPE, ...>, которая закрепляет фиксированный тип за выбранными путями, чтобы структура хранения и индексы оставались стабильными между загрузками.Sparse column: общий резервный столбец, в который попадают редко встречающиеся пути, когда число путей превышает лимит подстолбцов. Один физический столбец содержит множество логических путей.Type promotion: правило, определяющее, что делать, если один и тот же путь имеет типINTв одной строке иSTRINGв другой. Совместимые типы расширяются (сTINYINTдоBIGINT), а несовместимые переходят кJSONB.
Как работает тип данных VARIANT в Apache Doris?
VARIANT в Apache Doris разбирает каждый документ JSON при записи, определяет тип для каждого пути JSON и сохраняет часто встречающиеся пути как независимые подстолбцы с колоночным хранением, а редко встречающиеся помещает в общий разреженный столбец. Жизненный цикл отдельного пути на диске выглядит так:
- Разбор и определение типа. Когда JSON поступает в memtable, компонент записи добавляет каждый путь к конечному значению в префиксное дерево и выбирает самый узкий тип, подходящий для значений:
BIGINT,DOUBLE,STRING, одномерныйARRAY<T>или вложенныйVARIANT. - Расширение типа или переход к резервному типу. Если новое значение несовместимо с текущим типом, Doris по возможности расширяет тип (
TINYINTвместе сDOUBLEдаётDOUBLE). Если расширение невозможно, путь переходит кJSONB, а запросы теряют возможность pushdown для этого пути до исправления схемы. - Запись в подстолбцы или разреженный столбец. Пути с наибольшей долей значений, отличных от NULL, становятся независимыми подстолбцами, которые кодируются и сжимаются точно так же, как статические столбцы. После достижения
variant_max_subcolumns_countоставшиеся редко встречающиеся пути упаковываются в разреженный столбец. - Объединение схем. Каждый rowset хранит собственную схему подстолбцов. При compaction данные rowset объединяются с использованием наименьшей общей схемы столбцов, поэтому добавленное сегодня поле не требует выполнения
ALTER TABLE. - Чтение по пути.
SELECT v['user_id']обращается к одному подстолбцу и читает только его, пропуская страницы с помощью zone map и bloom filter так же, как при работе со статическими столбцами. Поддерживается pushdown предикатов:CAST(v['user_id'] AS BIGINT) = 42выполняется над типизированным подстолбцом, а не над текстом JSON.
Инвертированный индекс работает с вложенными путями аналогичным образом. Конструкция INDEX idx_v(v) USING INVERTED PROPERTIES("parser" = "english") позволяет выполнять поиск по каждому текстовому подстолбцу, а Schema Template позволяет закрепить индекс за конкретным путём.
Быстрый старт
CREATE TABLE github_events (
id BIGINT,
type VARCHAR(30),
payload VARIANT,
created_at DATETIME,
INDEX idx_payload (payload) USING INVERTED PROPERTIES("parser" = "english")
)
DUPLICATE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 4
PROPERTIES ("storage_format" = "V3");
INSERT INTO github_events VALUES
(1, 'PushEvent', '{"action":"opened","commits":[{"sha":"abc"}],"size":4}', '2026-05-01'),
(2, 'IssueCommentEvent', '{"action":"created","issue":{"number":42}}', '2026-05-01');
SELECT CAST(payload['action'] AS STRING) AS action, COUNT(*) AS n
FROM github_events
WHERE payload['action'] MATCH 'opened'
GROUP BY action;
Ожидаемый результат
+--------+------+
| action | n |
+--------+------+
| opened | 1 |
+--------+------+
Фильтр читает один подстолбец, обращается к инвертированному индексу и вообще не затрагивает массив commits. Выполните SET describe_extend_variant_column = true; DESC github_events;, чтобы увидеть автоматически определённые подстолбцы (payload.action, payload.commits, payload.issue.number, ...), как если бы вы объявили их вручную.
Когда следует использовать тип данных VARIANT в Apache Doris?
Используйте VARIANT в Apache Doris, когда схема JSON меняется, но большинство запросов обращаются к небольшому числу привычных путей. Если структура фиксирована, выбирайте статические столбцы или STRUCT. Не используйте VARIANT для первичных ключей, ключей сортировки или соединения.
Подходящие сценарии
- Журналы событий и данные аудита, где большинство запросов обращаются к нескольким привычным путям, а остальная часть документа просто хранится вместе с ними.
- Таблицы телеметрии и профилей пользователей, в которые источники данных постоянно добавляют поля, а приостанавливать загрузку ради миграции схемы нельзя.
- Данные наблюдаемости с тысячами необязательных тегов. Часто используемые теги становятся подстолбцами, а редко встречающиеся попадают в разреженный столбец, не увеличивая чрезмерно объём работы при compaction.
- Полнотекстовый поиск внутри JSON с помощью сочетания
VARIANTиUSING INVERTED.
Неподходящие сценарии
- Таблицы со стабильной, хорошо изученной схемой. Обычные типизированные столбцы проще понимать и индексировать, а их тип не может неожиданно перейти к
JSONB. ИспользуйтеVARIANT, только если схема действительно меняется. - Первичные ключи, ключи сортировки или соединения.
VARIANTне может использоваться ни в одной из этих ролей, а условие соединенияCAST(v['id'] AS BIGINT)лишает планировщик большинства вариантов оптимизации. Еслиidслужит ключом соединения, объявите его как статический столбецBIGINT. - Поиск по всему документу, например
WHERE v LIKE '%doris%'. Инвертированный индекс строится для вложенных путей, а не для всего столбца. Храните исходный JSON в отдельном столбцеSTRINGи индексируйте его либо включите режим DOC для быстрого выполненияSELECT v. - Фиксированная структура, известная ещё при проектировании. Если каждая строка содержит ровно
{a, b, c}, а типы никогда не меняются,STRUCTзадаёт более подходящий контракт: он запрещает неожиданные поля, тогда какVARIANTпринимает их без предупреждения. - Очень широкий JSON (10k+ путей) без настройки. По умолчанию выделение подстолбцов ограничено 2048 путями. Если путей больше, предусмотрите разреженные столбцы или режим DOC и прочитайте руководство по рабочим нагрузкам VARIANT, прежде чем загружать производственные данные.
Производительность и показатели
- ClickBench, 43 запроса на экземпляре EC2 с 16 ядрами и 64 ГБ памяти: VARIANT занял 12.7 ГБ против 35.7 ГБ для JSON (на ~65% меньше). На прогретом кеше запросы выполнялись примерно в 8 раз быстрее, чем с JSON, а разница с заранее определёнными статическими столбцами составляла не более ~10%. Источник: Variant в Apache Doris 2.1.0.
- GuanceDB, перенос данных наблюдаемости из Elasticsearch в Doris с использованием VARIANT: затраты на вычислительные ресурсы снизились на 70%, общая скорость запросов выросла примерно в 2 раза, простые запросы ускорились более чем в 4 раза. Источник: та же публикация.
Дополнительные материалы
- Справочник SQL по VARIANT: полный синтаксис, правила типов, индексы и конфигурация
- Руководство по рабочим нагрузкам VARIANT: когда выбирать режим по умолчанию, разреженное хранение, режим DOC или Schema Template
- Загрузка данных VARIANT: примеры Stream Load, Routine Load и Broker Load
- Карточка функции «Колоночное хранение»: как устроены кодирование, разбиение на страницы и индексирование подстолбцов
- Обзор инвертированного индекса: индекс, который VARIANT использует для поиска текста внутри JSON
- Функции SQL для LLM: семейство
AI_*, чей результат в формате JSON (отAI_EXTRACT,AI_CLASSIFYи других функций) хорошо подходит для столбца VARIANT. - Блог Apache Doris: Variant в Apache Doris 2.1.0, первоначальный анонс и результаты тестов производительности