Раздел 26 · Data Engineering

Lakehouse: Iceberg, Delta, Hudi

senior~55 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Перед этим уроком

Lakehouse: Iceberg, Delta, Hudi

В уроке 03 разбирали Snowflake/BigQuery/Redshift, у каждого свой закрытый storage. В уроке 05, Parquet как открытый файл. Lakehouse, это попытка получить ACID-семантику и time travel прямо поверх Parquet в S3, без вендор-привязки. Один датасет, читаемый Spark-ом, Trino, DuckDB, ClickHouse, Snowflake, без миграций между вендорами.

Аудитория: ты прошёл уроки 03 и 05, знаешь, что Parquet, это файл, и понимаешь, чем cloud DWH отличается от self-hosted CH. На работе встречается требование “несколько engines читают один датасет в S3” или “нужны ACID-операции на S3, не запихивая всё в Snowflake”. Этот урок отвечает, как.

Урок не учит pyspark с нуля и не описывает каждую DML-операцию каждого формата. Цель, дать архитектурную картинку: как из неизменяемого Parquet получаются “таблицы” с UPDATE/DELETE, time travel и schema evolution. Дальше детали ты добираешь в документации конкретного формата.

0. Откуда взялся lakehouse

Эпоха 2005-2015: data warehouse (Teradata, Vertica), закрытый, дорогой, но даёт SQL и ACID. Аналитика жила здесь.

Эпоха 2010-2018: data lake (Hadoop + S3 + Parquet), открытый, дешёвый, но без транзакций и схемы. Аналитики писали скрипты, обрабатывающие “папки с Parquet”. Один сбой ETL, и в lake появлялись неконсистентные срезы; никто не знал, какая последняя успешная версия.

Vendor-патчи начались в 2017-2018: Databricks дал Delta Lake (Project Tahoe), Netflix запустил Iceberg, Uber, Hudi. Идея у всех одна: хранить таблицы поверх файлов, добавив metadata-слой, который описывает, какие файлы живые на текущий момент.

Имя “lakehouse” пустил в обращение Databricks (paper 2021): объединение data lake (open files) и data warehouse (ACID, schema). К 2023 году все три формата вышли в Apache foundation; в 2024-2025 индустрия начала консолидироваться вокруг Iceberg как стандарта, Delta и Hudi сильны в нишах.

Сегодня (2026 год) реальная картина:

  • Iceberg, открытый стандарт, поддержан Snowflake, BigQuery, AWS Athena, Trino, DuckDB, ClickHouse, Spark, Flink. Стал дефолтом для нового lakehouse.
  • Delta Lake, тесно интегрирован с Databricks; за пределами Databricks популярен меньше. Версия Delta 3.0+ открыла протокол совместимости с другими движками (Delta UniForm).
  • Hudi, силен в streaming-upsert (CDC, real-time pipeline). Если у тебя CDC из реляционной БД на 1000 апдейтов в секунду, Hudi MoR хорош.

Дальше разбираем общую идею (как формат превращает Parquet-файлы в таблицу), потом сравниваем три формата, потом ACID и time travel.

1. Идея: metadata layer поверх файлов

Lake (без lakehouse-формата): набор файлов в S3.

s3://lake/orders/
├── dt=2026-05-01/
│   ├── part-001.parquet
│   ├── part-002.parquet
│   └── part-003.parquet
└── dt=2026-05-02/
    ├── part-001.parquet
    └── part-002.parquet

Чтобы прочитать всё, делаешь listObjects в S3, потом параллельно читаешь Parquet. Проблемы:

  1. Атомарности нет. Если ETL писал 5 файлов, и 3-й упал, в lake лежит частичный результат. Reader увидит неконсистентный снимок.
  2. Schema evolution через костыли. Старые Parquet с одной схемой, новые с другой; reader должен сам разруливать.
  3. DELETE и UPDATE невозможны в общем случае. Parquet immutable; чтобы удалить строку, нужно переписать весь файл, а как сказать reader-ам, что “теперь читай этот файл вместо того”?
  4. Time travel невозможен. Если ты перезаписал файл, старая версия пропала.
  5. listObjects дорогой. На 1M партиций он становится узким местом.

Lakehouse-формат добавляет metadata layer: отдельные файлы, описывающие “какие data-файлы живые на момент snapshot N”. Reader сначала читает метаданные, потом по ним читает только нужные data-файлы. Это решает все 5 проблем.

