Apache Iceberg: формат таблиц для современного data lakehouse

Курс по Apache Iceberg — открытому формату таблиц для data lakehouse. Вы узнаете, как устроены таблицы Iceberg, как работают ACID-транзакции, time travel и скрытое партиционирование, как мигрировать с Hive без простоя и оптимизировать аналитические запросы в стеке Spark, Flink и Trino.

1. Плюсы и минусы Apache Iceberg

Плюсы и минусы Apache Iceberg

Представьте, что вы — архитектор данных в крупном ритейлере. У вас десятки терабайт транзакций, лежащих в S3 в формате Parquet, поверх которых построен Hive. Запросы аналитиков тормозят, потому что каждый SELECT с фильтром по дате вынужден сканировать тысячи мелких файлов. Партиционирование по году и месяцу помогает, но когда кто-то случайно записывает данные не в ту партицию — возникают дубли. Откатить ошибочную вставку можно только вручную, перезаписав файлы. Знакомо? Именно для решения таких проблем был создан Apache Iceberg — открытый табличный формат, который превращает «просто набор файлов» в полноценную таблицу с гарантиями целостности.

Но Iceberg — не волшебная таблетка. У него есть свои ограничения, компромиссы и области, где альтернативы оказываются лучше. Прежде чем переходить на него, нужно честно понять обе стороны.

Что Iceberg даёт «из коробки»

ACID-транзакции поверх объектного хранилища. S3 и HDFS — это файловые системы без встроенных транзакций. Iceberg решает это на уровне метаданных: каждое изменение таблицы фиксируется атомарным обновлением указателя на новый снепшот. Два писателя не смогут одновременно записать данные и создать противоречивое состояние — Iceberg обнаружит конфликт при коммите и откатит одну из транзакций. Это критично для пайплайнов, где несколько джобов пишут в одну таблицу параллельно.

Time Travel и откаты. Каждый снепшот — это неизменяемый снимок состояния таблицы. Можно запросить данные на любой момент времени или по номеру снепшота. Совершили ошибочную вставку? Достаточно откатить указатель текущего снепшота — без ручного удаления файлов. Это работает как «Ctrl+Z» для всей таблицы.

Эволюция схемы без простоя. Добавить столбец, переименовать его или изменить тип — всё это делается без перезаписи данных. Iceberg хранит схему отдельно от файлов данных, поэтому изменение схемы — это просто новая запись в метаданных. Старые файлы Parquet продолжают читаться корректно, потому что Iceberg сопоставляет столбцы по идентификаторам, а не по именам.

Скрытое партиционирование. В Hive вы обязаны знать структуру каталогов и явно указывать партиции в запросах. Iceberg скрывает это: вы пишете WHERE event_date = '2024-01-15', а движок сам определяет, какие файлы читать. При этом Iceberg поддерживает трансформации партиционирования — например, bucket(16, user_id) или days(event_ts) — без создания физической иерархии каталогов.

Data skipping через метаданные. Каждый manifest file хранит статистику (min/max, количество null) по каждому столбцу в каждом файле данных. Движок запросов может отсечь ненужные файлы ещё до их чтения. Как отмечается в статье на Хабр, если установить порядок записи (WRITE ORDERED BY), то min/max границы становятся компактными, и запрос с фильтром id < 196470 прочитает один файл вместо всей таблицы.

Совместимость с экосистемой. Iceberg поддерживают Apache Spark, Trino, Flink, Dremio, Starburst, Athena и десятки других движков. Один и тот же набор данных можно читать из Spark-джоба и из Trino-запроса аналитика — без конвертации.

Где Iceberg проигрывает

Сложность начальной настройки. Чтобы начать использовать Iceberg, нужен каталог — внешний сервис (Hive Metastore, AWS Glue, REST Catalog, Nessie), который хранит указатели на метаданные таблиц. Это дополнительный компонент инфраструктуры, который нужно развернуть, мониторить и поддерживать. Для небольших проектов это может быть избыточно.

