Apache Iceberg. Упражнение: Ветвление и тегирование

Практика Git‑подобного workflow

|

До сих пор вы работали со снапшотами по их ID или временным меткам. Это работает, но попробуйте объяснить вашему финансовому директору, что «квартальный отчёт использует снапшот 7851662191363948742». Не очень удобно для пользователя.

Теги и ветки привносят Git‑подобную семантику в таблицы Iceberg:

  • Теги — это именованные ссылки на конкретные снапшоты. Думайте о них как о закладках: «это данные, которые мы использовали для отчёта за Q4» или «это состояние перед большой миграцией».
  • Ветки — это независимые линии разработки. Вы можете писать в ветку, не влияя на основную таблицу, валидировать изменения, а затем либо опубликовать их, либо выбросить. Это основа workflow Write‑Audit‑Publish (WAP).

В этом упражнении вы создадите теги для важных снапшотов (для соответствия требованиям), создадите staging‑ветку, загрузите в неё данные, проверите их и опубликуете в продакшен.

Цели обучения

К концу упражнения вы сможете:

  • Создавать теги для снапшотов и управлять ими
  • Запрашивать данные по конкретному тегу
  • Создавать ветки для изолированных записей
  • Реализовывать workflow Write‑Audit‑Publish
  • Выполнять fast‑forward основной ветки для включения проверенных изменений

Предварительные требования

  • Завершённое упражнение 4 (или запущенное окружение)
  • Около 25 минут

Шаг 1: Подготовка окружения

Запустите окружение, если оно ещё не запущено:

docker compose up -d

Запустите Spark SQL:

docker compose exec -it spark-iceberg spark-sql --conf "spark.hadoop.hive.cli.print.header=true"

Создадим таблицу, представляющую финансовый журнал — то, где качество данных имеет значение:

CREATE NAMESPACE IF NOT EXISTS demo.finance;USE demo.finance; DROP TABLE IF EXISTS transactions; CREATE TABLE transactions (    transaction_id BIGINT,    account_id BIGINT,    transaction_date DATE,    amount DECIMAL(15, 2),    description STRING,    verified BOOLEAN)USING icebergTBLPROPERTIES ('format-version'='2', 'write.format.default'='parquet');

Загрузите некоторые начальные проверенные транзакции:

INSERT INTO transactions VALUES    (1001, 100, CAST('2025-01-02' AS DATE), 5000.00, 'Начальный депозит', true),    (1002, 100, CAST('2025-01-05' AS DATE), -150.00, 'Канцелярские товары', true),    (1003, 101, CAST('2025-01-03' AS DATE), 12000.00, 'Начальный депозит', true),    (1004, 101, CAST('2025-01-10' AS DATE), -3200.00, 'Покупка оборудования', true),    (1005, 102, CAST('2025-01-04' AS DATE), 8500.00, 'Начальный депозит', true);

Проверьте данные:

SELECT * FROM transactions ORDER BY transaction_id;
transaction_id account_id transaction_date amount description verified
1001 100 2025-01-02 5000.00 Начальный депозит true
1002 100 2025-01-05 -150.00 Канцелярские товары true
1003 101 2025-01-03 12000.00 Начальный депозит true
1004 101 2025-01-10 -3200.00 Покупка оборудования true
1005 102 2025-01-04 8500.00 Начальный депозит true

Шаг 2: Создайте тег для закрытия месяца

31 января. Финансовой команде нужно закрыть книги. Давайте пометим текущее состояние тегом, чтобы мы всегда могли воспроизвести числа за январь:

ALTER TABLE transactions CREATE TAG `jan-2025-close`;

Проверьте, что тег создан:

SELECT * FROM demo.finance.transactions.refs;

Вы должны увидеть:

name type snapshot_id max_reference_age_in_ms ...
jan-2025-close TAG ... NULL
main BRANCH ... NULL

Обратите внимание, что main также указан — это ветка по умолчанию, которая есть у каждой таблицы Iceberg.

Шаг 3: Продолжаем обычные операции

Наступает февраль. Поступают новые транзакции:

INSERT INTO transactions VALUES    (1006, 100, CAST('2025-02-01' AS DATE), -89.99, 'Подписка на ПО', true),    (1007, 102, CAST('2025-02-03' AS DATE), -1200.00, 'Оплата подрядчику', true),    (1008, 101, CAST('2025-02-05' AS DATE), 4500.00, 'Получен платёж от клиента', true);

Текущая таблица теперь содержит 8 транзакций:

SELECT COUNT(*) FROM transactions;

Шаг 4: Запрос помеченного снапшота

Приходят аудиторы. Они хотят увидеть точно, как выглядели книги на момент закрытия января. Без проблем:

SELECT *FROM transactions VERSION AS OF 'jan-2025-close'ORDER BY transaction_id;
transaction_id account_id transaction_date amount description verified
1001 100 2025-01-02 5000.00 Начальный депозит true
1002 100 2025-01-05 -150.00 Канцелярские товары true
1003 101 2025-01-03 12000.00 Начальный депозит true
1004 101 2025-01-10 -3200.00 Покупка оборудования true
1005 102 2025-01-04 8500.00 Начальный депозит true

