Learning Platform
Глоссарий Troubleshooting
Урок 10.07 · 24 мин
Средний
icebergmetadata-tablesiceberg-v3materialized-views

Metadata-таблицы, Iceberg v2 и v3, тип VARIANT, материализованные представления

Завершаем модуль по Iceberg тремя темами, которые делают его инструментом эксплуатации, а не только хранения. Metadata-таблицы — окно в дерево метаданных из урока 3, через которое инженер диагностирует таблицу обычным SQL. Версии формата (v1, v2, v3) определяют, что таблица вообще умеет — от row-level DELETE до нового типа VARIANT. И материализованные представления — способ ускорить тяжёлые запросы поверх lakehouse. Этот урок собирает их вместе и даёт инструментарий для повседневной работы с Iceberg.

Metadata-таблицы: дерево метаданных как обычные таблицы

В уроке 3 мы разобрали дерево метаданных: metadata-файл, снапшоты, manifest list, манифесты, файлы данных. Iceberg-коннектор Trino даёт доступ к каждому уровню этого дерева через metadata-таблицы — виртуальные таблицы, к которым обращаются, дописав к имени таблицы суффикс $имя. Это не отдельные объекты в storage, а представление метаданных в табличном виде. Их можно фильтровать, джойнить, агрегировать — обычным SQL.

Основные metadata-таблицы:

ТаблицаЧто показываетКогда нужна
$snapshotsВсе снапшоты: id, время, операция, summaryИстория изменений, выбор снапшота для time travel
$historyЛиния родитель-потомок снапшотовПонять, как развивалась таблица, найти ветвления
$filesКаждый файл данных: путь, число строк, размер, статистикаДиагностика мелких файлов, анализ pruning
$manifestsМанифесты текущего снапшотаОценить, не пора ли optimize_manifests
$partitionsСводка по партициям: число файлов и строк в каждойНайти перекошенные или мелкие партиции
$refsВетки и теги таблицыАудит именованных указателей
$propertiesСвойства таблицыПроверить настройки формата, retention

Имя metadata-таблицы содержит $, поэтому в Trino его берут в двойные кавычки:

-- Здоровье таблицы одним запросом: распределение файлов по партициям
SELECT partition,
       record_count,
       file_count,
       file_count > 50 AS too_many_files
FROM iceberg.sales."events$partitions"
ORDER BY file_count DESC;

--    partition       | record_count | file_count | too_many_files
-- -------------------+--------------+------------+----------------
--  {order_day: 5-19} |      2950000 |       1843 | true
--  {order_day: 5-20} |       120000 |         12 | false

Этот запрос мгновенно показывает: партиция за 19 мая деградировала (1843 файла) и просит OPTIMIZE, остальные здоровы. То, что в обычной БД потребовало бы лезть в системные представления движка, в Iceberg делается переносимым SQL поверх metadata-таблиц.

-- Найти мелкие файлы по всей таблице
SELECT count(*) AS small_files,
       sum(record_count) AS rows_in_small_files
FROM iceberg.sales."events$files"
WHERE file_size_in_bytes < 8 * 1024 * 1024;

--  small_files | rows_in_small_files
-- -------------+---------------------
--         1791 |             2410000
TIP

Metadata-таблицы — основа автоматизации обслуживания. Пайплайн обслуживания не запускает OPTIMIZE вслепую по расписанию, а сначала запросом к $partitions или $files проверяет, есть ли деградация, и компактит только те партиции, где она реально есть. Это экономит дорогой I/O компакции.

Airflow: автоматизация обслуживания Iceberg через DAG

Версии формата: v1, v2, v3

У формата Iceberg есть номер версии спецификации, и он определяет, что таблица умеет. Версия задаётся свойством format_version при создании.

v1 — базовая версия. Таблица — это набор файлов данных, снапшоты, партиционирование. v1 умеет append (добавление данных) и перезапись на уровне файлов, но не умеет эффективный row-level DELETE. Сегодня v1 — фактически legacy.

v2 — версия по умолчанию в актуальном Trino. Главное нововведение — delete files. Они позволяют удалить отдельные строки, не переписывая весь файл данных. Вспомните: файлы данных неизменяемы. Как тогда удалить одну строку из файла на миллион строк? v2 пишет рядом небольшой delete file, который говорит «в таком-то файле строка с такой-то позицией удалена». При чтении движок применяет delete files поверх данных. Именно delete files делают возможными DELETE, UPDATE и MERGE на уровне строк без переписывания крупных файлов. Для большинства задач v2 — правильный выбор.

v3 — новейшая версия, экспериментальная в актуальном Trino. Её стоит знать, но в продакшене применять осознанно, понимая статус «экспериментально». v3 приносит набор возможностей:

  • Тип VARIANT — о нём отдельно ниже; это флагманская фича v3, и VARIANT доступен только в v3.
  • Binary deletion vectors — более эффективное представление удалённых строк, чем delete files v2: компактный битмап позиций вместо отдельных файлов.
  • Column default values — значения по умолчанию для колонок на уровне формата.
  • Nanosecond timestamps — временные метки наносекундной точности.
  • Row lineage — отслеживание происхождения строк между снапшотами.
