Apache Iceberg. Упражнение: Time Travel

Практика путешествий во времени

|

Вы создавали таблицы, меняли их схемы и партицировали для производительности. Теперь поговорим об одной из самых крутых функций Iceberg — time travel (путешествие во времени).

Каждая операция записи в Iceberg создаёт снапшот — неизменяемую запись состояния таблицы в этот момент. Эти снапшоты — не просто для галочки. Вы можете их запрашивать. Можете откатываться к ним. Можете использовать, чтобы ответить на вопросы вроде «Как выглядела эта таблица вчера?» или «Каков был баланс клиента до того случайного UPDATE?».

В этом упражнении вы внесёте изменения в таблицу, изучите историю её снапшотов и запросите предыдущие версии данных.

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

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

  • Просматривать историю снапшотов таблицы
  • Запрашивать данные, какими они были в определённый момент времени
  • Использовать как ID снапшотов, так и временные метки для time travel
  • Откатывать таблицу к предыдущему состоянию

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

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

Шаг 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.ecommerce;USE demo.ecommerce; CREATE TABLE customers (    customer_id BIGINT,    name STRING,    email STRING,    balance DECIMAL(10, 2),    status STRING)USING icebergTBLPROPERTIES ('format-version'='2', 'write.format.default'='parquet');

Вставьте начальные данные:

INSERT INTO customers VALUES    (1, 'Алиса Чен', 'alice@example.com', 1500.00, 'active'),    (2, 'Боб Смит', 'bob@example.com', 2300.50, 'active'),    (3, 'Кэрол Джонс', 'carol@example.com', 890.25, 'active'),    (4, 'Дэвид Ли', 'david@example.com', 4200.00, 'active'),    (5, 'Ева Мартинес', 'eva@example.com', 150.75, 'inactive');

Проверьте стартовую точку:

SELECT * FROM customers ORDER BY customer_id;
customer_id name email balance status
1 Алиса Чен alice@example.com 1500.00 active
2 Боб Смит bob@example.com 2300.50 active
3 Кэрол Джонс carol@example.com 890.25 active
4 Дэвид Ли david@example.com 4200.00 active
5 Ева Мартинес eva@example.com 150.75 inactive

Шаг 2: Создаём историю

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

Обновление 1: Боб погасил часть своего баланса:

UPDATE customersSET balance = 1800.50WHERE customer_id = 2;

Обновление 2: Кэрол совершила крупную покупку:

UPDATE customersSET balance = 2890.25WHERE customer_id = 3;

Ой‑ой: Кто‑то запустил плохое обновление — случайно обнулил все балансы:

UPDATE customersSET balance = 0.00;

Проверьте ущерб:

SELECT * FROM customers ORDER BY customer_id;
customer_id name email balance status
1 Алиса Чен alice@example.com 0.00 active
2 Боб Смит bob@example.com 0.00 active
3 Кэрол Джонс carol@example.com 0.00 active
4 Дэвид Ли david@example.com 0.00 active
5 Ева Мартинес eva@example.com 0.00 inactive

Все балансы теперь равны нулю. В традиционной базе данных вы бы лихорадочно искали бэкапы. С Iceberg у нас есть варианты.

Шаг 3: Изучаем историю снапшотов

Давайте посмотрим каждую версию этой таблицы:

SELECT    snapshot_id,    parent_id,    committed_at,    operationFROM demo.ecommerce.customers.snapshotsORDER BY committed_at;
snapshot_id parent_id committed_at operation
5839555060007264943 NULL 2026-05-30 10:36:22.083 append
8919815589576886702 5839555060007264943 2026-05-30 10:36:45.328 overwrite
387317294377428188 8919815589576886702 2026-05-30 10:36:53.418 overwrite
8123709836980851676 387317294377428188 2026-05-30 10:36:59.584 overwrite

Вы должны увидеть четыре снапшота:

  1. Начальный append (наш INSERT)
  2. Overwrite (обновление Боба)
  3. Ещё один overwrite (обновление Кэрол)
  4. Финальный overwrite (случайное массовое обновление)

Запомните значения snapshot_id — они понадобятся через мгновение. Также обратите внимание на временные метки в committed_at.

Шаг 4: Запрос предыдущего снапшота

Давайте посмотрим на данные до катастрофы. Найдите ID снапшота, сделанного прямо перед массовым обновлением (второй overwrite в результатах таблицы snapshots), и запросите его:

-- Замените <SNAPSHOT_ID> на реальный ID из вашего выводаSELECT *FROM customers VERSION AS OF <SNAPSHOT_ID>ORDER BY customer_id;

Вы увидите:

customer_id name email balance status
1 Алиса Чен alice@example.com 1500.00 active
2 Боб Смит bob@example.com 1800.50 active
3 Кэрол Джонс carol@example.com 2890.25 active
4 Дэвид Ли david@example.com 4200.00 active
5 Ева Мартинес eva@example.com 150.75 inactive

Вот ваши данные, целые и невредимые. У Алисы всё ещё $1,500. У Боба $1,800.50 (после его платежа). У Кэрол $2,890.25 (после её покупки).

Вы также можете запросить по временной метке. Это часто практичнее — вы можете не знать ID снапшота, но знаете, что «данные были корректны в 14:00 вчера»:

-- Замените на метку времени между снапшотом 3 и 4SELECT *FROM customers TIMESTAMP AS OF '2026-05-30 10:36:58'ORDER BY customer_id;

