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


→ Вперёд к тегированию и ветвлению

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