Версии формата Iceberg: что добавляет каждая
v1Базовый формат: файлы данных, снапшоты, партиционирование; только append и перезапись на уровне файлов, нет эффективного row-level DELETE; сегодня legacy
v2 добавляет delete files
v2Версия по умолчанию: delete files дают row-level DELETE, UPDATE, MERGE без переписывания крупных файлов данных; правильный выбор для большинства задач
v3 добавляет VARIANT и прочее
v3Экспериментально в актуальном Trino: тип VARIANT, binary deletion vectors, column defaults, nanosecond timestamps, row lineage
-- v2 (по умолчанию): row-level DELETE через delete files,
-- крупные файлы данных не переписываются
CREATE TABLE iceberg.sales.accounts (
  account_id BIGINT,
  status     VARCHAR,
  updated_at TIMESTAMP(6)
) WITH (format = 'PARQUET', format_version = 2);

DELETE FROM iceberg.sales.accounts WHERE status = 'closed';
-- DELETE: 37 rows

-- v3: создаётся осознанно, когда нужен VARIANT или другие фичи v3
CREATE TABLE iceberg.sales.events_v3 (
  event_id BIGINT,
  payload  VARCHAR
) WITH (format = 'PARQUET', format_version = 3);

Тип VARIANT: полуструктурированные данные в колонке

VARIANT — флагманский тип Iceberg v3 (в Trino — экспериментальный, как и сам v3). Он решает реальную проблему дата-инженерии: данные, у которых нет фиксированной схемы.

Событийные логи, payload вебхуков, ответы внешних API — у каждой записи свой набор полей, и схема меняется со временем без предупреждения. Загнать такое в строгие колонки нельзя: одно событие имеет поле device.os, другое — нет, третье добавляет вложенный массив. Раньше выбор был между двумя плохими вариантами. Хранить как VARCHAR с JSON-текстом — тогда движок не понимает структуру, каждый доступ к полю парсит строку заново, фильтры по полям не оптимизируются. Либо завести колонку под каждое возможное поле — но полей сотни, они меняются, схема превращается в свалку.

VARIANT — третий путь. Это тип для полуструктурированных данных: внутри одной колонки хранится произвольная вложенная структура — объекты, массивы, скаляры — но хранится в эффективном бинарном представлении, а не как текст. Движок понимает структуру VARIANT-значения, умеет доставать вложенные поля и не парсит строку на каждое обращение. Это золотая середина между «слепым текстом» и «жёсткими колонками».

-- VARIANT доступен только в Iceberg v3 (экспериментально в Trino)
CREATE TABLE iceberg.sales.events_var (
  event_id BIGINT,
  props    VARIANT
) WITH (format = 'PARQUET', format_version = 3);

-- В одну VARIANT-колонку ложатся записи с разным набором полей
INSERT INTO iceberg.sales.events_var VALUES
  (1, CAST(JSON '{"device":{"os":"iOS"},"premium":true}' AS VARIANT)),
  (2, CAST(JSON '{"device":{"os":"Android"},"referrer":"ads"}' AS VARIANT));
-- INSERT: 2 rows
Apache Iceberg v3: тип VARIANT на уровне формата

VARIANT не отменяет обычные колонки. Стабильные, всегда присутствующие поля (event_id, user_id, event_ts) держат строгими типизированными колонками — по ним идёт партиционирование, статистика, pruning. А «хвост» из изменчивых, разреженных, необязательных атрибутов уходит в одну VARIANT-колонку. Это типовой паттерн событийных таблиц в lakehouse 2026 года.

Почему именно такое разделение, а не «всё в VARIANT»? Потому что у строгой типизированной колонки и у поля внутри VARIANT разная цена доступа. По строгой колонке Iceberg ведёт статистику min/max в манифестах — значит, по ней работает file pruning и партиционирование. Поле внутри VARIANT движок видит, но это полуструктурированное значение, и отсечь файлы по нему так же дёшево, как по обычной колонке, не всегда возможно. Отсюда правило: всё, по чему вы фильтруете, партиционируете и джойните, должно быть строгой колонкой; в VARIANT уходит то, что вы в основном просто читаете и достаёте по необходимости. Неправильное разделение — например, ключ join, спрятанный в VARIANT, — лишит запрос оптимизаций так же, как и в случае со слепым JSON-текстом.

Стоит также отметить эволюционную связь возможностей v3. Тип VARIANT, binary deletion vectors, row lineage — это не разрозненный набор, а согласованное движение Iceberg к зрелости: deletion vectors делают row-level DML эффективнее, row lineage — отслеживаемее, VARIANT — расширяет применимость на полуструктурированные данные. Все они приходят вместе в v3 именно потому, что v3 — это следующий большой шаг спецификации. Для дата-инженера практический смысл прост: v3 стоит держать на радаре, отслеживать в release notes Trino степень его готовности, но строить продакшен сегодня на проверенном v2.

WARNING