s3://lake/orders/
├── data/                            ← собственно Parquet-файлы (immutable)
│   ├── 00001-data.parquet
│   ├── 00002-data.parquet
│   ├── 00003-data.parquet
│   └── ...
└── metadata/                        ← metadata layer (специфичен формату)
    ├── 00001-snapshot.json
    ├── 00002-snapshot.json
    └── ...

Atomic commit, это атомарная замена указателя в metadata layer на новую версию. Concurrent writers договариваются через optimistic locking (или через catalog), чтобы не перезаписать друг друга. Reader держит указатель на конкретный snapshot и видит консистентный набор файлов на этот момент.

Рассмотрим эту идею интерактивно.

Виджет проигрывает четыре операции (insert, insert, update, delete) и показывает, как меняется набор живых файлов в зависимости от формата. Ключевое наблюдение: data-файлы остаются на S3 после rollback или удаления. Это и есть физическая основа time travel и cheap rollback. Очистка идёт фоновым job-ом (expire_snapshots в Iceberg, VACUUM в Delta, hoodie.cleaner в Hudi), не сразу.

2. Apache Iceberg

Iceberg, наиболее структурированный из трёх форматов. Иерархия metadata:

catalog                              (REST, Glue, Nessie, Polaris, ...)
   │
   └─ table -> metadata.json          (current snapshot pointer)
              │
              └─ snapshot -> manifest-list.avro
                            │
                            ├─ manifest-1.avro -> data-files-1...N
                            └─ manifest-2.avro -> data-files-N+1...M

2.1 Catalog

Catalog, центральный реестр таблиц. Без catalog Iceberg-таблицы технически нет: catalog хранит указатель на текущий metadata.json и обеспечивает atomic-swap указателя при коммите.

Реализации:

  • Hive Metastore, legacy, тащится из Hadoop-стека.
  • AWS Glue, для AWS-окружения.
  • REST catalog, открытый спек 2023 года. Реализации: Tabular (был, куплен Databricks в 2024), Polaris (Snowflake, open-source), Nessie (Dremio, GitOps-стиль).
  • JDBC catalog, простой вариант на базе любой реляционной БД.

В 2025-2026 годах REST catalog стал стандартом для нового lakehouse. Polaris (Apache Polaris с 2024 года) и Lakekeeper, два самых живых open-source выбора.

2.2 Manifest list и manifests

Snapshot ссылается на manifest list (Avro-файл со списком manifests). Manifest, это Avro-файл со списком data-файлов (включая статистики per-file: row_count, partition_data, lower_bounds, upper_bounds, null_counts).

Зачем два уровня: один манифест, тысячи файлов; manifest list даёт быстро отфильтровать по партиции (читая только нужные manifest, не все). Это даёт прайнинг на уровне metadata, до того, как откроется хоть один Parquet.

2.3 Partition evolution

Уникальная фишка Iceberg, partition evolution: ты можешь сменить схему партиций без переписки данных. Старые data-файлы остаются партиционированными по старой схеме; новые пишутся по новой; manifest хранит partition spec для каждого файла.

Пример: начали с PARTITIONED BY (days(ts)), через год поняли, что нужно PARTITIONED BY (months(ts)). В Hive это требует rewrite всей таблицы; в Iceberg, одна команда ALTER TABLE ... DROP PARTITION FIELD days(ts), ADD PARTITION FIELD months(ts).

2.4 Hidden partitioning

Hive-style партиции (s3://lake/events/dt=2026-05-01/) требуют, чтобы пользователь сам писал WHERE dt = '2026-05-01'. Если он напишет WHERE event_time >= '2026-05-01', partition pruning не сработает.

Iceberg ввёл hidden partitioning: партиция вычисляется как трансформ от колонки (days(ts), months(ts), bucket(16, user_id)). Пользователь пишет естественный фильтр по timestamp, Iceberg сам резолвит партиции. Это устранило одну из главных Hive-болей.

2.5 Row-level deletes

Iceberg поддерживает три способа delete:

  • Copy on Write, переписать целые data-файлы (как Hudi CoW). Просто, но дорого.
  • Positional deletes, отдельный delete-файл со списком (data-file-path, row-position). Reader пропускает строки на лету.
  • Equality deletes, delete-файл со списком pk-значений; reader фильтрует строки по соответствию. Более гибко, но дороже на read.

Iceberg V2 формат поддерживает row-level deletes; V3 (2024-2025) добавил deletion vectors (binary bitmap, как у Delta). В 2026 году deletion vectors, рекомендованный режим для большинства движков.

урок из курса

Урок из курса «Data Engineering». Курс покупается один раз и остаётся навсегда: 500 ₽.

Оплата пока не подключена: премиум и курсы открываются промокодом.