В этой статье я хотел был разобраться в механике Iceberg. Понимание того, как Iceberg организует свои метаданные, отделяет уровень «я умею создавать таблицы» от уровня «я умею разбираться, почему мои запросы медленные, а объём хранилища разрастается».
Это фундамент для всего остального в серии. Если вы разберётесь с четырьмя слоями модели метаданных Iceberg, любая фича (time travel, эволюция схемы, эволюция партиций, компактизация) будет восприниматься интуитивно.
Четыре слоя Iceberg
Каждая таблица Iceberg организована в четыре отдельных слоя. Сверху вниз:
Слой 1: Каталог. Указывает на текущий файл метаданных для каждой таблицы. Именно такую ссылку хранит Hive Metastore (HMS): указатель на расположение последнего файла метаданных.
Слой 2: Файлы метаданных. JSON-файлы, которые содержат схему таблицы, partition spec, историю снимков и указатель на manifest list текущего снимка. Новый файл метаданных записывается при каждом изменении схемы, изменении свойств или коммите снимка.
Слой 3: Manifest lists (их также называют snapshot manifests). Avro-файлы, которые перечисляют все manifest-файлы, составляющие конкретный снимок. В каждой записи указаны путь к manifest-файлу, диапазон партиций, который он покрывает, и статистика содержимого (количество добавленных и удалённых файлов).
Слой 4: Manifest files. Avro-файлы, которые перечисляют сами файлы данных. В каждой записи указаны путь к файлу данных, значения партиций, количество записей, размер файла и статистика по столбцам (min/max значения, количество null).
Файлы данных лежат под всем этим. Это Parquet, ORC или Avro, содержащие сами строки.
Зачем такое слоистое устройство
Ключевая идея в том, что чтение никогда не требует листинга директорий. В Hive запрос к партиционированной таблице требует сначала перечислить директории партиций, потом перечислить файлы в каждой релевантной партиции. На таблице с 50 000 партиций один только листинг директорий может занимать минуты.
Iceberg полностью пропускает листинг директорий. Планировщик запросов читает файл метаданных, находит нужный manifest list, читает manifest-файлы и уже точно знает, какие файлы данных открывать, включая min/max статистику по столбцам для predicate pushdown. Вся стадия планирования опирается на индексированные метаданные, а не на операции файловой системы.
Именно поэтому запросы в Iceberg планируются за секунды на таблицах, где планирование в Hive занимает минуты.
Разбор на реальном примере
Проследим, что происходит при создании таблицы и вставке данных. Использую реальную структуру файлов с моего CDP-кластера.
Шаг 1: Создаём таблицу
CREATE TABLE warehouse.events ( event_id BIGINT, event_type STRING, user_id BIGINT, event_time TIMESTAMP, payload STRING)USING icebergPARTITIONED BY (days(event_time))TBLPROPERTIES ('format-version' = '2');
После этой команды Iceberg создаёт:
-
Файл метаданных (
v1.metadata.json), содержащий схему, partition spec (days(event_time)) и пустой список снимков. -
Запись в каталоге HMS обновляется и теперь указывает на этот файл метаданных.
-
Ни manifest lists, ни manifest-файлов, ни файлов данных пока нет.
Шаг 2: Вставляем данные
INSERT INTO warehouse.events VALUES (1, 'click', 100, timestamp '2026-03-15 10:30:00', '{"page": "home"}'), (2, 'purchase', 101, timestamp '2026-03-15 14:20:00', '{"item": "laptop"}'), (3, 'click', 102, timestamp '2026-03-16 09:15:00', '{"page": "search"}');
Теперь структура файлов выглядит так:
warehouse/events/├── metadata/│ ├── v1.metadata.json ← начальный (пустая таблица)│ ├── v2.metadata.json ← после INSERT (текущий)│ ├── snap-8823748...-manifest-list.avro│ └── 9f3e2a1b...-manifest.avro└── data/ ├── event_time_day=2026-03-15/ │ └── 00001-1-a8f3e2b1.parquet (2 строки) └── event_time_day=2026-03-16/ └── 00001-1-c7d2f4a3.parquet (1 строка)
Проследим цепочку указателей:
-
HMS говорит, что текущий файл метаданных, это
v2.metadata.json. -
v2.metadata.jsonговорит, что текущий снимок, этоsnap-8823748..., и указывает на его manifest list. -
Manifest list содержит одну запись, указывающую на
9f3e2a1b...-manifest.avro. -
Manifest-файл содержит две записи:
-
event_time_day=2026-03-15/00001-1-a8f3e2b1.parquet(2 записи, min event_id=1, max event_id=2) -
event_time_day=2026-03-16/00001-1-c7d2f4a3.parquet(1 запись, min event_id=3, max event_id=3)
-
Шаг 3: Вставляем ещё данных
INSERT INTO warehouse.events VALUES (4, 'login', 103, timestamp '2026-03-16 11:00:00', '{"device": "mobile"}');
Теперь появился второй снимок. Структура файлов растёт:
warehouse/events/├── metadata/│ ├── v1.metadata.json│ ├── v2.metadata.json│ ├── v3.metadata.json ← НОВЫЙ (текущий)│ ├── snap-8823748...-manifest-list.avro ← снимок 1│ ├── snap-9912637...-manifest-list.avro ← снимок 2 (НОВЫЙ)│ ├── 9f3e2a1b...-manifest.avro ← переиспользуется!│ └── b4c8d1e2...-manifest.avro ← НОВЫЙ└── data/ ├── event_time_day=2026-03-15/ │ └── 00001-1-a8f3e2b1.parquet └── event_time_day=2026-03-16/ ├── 00001-1-c7d2f4a3.parquet └── 00002-1-d9e5f6a7.parquet ← НОВЫЙ (1 строка)
Ключевая деталь: manifest list второго снимка ссылается и на старый manifest-файл (для данных за 15 марта, которые не менялись), и на новый manifest-файл (для нового файла за 16 марта). Iceberg не копирует и не переписывает старый manifest. Он его переиспользует.
Именно так Iceberg сохраняет быстрые записи даже на огромных таблицах. Каждая операция записи создаёт метаданные пропорционально изменившимся данным, а не всему размеру таблицы.
Статистика по столбцам: скрытый драйвер производительности
Каждая запись в manifest-файле включает min/max статистику по столбцам. Когда вы выполняете:
SELECT * FROM warehouse.events WHERE event_id = 3;
Планировщик читает manifest-файлы и видит:
-
Файл 1: event_id min=1, max=2 → пропустить (3 не попадает в диапазон)
-
Файл 2: event_id min=3, max=3 → прочитать
-
Файл 3: event_id min=4, max=4 → пропустить
Читается только один файл. На таблице с 10 000 файлов данных такое отсечение на основе метаданных может исключить 99% чтений файлов ещё до обращения к самим данным.
Это работает для любого столбца, а не только для столбцов партиционирования. Огромный шаг вперёд по сравнению с Hive, где predicate pushdown работает на уровне партиций для partition columns и на уровне row group внутри файлов.
Изоляция снимков: как работают одновременные чтения и записи
Каждый запрос читает из конкретного снимка. SELECT фиксируется на текущем снимке в момент старта запроса. Если другой процесс в этот момент вставляет данные, INSERT создаёт новый снимок, но запущенный SELECT его не видит. Он продолжает читать из того снимка, за которым закрепился.
Это означает:
-
Нет грязных чтений.
-
Не нужны блокировки на чтение.
-
Писатели не блокируют читателей.
-
Читатели не блокируют писателей.
Для одновременных записей Iceberg использует оптимистичную конкурентность. Оба писателя работают независимо. Когда второй писатель пытается закоммитить, Iceberg проверяет, конфликтуют ли его изменения с изменениями первого. Если они писали в разные партиции или разные файлы, оба коммита проходят (метаданные второго писателя перебазируются поверх коммита первого). Если конфликт есть, второй писатель повторяет попытку.
На практике это значит, что можно одновременно гонять ETL-вставки и ad-hoc запросы по одной таблице без координации. После Hive ACID, где одновременные записи регулярно приводят к конфликтам блокировок и таймаутам, это заметно упрощает жизнь.
Версии формата: v1 против v2
У Iceberg две версии формата:
Версия 1 поддерживает операции append и overwrite. Никаких построчных удалений и обновлений. Проще, чуть меньше накладных расходов на метаданные.
Версия 2 добавляет построчные удаления (через delete files) и операции обновления. Поддерживает и position deletes (пометка конкретных строк по файлу и позиции), и equality deletes (пометка строк по значениям столбцов). Merge-on-read, это стратегия удаления по умолчанию: delete-файлы пишутся отдельно и сливаются с файлами данных на этапе чтения.
На CDP 7.3.1 я рекомендую всегда использовать формат версии 2. Накладные расходы минимальны, а наличие возможности построчного удаления (даже если вы не используете её сразу) избавит от болезненной миграции формата позже.
TBLPROPERTIES ('format-version' = '2')
Что это значит для администраторов Hadoop
Если вы пришли из администрирования HDFS и Hive, операционная модель меняется в двух аспектах.
Меньше управления на уровне файловой системы. Вы перестаёте думать про структуру директорий, директории партиций и количество файлов в каждой директории. Iceberg управляет всем этим через метаданные. Работа смещается от «управлять файлами в HDFS» к «управлять свойствами таблиц Iceberg и процедурами обслуживания».
Больше внимания к метаданным. Файлы метаданных, manifest lists и manifest-файлы накапливаются со временем. Без обслуживания (подробнее в части 5) эти файлы растут, а планирование запросов замедляется. Процедуры expire_snapshots и rewrite_manifests становятся частью обычного набора эксплуатационных инструментов.
Размен того стоит. Вы меняете ручное управление файловой системой, которое подвержено ошибкам и плохо масштабируется, на автоматическое управление метаданными, которое детерминировано и самоописательно.
Если после этой статьи захотелось самому поковырять Iceberg руками, мы сделали симулятор по проектированию Lakehouse на стеке Apache Spark и Iceberg. Также подписывайтесь на наш телеграм.
ссылка на оригинал статьи https://habr.com/ru/articles/1024488/