VARIANT и весь Iceberg v3 в актуальном Trino — экспериментальные. Для учебных задач и пилотов это нормально, но прежде чем строить продакшен на v3, сверьтесь с release notes вашей версии Trino: набор поддержанных операций над VARIANT расширяется от релиза к релизу. Стабильный, проверенный выбор для продакшена сегодня — v2.

Материализованные представления над lakehouse

Iceberg-коннектор поддерживает материализованные представления (materialized views). Обычный VIEW — это сохранённый текст запроса: каждое обращение к нему заново выполняет весь запрос. Материализованное представление хранит результат запроса как настоящую Iceberg-таблицу.

Зачем это в lakehouse. Тяжёлый запрос — join нескольких больших таблиц с агрегацией — может идти минуты. Если он стоит за дашбордом, который открывают сто раз в день, пересчитывать его каждый раз расточительно. Материализованное представление считает результат один раз, сохраняет в Iceberg-таблицу, и обращения читают готовый результат — быстро.

-- Тяжёлая агрегация считается один раз и хранится как Iceberg-таблица
CREATE MATERIALIZED VIEW iceberg.sales.mv_daily_revenue AS
SELECT date(order_ts) AS order_day,
       count(*)       AS orders,
       sum(amount)    AS revenue
FROM iceberg.sales.events
GROUP BY date(order_ts);

-- Обновить сохранённый результат, когда исходные данные изменились
REFRESH MATERIALIZED VIEW iceberg.sales.mv_daily_revenue;

-- Обращение читает готовый результат, а не пересчитывает join и агрегацию
SELECT * FROM iceberg.sales.mv_daily_revenue
WHERE order_day = DATE '2026-05-19';

Ключевая ответственность инженера — свежесть. Материализованное представление не обновляется само: после изменения исходных таблиц нужно REFRESH, иначе представление отдаёт устаревший результат. Преимущество Iceberg-реализации в том, что REFRESH может быть инкрементальным — благодаря снапшотам Iceberg знает, что изменилось с прошлого обновления, и пересчитывает не всё, а только дельту. На больших таблицах это разница между секундами и минутами. REFRESH обычно ставят в тот же пайплайн обслуживания, что и OPTIMIZE с expire_snapshots из урока 5.

Попробуй сам

В песочнице возьмите Iceberg-таблицу из прошлых уроков. Упражнение первое, metadata-таблицы: напишите один SQL-запрос к $partitions, который выводит партиции, отсортированные по числу файлов, и помечает флагом те, где файлов больше 50 — это ваш «детектор деградации». Запросом к $snapshots посчитайте, снапшоты каких операций (append, delete, overwrite) встречаются в истории и сколько каждого. Упражнение второе, версии: создайте две таблицы — с format_version = 2 и format_version = 3 — выполните DELETE нескольких строк в каждой и сравните, посмотрев $files и $snapshots. Упражнение третье, материализованное представление: создайте MV с агрегацией поверх таблицы, выполните SELECT из него, затем вставьте новые строки в исходную таблицу и снова сделайте SELECT из MV — изменился ли результат? Выполните REFRESH и проверьте снова. Письменно объясните, почему MV не обновился сам и в чём преимущество инкрементального REFRESH в Iceberg.


Проверка знанийKnowledge check
Чем версии формата Iceberg v2 и v3 отличаются по возможностям, зачем нужен тип VARIANT и в чём разница между обычным VIEW и материализованным представлением в lakehouse?
ОтветAnswer
Версия формата Iceberg определяет, что таблица умеет. v2 — версия по умолчанию в актуальном Trino; её главное нововведение — delete files: они позволяют удалять отдельные строки, не переписывая весь файл данных, поскольку файлы данных неизменяемы. Рядом с файлом данных пишется небольшой delete file, помечающий удалённые позиции, и при чтении движок применяет его поверх данных. Именно delete files делают возможными row-level DELETE, UPDATE и MERGE. v3 — новейшая версия, экспериментальная в Trino; она добавляет тип VARIANT, binary deletion vectors как более эффективную замену delete files, column default values, nanosecond timestamps и row lineage. Тип VARIANT нужен для полуструктурированных данных без фиксированной схемы — событийных логов, payload вебхуков, ответов API, где у каждой записи свой набор полей. VARIANT хранит произвольную вложенную структуру в одной колонке в эффективном бинарном представлении, а не как текст; движок понимает структуру и достаёт вложенные поля без повторного парсинга строки. Это золотая середина между слепым VARCHAR с JSON и сотней жёстких колонок; стабильные поля при этом оставляют строгими колонками, а изменчивый хвост уходит в VARIANT. VARIANT доступен только в v3. Разница между VIEW и материализованным представлением: обычный VIEW — сохранённый текст запроса, каждое обращение выполняет запрос заново; материализованное представление хранит результат запроса как настоящую Iceberg-таблицу, поэтому тяжёлый join с агрегацией считается один раз, а обращения читают готовый результат. Цена — свежесть: MV не обновляется сам, после изменения исходных таблиц нужен REFRESH, который в Iceberg может быть инкрементальным, пересчитывая по снапшотам только дельту.

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

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Что такое metadata-таблицы Iceberg вроде events$snapshots или events$files в Trino?

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

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

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

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