Накладные расходы на метаданные. Каждая запись создаёт новый снепшот, manifest list и manifest file. При интенсивной стриминговой записи (тысячи мелких вставок в минуту) метаданные разрастаются, и чтение таблицы замедляется, потому что движку нужно обработать все манифесты. Решение — регулярный compaction метаданных и файлов данных, но это дополнительная операционная нагрузка.

Отсутствие вторичных индексов. Iceberg не хранит B-tree или bitmap-индексы. Data skipping работает только по статистикам файлов (min/max). Если ваш запрос фильтрует по столбцу с высокой кардинальностью и данные не отсортированы — вы получите полное сканирование. Некоторые движки (например, Trino через бloom filter) добавляют свои механизмы, но это не часть спецификации Iceberg.

Merge-on-Read откладывает стоимость на чтение. Стратегия MoR позволяет быстро записывать обновления и удаления через delete-файлы, но при чтении движок должен применить эти удаления поверх основных данных. Как подчёркивает Хабр-статья, с форматом v3 появились deletion vectors (битмаски), которые ускоряют этот процесс, но стоимость чтения всё равно выше, чем у статических таблиц.

Зависимость от зрелости движка. Спецификация Iceberg — это стандарт, но каждый движок реализует его по-разному. Spark поддерживает Iceberg наиболее полно, Trino — хорошо, Flink — активно развивается. Если ваша команда использует движок с неполной поддержкой (например, старые версии Trino), вы столкнётесь с ограничениями.

Сравнение с альтернативами

| Критерий | Iceberg | Delta Lake | Hive (ACID) | |---|---|---|---| | Открытый стандарт | Да (Apache Foundation) | Частично (Databricks) | Да (Apache Foundation) | | Time Travel | Да, по снепшотам | Да, по версиям | Ограниченно | | Эволюция схемы | Идентификаторы столбцов | Имена столбцов | Через SerDe | | Партиционирование | Скрытое, с трансформациями | Скрытое | Явное (каталоги) | | Поддержка движков | Широкая | Преимущественно Spark | Широкая | | Стриминг | Flink, Spark Structured Streaming | Spark Structured Streaming | Нет нативной |

Delta Lake проще в настройке, если вы уже на Databricks, но привязывает вас к экосистеме одного вендора. Hive ACID работает, но медленно развивается и не поддерживает скрытое партиционирование. Iceberg занимает позицию «открытого стандарта с максимальной совместимостью».

Когда стоит выбирать Iceberg

Iceberg — оптимальный выбор, когда у вас есть несколько движков, работающих с одними данными; когда нужен time travel для аудита или отладки; когда данные растут и партиционирование Hive перестаёт масштабироваться; когда вы строите lakehouse-архитектуру и хотите ACID-гарантии без привязки к вендору.

Если же у вас небольшой объём данных, один движок (например, только Spark на Databricks) и нет требований к time travel — Delta Lake может оказаться проще. Если данные статичны и записываются раз в день без обновлений — обычный Parquet с каталогом вполне достаточен.

Ключевой вывод: Iceberg решает проблему управления данными на data lake, а не проблему хранения. Если ваша боль — целостность, версионирование, эволюция схемы и совместимость движков — Iceberg оправдает инвестиции в инфраструктуру. Если боль в другом месте — начните с более простого решения.

2. Архитектура таблиц Iceberg: метаданные, снепшоты и каталог

Архитектура таблиц Iceberg: метаданные, снепшоты и каталог

Когда вы делаете SELECT FROM orders в Trino, за плечами этого простого запроса происходит цепочка обращений к метаданным, о которой большинство аналитиков даже не подозревает. Движок сначала спрашивает каталог: «Где лежат метаданные этой таблицы?» — получает путь к metadata file, затем читает manifest list текущего снепшота, открывает каждый manifest file*, проверяет статистику и только потом начинает читать данные. Понимание этой цепочки — ключ к оптимизации запросов и диагностике проблем.

Четырёхуровневая иерархия метаданных

