Apache Iceberg. Time Travel и откат изменений
Путешествие во времени для ваших данных
Содержание
Давайте поговорим об одной из самых крутых функций Iceberg — time travel (путешествие во времени). Вы можете запрашивать свои данные в том виде, в каком они существовали в любой момент прошлого. Концепция проста, но на практике она невероятно мощна.
Основы: снапшоты как коммиты Git
CREATE TABLE sales ( sale_id BIGINT, product STRING, region STRING, amount DECIMAL (10,2), sale_date DATE); -- продажиINSERT INTO sales VALUES (1, 'Widget A', 'US', 150.00, DATE '2025-10-21'), (2, 'Widget B', 'APAC', 200.00, DATE '2025-10-21'), (3, 'Widget C', 'US', 175.00, DATE '2025-10-21'); -- ещё продажи!!INSERT INTO sales VALUES (4, 'Widget D', 'US', 225.00, DATE '2025-10-21'), (5, 'Widget E', 'EMEA', 180.00, DATE '2025-10-21'); -- упс, ошибкаUPDATE sales SET amount = 165.00 WHERE sale_id = 1; -- а этот покупатель передумалDELETE FROM sales WHERE sale_id = 3;
Представьте нашу таблицу продаж, которая обновляется в течение дня: новые продажи, исправления, корректировки — много активности. Каждый раз, когда вы изменяете таблицу, Iceberg создаёт снапшот — запись состояния таблицы в этот момент времени. Это похоже на коммиты Git для ваших данных: отслеживается, что изменилось, когда, и позволяет вернуться к предыдущему снапшоту, если нужно.
graph LR
%% Узлы-круги
A((empty)) -->|INSERT| B((Snapshot 1))
B -->|INSERT| C((Snapshot 2))
C -->|UPDATE| D((Snapshot 3))
D -->|DELETE| E((Snapshot 4))Iceberg хранит все снапшоты в специальной метатаблице snapshots. Чтобы увидеть их, можно выполнить запрос:
-- Конкретный синтаксис может отличаться между движками SELECT * FROM sales.snapshots;
В результате вы увидите ключевые колонки: когда снапшот был зафиксирован, его ID и операцию, которую Iceberg считал выполненной (append, overwrite, delete, replace и т.д.).
Time travel: запрос данных на момент прошлого
Для путешествия во времени используется конструкция FOR SYSTEM_TIME AS OF с указанием метки времени:
SELECT * FROM salesFOR SYSTEM_TIME AS OF TIMESTAMP '2025-03-15 14:30:00';
Iceberg находит подходящий снапшот — первый снапшот до указанной метки — и возвращает вам данные в том виде, в каком они были в тот момент.
Вы также можете запросить конкретный снапшот по его ID, если знаете его (например, из метатаблицы snapshots):
SELECT * FROM sales FOR VERSION AS OF 123456;
Если запрашивать таблицу без time travel, вы всегда получаете текущее состояние — самый свежий снапшот, «сейчас». Это поведение, привычное для обычных баз данных.
Откат изменений (rollback)
Iceberg умеет откатываться к предыдущему состоянию. Если вы хотите отменить удаление, можно откатиться к снапшоту, сделанному до этого удаления.
Синтаксис:
CALL iceberg.system.rollback_to_snapshot( 'demo.sales', 123456);
Или откат к метке времени:
CALL iceberg.system.rollback_to_timestamp( 'demo.sales', TIMESTAMP '2025-03-15 14:30:00');
Что делает откат? Он помечает старый снапшот как текущий now. Все будущие запросы и записи будут начинаться с этого снапшота. Визуально после отката к снапшоту 3 всё, что было после него (например, снапшот 4), остаётся вне активной временной линии. Вы по‑прежнему можете путешествовать во времени к нему, но не можете откатиться к нему — Iceberg не позволит, потому что это было бы откатом вперёд».
Если после отката вы внесёте изменения, Iceberg начнёт новую линию от этого места, а старая история (снапшот 4) останется отключённой.
Ядерный вариант: set_current_snapshot
Когда нужен абсолютный контроль — вы хотите перейти к любому снапшоту без проверок и ограничений — используется операция set_current_snapshot. «Смотрите, я знаю, что делаю, просто переместите меня сюда»:
CALL iceberg.system.set_current_snapshot( 'demo.sales', 123456);
Вы можете прыгнуть на любой снапшот, вперёд или назад. Никаких проверок, никаких ограничений, никакой опеки. Это острый нож — так можно создать большой беспорядок, поэтому держите своё резюме обновлённым, если планируете делать это часто.
Реальные сценарии, где time travel спасает положение
1. Кто‑то запустил DELETE без WHERE
Прошёл месяц, бизнес процветает, продажи текут рекой. Вдруг кто‑то выполняет DELETE и забывает про WHERE‑условие. Последствия налицо.
Сначала проверяем, с чем мы имеем дело:
SELECT COUNT(*) FROM sales; -- 92 строки
Ой‑ой, должно быть миллион. Покупатели всё ещё активно совершают покупки, эти 92 продажи только что поступили, но таблица была «очищена» совсем недавно.
Мы не можем просто откатить и потерять эти 92 новые покупки, которые произошли после инцидента. Никто не хочет разговора с финансовым директором о пропавших записях выручки за вторник, пока мы пытаемся починить проблему.
Но нет проблем: мы можем запросить таблицу до инцидента:
SELECT COUNT(*) FROM salesFOR SYSTEM_TIME AS OF TIMESTAMP '2025-03-15 14:29:00'; -- миллионы строк
Данные в порядке, они просто не видны в текущем состоянии. Дальше действуем так:
- Создаём резервную таблицу.
- Наполняем её историческими данными из time travel‑запроса.
- Пока резервная таблица заполняется, новые продажи продолжают поступать в основную таблицу — система работает.
- Затем просто переливаем данные из резервной таблицы обратно в основную.
-- резервная таблица из снэпшотаCREATE OR REPLACE TABLE sales_backup ASSELECT * FROM salesFOR SYSTEM_TIME AS OF '2025-11-15 14:30:00'; -- копируем в основнуюINSERT INTO salesSELECT * FROM sales_backup;
Кризис устранён парой SQL‑запросов. Выходные спасены. Глубокий вдох, расслабьтесь.
2. Регуляторное соответствие и аудит
Time travel идеален для аудиторских проверок, следов аудита и ответов на каверзные вопросы стейкхолдеров о прошлых отчётах. Иногда нужно показать аудиторам, как именно выглядели данные в конкретный момент времени — например, выручка компании, отражённая в отчёте за октябрь. Не нужно гадать — можно просто «перенестись» туда и показать точные данные.
SELECT region, SUM (amount) as total_revenueFROM sales FOR SYSTEM_TIME AS OF '2025-10-31 10:00:00'WHERE sale_date BETWEEN '2025-10-01' AND '2025-10-31'GROUP BY region;
3. Обнаружение изменений между двумя точками времени
Time travel позволяет находить изменения между двумя моментами. Например, этот запрос соединяет результаты двух time travel‑запросов с разными метками времени и выбирает только строки, где суммы продаж различаются:
WITH snapshot_1 AS ( SELECT * FROM sales FOR SYSTEM_VERSION AS OF 7047890836598091613),snapshot_2 AS ( SELECT * FROM sales FOR SYSTEM_VERSION AS OF 2037865377164023090)SELECT s2.sale_id, s1.amount AS old_amount, s2.amount AS new_amount, s2.regionFROM snapshot_2 s2LEFT JOIN snapshot_1 s1 ON s2.sale_id = s1.sale_idWHERE s1.amount IS NULL OR s1.amount != s2.amount;
Раньше для такого анализа пришлось бы проектировать отдельную систему исторических данных, встраивать её в архитектуру, заставлять ETL‑процессы об этом думать. Теперь это просто свойство вашего data lake.
Производительность и рекомендации
Time travel может быть быстрым, но легко выстрелить себе в ногу. Давайте не будем этого делать.
- Используйте
WHERE‑условия и узкие временные диапазоны, когда это возможно. Если можно сканировать шесть дней вместо шести месяцев — делайте это. - Отсечение партиций работает с time travel. Предикаты с временными метками помогают движку пропускать ненужные файлы.
- Регулярно обслуживайте таблицы. Compaction и expiration снапшотов держат историю компактной и быстрой.
- Используйте time travel осознанно. Он отлично подходит для отладки и аудита, но для частых операционных запросов может быть не лучшим инструментом — возможно, вам потребуется встроить историчность прямо в схему данных.
Time travel в Iceberg действительно меняет то, как вы управляете данными. Держите фокус на use‑cases: отладка, аудит, доверие к исторической картине. Это не замена всем возможным сценариям работы с историческими данными — какие‑то из них всё равно придётся проектировать отдельно, — но чертовски крутая фича, когда она нужна.
«Time travel — это как машина времени для данных: можно вернуться и посмотреть, что было вчера, позавчера или даже до того, как кто‑то случайно удалил всю таблицу. Только не пытайтесь убить дедушку парадокса.»

