TL;DR — the 12 lines— 12 строк
- Table format = metadata + ACID over data files. NOT a file format, NOT a storage engine.
- Layers: catalog → metadata.json → manifest list → manifests → data files.
- Atomicity = catalog compare-and-swap pointer; isolation = snapshot isolation; writers = optimistic concurrency.
- Schema evolution = by field ID (add/drop/rename/reorder/widen, metadata-only, no rewrite).
- Hidden partitioning: query the raw column, Iceberg prunes via a stored transform. No derived date column footgun.
- Partition evolution: change the spec without rewriting old data.
- Snapshots → time travel + rollback;
expire_snapshotsto reclaim space & truly delete. - CoW (read-fast / write-heavy) vs MoR (write-fast / read-merge) + compaction (
rewrite_data_files). - Planning = metadata pruning (manifest-list → manifest stats → Parquet footers). No object-store LIST.
- Iceberg (neutral, partition evolution) vs Delta (Spark/Databricks, txn log) vs Hudi (upsert/CDC).
- Lakehouse = warehouse guarantees + lake economics + engine neutrality.
- Why at scale: billions of events/day, many engines (Spark/Flink/Trino), one open copy of data.
- Табличный формат = метаданные + ACID поверх файлов данных. НЕ формат файла, НЕ движок хранения.
- Слои: catalog → metadata.json → manifest list → manifests → data files.
- Атомарность = compare-and-swap указателя в каталоге; изоляция = snapshot isolation; писатели = оптимистичная конкуренция.
- Эволюция схемы = по field ID (add/drop/rename/reorder/widen, только метаданные, без перезаписи).
- Скрытое партиционирование: запрос идёт по исходному столбцу, Iceberg прунит через сохранённое преобразование. Никаких производных date-столбцов с ошибками.
- Эволюция партиционирования: изменить спецификацию без перезаписи старых данных.
- Снапшоты → time travel + rollback;
expire_snapshotsдля освобождения места и настоящего удаления. - CoW (быстрое чтение / тяжёлая запись) vs MoR (быстрая запись / чтение с мёржем) + компакция (
rewrite_data_files). - Планирование = прунинг метаданных (manifest-list → статистики manifest → footers Parquet). Никакого LIST в объектном хранилище.
- Iceberg (нейтральный, эволюция партиций) vs Delta (Spark/Databricks, txn log) vs Hudi (upsert/CDC).
- Lakehouse = гарантии хранилища + экономика озера + нейтральность движков.
- Зачем на масштабе: миллиарды событий/день, множество движков (Spark/Flink/Trino), одна открытая копия данных.
What is a table format (and why)Что такое табличный формат (и зачем)
A table format is a spec + metadata layer that turns a pile of files in object storage into a real, transactional table: schema, snapshots, partitioning, ACID commits.
Табличный формат — это спецификация + метаданные, превращающие кучу файлов в объектном хранилище в настоящую транзакционную таблицу: схема, снапшоты, партиционирование, ACID-коммиты.
| Layer | Example | Responsibility |
|---|---|---|
| Query engine | Spark, Flink, Trino, Snowflake | Plan & execute SQL |
| Table format | Iceberg, Delta, Hudi | Schema, snapshots, ACID, partitioning, metadata |
| File format | Parquet, ORC, Avro | Bytes inside one file (encoding, stats) |
| Object storage | S3, GCS, HDFS | Durable bytes |
| Слой | Пример | Ответственность |
|---|---|---|
| Движок запросов | Spark, Flink, Trino, Snowflake | Планирование и выполнение SQL |
| Табличный формат | Iceberg, Delta, Hudi | Схема, снапшоты, ACID, партиционирование, метаданные |
| Формат файла | Parquet, ORC, Avro | Байты внутри одного файла (кодирование, статистики) |
| Объектное хранилище | S3, GCS, HDFS | Надёжные байты |
It exists to fix the Hive table problem: a "table" was just a directory of files with partition info in a metastore — no atomic commits, expensive LIST on object stores, unsafe concurrent writes, fragile schema/partition changes.
Существует, чтобы решить проблему таблиц Hive: «таблица» была просто папкой файлов с информацией о партициях в метасторе — никаких атомарных коммитов, дорогой LIST в объектных хранилищах, небезопасные параллельные записи, хрупкие изменения схемы/партиций.
Metadata layers — read & write pathСлои метаданных — путь чтения и записи
On read: catalog → current metadata → pick snapshot → manifest-list prune → manifest stats prune → Parquet row-group prune. No directory LIST; planning scales to millions of files.
При чтении: catalog → текущие метаданные → выбрать снапшот → прунить manifest-list → прунить статистики manifest → прунить row-group Parquet. Никакого LIST папок; планирование масштабируется на миллионы файлов.
On write (commit): write new data files + manifests + manifest list + new metadata.json, then atomically swap the catalog pointer using optimistic concurrency (retry if someone committed first).
При записи (коммит): записать новые файлы данных + манифесты + manifest list + новый metadata.json, затем атомарно заменить указатель в каталоге через оптимистичную конкуренцию (ретрай, если кто-то закоммитил первым).
Schema & partition evolution + hidden partitioningЭволюция схемы и партиционирования + скрытое партиционирование
Schema evolution — by field IDЭволюция схемы — по field ID
Iceberg tracks every column by a unique field ID, not by name or position. So you can add, drop, rename, reorder, widen columns as a metadata-only change — no data rewrite.
Iceberg отслеживает каждый столбец по уникальному field ID, а не по имени или позиции. Поэтому можно добавлять, удалять, переименовывать, переупорядочивать, расширять столбцы как изменение только метаданных — без перезаписи данных.
- Add → old files return NULL for the new column.
- Rename → ID unchanged, only the name mapping changes (safe).
- Drop → ID no longer projected.
- Добавление → старые файлы возвращают NULL для нового столбца.
- Переименование → ID не меняется, меняется только маппинг имени (безопасно).
- Удаление → ID больше не проецируется.
Hidden partitioning — the footgun killerСкрытое партиционирование — убийца ошибок
You declare a partition transform on a real column; Iceberg derives and stores the partition value. Users query the raw column and pruning happens automatically.
Вы объявляете партиционное преобразование на реальный столбец; Iceberg вычисляет и сохраняет значение партиции. Пользователи запрашивают исходный столбец, а прунинг происходит автоматически.
CREATE TABLE events ( event_time timestamp, user_id bigint, payload string ) PARTITIONED BY (days(event_time), bucket(16, user_id)); -- analyst queries the RAW column; Iceberg prunes partitions for them: SELECT * FROM events WHERE event_time >= '2026-06-01';
CREATE TABLE events ( event_time timestamp, user_id bigint, payload string ) PARTITIONED BY (days(event_time), bucket(16, user_id)); -- аналитик запрашивает ИСХОДНЫЙ столбец; Iceberg прунит партиции за него: SELECT * FROM events WHERE event_time >= '2026-06-01';
Transforms:Преобразования: years/months/days/hours, bucket(N,col), truncate(N,col), identity.
Partition evolution — change granularity, no rewriteЭволюция партиций — смена гранулярности без перезаписи
Change the partition spec over time (e.g. days(ts) → hours(ts)) without rewriting old data. Old files keep their old spec; new files use the new one; manifests record which spec each file used, so planning handles the mix.
Изменить спецификацию партиций со временем (напр., days(ts) → hours(ts)) без перезаписи старых данных. Старые файлы сохраняют старую спецификацию; новые используют новую; манифесты записывают, какую спецификацию использовал каждый файл, так что планировщик обрабатывает смесь.
Snapshots, time travel & ACIDСнапшоты, time travel и ACID
Every write creates an immutable snapshot (a manifest list + reachable data files). History is kept in metadata.json.
Каждая запись создаёт неизменный снапшот (manifest list + достижимые файлы данных). История хранится в metadata.json.
-- time travel SELECT * FROM t FOR SYSTEM_VERSION AS OF 8675309; -- snapshot id SELECT * FROM t FOR SYSTEM_TIME AS OF '2026-06-10 00:00:00'; -- rollback = your incident button (metadata-only, cheap) CALL catalog.system.rollback_to_snapshot('db.t', 8675309); -- reclaim storage + truly delete old data CALL catalog.system.expire_snapshots('db.t', TIMESTAMP '2026-06-01 00:00:00');
-- time travel SELECT * FROM t FOR SYSTEM_VERSION AS OF 8675309; -- snapshot id SELECT * FROM t FOR SYSTEM_TIME AS OF '2026-06-10 00:00:00'; -- rollback = кнопка инцидента (только метаданные, дёшево) CALL catalog.system.rollback_to_snapshot('db.t', 8675309); -- освободить хранилище + действительно удалить старые данные CALL catalog.system.expire_snapshots('db.t', TIMESTAMP '2026-06-01 00:00:00');
| ACID property | How Iceberg delivers it |
|---|---|
| Atomicity | One commit = one atomic catalog pointer swap; all-or-nothing. |
| Consistency | Readers always see a complete snapshot, never half-written files. |
| Isolation | Snapshot isolation: reader pinned to snap N unaffected by writer making N+1. Writers use optimistic concurrency. |
| Durability | Files + metadata persisted in object storage. |
| Свойство ACID | Как Iceberg его обеспечивает |
|---|---|
| Атомарность | Один коммит = одна атомарная замена указателя каталога; всё или ничего. |
| Согласованность | Читатели всегда видят полный снапшот, никогда не наполовину записанные файлы. |
| Изоляция | Изоляция снапшотами: читатель, прикреплённый к снапшоту N, не затрагивается писателем, создающим N+1. Писатели используют оптимистичную конкуренцию. |
| Надёжность | Файлы + метаданные сохранены в объектном хранилище. |
expire_snapshots + remove_orphan_files. True erasure needs snapshot expiry, not just the DELETE.expire_snapshots + remove_orphan_files. Настоящее стирание требует истечения снапшотов, а не только DELETE.Copy-on-write vs Merge-on-read + compactionCopy-on-write vs Merge-on-read + компакция
| Copy-on-write (CoW) | Merge-on-read (MoR) | |
|---|---|---|
| Update/delete | Rewrite affected data files entirely | Write small delete files (position / equality) |
| Write cost | High | Low |
| Read cost | Low (no merge) | Higher (merge data + delete files) |
| Best for | Read-heavy, infrequent updates | Streaming upserts / frequent CDC |
| Copy-on-write (CoW) | Merge-on-read (MoR) | |
|---|---|---|
| Update/delete | Перезапись затронутых файлов данных целиком | Запись маленьких delete files (position / equality) |
| Цена записи | Высокая | Низкая |
| Цена чтения | Низкая (нет мёржа) | Выше (мёрж данных + delete files) |
| Лучше для | Read-heavy, редкие обновления | Потоковые upsert / частый CDC |
Delete file types (MoR): position deletes = "row X in file Y is gone"; equality deletes = "rows where id=123 are gone".
Типы delete files (MoR): position deletes = «строка X в файле Y удалена»; equality deletes = «строки, где id=123, удалены».
Compaction (rewrite_data_files) merges small files into ~128–512 MB files, applies MoR deletes, and can re-sort/cluster for tighter stats. It runs as its own atomic commit; readers keep using the old snapshot until it lands.
Компакция (rewrite_data_files) мержит мелкие файлы в ~128–512 MB файлы, применяет MoR-удаления, может пересортировать/кластеризовать для более точных статистик. Запускается как собственный атомарный коммит; читатели продолжают использовать старый снапшот, пока не приземлится.
Companion maintenance:Сопутствующее обслуживание: rewrite_manifests (compact metadata)(компакция метаданных), expire_snapshots, remove_orphan_files. Schedule them off the hot write path (e.g. Airflow).Запускать вне горячего write path (напр., Airflow).
Iceberg vs Delta vs Hudi vs Hive
| Dimension | Iceberg | Delta Lake | Hudi |
|---|---|---|---|
| Origin | Netflix → Apache | Databricks | Uber → Apache |
| Metadata model | Snapshot tree (metadata → manifest list → manifests) | Transaction log (_delta_log JSON + checkpoints) | Timeline of commits + indexed file groups |
| Engine neutrality | Strongest (Spark/Flink/Trino/Snowflake/BigQuery) | Best on Spark/Databricks | Spark-centric (+Flink) |
| Partition evolution | Native | Limited (liquid clustering) | Limited |
| Hidden partitioning | Yes | No | No |
| Update model | CoW + MoR (v2) | CoW + deletion vectors | CoW & MoR first-class |
| Sweet spot | Multi-engine, evolving large tables | Spark/Databricks BI+ML | Upsert / CDC-heavy ingestion |
| Измерение | Iceberg | Delta Lake | Hudi |
|---|---|---|---|
| Происхождение | Netflix → Apache | Databricks | Uber → Apache |
| Модель метаданных | Дерево снапшотов (metadata → manifest list → manifests) | Транзакционный лог (_delta_log JSON + checkpoints) | Timeline коммитов + индексированные группы файлов |
| Нейтральность движков | Сильнейшая (Spark/Flink/Trino/Snowflake/BigQuery) | Лучше на Spark/Databricks | Spark-центричный (+Flink) |
| Эволюция партиций | Встроенная | Ограниченная (liquid clustering) | Ограниченная |
| Скрытое партиционирование | Да | Нет | Нет |
| Модель обновления | CoW + MoR (v2) | CoW + deletion vectors | CoW & MoR первого класса |
| Сильная сторона | Мульти-движки, эволюционирующие большие таблицы | Spark/Databricks BI+ML | Upsert / тяжёлый CDC ingestion |
One-liners: Iceberg for neutrality + clean evolution at scale. Delta if all-in on Spark/Databricks. Hudi for record-level upserts / incremental CDC.
Одной строкой: Iceberg для нейтральности + чистая эволюция на масштабе. Delta, если all-in на Spark/Databricks. Hudi для upsert на уровне записей / инкрементальный CDC.
Iceberg vs plain Hive tables — the concrete winsIceberg vs обычные таблицы Hive — конкретные победы
- Atomic commits + snapshot isolation (Hive: none, dirty reads possible).
- Metadata planning (Hive: expensive directory LIST).
- Schema evolution by field ID (Hive: positional/name pitfalls).
- Hidden partitioning + partition evolution (Hive: physical dirs, full rewrite to change).
- Time travel + rollback (Hive: none).
- Engine-neutral spec (Hive metastore is its own coupling).
- Атомарные коммиты + изоляция снапшотами (Hive: нет, возможны грязные чтения).
- Планирование по метаданным (Hive: дорогой LIST папок).
- Эволюция схемы по field ID (Hive: подводные камни позиции/имени).
- Скрытое партиционирование + эволюция партиций (Hive: физические папки, полная перезапись для изменения).
- Time travel + rollback (Hive: нет).
- Нейтральная к движкам спецификация (метастор Hive — собственная связанность).
File formats — Parquet / ORC / AvroФорматы файлов — Parquet / ORC / Avro
| Format | Layout | Best for | Notes |
|---|---|---|---|
| Parquet | Columnar | Analytics (the default) | Row groups → column chunks → pages; broadest ecosystem; rich nested types |
| ORC | Columnar | Hive/Tez world | Stripes + lightweight indexes + bloom filters; slightly better compression sometimes |
| Avro | Row-based | Streaming / serialization | Iceberg uses it for its own manifest files; bad for wide scans |
| Формат | Раскладка | Лучше для | Заметки |
|---|---|---|---|
| Parquet | Столбцовый | Аналитика (по умолчанию) | Row groups → column chunks → pages; широчайшая экосистема; богатые вложенные типы |
| ORC | Столбцовый | Мир Hive/Tez | Stripes + лёгкие индексы + bloom filters; иногда чуть лучше сжатие |
| Avro | Строковый | Стриминг / сериализация | Iceberg использует его для собственных manifest files; плох для широких сканов |
Columnar = read only needed columns, compress better (similar values adjacent), embed stats for pushdown. Columnar-vs-row (Parquet vs Avro) matters far more than Parquet-vs-ORC for analytics. For new Iceberg tables: Parquet is the safe default.
Столбцовый = читать только нужные столбцы, лучше сжимается (похожие значения рядом), встроенные статистики для pushdown. Столбцовый-vs-строковый (Parquet vs Avro) важнее, чем Parquet-vs-ORC, для аналитики. Для новых таблиц Iceberg: Parquet — безопасный дефолт.
Predicate pushdown end-to-endPredicate pushdown от конца до конца
Lakehouse architectureАрхитектура Lakehouse
| Data lake | Data warehouse | Lakehouse | |
|---|---|---|---|
| Storage | Open files, object store | Proprietary, coupled | Open files + table format on object store |
| ACID / schema | Weak | Strong | Strong |
| Cost | Low | High | Low (lake economics) |
| Engine access | Many | One (vendor) | Many, neutral |
| Risk | "Data swamp" | Lock-in | Needs maintenance discipline |
| Data lake | Data warehouse | Lakehouse | |
|---|---|---|---|
| Хранение | Открытые файлы, объектное хранилище | Проприетарное, связанное | Открытые файлы + табличный формат на объектном хранилище |
| ACID / схема | Слабые | Сильные | Сильные |
| Стоимость | Низкая | Высокая | Низкая (экономика озера) |
| Доступ движков | Много | Один (вендор) | Много, нейтрально |
| Риск | «Data swamp» | Привязка | Нужна дисциплина обслуживания |
Lakehouse = warehouse guarantees (ACID, schema, time travel) + lake economics + engine neutrality. One copy of data, many engines, no storage lock-in.
Lakehouse = гарантии хранилища (ACID, схема, time travel) + экономика озера + нейтральность движков. Одна копия данных, множество движков, нет привязки к хранилищу.
Reference pipeline (the design-round answer)Эталонный пайплайн (ответ на design-раунде)
Self-quiz — tap a card to flipСамопроверка — тап для переворота
days(ts)); Iceberg stores the derived partition value so analysts query the raw column and still get pruning — no derived date column.
Вы объявляете преобразование (напр., days(ts)); Iceberg сохраняет производное значение партиции, чтобы аналитики запрашивали исходный столбец и всё равно получали прунинг — никакого производного date-столбца.
expire_snapshots + remove_orphan_files for true physical erasure.
Байты выживают в старых снапшотах. Нужно expire_snapshots + remove_orphan_files для настоящего физического стирания.
Operational gotchas — the depth probesОперационные подвохи — глубинные вопросы
- No snapshot expiration → unbounded metadata + storage growth, slower planning. Schedule
expire_snapshots. - No compaction on streaming tables → small-file explosion → slow reads.
- Catalog choice matters — atomicity depends on the catalog's CAS; a naive file/HDFS catalog can break it under concurrency. Prefer REST/Glue/Nessie.
- Wrong partition transform → tiny partitions or skew. Use
bucket()for high-cardinality keys,days/hoursfor time. - MoR read amplification if delete files accumulate without compaction.
- Orphan files after failed writes →
remove_orphan_files(respect in-flight writers). - Field-ID reuse — drop-then-re-add a name = new ID; old data won't reappear.
- Нет истечения снапшотов → неограниченный рост метаданных + хранилища, медленнее планирование. Запланировать
expire_snapshots. - Нет компакции на потоковых таблицах → взрыв мелких файлов → медленное чтение.
- Выбор каталога важен — атомарность зависит от CAS каталога; наивный file/HDFS каталог может сломать её при конкуренции. Предпочесть REST/Glue/Nessie.
- Неправильное преобразование партиций → крошечные партиции или скос. Использовать
bucket()для высококардинальных ключей,days/hoursдля времени. - Усиление чтения MoR, если delete files накапливаются без компакции.
- Orphan files после упавших записей →
remove_orphan_files(уважать активных писателей). - Переиспользование field-ID — удалить-потом-вернуть имя = новый ID; старые данные не всплывут.
Bonus depth: branching / write-audit-publishБонусная глубина: ветвление / write-audit-publish
Iceberg supports branches (independent snapshot lines) and tags (immutable snapshot refs). WAP: write to a branch → run DQ checks → fast-forward main. Tag a snapshot for reproducible ML training or audit.
Iceberg поддерживает ветки (независимые линии снапшотов) и теги (неизменяемые ссылки на снапшот). WAP: записать в ветку → запустить DQ-проверки → fast-forward main. Тегировать снапшот для воспроизводимого ML-обучения или аудита.