Эволюция схем в Table Formats
В уроке 01 мы разобрали, что Parquet и ORC сами по себе поддерживают только базовую эволюцию: добавление колонок в конец. Переименование, удаление, изменение порядка — невозможны без перезаписи файлов.
Table formats (Delta Lake, Iceberg, Hudi, Paimon) решают эту проблему: они добавляют metadata layer поверх Parquet/ORC, который отслеживает колонки не по позиции, а по ID или имени, и позволяет менять схему без rewrite данных.
Каждый формат решает эту задачу по-разному. Iceberg — наиболее мощный (ID-based tracking, partition evolution). Delta Lake — фокус на enforcement и type widening. Hudi — минимальная эволюция. Paimon — streaming-first подход.
Delta Lake: Schema Enforcement + Evolution
Delta Lake различает два режима:
Schema Enforcement (по умолчанию)
Schema enforcement — это строгая проверка: каждая write-операция должна точно соответствовать текущей схеме таблицы. Если schema не совпадает — write отклоняется:
# Текущая схема: id: int, name: string, email: string
# Попытка записать: id: int, name: string, phone: string
# → AnalysisException: phone не существует в схеме таблицы
df_with_phone.write.format("delta").mode("append").save("/data/users")
Enforcement защищает от случайного добавления “мусорных” колонок. Это schema-on-write: ошибка обнаруживается в момент записи, а не при чтении.
Schema Evolution (opt-in)
Чтобы разрешить эволюцию, нужно явно включить mergeSchema:
# Добавление новой колонки phone
df_with_phone.write.format("delta") \
.mode("append") \
.option("mergeSchema", "true") \
.save("/data/users")
Write: новая колонка
DataFrame содержит данные с новой колонкой (phone), которой нет в текущей схеме таблицы DeltamergeSchema?
Delta проверяет: совпадает ли schema DataFrame со schema таблицы? Если нет — проверяет флаг mergeSchema.AnalysisException Нет
AnalysisException — write отклонён. Данные не записаны. Схема таблицы не изменена. Это schema enforcement — защита от случайных изменений.Schema merge → commit
Delta обновляет schema в _delta_log — добавляет новую колонку. Новые Parquet файлы содержат колонку. Старые файлы — нет (при чтении вернут NULL).Успех: schema обновлена +
Запись успешна. Новая колонка добавлена в metadata. При чтении старых файлов без этой колонки — значение NULL.Поддерживаемые операции
| Операция | Поддержка | Механизм |
|---|---|---|
| Добавление колонки | mergeSchema или ALTER TABLE ADD COLUMN | |
| Удаление колонки | ALTER TABLE DROP COLUMN (column mapping mode) | |
| Переименование | ALTER TABLE RENAME COLUMN (column mapping mode) | |
| Изменение порядка | Не поддерживается | |
| Type widening | byte→short→int→long, float→double, date→timestamp | |
| Изменение nullability | NOT NULL → nullable (обратно — нельзя без rewrite) |
Type Widening
Delta Lake поддерживает безопасное расширение типов без rewrite:
-- Включить type widening для таблицы
ALTER TABLE users SET TBLPROPERTIES ('delta.enableTypeWidening' = 'true');
-- Теперь можно менять тип
ALTER TABLE users ALTER COLUMN age TYPE BIGINT; -- int → bigint
Безопасные widening-пути: byte → short → int → long, float → double, date → timestampNTZ.
Column mapping mode (delta.columnMapping.mode = 'name' или 'id') — обязательное условие для rename/drop операций. По умолчанию Delta использует позиционный маппинг (column 0 = первая колонка), что не позволяет переименовывать или удалять. Включение column mapping — одноразовая операция, обратно отключить нельзя.
Apache Iceberg: ID-based Column Tracking
Iceberg — наиболее мощный формат с точки зрения schema evolution. Ключевое отличие: каждая колонка имеет уникальный integer ID, который никогда не переиспользуется.
Как работает ID-based tracking
Result: id=1 +, name=2 +, email=3 SKIP, phone=4 → NULL
При чтении Iceberg сопоставляет колонки Parquet файла с текущей схемой по field-id. Колонка с id=3 (email) удалена из schema → пропускается. Колонка id=4 (phone) — нет в файле → NULL.Все операции Iceberg
Iceberg поддерживает полный набор schema evolution операций — без rewrite данных:
| Операция | Поддержка | Как работает |
|---|---|---|
| Добавление колонки | Новый field-id, старые файлы → NULL | |
| Удаление колонки | Field-id помечается deleted, при чтении — пропускается | |
| Переименование | Меняется имя, field-id остаётся — маппинг не ломается | |
| Изменение порядка | Порядок — metadata only, файлы не перезаписываются | |
| Type widening | int→long, float→double, decimal precision increase | |
| Вложенные структы | Поля внутри struct/map/list тоже имеют field-id |
Partition Evolution
Уникальная фича Iceberg — partition evolution: можно менять partition scheme без rewrite:
-- Начали с партиционирования по дням
ALTER TABLE events ADD PARTITION FIELD day(event_time);
-- Через год: данных слишком много, меняем на часы
ALTER TABLE events REPLACE PARTITION FIELD day(event_time) WITH hour(event_time);
Iceberg хранит partition spec per-snapshot. Старые файлы остаются в дневных партициях, новые — в часовых. При query planning Iceberg использует оба spec для partition pruning.
Partition evolution — одно из главных преимуществ Iceberg над Delta Lake. В Delta Lake изменение partitioning требует полной перезаписи таблицы (REPLACE TABLE). В Iceberg — одна metadata-операция.
Iceberg Spec v3
Spec v3 (стабилизирован в 2025) добавляет:
- Row-level lineage: отслеживание происхождения строк
- Multi-argument transforms: composite partition transforms
- Default values: значения по умолчанию для новых колонок (ранее всегда NULL)
- Variant type: semi-structured данные (JSON-like) с типизацией
Apache Hudi: Ограниченная эволюция
Hudi исторически фокусировался на upsert/incremental processing, а не на schema evolution. Возможности эволюции ограничены:
| Операция | Поддержка | Комментарий |
|---|---|---|
| Добавление колонки | Поддерживается, старые файлы → NULL | |
| Удаление колонки | ! | Через schema-on-read (колонка игнорируется), не из метаданных |
| Переименование | Не поддерживается нативно | |
| Изменение порядка | Не поддерживается | |
| Type widening | ! | Ограниченный набор: int→long, float→double |
Hudi 1.x значительно улучшил ситуацию с metadata management, но schema evolution по-прежнему не является фокусом проекта. Если вам критичны rename/drop/reorder — Iceberg или Delta Lake будут лучшим выбором.
Apache Paimon: Streaming-first эволюция
Apache Paimon — table format, оптимизированный для streaming use cases (Flink-native).
Schema evolution в Paimon:
| Операция | Поддержка | Механизм |
|---|---|---|
| Добавление колонки | ALTER TABLE ADD COLUMN | |
| Удаление колонки | ALTER TABLE DROP COLUMN | |
| Переименование | ALTER TABLE RENAME COLUMN | |
| Изменение типа | Ограниченный набор safe casts | |
| Partition evolution | Требует recreate таблицы |
Paimon использует schema ID для версионирования — каждый data file ссылается на конкретную версию schema. При чтении файлов с другой schema version — Paimon выполняет schema reconciliation (аналогично Avro ResolvingDecoder, но на уровне колоночных файлов).
Streaming-first особенность: Paimon поддерживает schema evolution в streaming write через Flink CDC:
-- Flink SQL: автоматическая синхронизация schema из MySQL CDC
CREATE TABLE paimon_users WITH (
'merge-engine' = 'deduplicate'
) AS TABLE mysql_users;
-- Если в MySQL добавляется колонка — Paimon автоматически обновляет schema
Сравнительная матрица: Table Format Evolution
Ключевой архитектурный паттерн: ID vs Position vs Name
Различия в schema evolution между table formats сводятся к фундаментальному вопросу: как формат идентифицирует колонки.
Этот паттерн мы уже видели в уроке 01: Avro использует имена (+ aliases), Protobuf — числовые ID, Parquet/ORC — позиции. Table formats повторяют ту же историю на уровне metadata layer.
Практические рекомендации
Для новых проектов
- Если schema evolution критична (аналитический data lake с частыми изменениями) → Iceberg. ID-based tracking + partition evolution = минимальные ограничения.
- Если Spark/Databricks — основной движок → Delta Lake. Включите column mapping mode (
name) сразу при создании таблицы — переключить позже нельзя без миграции. - Если streaming-first (Flink CDC) → Paimon. Автоматическая синхронизация schema из upstream sources.
- Если уже на Hudi → планируйте schema changes как add-only. Rename/drop — через создание view или materialized table.
Миграция между форматами
Delta Lake, Iceberg и Hudi хранят данные в одних и тех же Parquet файлах. Разница — в metadata. Это позволяет:
- UniForm (Delta Lake): генерирует Iceberg metadata поверх Delta Parquet файлов
- Apache XTable (incubating): конвертирует metadata между Delta ↔ Iceberg ↔ Hudi
- Одни и те же Parquet файлы — три разных metadata view
UniForm и XTable не конвертируют данные — они транслируют metadata. Schema evolution семантика при этом определяется целевым форматом. Iceberg metadata даёт ID-based tracking даже поверх Delta-записанных файлов.
Итоги
Schema evolution в table formats — это metadata layer поверх Parquet/ORC, который снимает ограничения нижележащих форматов:
- Iceberg — наиболее мощный: ID-based tracking, полный набор операций (add/drop/rename/reorder), partition evolution, spec v3
- Delta Lake — schema enforcement по умолчанию (защита от случайных изменений) + evolution через mergeSchema/column mapping, type widening
- Hudi — минимальная эволюция (add columns), фокус на upsert/incremental, не на schema changes
- Paimon — streaming-first: автоматическая синхронизация schema из CDC sources, add/drop/rename
Ключевой architectural insight: способ идентификации колонок (ID vs name vs position) определяет все возможности эволюции — как на уровне serialization formats, так и на уровне table formats.