Архитектура Iceberg построена как вложенные слои, каждый из которых ссылается на нижележащий. Представьте это как систему оглавлений в книге: каталог — это библиотечный каталог, metadata file — обложка книги с оглавлением, manifest list — глава, manifest file — параграф, а data file — конкретная страница.

Data files — это Parquet (или ORC, Avro) файлы с фактическими данными. Они лежат в объектном хранилище и не содержат никакой информации о таблице. Каждый файл — просто набор строк.

Manifest files — JSON-файлы, каждый из которых описывает набор data files. Для каждого data file манифест хранит путь, формат, диапазон партиций, а также статистику по столбцам: минимальное и максимальное значение, количество null. Именно эти статистики позволяют движку отсекать файлы при чтении — механизм, который Хабр-статья называет одной из «киллер-фич» Iceberg.

Manifest list — файл, содержащий список manifest files, которые вместе образуют один снепшот. Один manifest list = один снепшот = одно состояние таблицы. Это критически важное тождество: если вы понимаете, что manifest list — это «фотография» всех данных на момент коммита, вы понимаете, как работает time travel.

Metadata file (или table metadata file) — корневой файл таблицы. Он содержит текущую схему, спецификацию партиционирования, список всех снепшотов, указатель на текущий снепшот и свойства таблицы. Каждый коммит создаёт новый metadata file; старые не удаляются (пока не истечёт retention).

Снепшоты: неизменяемые фотографии таблицы

Снепшот — это полное, неизменяемое описание состояния таблицы на момент коммита. Он содержит ссылку на свой manifest list, временную метку и тип операции (append, overwrite, delete).

Когда вы делаете INSERT INTO orders VALUES (...), движок записывает новый data file, создаёт manifest file с описанием этого файла, формирует manifest list и новый metadata file, а затем атомарно обновляет указатель в каталоге. Если два писателя пытаются закоммитить одновременно, один из них получит конфликт — Iceberg проверяет, что текущий снепшот в каталоге совпадает с тем, который видел писатель при начале транзакции. Это оптимистичная блокировка на уровне каталога.

Снепшоты формируют линейную историю изменений. Каждый снепшот знает своего «родителя» — предыдущий снепшот. Эта цепочка позволяет перемещаться во времени: запросив данные по конкретному снепшоту, вы получите ровно те файлы данных, которые были актуальны на тот момент.

Важная деталь: снепшоты не копируют данные. Новый снепшот может переиспользовать manifest files предыдущего, если часть данных не изменилась. Это делает создание снепшота дешёвой операцией — стоимость пропорциональна количеству изменённых файлов, а не размеру таблицы.

Каталог: точка входа

Каталог — это единственная внешняя зависимость Iceberg. Он хранит отображение имя_таблицы → путь к metadata file. Когда движок хочет прочитать таблицу, он идёт в каталог, получает путь к metadata file и дальше работает только с файлами в объектном хранилище.

