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