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 | 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 | 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 |
Вы должны увидеть четыре снапшота:
- Начальный append (наш INSERT)
- Overwrite (обновление Боба)
- Ещё один overwrite (обновление Кэрол)
- Финальный 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 | 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 | 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.
Чтобы не пропустить выход новых уроков:
- Подпишитесь на канал SubQuery в Telegram — там мы анонсируем все обновления курса.
- А чтобы ТОЧНО не пропустить, присоединяйтесь к каналу в MAX 🤭
Спасибо, что учитесь с нами! 🚀