Типы каталогов:

  • Hive Metastore — классический вариант, если у вас уже есть Hive-инфраструктура. Каталог хранит путь к metadata file в свойствах Hive-таблицы.
  • AWS Glue — управляемый сервис от AWS, по сути hosted Hive Metastore.
  • REST Catalog — lightweight-сервер, который реализует REST API спецификации Iceberg. Не требует базы данных, может работать с SQLite или in-memory. Идеален для локальной разработки и тестирования.
  • Nessie — каталог с поддержкой git-подобных веток и тегов для таблиц. Позволяет создавать изолированные ветки данных для экспериментов.
  • Hadoop Catalog — использует файловую систему (HDFS) для хранения метаданных. Прост, но не поддерживает атомарные операции на S3 без дополнительных механизмов.
  • Как отмечается в статье про работу Iceberg с Trino и REST Catalog, каталог — это именно «менеджер метаданных», а не хранилище данных. Все данные и绝大部分 метаданных лежат в объектном хранилище; каталог хранит только указатель.

    Как выглядит путь запроса

    Разберём на конкретном примере. Вы выполняете в Trino:

  • Trino через Iceberg Connector обращается к каталогу и получает путь к metadata file таблицы sales.orders.
  • Из metadata file Trino читает текущий снепшот и находит对应的 manifest list.
  • Из manifest list Trino получает список manifest files.
  • Для каждого manifest file Trino проверяет статистику: если min/max значения столбца order_date в манифесте не пересекаются с 2024-03-15, весь manifest file (и все его data files) пропускаются.
  • Из оставшихся manifest files Trino извлекает список data files, которые могут содержать нужные данные.
  • Для каждого data file Trino снова проверяет min/max статистику на уровне файла.
  • Только файлы, прошедшие обе проверки, читаются из S3/HDFS.
  • Этот двухуровневый фильтр (манифест → файл) — основа производительности Iceberg. Чем компактнее границы min/max в манифестах, тем больше файлов отсекается. Именно поэтому сортировка данных при записи (порядок записи) даёт такой ощутимый прирост.

    Свойства таблиц и их влияние

    Metadata file хранит свойства таблицы — пары ключ-значение, которые управляют поведением Iceberg. Некоторые из них критичны для производительности:

  • write.metadata.metrics.default — уровень сбора статистик по умолчанию (none, counts, truncate, full). Как рекомендует Хабр, для широких таблиц стоит отключать статистику на неиспользуемых в фильтрации столбцах.
  • write.target-file-size-bytes — целевой размер data file (по умолчанию 512 МБ). Мелкие файлы замедляют чтение; слишком большие — ограничивают параллелизм.
  • history.expire.max-snapshot-age-ms — как долго хранить старые снепшоты. По умолчанию 5 дней. Увеличьте, если нужен длительный time travel; уменьшите, чтобы экономить место.
  • commit.retry.num-retries — количество попыток коммита при конфликте (по умолчанию 4).
  • Понимание этой четырёхуровневой архитектуры — фундамент для всех последующих тем: ACID-транзакции работают через атомарное обновление указателя на снепшот, time travel — через навигацию по цепочке снепшотов, а оптимизация запросов — через статистики в манифестах.

    3. ACID-транзакции, эволюция схемы и скрытое партиционирование

    ACID-транзакции, эволюция схемы и скрытое партиционирование

    Представьте, что вы ведёте учёт заказов в Excel-файле на общем диске. Два менеджера одновременно открывают файл, добавляют строки и сохраняют. Один перезаписывает изменения другого — данные теряются. Именно так работали data lake до появления табличных форматов: несколько Spark-джобов писали Parquet-файлы в одну директорию, и никто не гарантировал, что результат будет консистентным. Apache Iceberg решает эту проблему тремя взаимосвязанными механизмами: ACID-транзакциями, эволюцией схемы и скрытым партиционированием.

    ACID на объектном хранилище

    Объектное хранилище (S3, GCS, ADLS) не поддерживает транзакции на уровне файловых операций. Вы не можете атомарно записать 10 файлов и удалить 5 — между этими операциями кто-то может прочитать промежуточное состояние. Iceberg обходит это ограничение, перенося атомарность на уровень метаданных.

    Механизм работает так: все изменения данных (запись новых data files, создание delete files) происходят в объектном хранилище до коммита. На момент коммита движок создаёт новый metadata file, ссылающийся на новый manifest list, и пытается обновить указатель в каталоге. Обновление указателя — единственная операция, которая должна быть атомарной, и эту гарантию даёт сам каталог (Hive Metastore использует базу данных с ACID, REST Catalog — оптимистичную блокировку).

    Если два писателя начали транзакцию с одного и того же снепшота и оба пытаются закоммитить, каталог обнаружит конфликт: текущий снепшот изменился с момента начала транзакции. Один из писателей получит ошибку и может повторить попытку (по умолчанию 4 ретрая). Это оптимистичная конкурентность — блокировки не ставятся, конфликты разрешаются в момент коммита.

    Атомарность — каждый коммит создаёт ровно один новый снепшот. Нет промежуточных состояний, видимых читателям.

    Консистентность — читатели всегда видят целостный снепшот. Невозможно прочитать «половину вставки».

    Изолированность — снепшотная изоляция (snapshot isolation). Читатель видит данные на момент начала запроса, даже если параллельно идёт запись. Нет «грязного чтения» и «неповторяемого чтения».

    Дурability — данные записаны в объектное хранилище до коммита. Если коммит прошёл — данные гарантированно сохранены.

    Важный нюанс: Iceberg не гарантирует serializable изоляцию. При параллельных записях в одну и ту же партицию возможны lost updates в редких сценариях. Для большинства ETL-пайплайнов snapshot isolation достаточно, но если вам нужна строгая сериализация — используйте внешнюю координацию (например, блокировки на уровне оркестратора).

    Эволюция схемы: идентификаторы вместо имён

    В классических системах (Hive, обычный Parquet) схема жёстко привязана к файлам данных. Хотите переименовать столбец? Придётся переписать все файлы. Хотите добавить столбец в середину? Hive добавит его в конец, и порядок столбцов сломает запросы, которые используют SELECT *.

    Iceberg подходит к этому иначе: каждый столбец в схеме имеет числовой идентификатор (например, 1, 2, 3). Файлы данных (Parquet) тоже хранят эти идентификаторы в своих метаданных. Когда движок читает файл, он сопоставляет столбцы не по именам, а по идентификаторам.

    Это даёт несколько практических возможностей:

    Добавление столбца. Новый столбец получает новый идентификатор. Старые файлы не содержат этого идентификатора — движок заполняет значения null. Перезапись данных не требуется.

    Переименование столбца. Меняется только имя в схеме; идентификатор остаётся прежним. Старые файлы читаются корректно.

    Удаление столбца. Столбец помечается как удалённый в схеме. Старые файлы всё ещё содержат данные этого столбца, но движок их игнорирует.

    Изменение типа. Iceberg поддерживает безопасные расширения типа (например, int → long). Старые файлы с int читаются как long без конвертации.

    Все эти операции — это просто обновление metadata file. Данные не перезаписываются. Это принципиальное отличие от Hive, где изменение схемы часто требует полной перезаписи таблицы.

    Скрытое партиционирование: конец эпохи «WHERE partition_col = ...»

    Партиционирование в Hive — это физическая структура каталогов. Таблица sales/partitioned_by=year=2024/month=03/ содержит файлы только за март 2024. Проблемы такого подхода:

  • Аналитик должен знать структуру партиций и явно указывать их в WHERE. Забыл — получил полное сканирование.
  • Изменить стратегию партиционирования (например, с month на day) — значит переписать всю таблицу.
  • Мелкие партиции (например, по часам) создают тысячи каталогов, что перегружает файловую систему.
  • Iceberg решает это через скрытое партиционирование (hidden partitioning). Партиционирование определяется трансформациями над столбцами данных, но результат трансформации не виден в запросах. Аналитик пишет WHERE event_date = '2024-03-15', а Iceberg внутри преобразует это значение через трансформацию и определяет, какие файлы читать.

    Поддерживаемые трансформации:

  • identity(value) — прямое значение (аналог Hive-партиционирования).
  • bucket(N, value) — хеш по модулю N. Равномерно распределяет данные, полезно для столбцов с высокой кардинальностью.
  • truncate(L, value) — обрезка строки до L символов или числа до разряда L. Группирует похожие значения.
  • year(value), month(value), day(value), hour(value) — извлечение компонентов даты.
  • Ключевое отличие от Hive: в Iceberg партиционирование — это метаданные, а не каталоги. Файлы данных лежат в плоской директории (или с произвольной структурой), а информация о принадлежности к партиции хранится в manifest file. Это означает:

  • Можно изменить партиционирование без перезаписи данных (новые файлы будут записываться по новой схеме, старые — останутся как есть).
  • Аналитику не нужно знать о партициях — движок сам применяет фильтры.
  • Нет проблемы мелких каталогов.
  • Практический пример: у вас таблица событий, изначально партиционированная по month(event_ts). Данные растут, и месячные партиции становятся слишком большими. Вы выполняете:

    Новые данные будут партиционироваться по дням. Старые данные останутся в месячных партициях. Запросы будут корректно работать с обоими наборами файлов — Iceberg хранит историю спецификаций партиционирования и применяет правильную для каждого файла.

    Как три механизма работают вместе

    ACID гарантирует, что изменение схемы или партиционирования — атомарная операция. Эволюция схемы через идентификаторы позволяет менять структуру без перезаписи данных. Скрытое партиционирование делает эти изменения невидимыми для аналитиков. Вместе они превращают data lake из «свалки файлов» в управляемое хранилище с гарантиями, сравнимыми с классическими СУБД.

    4. Time Travel, инкрементальная обработка и стриминг с Flink

    Time Travel, инкрементальная обработка и стриминг с Flink

    Вы обнаружили, что вчерашний отчёт по продажам содержит аномальные значения. Кто-то загрузил дублирующие данные в 3 часа ночи. В классическом data lake вам пришлось бы искать проблемные файлы вручную, удалять их и пересчитывать отчёт. С Iceberg вы делаете один запрос: «Покажи мне таблицу такой, какой она была до полуночи» — и получаете корректные данные. Это time travel — одна из самых практически ценных возможностей Iceberg.

    Time Travel: навигация по истории таблицы

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

    Iceberg предоставляет три способа указать нужный момент времени:

    По номеру снепшота. Каждый снепшот имеет уникальный snapshot_id. Зная его, можно запросить точное состояние:

    По временной метке. Iceberg найдёт ближайший снепшот, созданный до указанного времени:

    По имени ветки или тега. Iceberg поддерживает git-подобные ветки и теги для таблиц. Можно создать тег release_2024_q1, привязав его к снепшоту, и читать по имени тега.

    Практическое применение time travel:

  • Отладка. Сравните данные до и после ETL-джоба, чтобы найти источник ошибки.
  • Аудит. Докажите, какие данные были в таблице на момент формирования отчёта.
  • Откат. Если новый снепшот содержит ошибочные данные, откатитесь к предыдущему, обновив указатель текущего снепшота в каталоге.
  • Воспроизводимость. Научный эксперимент или ML-пайплайн может зафиксировать версию данных, на которой обучалась модель.
  • Важное ограничение: снепшоты хранятся не вечно. По умолчанию старые снепшоты удаляются через 5 дней (параметр history.expire.max-snapshot-age-ms). Если вам нужен длительный time travel — увеличьте retention, но будьте готовы к росту метаданных.

    Инкрементальная обработка: читай только изменения

    Полное перечитывание таблицы при каждом запуске ETL-джоба — расточительно. Если таблица содержит 10 ТБ, а за час изменилось 100 МБ, сканировать 10 ТБ ради 100 МБ неразумно. Iceberg позволяет читать только изменения между двумя снепшотами.

    В Spark это делается через incremental scan:

    Spark вернёт только те строки, которые были добавлены, изменены или удалены между указанными снепшотами. Это основа для построения CDC (Change Data Capture) пайплайнов без специализированных инструментов вроде Debezium.

    Практический сценарий: каждый час ваш ETL-джоб запоминает snapshot_id после завершения, а в следующем запуске читает данные от этого снепшота до текущего. Так вы получаете инкрементальный пайплайн, который обрабатывает только изменения.

    В Trino инкрементальное чтение доступно через системные таблицы:

    Это позволяет построить пайплайн, который отслеживает изменения на уровне снепшотов и запускает downstream-обработку только при наличии новых данных.

    Стриминг с Apache Flink

    Apache Flink — движок потоковой обработки, который нативно интегрируется с Iceberg. Это позволяет стримить данные прямо в Iceberg-таблицы и читать изменения в реальном времени.

    Запись стрима в Iceberg. Flink DataStream API или Flink SQL позволяют писать данные из Kafka, Pulsar или другого источника в Iceberg-таблицу:

    Flink группирует записи и периодически коммитит новые снепшоты. Интервал коммита настраивается через commit.interval-ms (по умолчанию 60 секунд). Каждый коммит — это отдельный снепшот с ACID-гарантиями.

    Чтение изменений из Iceberg. Flink может непрерывно мониторить Iceberg-таблицу и выдавать новые данные по мере их появления:

    Flink периодически проверяет, появились ли новые снепшоты, и если да — вычитывает изменения между последним прочитанным и новым снепшотом. Это превращает Iceberg-таблицу в источник стриминговых данных.

    Стратегии записи: Copy-on-Write vs Merge-on-Read. При стриминговой записи обновлений и удалений Iceberg поддерживает две стратегии:

  • Copy-on-Write (CoW) — при обновлении перезаписывается весь файл данных. Быстрое чтение, медленная запись.
  • Merge-on-Read (MoR) — обновления записываются в отдельные delete files. Быстрая запись, чтение требует наложения удалений.
  • Для стриминга MoR обычно предпочтительнее, потому что частые обновления с CoW создают слишком много мелких файлов. С появлением deletion vectors в спецификации v3 чтение при MoR стало значительно быстрее — вместо тяжёлого join delete-файла с data-файлом движок применяет компактную битмаску.

    Практическая архитектура стриминга:

    Поток данных из Kafka записывается в Iceberg с MoR-стратегией. Отдельный Flink-джоб (или Spark-джоб) периодически запускает compaction — объединяет мелкие файлы и применяет delete files, формируя оптимизированные Parquet-файлы. Так вы получаете и низкую задержку записи, и высокую производительность чтения.

    Комбинация time travel и стриминга

    Time travel особенно ценен в стриминговых сценариях. Представьте: ваш стриминговый пайплайн записывает данные каждые 30 секунд. Один из снепшотов содержит ошибочные данные из-за бага в трансформации. Вы можете:

  • Определить проблемный снепшот по временной метке.
  • Откатить таблицу к предыдущему снепшоту.
  • Исправить баг в Flink-джобе.
  • Перезапустить обработку — Flink прочитает пропущенные данные из Kafka и запишет их корректно.
  • Без time travel вам пришлось бы вручную разбираться с файлами в S3. С Iceberg это операция на уровне одной SQL-команды.

    5. Миграция с Hive на Iceberg и оптимизация запросов в Trino/Spark

    Миграция с Hive на Iceberg и оптимизация запросов в Trino/Spark

    У вас есть Hive-каталог с сотнями таблиц, над которыми работают десятки ETL-джобов на Spark и аналитических запросов в Trino. Бизнес требует ACID-транзакций, time travel и снижения стоимости хранения. Вы понимаете, что Iceberg — правильное направление, но мысль о миграции вызывает холодный пот: как перевести все таблицы, не останавливая продакшн, не теряя данные и не ломая существующие пайплайны?

    Стратегия миграции: три подхода

    Существует три основных подхода к миграции с Hive на Iceberg, и выбор зависит от допустимого простоя и объёма данных.

    Подход 1: Миграция на лету (in-place migration)

    Начиная с версии 0.14, Iceberg поддерживает in-place миграцию Hive-таблиц. Суть: вы не копируете данные, а добавляете Iceberg-метаданные поверх существующих Parquet/ORC файлов.

    Эта процедура сканирует существующие файлы в директории Hive-таблицы, создаёт manifest files и metadata file, регистрирует таблицу в Iceberg-каталоге. Физические данные остаются на месте.

    Преимущества: нет простоя, нет копирования данных, мгновенный результат.

    Ограничения:

  • Hive-партиционирование преобразуется в Iceberg-партиционирование с identity трансформацией. Если вы хотите скрытое партиционирование с трансформациями — потребуется дополнительный шаг.
  • Таблица должна быть в формате Parquet или ORC. Text/CSV/JSON не поддерживаются.
  • После миграции Hive больше не сможет читать таблицу (структура метаданных изменилась).
  • Практический сценарий: вы мигрируете таблицу, но оставляете Hive-джобы работающими через Spark с Iceberg-зависимостями. Spark с библиотекой iceberg-spark-runtime может читать и писать в Iceberg-таблицы тем же кодом, что и в Hive — достаточно изменить формат таблицы в конфигурации.

    Подход 2: Параллельная запись (dual-write)

    Запускаете ETL-джобы, которые пишут данные одновременно в Hive-таблицу и в новую Iceberg-таблицу. По мере готовности переключаете потребителей на Iceberg.

    Преимущества: нет риска — Hive-таблица остаётся рабочей. Можно постепенно переключать потребителей.

    Недостатки: удвоенная запись данных, необходимость синхронизации, более длительный процесс миграции.

    Подход 3: Полная перезапись (CTAS)

    Создаёте новую Iceberg-таблицу и загружаете данные из Hive через CREATE TABLE AS SELECT:

    Преимущества: чистый старт, можно сразу настроить оптимальное партиционирование и сортировку.

    Недостатки: требует полного чтения исходной таблицы, что для больших таблиц может занять часы. На время копирования Hive-таблица доступна для чтения, но запись может создать расхождения.

    Рекомендуемый план миграции

    Для production-среды оптимальна комбинация подходов:

  • Начните с in-place миграции для таблиц, которые не требуют изменения партиционирования.
  • Используйте CTAS для таблиц, где нужно перестроить партиционирование или сортировку.
  • Применяйте dual-write для критичных таблиц, где недопустимы ошибки.
  • Мигрируйте в порядке приоритета: сначала небольшие справочники, затем крупные фактовые таблицы, в последнюю очередь — таблицы с интенсивной записью.
  • Оптимизация запросов в Trino

    После миграции на Iceberg важно настроить Trino для максимальной производительности.

    Настройка Iceberg Connector

    В файле catalog/iceberg.properties ключевые параметры:

    Оптимизация через ALTER TABLE EXECUTE

    Trino предоставляет процедуры оптимизации, которые вызываются через ALTER TABLE EXECUTE:

    optimize — самая важная процедура. Она объединяет мелкие файлы в крупные (по умолчанию до 512 МБ), что критически снижает количество файлов для сканирования. После миграции с Hive, где часто встречаются тысячи мелких файлов, optimize даёт наибольший прирост производительности.

    Чтение системных таблиц

    Trino позволяет запрашивать метаданные Iceberg как обычные таблицы:

    Эти системные таблицы незаменимы для диагностики: вы можете увидеть, сколько файлов содержит таблица, какой у них размер, какие снепшоты существуют.

    Оптимизация запросов в Spark

    Настройка порядка записи

    Как показывает практический пример на Хабр, сортировка данных при записи даёт колоссальный прирост за счёт компактных min/max границ в манифестах:

    После этой настройки все последующие INSERT-операции будут сортировать данные по указанным столбцам. Для уже записанных данных нужно запустить rewrite_data_files:

    Настройка сбора статистик

    Для широких таблиц с десятками столбцов отключите сбор статистик на столбцах, которые не используются в WHERE:

    Уровень full собирает min/max и count null, counts — только счётчики, none — ничего. Экономия на сборе статистик для 50-колоночной таблицы может составить 20–30% времени записи.

    Compaction и управление жизненным циклом

    Регулярно запускайте процедуры обслуживания:

    Чек-лист после миграции

    После завершения миграции убедитесь, что:

  • Все ETL-джобы переключены на Iceberg-каталог и формат.
  • Запущен optimize / rewrite_data_files для объединения мелких файлов.
  • Настроен порядок записи для таблиц с частыми фильтрациями.
  • Настроен сбор статистик — full для фильтруемых столбцов, none для остальных.
  • Настроены регулярные процедуры: expire_snapshots, remove_orphan_files, rewrite_manifests.
  • Проверена работа time travel: запросите данные по старому снепшоту.
  • Настроен мониторинг: количество файлов, размер манифестов, количество снепшотов.
  • Миграция с Hive на Iceberg — это не одноразовое событие, а процесс. Начните с in-place миграции, оптимизируйте постепенно, мониторьте метрики. Iceberg даёт инструменты; от вас требуется их систематическое применение.