Зображення створено автором.
Вступ
Моя подорож у світі написання продовжиться з Apache Iceberg. Якщо запитати мене, чому я обрав Iceberg, я не зможу дати чітку відповідь. Просто відчуваю, що з цим відкритим форматом я матиму багато цікавих можливостей і зможу реалізувати круті ідеї.
(Не надто інженерна причина від дата-інженера)
У цій статті я надам огляд Apache Iceberg.
Що таке Iceberg?
Ми відповімо на це запитання через інше: що таке формат таблиці? Якщо ви працюєте з базами даних або дата-складами, то вони надають зручну абстракцію: таблицю. Система приховує від вас фізичний рівень даних, тому вам не потрібно занурюватися у технічні деталі.
Зображення створено автором.
Однак усе змінюється, коли ви хочете реалізувати рішення Data Lakehouse (озеро даних). Ваші дані зберігаються в об’єктному сховищі, і ви можете використовувати свій улюблений рушій, наприклад, Spark чи Trino, щоб виконувати запити. Немає потреби переміщувати дані — усе знаходиться в одному місці, що підходить як для швидких запитів, так і для завдань машинного навчання.
Але є одне питання: як нам або рушію дізнатися, які файли належать до конкретного набору даних?
Формати таблиць приходять на допомогу.
Вони допомагають організувати й керувати файлами даних, дозволяючи зберігати тисячі файлів у сховищі та впевнено заявляти:
“Ось ваша таблиця. Використовуйте її.“
Детальніше, формат таблиці переносить можливості дата-складу у дата-озеро: абстракція таблиць, ACID-транзакції, подорож у часі (time travel) тощо. Усе це досягається завдяки метаданим.
Різні формати таблиць організовують метадані по-різному. Apache Hive використовував підхід на основі директорій. Його метадані стверджували: “Ваша таблиця або розділ — це всі файли у цій директорії.”
“Попри простоту та довготривале використання, існують певні обмеження: неефективність при незначних модифікаціях, неможливість безпечно змінювати дані в кількох розділах одночасно, або необхідність для користувача знати про структуру базової таблиці.
Тому потрібен новий формат таблиці.
Новий формат наступного покоління використовує підхід на основі файлів, керуючи метаданими на рівні файлів. Apache Iceberg був спочатку створений Netflix. Завдяки специфікації Iceberg, Netflix прагне досягти більшої коректності таблиць та швидшого планування запитів (порівняно з Hive), а також дозволити користувачам менше турбуватися про фізичну організацію даних.
Архітектуру Iceberg ми розглянемо в наступних розділах.
Архітектура
Таблиця Apache Iceberg має три рівні, організовані ієрархічно: каталог (catalog layer) знаходиться на вершині, під ним розташований шар метаданих (metadata layer), який включає файли метаданих, список маніфестів і файли маніфестів. Останній шар — це дані (data layer). Оскільки кожен вищий шар відслідковує інформацію про нижчий, почнемо знизу і поступово підніматимемося.
Шар даних
Цей шар зберігає фактичні дані таблиці, включно з файлами даних (data files) та файлами видалень (delete files). Файли даних містять самі дані, а файли видалень відслідковують записи таблиці, які було видалено. У Iceberg файли видалень використовують стратегію "злиття під час читання" (merge-on-read). Вона створює новий файл, який фіксує видалення, а читач має об’єднати файли видалень із файлами даних, щоб отримати кінцевий результат.
Файли маніфестів
Файли маніфестів (Manifest Files) відстежують файли даних у шарі даних і надають статистику, наприклад, мінімальні та максимальні значення для кожного стовпця у файлі даних. Також вони визначають формат файлів даних, таких як Parquet, ORC або Avro.
Список маніфестів
Список маніфестів (Manifest Lists) фіксує знімок стану таблиці Iceberg у конкретний момент часу. Він містить список файлів маніфестів та деталі, такі як місцезнаходження файлів маніфестів і інформація про розділи.
По суті, список маніфестів (Manifest List) — це масив структур, кожна з яких представляє окремий файл маніфесту.
Файли метаданих
Файли метаданих (Metadata Files) містять інформацію про таблицю Iceberg на певний момент часу, наприклад, схему таблиці, деталі розділів, знімки (snapshots) та останній знімок. Коли таблиця Iceberg змінюється, каталог створює новий файл метаданих і позначає його як останню версію. Цей процес підтримує лінійну історію комітів таблиці. Крім того, Iceberg завжди надає читачу останню версію таблиці.
Каталог
Каталог (Catalog) — це місце, з якого починається кожна операція з даними в Iceberg. Він вказує, куди звернутися спочатку. Каталог вказує на місцезнаходження поточного покажчика метаданих. Iceberg-каталог має підтримувати атомарні операції (atomic operations) під час оновлення покажчика, що забезпечує синхронний доступ до стану таблиці для всіх читачів і записувачів.
Різні бекенди можуть використовуватися як каталоги для Iceberg, такі як Hive Metastore, Nessie або AWS Glue Catalog.
Наступні розділи описуватимуть деякі типові операції з даними в Apache Iceberg.
Операції запису та читання (Write-Read Operation)
Створення порожньої таблиці
Зображення створено автором.
Перший етап будь-якого запиту — це розбір. Для оператора CREATE
створюється лише файл метаданих.
Під час створення порожньої таблиці рушій створить файл метаданих зі знімком s0
. Загальний шлях до файлу метаданих виглядає приблизно так: …/metadata/v1.metadata.json
. Метадані включатимуть схему таблиці та унікальний ідентифікатор таблиці: table-uuid
.
На фінальному етапі рушій оновить запис каталогу, щоб поточний покажчик метаданих цієї таблиці вказував на розташування створеного файлу метаданих.
Вставка даних
Зображення створено автором.
Тепер подивимося, що відбувається, коли ми додаємо дані до таблиці:
- Користувач надсилає запит
INSERT
, наприклад,INSERT INTO this_table VALUES
. - Перший етап, який виконує рушій, — це розбір запиту (query parsing).
- Оскільки це запит на запис даних, рушій потребує інформації про таблицю. Він звертається до каталогу (Catalog), щоб отримати місцезнаходження поточного файлу метаданих та дізнатися про схему таблиці та схему розділів.
- Потім створюється файл даних Parquet за шляхом
…/data/abc.parquet
. Якщо в файлі метаданих таблиці визначено схему розділів (partitioning scheme), рушій записуватиме дані, використовуючи цю схему. - Створюється файл маніфесту (Manifest File) за шляхом
…/metadata/def.avro
. Він вказує на місцезнаходження файлу даних. Крім того, рушій записує статистичні деталі, такі як верхня та нижня межі значень для стовпців. Це дозволяє рушію запитів пропускати непотрібні файли під час читання даних. - Створюється список маніфестів (Manifest List) за шляхом
…/metadata/snap-xyz.avro
, щоб відслідковувати файл маніфесту. Він вказує на місцезнаходження файлу маніфесту і містить деталі, наприклад, кількість доданих або видалених файлів даних чи рядків. Також записується статистика про розділи, наприклад, верхні та нижні межі для стовпців розділу. - Створюється новий файл метаданих (Metadata File) на основі попереднього поточного файлу метаданих, але вже з новим знімком
s1
, при цьому зберігаючи інформацію про попередній знімокs0
. У ньому містяться посилання на список маніфестів, а також деталі, такі як шлях до файлу списку маніфестів, ідентифікатор знімка (snapshot ID) та підсумок операції. - Каталог оновлює покажчик поточних метаданих таблиці, щоб він вказував на новий файл метаданих.
Запит SELECT
Зображення створено автором.
Що відбувається, коли ми вибираємо дані з таблиці:
- Рушій починає з отримання місцезнаходження поточного файлу метаданих таблиці.
- Відкривається файл метаданих, щоб отримати необхідну інформацію.
Спочатку рушій отримує схему таблиці, щоб налаштувати свої внутрішні структури пам'яті для читання даних. Потім він отримує інформацію про схему розділів таблиці, щоб зрозуміти фізичну організацію даних. Це допомагає рушію запитів пропускати непотрібні файли даних пізніше. Далі він знаходить місцезнаходження списку маніфестів для поточного знімка, s1
.
- Потім відкривається список маніфестів, щоб отримати місцезнаходження файлу маніфесту.
- Рушій відкриває файл маніфесту та визначає шлях до файлу даних, орієнтуючись на фільтр розділів (якщо він зазначений у запиті).
- Рушій слідує шляху до файлів даних і повертає дані користувачу.
Читання даних з минулого
Зображення створено автором.
Припустимо, після ще однієї операції вставки даних поточний знімок таблиці — S2
, і з якоїсь причини користувач хоче здійснити тимчасову подорож назад до знімка S1
. Ось що відбудеться за лаштунками:
- Спочатку рушій розбирає вхідний запит.
- Як і в звичайній операції читання даних, він також звертається до каталогу (Catalog), щоб знайти поточний файл метаданих.
- Потім рушій читає поточний файл метаданих, щоб отримати інформацію про таблицю; оскільки файл метаданих відслідковує всі знімки таблиці, рушій може вибрати знімок для читання, орієнтуючись на фільтр тимчасової подорожі з запиту.
- У цьому випадку рушій слідує до місцезнаходження списку маніфестів для знімка
s1
. - Потім рушій відкриває список маніфестів, щоб отримати місцезнаходження файлу маніфесту.
- Рушій відкриває файл маніфесту та визначає шлях до файлу даних. Рушій слідує цьому шляху, щоб прочитати дані та повернути їх користувачу.
Висновок
На основі того, що я дізнався, я лише розглянув загальний огляд Iceberg.
Хоча ця стаття може зайняти лише 10 хвилин для прочитання, я сподіваюся, що вона дасть вам базове розуміння Apache Iceberg, чому він був створений та його архітектури.
Я пишу про Iceberg з перспективи новачка, тому, якщо ви помітите якісь непорозуміння чи неточності, будь ласка, не вагайтесь обговорити це і виправити мене. Це буде чудовий спосіб для всіх нас вчитися разом.
Джерела
- Томер Ширан, Джейсон Х'юз та Алекс Мерсед, Apache Iceberg: The Definitive Guide (2024)
- Джейсон Х'юз, Apache Iceberg: An Architectural Look Under the Covers
~~~
Перекладено з: I spent 4 hours learning Apache Iceberg. Here’s what I found.