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

Тип данных VARIANT

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

Тип данных Apache Doris VARIANT: тип столбца, который принимает JSON без фиксированной схемы и сохраняет часто используемые пути как отдельные подстолбцы в колоночном формате для аналитики со скоростью колоночного хранилища.

Зачем использовать тип данных VARIANT в Apache Doris?​

Тип данных Apache Doris VARIANT устраняет давний компромисс между гибкостью хранения JSON и скоростью запросов к колоночному хранилищу. Хранение JSON в OLAP-системах долгое время требовало уступок.

  • Столбец STRING или JSONB обеспечивает гибкость, но каждый запрос, обращающийся к пути, разбирает весь документ. Условие WHERE payload['action'] = 'opened' на миллиарде строк требует прочитать каждый байт всех данных payload, даже если action занимает лишь одно короткое поле в большом документе.
  • Статические столбцы работают быстро, но только при стабильной схеме. Когда в следующем спринте источники данных добавят поля, вам снова придётся выполнять миграции через ALTER TABLE или заводить дополнительный столбец JSON.

Apache Doris VARIANT избавляет от необходимости выбирать. Вы объявляете один столбец, записываете в него JSON, а Apache Doris автоматически переносит часто используемые пути в полноценное колоночное хранилище. Запросы по-прежнему пишутся на SQL, а на диске таблица во многом напоминает ту, которую вы спроектировали бы вручную.

Что представляет собой тип данных Apache Doris VARIANT?​

Apache Doris VARIANT представляет собой тип столбца для полуструктурированных данных. 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.

Как работает тип данных Apache Doris VARIANT?​

Apache Doris VARIANT разбирает каждый документ JSON при записи, определяет тип для каждого пути JSON и сохраняет часто встречающиеся пути как независимые подстолбцы в колоночном формате, а редко встречающиеся пути помещает в общий разреженный столбец. Жизненный цикл одного пути на диске выглядит так:

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

-- INSERT keeps a plain string as a VARIANT string, so parse the JSON text.
-- Load jobs such as Stream Load parse JSON automatically.
INSERT INTO github_events VALUES
(1, 'PushEvent', PARSE_TO_VARIANT('{"action":"opened","commits":[{"sha":"abc"}],"size":4}'), '2026-05-01'),
(2, 'IssueCommentEvent', PARSE_TO_VARIANT('{"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, ...) так, словно вы объявили их вручную.

Когда стоит использовать тип данных Apache Doris VARIANT?​

Используйте Apache Doris VARIANT, когда схема JSON меняется, но большинство запросов обращается к небольшому числу привычных путей. Если структура фиксирована, выбирайте статические столбцы или STRUCT, а для первичных ключей, ключей сортировки и соединения используйте типизированные столбцы.

Подходящие сценарии

  • Журналы событий и данные аудита, где большинство запросов обращается к нескольким привычным путям, а остальная часть документа просто хранится вместе с ними.
  • Таблицы телеметрии и профилей пользователей, в которых источники данных постоянно добавляют поля, а приём данных нельзя останавливать ради миграции схемы.
  • Данные наблюдаемости с тысячами необязательных тегов. Часто используемые теги становятся подстолбцами, а редко встречающиеся попадают в разреженный столбец, не увеличивая объём работы при компактации.
  • Полнотекстовый поиск внутри JSON за счёт сочетания VARIANT с USING INVERTED.

Неподходящие сценарии

  • Таблицы со стабильной, хорошо понятной схемой. Обычные типизированные столбцы проще понимать и индексировать, и они не могут неожиданно перейти к типу JSONB. Используйте VARIANT, только когда схема действительно меняется.
  • Первичные ключи, ключи сортировки или соединения. Столбец VARIANT не может быть ключевым столбцом. Значения VARIANT можно использовать при соединении и сортировке, но это медленнее, чем работа со статическими столбцами. Если 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 GB памяти: VARIANT занимал 12.7 GB против 35.7 GB для JSON (на ~65% меньше). При прогретом кэше запросы выполнялись примерно в 8 раз быстрее, чем при хранении в JSON, а расхождение с показателями заранее заданных статических столбцов укладывалось в ~10%. Источник: Variant в Apache Doris 2.1.0.
  • GuanceDB при переносе данных наблюдаемости из Elasticsearch в Doris с использованием VARIANT: затраты на вычислительные ресурсы снизились на 70%, общая скорость запросов выросла в ~2 раза, простые запросы ускорились в 4 раза и более. Источник: та же публикация.

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