Только исходные 5 транзакций. Февральские данные в этом теге не существуют.

Давайте проверим, что балансы счетов соответствуют отчётности:

SELECT    account_id,    SUM(amount) AS balance_at_jan_closeFROM transactions VERSION AS OF 'jan-2025-close'GROUP BY account_idORDER BY account_id;

Вы должны увидеть:

account_id balance_at_jan_close
100 4850.00
101 8800.00
102 8500.00

Эти числа неизменяемы. Независимо от того, что произойдёт с таблицей в будущем, этот тег всегда будет показывать одни и те же данные.

Шаг 5: Создайте staging‑ветку

Теперь давайте рассмотрим более сложный сценарий. Ваш ETL‑пайплайн имеет новую партию транзакций для загрузки, но вы хотите проверить их, прежде чем они попадут в продакшен.

Создайте staging‑ветку:

ALTER TABLE transactions CREATE BRANCH staging;

Проверьте ссылки:

SELECT name, type, snapshot_idFROM demo.finance.transactions.refs;
name type snapshot_id
main BRANCH 4187204227757276764
staging BRANCH 4187204227757276764
jan-2025-close TAG 5170763347723356942

Теперь у вас три ссылки: main, staging и jan-2025-close. Обратите внимание, что staging начинается с того же снапшота, что и main.

Шаг 6: Запись в staging‑ветку

Чтобы писать в ветку с использованием Write‑Audit‑Publish, сначала нужно включить эту возможность:

ALTER TABLE demo.finance.transactions    SET TBLPROPERTIES ('write.wap.enabled' = 'true');

Затем нужно установить, какую ветку вы хотите использовать как цель записи:

SET spark.wap.branch = staging;

Теперь любые записи идут в staging‑ветку, а не в main. Загрузите новую партию:

INSERT INTO transactions VALUES    (1009, 100, CAST('2025-02-10' AS DATE), -500.00, 'Консалтинговый сбор', false),    (1010, 103, CAST('2025-02-10' AS DATE), 15000.00, 'Депозит нового клиента', false),    (1011, 101, CAST('2025-02-11' AS DATE), -750.00, 'Командировочные расходы', false),    (1012, 100, CAST('2025-02-12' AS DATE), 2000.00, 'Получен возврат', false);

Теперь проверьте обе ветки:

-- Продакшен (ветка main) — нужно явно запроситьSELECT COUNT(*) AS main_countFROM transactions VERSION AS OF 'main'; -- Staging‑ветка (текущая цель записи, также доступна по имени)SELECT COUNT(*) AS staging_countFROM transactions VERSION AS OF 'staging';

В продакшене 8 транзакций. В staging — 12. Ветки разошлись.

Шаг 7: Валидация данных в staging

Перед публикацией давайте запустим некоторые проверки качества данных в staging:

-- Проверка на отрицательные балансы (нарушение бизнес‑правила)SELECT    account_id,    SUM(amount) AS balanceFROM transactions VERSION AS OF 'staging'GROUP BY account_idHAVING SUM(amount) < 0;

Отрицательных балансов нет — хорошо.

-- Проверка на непроверенные транзакции (требуют ревью)SELECT *FROM transactions VERSION AS OF 'staging'WHERE verified = false;
transaction_id account_id transaction_date amount description verified
1009 100 2025-02-10 -500.00 Консалтинговый сбор false
1010 103 2025-02-10 15000.00 Депозит нового клиента false
1011 101 2025-02-11 -750.00 Командировочные расходы false
1012 100 2025-02-12 2000.00 Получен возврат false

Новые транзакции непроверенные. В реальном workflow вы могли бы:

  • Запустить автоматические правила валидации
  • Отправить алерты, если обнаружены аномалии
  • Провести человеческий ревью и утверждение

Вы можете проверить, что продакшен всё ещё нетронут:

SELECT COUNT(*)FROM transactions VERSION AS OF 'main'WHERE verified = false;

Ноль непроверенных в продакшене.

Давайте пометим транзакции в staging как проверенные (помните, мы всё ещё пишем в staging):

UPDATE transactionsSET verified = trueWHERE verified = false;

Проверьте обновление (всё ещё в staging):

SELECT transaction_id, verifiedFROM transactions VERSION AS OF 'staging'WHERE transaction_id >= 1009;
transaction_id verified
1011 true
1012 true
1010 true
1009 true

Все проверены.

Шаг 8: Публикация проверенных изменений

Данные валидированы. Пора публиковать в продакшен.

Сначала посмотрим, куда указывает каждая ветка:

SELECT name, type, snapshot_idFROM demo.finance.transactions.refs;
name type snapshot_id
main BRANCH 4187204227757276764
staging BRANCH 3530026071892536539
jan-2025-close TAG 5170763347723356942

Staging опережает main. Чтобы опубликовать, мы делаем fast‑forward main к снапшоту staging:

CALL system.fast_forward('demo.finance.transactions', 'main', 'staging');
branch_updated previous_ref updated_ref
main 4187204227757276764 3530026071892536539

