apache iceberg и его философия

от автора

Всем привет! В этой статье хочу рассказать про то, как Iceberg работает под капотом, и про то, как он эффективно может взаимодействовать с данными через свою metadata.

Icebergтабличный формат для больших аналитических наборов данных.
По сути, Iceberg — это прослойка между Data Lake и движками запросов, которая с помощью metadata позволяет движкам делать эффективные запросы.

философия iceberg

  • разделение data и metadata

  • атомарные обновления, консистентность и изоляция

  • time travel и branch

  • поддержка schema evolution и partition evolution

metadata iceberg

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

  1. metadata.json

  2. snapshot

  3. manifest list

  4. manifest file

  5. data files

и служит для оптимизации запросов к Iceberg.

1. metadata.json

metadata.json хранит в себе версию таблицы и ссылки на другие элементы метаданных: схемы, список snapshot и список manifest-файлов.
По сути, является «таблицей версий» и consistency-файлом для всех читающих и пишущих процессов.

2. snapshot

snapshot — зафиксированное состояние таблицы в конкретный момент времени, которое определяет, какие файлы сейчас составляют таблицу.
В snapshot указана ссылка на manifest list.

3. manifest list

Перечисление всех manifest-файлов, которые относятся к конкретному snapshot.
Он нужен для облегчения чтения: вместо прохода по всем файлам Iceberg читает только те, которые реально относятся к запросу.

4. manifest file

Содержит ссылки на данные в рамках конкретного снимка:

  1. путь к файлу

  2. формат

  3. размер

  4. количество строк и сведения о разделах

  5. статистики по столбцам

    • min

    • max

    • null-count

    • и прочие метрики

Благодаря manifest-метаданным можно проводить раннюю фильтрацию на уровне файлов, что снижает стоимость выполнения фильтров в движке запросов.

Схема чтения metadata

В момент исполнения запроса движок сначала читает текущий metadata.json -> выбирает нужный snapshot -> получает относящийся к выбранному snapshot manifest list -> подгружает manifest-файлы.

Благодаря структуре чтения metadata, Iceberg позволяет исключить наборы данных, не удовлетворяющие запросу.

какие проблемы решаются благодаря metadata

1. ACID

Это ключевые гарантии, которые должна обеспечивать БД:

  1. Atomicity

  2. Consistency

  3. Isolation

  4. Durability

Atomicity

Изменения проходят полностью либо вообще не проходят. Рассматривая Atomicity в OLTP-БД, мы видим, что БОЛЬШОЕ количество транзакций изменяет НЕБОЛЬШОЕ количество записей. Это связано с тем, что единицей транзакции является запись. В нашем случае Iceberg — это OLAP-нагрузка, и единицей транзакции является таблица.

Consistency

Все записи и изменения схем приводят к созданию нового snapshot и, следовательно, нового manifest list.
Тем самым Iceberg переводит базу данных из одного корректного состояния в другое.

Isolation

Читатели и писатели не мешают друг другу.
И каждый query видит консистентное состояние таблицы. Iceberg реализует это через snapshot isolation.

Проблема обычного Data Lake

  1. Writer переписывает partition dt=2026-04-01/

  2. Reader в этот момент делает SELECT Он может увидеть: — часть старых файлов

    • часть новых

    • missing files Inconsistent table state.

Reader и Writer

  1. создаёт новые data files

  2. создаёт новый snapshot

  3. атомарно переключает metadata pointer

Каждый query читает один конкретный snapshot.

пример: был snapshot 100 files: A, B Reader начал query. Writer создаёт snapshot 101 files: A, B, C Что увидит reader? Reader продолжит читать snapshot 100: A, B

Writer и Writer

Writer A создаёт snapshot 101 Writer B тоже начал от snapshot 100 Перед commit iceberg проверяет: изменилась ли таблица с начала моего query? Если изменилась, то commit B fail/retry.

Durability

Iceberg не отвечает за надёжное хранение данных.
За durability отвечает само файловое хранилище (HDFS, S3 и т. п.).

Time travel и branch

Функция time travel позволяет получить данные в том виде, в котором они были в конкретный момент времени, благодаря snapshot. Каждый snapshot представляет собой полную и согласованную версию таблицы на определённый момент времени.

Branch — это развитие идеи time travel, очень похожее на Git.

Обычный Time travel

последовательность snapshot, между которыми можно перемещаться.

Time travel с branch

Благодаря branch можно не только путешествовать в конкретное время, но и:

  • тестировать новые варианты хранения и работы с данными без влияния на производственные данные;

  • строить аналитику и отчётность с разными аналитическими моделями.

Поддержка schema evolution и partition evolution

Schema evolution и partition evolution в Iceberg работают без переписывания всей таблицы благодаря metadata. Она хранит не одну структуру таблицы, а историю версий схем и спецификаций партиций.

schema evolution

в схемах iceberg у каждой колонки есть immutable column ID Пример:

{  "name": "id",  "id": 1  },{  "name": "name",  "id": 2  }

И если мы хотим добавить колонку age, то наша новая схема будет выглядеть так:

{  "name": "id",  "id": 1  },{  "name": "name",  "id": 2  },{  "name": "age",  "id": 3  }

И теперь при чтении старых файлов Iceberg понимает, что age = NULL для старых файлов.

rename column

Также в Iceberg можно просто переименовывать колонки, ведь мы просто меняем в metadata значение имени колонки. Пример: ДО:

{  "name": "id",  "id": 1  },{  "name": "name",  "id": 2  },{  "name": "age",  "id": 3  }

ПОСЛЕ:

{  "name": "id",  "id": 1  },{  "name": "person_name",  "id": 2  },{  "name": "age",  "id": 3  }

delete column

При удалении колонки Iceberg просто перестаёт читать данные, относящиеся к ней.

reorder columns

просто меняется позиция в списке

Пример: Физически parquet может хранить: [id][name][age] schema v1

position

name

id

1

id

1

2

name

2

3

age

3

меняем местами

schema v2

position

name

id

3

age

3

1

id

1

2

name

2

partition evolution

Так же, как и со схемами, всё завязано не на переписывании самих данных, а на изменении metadata.
Пример:
у нас было партиционирование по дням, а мы хотим делать партиции по месяцу. Было:

{  "spec-id": 1,  "fields": [  {  "source-id": 2,  "transform": "day"  }  ]  }

Стало:

{  "spec-id": 2,  "fields": [  {  "source-id": 2,  "transform": "month"  }  ]  }

И получается, что старые и новые данные физически разбиты по-разному, но для пользователя это остаётся одной таблицей.

Заключение

Вся философия и весь принцип работы Iceberg заключается в metadata.
Благодаря ей мы можем эффективно выполнять запросы, получать статистику по колонкам, не тратя огромные ресурсы каждый раз, использовать time travel и branch для тестирования фичей и командной работы, изменять схемы и партиции, не переписывая сами файлы с данными.

ссылка на оригинал статьи https://habr.com/ru/articles/1033546/