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'; -- миллионы строк

Данные в порядке, они просто не видны в текущем состоянии. Дальше действуем так:

  1. Создаём резервную таблицу.
  2. Наполняем её историческими данными из time travel‑запроса.
  3. Пока резервная таблица заполняется, новые продажи продолжают поступать в основную таблицу — система работает.
  4. Затем просто переливаем данные из резервной таблицы обратно в основную.
-- резервная таблица из снэпшота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 может быть быстрым, но легко выстрелить себе в ногу. Давайте не будем этого делать.

  1. Используйте WHERE‑условия и узкие временные диапазоны, когда это возможно. Если можно сканировать шесть дней вместо шести месяцев — делайте это.
  2. Отсечение партиций работает с time travel. Предикаты с временными метками помогают движку пропускать ненужные файлы.
  3. Регулярно обслуживайте таблицы. Compaction и expiration снапшотов держат историю компактной и быстрой.
  4. Используйте time travel осознанно. Он отлично подходит для отладки и аудита, но для частых операционных запросов может быть не лучшим инструментом — возможно, вам потребуется встроить историчность прямо в схему данных.

Time travel в Iceberg действительно меняет то, как вы управляете данными. Держите фокус на use‑cases: отладка, аудит, доверие к исторической картине. Это не замена всем возможным сценариям работы с историческими данными — какие‑то из них всё равно придётся проектировать отдельно, — но чертовски крутая фича, когда она нужна.


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


→ Вперёд к упражнению по Time Travel

← Назад к кластеризации