Это перемещает указатель ветки main так, чтобы он совпадал с staging. Никакие данные не копируются — это просто обновление метаданных.

Теперь сбросьте цель записи обратно на main:

ALTER TABLE demo.finance.transactions    UNSET TBLPROPERTIES ('write.wap.enabled');

Проверьте, что продакшен теперь содержит все данные:

SELECT * FROM transactions ORDER BY transaction_id;
transaction_id account_id transaction_date amount description verified
1001 100 2025-01-02 5000.00 Начальный депозит true
1002 100 2025-01-05 -150.00 Канцелярские товары true
1003 101 2025-01-03 12000.00 Начальный депозит true
1004 101 2025-01-10 -3200.00 Покупка оборудования true
1005 102 2025-01-04 8500.00 Начальный депозит true
1006 100 2025-02-01 -89.99 Подписка на ПО true
1007 102 2025-02-03 -1200.00 Оплата подрядчику true
1008 101 2025-02-05 4500.00 Получен платёж от клиента true
1009 100 2025-02-10 -500.00 Консалтинговый сбор true
1010 103 2025-02-10 15000.00 Депозит нового клиента true
1011 101 2025-02-11 -750.00 Командировочные расходы true
1012 100 2025-02-12 2000.00 Получен возврат true

Все 12 транзакций, все проверены.

Шаг 9: Очистка ветки

Staging‑ветка выполнила свою задачу. Вы можете удалить её:

ALTER TABLE transactions DROP BRANCH staging;

Проверьте ссылки:

SELECT name, type FROM demo.finance.transactions.refs;
name type
main BRANCH
jan-2025-close TAG

Остались только main и jan-2025-close.

Шаг 10: Создайте ещё один тег

Давайте пометим закрытие февраля:

ALTER TABLE transactions CREATE TAG `feb-2025-close`;

Теперь вы можете сравнить любые две точки во времени:

SELECT period, account_id, balance FROM (    -- Балансы января    SELECT 'Январь' AS period, account_id, SUM(amount) AS balance    FROM transactions VERSION AS OF 'jan-2025-close'    GROUP BY account_id    UNION ALL    -- Балансы февраля    SELECT 'Февраль' AS period, account_id, SUM(amount) AS balance    FROM transactions VERSION AS OF 'feb-2025-close'    GROUP BY account_id) resultsORDER BY period, account_id;

Вы должны увидеть:

period account_id balance
Февраль 100 6260.01
Февраль 101 12550.00
Февраль 102 7300.00
Февраль 103 15000.00
Январь 100 4850.00
Январь 101 8800.00
Январь 102 8500.00

Это невероятно мощно для финансовой отчётности, аудиторских следов и анализа трендов.

Шаг 11: Управление тегами

Теги могут иметь политики удержания. Создайте тег с автоматическим истечением:

ALTER TABLE transactions CREATE TAG `temp-debug-tag` RETAIN 7 DAYS;

Через 7 дней этот тег станет кандидатом на очистку во время экспирации снапшотов.

Перечислите все теги с их свойствами:

SELECT    name,    type,    snapshot_id,    max_reference_age_in_ms / 86400000 AS retention_daysFROM demo.finance.transactions.refsWHERE type = 'TAG';
name type snapshot_id retention_days
feb-2025-close TAG 3530026071892536539 NULL
jan-2025-close TAG 5170763347723356942 NULL
temp-debug-tag TAG 3530026071892536539 7.0

Не забывайте регулярно удалять теги, которые больше не нужны, чтобы держать ссылки таблицы в порядке:

ALTER TABLE transactions DROP TAG `temp-debug-tag`;

Шаг 12: Очистка

Выйдите из Spark SQL:

exit;

Если вы закончили:

docker compose down -v

Что вы узнали

  • Теги — это именованные неизменяемые ссылки на снапшоты — идеально для соответствия требованиям и воспроизводимости.
  • Ветки позволяют изолированные записи без влияния на продакшен.
  • Workflow Write‑Audit‑Publish (WAP): stage → validate → fast‑forward.
  • fast_forward публикует ветку, перемещая указатель main.
  • Теги и ветки — это операции только с метаданными — мгновенные и дешёвые.

Практические use‑cases

Теги:

  • Регуляторное соответствие («это Q4 2024, как отчитано в SEC»)
  • Воспроизводимость машинного обучения («модель v2.3 обучалась на этом снапшоте»)
  • Маркеры релизов («before‑migration», «after‑migration»)

Ветки:

  • Валидация ETL перед публикацией
  • A/B‑тестирование различных трансформаций данных
  • Экспериментирование без риска для продакшена
  • Blue/green‑деплойменты данных

«Ветвление и тегирование в Iceberg — это как Git для данных: можно ставить закладки на важные моменты и экспериментировать в изоляции, не боясь сломать продакшен. Только не забывайте удалять старые ветки, иначе ваш data lake превратится в архив забытых экспериментов.»


→ Вперёд к метатаблицам Iceberg

← Назад к тегированию и ветвлению