Apache Iceberg. Упражнение: Эволюция схемы
Практика эволюции схемы
Содержание
Пора применить теорию на практике. В этом упражнении вы создадите таблицу Iceberg, последовательно измените её схему и убедитесь, что все операции безопасны и обратимы.
Цели
- Создать таблицу
user_eventsс базовой схемой. - Добавить новые колонки без перезаписи данных.
- Удалить колонку и проверить, что данные остаются доступны через time travel.
- Переименовать колонку и убедиться, что исторические запросы работают.
- Расширить тип колонки (widening).
- Изменить порядок колонок для улучшения читаемости.
Предварительные требования
- Локальное окружение Iceberg (как в упражнении «Каталоги»).
- Доступ к Spark SQL или другому движку, поддерживающему Iceberg.
- Около 20 минут.
Шаг 1: Запуск окружения
Если у вас ещё не запущено локальное окружение, выполните команды из упражнения «Каталоги»:
cd iceberg-course-exercisesdocker compose up -d
Подключитесь к Spark SQL:
docker compose exec -it spark-iceberg spark-sql --conf "spark.hadoop.hive.cli.print.header=true"
Убедитесь, что каталог demo доступен:
SHOW CATALOGS;
Создайте базу данных для упражнения:
CREATE NAMESPACE demo.schema_evolution;USE demo.schema_evolution;
Шаг 2: Создание исходной таблицы
Создадим таблицу user_events с тремя колонками:
CREATE TABLE user_events ( event_id BIGINT, user_id INT, event_type STRING)USING icebergTBLPROPERTIES ('format-version'='2');
Вставим несколько тестовых записей:
INSERT INTO user_events VALUES (1, 1001, 'login'), (2, 1002, 'purchase'), (3, 1001, 'logout');
Проверим, что данные записались:
SELECT * FROM user_events ORDER BY event_id;
| event_id | user_id | event_type |
|---|---|---|
| 1 | 1001 | login |
| 2 | 1002 | purchase |
| 3 | 1001 | logout |
Шаг 3: Добавление колонок
Маркетинг просит добавить информацию об устройстве и версии браузера. Выполним:
ALTER TABLE user_events ADD COLUMNS ( device_type STRING, browser_version STRING);
Теперь вставим новую запись с заполненными новыми колонками:
INSERT INTO user_events VALUES (4, 1003, 'view', 'mobile', 'Chrome 120');
Запросим таблицу:
SELECT * FROM user_events ORDER BY event_id;
| event_id | user_id | event_type | device_type | browser_version |
|---|---|---|---|---|
| 1 | 1001 | login | NULL | NULL |
| 2 | 1002 | purchase | NULL | NULL |
| 3 | 1001 | logout | NULL | NULL |
| 4 | 1003 | view | mobile | Chrome 120 |
Вы видите, что старые записи имеют NULL в новых колонках, а новая запись — заполненные значения. Это обычное поведение реляционных баз, но в Iceberg оно достигнуто без перезаписи файлов.
Шаг 4: Удаление колонки
Через некоторое время выясняется, что browser_version больше не нужна. Удалим её:
ALTER TABLE user_events DROP COLUMN browser_version;
Запросим таблицу — колонка исчезла:
SELECT * FROM user_events ORDER BY event_id;
| event_id | user_id | event_type | device_type |
|---|---|---|---|
| 1 | 1001 | login | NULL |
| 2 | 1002 | purchase | NULL |
| 3 | 1001 | logout | NULL |
| 4 | 1003 | view | mobile |
Куда делись данные? Они остались в хранилище, но скрыты от текущего снапшота. Давайте проверим с помощью time travel.
Шаг 5: Time travel к предыдущему снапшоту
Узнаем ID снапшота перед удалением колонки. Сначала посмотрим историю снапшотов:
SELECT snapshot_id, committed_at, operationFROM demo.schema_evolution.user_events.snapshotsORDER BY committed_at;
Запомните snapshot_id того снапшота, где колонка ещё существовала (скорее всего, второй сверху). Используем его в запросе:
| snapshot_id | committed_at | operation |
|---|---|---|
| 6360110623641157946 | 2026-05-17 15:59:41.026 | append |
| 4736823070948074103 | 2026-05-17 16:00:08.225 | append |
SELECT *FROM demo.schema_evolution.user_eventsFOR VERSION AS OF <snapshot_id>ORDER BY event_id;
Вы снова увидите колонку browser_version! Это доказывает, что Iceberg не стирает данные при удалении колонки — он лишь помечает её как удалённую в метаданных.
Шаг 6: Переименование колонки
Колонка event_type кажется недостаточно описательной. Переименуем её в action_category:
ALTER TABLE user_events RENAME COLUMN event_type TO action_category;
Проверим, что переименование сработало:
SELECT action_category, user_id FROM user_events ORDER BY event_id;
| action_category | user_id |
|---|---|
| login | 1001 |
| purchase | 1002 |
| logout | 1001 |
| view | 1003 |
Исторические данные остались доступны — они просто теперь фигурируют под новым именем. Если вы выполните time travel к снапшоту до переименования, там колонка будет называться event_type.
Шаг 7: Расширение типа колонки (widening)
Предположим, у вас внезапно появилось более 4,2 миллиарда пользователей (поздравляем!). Колонка user_id типа INT уже не вмещает такие значения. Расширим её до BIGINT:
ALTER TABLE user_events ALTER COLUMN user_id TYPE BIGINT;
Вставьте запись с большим значением user_id:
INSERT INTO user_events VALUES (5, 5000000000, 'click', 'desktop');
Запросите таблицу — всё работает. Старые данные по‑прежнему хранятся как INT, но при чтении автоматически приводятся к BIGINT.
Шаг 8: Изменение порядка колонок
Таблица стала выглядеть неупорядоченно. Давайте переместим action_category сразу после event_id для лучшей читаемости:
ALTER TABLE user_events ALTER COLUMN action_category AFTER event_id;
Проверим схему:
DESCRIBE user_events;
| col_name | data_type | comment |
|---|---|---|
| event_id | bigint | |
| action_category | string | |
| user_id | bigint | |
| device_type | string |
Колонки теперь идут в порядке: event_id, action_category, user_id, device_type. Ни один файл данных не был перезаписан — только метаданные.
Шаг 9: Эволюция комплексного типа (опционально)
Если ваш движок поддерживает, можно попробовать работу со структурами. Создадим новую таблицу с полем‑структурой:
CREATE TABLE user_prefs ( user_id BIGINT, preferences STRUCT<email_notifications:BOOLEAN, color_theme:STRING>)USING iceberg;
Добавим поле в структуру:
ALTER TABLE user_prefs ADD COLUMN preferences.mobile_notifications BOOLEAN;
| col_name | data_type | comment |
|---|---|---|
| user_id | bigint | |
| preferences | struct<email_notifications:boolean,color_theme:string,mobile_notifications:boolean> |
Это демонстрирует, что эволюция схемы работает и для вложенных типов.
Что вы узнали
- Добавление колонок — мгновенная операция, старые записи получают
NULL. - Удаление колонок — данные не стираются, остаются доступны через time travel.
- Переименование колонок — меняется только имя в метаданных, исторические запросы работают.
- Расширение типов — безопасное приведение при чтении без перезаписи.
- Изменение порядка колонок — чисто косметическое изменение, не затрагивающее данные.
- Time travel — мощный инструмент для доступа к предыдущим состояниям схемы и данных.
Эволюция — это не революция. Iceberg позволяет менять схему постепенно, без катастрофических последствий. Как садовник, который подрезает ветви, а не вырубает дерево.
Что дальше? В следующем уроке мы изучим Партицирование и эволюция партиций — как организовать данные для эффективных запросов и менять стратегию партицирования без перезаписи.