Перейти к содержимому
Последнее обновление

Тип данных 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: тип столбца, который принимает JSON без фиксированной схемы, а затем сохраняет часто используемые пути как отдельные подстолбцы с колоночным хранением для быстрой аналитики.

Зачем использовать тип данных 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 и сохраняет часто встречающиеся пути как независимые подстолбцы с колоночным хранением, а редко встречающиеся помещает в общий разреженный столбец. Жизненный цикл отдельного пути на диске выглядит так:

  1. Разбор и определение типа. Когда JSON поступает в memtable, компонент записи добавляет каждый путь к конечному значению в префиксное дерево и выбирает самый узкий тип, подходящий для значений: BIGINT, DOUBLE, STRING, одномерный ARRAY<T> или вложенный VARIANT.
  2. Расширение типа или переход к резервному типу. Если новое значение несовместимо с текущим типом, Doris по возможности расширяет тип (TINYINT вместе с DOUBLE даёт DOUBLE). Если расширение невозможно, путь переходит к JSONB, а запросы теряют возможность pushdown для этого пути до исправления схемы.
  3. Запись в подстолбцы или разреженный столбец. Пути с наибольшей долей значений, отличных от NULL, становятся независимыми подстолбцами, которые кодируются и сжимаются точно так же, как статические столбцы. После достижения variant_max_subcolumns_count оставшиеся редко встречающиеся пути упаковываются в разреженный столбец.
  4. Объединение схем. Каждый rowset хранит собственную схему подстолбцов. При compaction данные rowset объединяются с использованием наименьшей общей схемы столбцов, поэтому добавленное сегодня поле не требует выполнения ALTER TABLE.
  5. Чтение по пути. 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 раза. Источник: та же публикация.

Дополнительные материалы​