Настройте метку времени в соответствии с вашими значениями committed_at. Любая метка после снапшота 3, но до снапшота 4 покажет вам состояние до катастрофы.

Шаг 5: Откат таблицы

Смотреть на старые данные — это приятно, но иногда нужно реально отменить ущерб. Iceberg поддерживает откат к предыдущему снапшоту.

В Spark SQL используйте процедуру rollback_to_snapshot:

CALL system.rollback_to_snapshot(  'demo.ecommerce.customers', 387317294377428188);
previous_snapshot_id current_snapshot_id
8123709836980851676 387317294377428188

Замените SNAPSHOT_ID на снапшот перед массовым обновлением.

Теперь проверьте текущее состояние:

SELECT * FROM customers ORDER BY customer_id;
customer_id name email balance status
1 Алиса Чен alice@example.com 1500.00 active
2 Боб Смит bob@example.com 1800.50 active
3 Кэрол Джонс carol@example.com 2890.25 active
4 Дэвид Ли david@example.com 4200.00 active
5 Ева Мартинес eva@example.com 150.75 inactive

Балансы восстановлены. Плохое обновление отменено.

Важно: Откат обновляет указатель текущего снапшота таблицы, чтобы указывать на предыдущий снапшот — он не удаляет плохой снапшот и не создаёт новый. Вы по‑прежнему можете путешествовать во времени, чтобы увидеть катастрофу, если нужно. Проверьте:

SELECT    snapshot_id,    committed_at,    operationFROM demo.ecommerce.customers.snapshotsORDER BY committed_at;
snapshot_id committed_at operation
5839555060007264943 2026-05-30 10:36:22.083
8919815589576886702 2026-05-30 10:36:45.328
387317294377428188 2026-05-30 10:36:53.418
8123709836980851676 2026-05-30 10:36:59.584

Вы всё ещё увидите те же четыре снапшота, что и раньше, но таблица теперь использует снапшот 3 как текущее состояние.

Вы можете убедиться в этом, выполнив:

SELECT * FROM demo.ecommerce.customers.refs;
name type snapshot_id max_reference_age_in_ms min_snapshots_to_keep max_snapshot_age_in_ms
main BRANCH 387317294377428188 NULL NULL NULL

Посмотрите, как ветка main указывает на снапшот, к которому вы откатились.

Шаг 6: Сравнение версий

Time travel отлично подходит для отладки. Давайте сравним, что изменилось между двумя снапшотами.

SELECT    curr.customer_id,    curr.name,    hist.balance AS balance_before,    curr.balance AS balance_after,    curr.balance - hist.balance AS differenceFROM customers currJOIN customers VERSION AS OF <FIRST_SNAPSHOT_ID> AS hist    ON curr.customer_id = hist.customer_idORDER BY curr.customer_id;

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

customer_id name balance_before balance_after difference
1 Алиса Чен 1500.00 1500.00 0.00
2 Боб Смит 2300.50 1800.50 -500.00
3 Кэрол Джонс 890.25 2890.25 2000.00
4 Дэвид Ли 4200.00 4200.00 0.00
5 Ева Мартинес 150.75 150.75 0.00

Этот паттерн бесценен для аудита изменений или выяснения, что именно пошло не так.

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

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

exit;

Если вы закончили на сегодня:

docker compose down -v

Что вы узнали

  • Каждая запись создаёт неизменяемый снапшот
  • FOR VERSION AS OF позволяет запрашивать по ID снапшота
  • FOR TIMESTAMP AS OF позволяет запрашивать по моменту времени
  • rollback_to_snapshot восстанавливает таблицу к предыдущему состоянию (без потери истории)
  • Time travel возможен, потому что файлы данных неизменяемы — Iceberg просто меняет, какие файлы ссылаются на каждый снапшот

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

  • Отладка: «Как выглядела эта строка до запуска того ETL‑задания?»
  • Аудит: «Покажите мне точно, какие данные были видны 31 декабря для соответствия требованиям»
  • Восстановление: «Отменить тот случайный DELETE»
  • Воспроизводимость: «Запустите этот анализ на тех же данных, что мы использовали в прошлом квартале»

«Time travel — это как машина времени для данных: можно вернуться и посмотреть, что было вчера, позавчера или даже до того, как кто‑то случайно удалил всю таблицу. Только не пытайтесь убить дедушку парадокса.»


На этом пока всё

Вы завершили четвёртую часть курса Apache Iceberg! Вы освоили Time Travel и откат изменений — научились работать со снапшотами, запрашивать прошлые версии таблицы и безопасно отменять ошибочные операции. Отличный прогресс!

Что будет дальше? В пятой части курса мы перейдём к продвинутым возможностям управления таблицами:

  • Тегирование и ветвление — Git‑подобный workflow для Iceberg: теги, ветки, fast‑forward мержи, Write‑Audit‑Publish.
  • Метатаблицы — отладка производительности и анализ файлов, партиций, entries через системные метатаблицы.
  • Обслуживание таблиц — удаление старых снапшотов, компактификация, очистка метаданных.
  • Движки запросов и экосистема — обзор Spark, Flink, Trino, облачных сервисов и Python.

Чтобы не пропустить выход новых уроков:

Спасибо, что учитесь с нами! 🚀

← Назад к Time Travel и откату изменений