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