fix(course): исправлена семантика Iceberg snapshots
- Зачем: объяснения CREATE OR REPLACE и rollback расходились с поведением Iceberg 1.10.0 и ломали повторный запуск упражнений. - Что: исправлены материалы и планы модулей 2, 4, 5, 7 и 8; одноразовые таблицы сбрасываются через DROP TABLE PURGE. - Проверка: JSON и Python AST валидны; на Spark 3.5.1, Iceberg 1.10.0, JDBC/MinIO и Trino 478 подтверждены rollback, replace, time travel и повторный сброс до одного snapshot-а.
This commit is contained in:
1 parent
ff474f05f6
commit
a085b2fc55
11 files changed
+106
-76
No files matched your search
@@ -1,7 +1,7 @@
|
||||
# Модуль 2. Ментальная модель Lakehouse: storage, catalog, compute
|
||||
|
||||
**Статус:** `Ready for validation`
|
||||
**Последнее обновление:** `2026-03-07`
|
||||
**Последнее обновление:** `2026-09-15`
|
||||
|
||||
## Цель
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
- запрос к PostgreSQL (каталогу метаданных), чтобы увидеть регистрацию таблицы;
|
||||
- запрос к MinIO (например, s3a list или boto3), чтобы увидеть появление файлов данных (parquet) и метаданных (json/avro).
|
||||
5. Сформулировать самостоятельное задание: студент создает свою таблицу, пишет в нее данные и находит её следы в хранилище и каталоге.
|
||||
6. Включить в конец ноутбука обязательный шаг очистки (cleanup) — удаление таблиц и namespace `module_02`, чтобы не оставлять мусор для следующих модулей.
|
||||
6. Включить в конец ноутбука обязательный шаг очистки (cleanup) — удаление одноразовых таблиц через `DROP TABLE ... PURGE` и namespace `module_02`, чтобы не оставлять данные и метаданные для следующих модулей.
|
||||
7. Описать вопросы для Checkpoint.
|
||||
|
||||
## Checkpoint
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Модуль 4. Первая рабочая Iceberg-таблица и слой bronze
|
||||
|
||||
**Статус:** `Draft`
|
||||
**Последнее обновление:** `2026-03-07`
|
||||
**Последнее обновление:** `2026-09-15`
|
||||
|
||||
## Цель
|
||||
|
||||
@@ -50,11 +50,12 @@
|
||||
|
||||
### Создание и наполнение таблицы: CTAS с явным списком колонок
|
||||
|
||||
Используем `CREATE OR REPLACE TABLE ... USING iceberg AS SELECT col1, col2, ... FROM raw_view` (CTAS). Это:
|
||||
Используем `CREATE OR REPLACE TABLE ... USING iceberg AS SELECT col1, col2, ... FROM raw_view` (CTAS). В настроенном `SparkCatalog` это атомарная замена. Она:
|
||||
- объединяет создание и наполнение в один шаг — понятнее и ближе к `CREATE TABLE AS SELECT` в PostgreSQL;
|
||||
- явный список колонок в SELECT исключает ошибки из-за несовпадения порядка или состава колонок (PRD Risk #3, #5);
|
||||
- `CREATE OR REPLACE` обеспечивает идемпотентность: повторный запуск пересоздаёт таблицу, snapshot history остаётся чистой;
|
||||
- trade-off: `CREATE OR REPLACE` уничтожает историю — в production это опасно, но для первичной загрузки учебного bronze допустимо. В Модуле 7 студент увидит, почему в рабочих сценариях нужен другой подход.
|
||||
- повторный запуск приводит текущее содержимое к тому же результату и не дублирует строки;
|
||||
- создаёт новый snapshot и сохраняет прежние snapshot-ы для time travel;
|
||||
- заменяет текущие данные, схему и partition spec; новый snapshot не имеет `parent_id`, поэтому прежние snapshot-ы перестают быть предками текущего состояния. Отдельная именованная ветка при этом не создаётся.
|
||||
|
||||
В Секции 2 DDL с явной схемой показывается как справочный пример (не выполняется) — чтобы студент увидел знакомую форму `CREATE TABLE (col type, ...)`.
|
||||
|
||||
@@ -105,7 +106,7 @@ Bronze нужен Модулю 5. Spark-сессия останавливает
|
||||
|
||||
### Секция 3: Загрузка raw → bronze (CTAS)
|
||||
- **[md]** Аналогия с `CREATE TABLE ods AS SELECT ... FROM stg` в Greenplum. Почему явный список колонок: `SELECT *` привязывается к порядку, а не к именам — это хрупко и может дать некорректное сопоставление полей. Используем `CREATE OR REPLACE TABLE ... AS SELECT col1, col2, ... FROM raw_yellow` — CTAS одновременно создаёт таблицу и наполняет данными.
|
||||
- **[md]** Идемпотентность: `CREATE OR REPLACE` при повторном запуске пересоздаёт таблицу с чистого листа — snapshot history остаётся чистой (один snapshot = одна загрузка). Trade-off: в production `CREATE OR REPLACE` — разрушительная операция (удаляет историю), но для первичной загрузки учебного bronze это осознанный выбор ради простоты. В Модуле 7 увидим, почему в рабочих сценариях нужен другой подход.
|
||||
- **[md]** Повторяемость: `CREATE OR REPLACE` при повторном запуске атомарно заменяет текущее содержимое и не дублирует строки, но добавляет новый snapshot. Прежние snapshot-ы сохраняются для time travel и перестают быть предками текущего состояния. Trade-off: операция может заменить схему и partition spec, поэтому для учебной полной загрузки она удобна, а в рабочих инкрементальных сценариях требует осторожности. В Модуле 7 семантика разбирается подробнее.
|
||||
- **[code]** Чтение raw → `createOrReplaceTempView("raw_yellow")` → `CREATE OR REPLACE TABLE lakehouse.bronze.nyc_taxi_yellow USING iceberg AS SELECT VendorID, tpep_pickup_datetime, ... FROM raw_yellow` (CTAS с явным списком колонок).
|
||||
- **[code]** Верификация: count bronze vs count raw — должны совпасть.
|
||||
|
||||
@@ -216,7 +217,7 @@ Bronze нужен Модулю 5. Spark-сессия останавливает
|
||||
- **Схема raw PARQUET.** Фиксируем колонки NYC Taxi 2024 в DDL. Если студент скачал другой год — DDL может не совпасть. Пояснение: придерживаться инструкции из `START_HERE.md`.
|
||||
- **Column case sensitivity.** NYC Taxi PARQUET использует mixed case (`VendorID`, `PULocationID`). Iceberg хранит имена case-sensitive. DDL должен совпадать с именами в PARQUET. Проверить на стенде при реализации.
|
||||
- **Мелкие файлы.** Spark может создать много мелких data files (по числу partitions). Нормально для Module 4 — тема compaction отложена до Модуля 8. Упомянуть, не решать.
|
||||
- **Идемпотентность.** Используем `CREATE OR REPLACE TABLE ... AS SELECT` (CTAS) для повторных запусков — пересоздаёт таблицу целиком, snapshot history остаётся чистой. Это проще и надёжнее, чем `DELETE FROM` + `INSERT INTO` (который усложняет snapshot history и требует проверки поддержки `DELETE` на текущем стеке). Компромисс: `CREATE OR REPLACE` уничтожает предыдущие snapshots, но для учебного bronze это допустимо. В markdown явно объяснить этот trade-off.
|
||||
- **Повторный запуск.** Используем `CREATE OR REPLACE TABLE ... AS SELECT` (CTAS): текущее содержимое заменяется атомарно и строки не дублируются. При каждом запуске появляется новый snapshot; предыдущие snapshot-ы сохраняются для time travel, но перестают быть предками текущего. В markdown явно объяснить этот trade-off и риск замены схемы и partition spec.
|
||||
|
||||
## Out of Scope
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Модуль 5. Слой silver и воспроизводимые трансформации
|
||||
|
||||
**Статус:** `Ready for validation`
|
||||
**Последнее обновление:** `2026-03-07`
|
||||
**Последнее обновление:** `2026-09-15`
|
||||
|
||||
## Цель
|
||||
|
||||
@@ -148,7 +148,7 @@ Silver нужен Модулям 6 и 8. Spark-сессия останавлив
|
||||
|
||||
### Секция 7: Создание silver-таблицы
|
||||
- **[md]** Используем CTAS с `CREATE OR REPLACE`. Namespace `lakehouse.silver`.
|
||||
- **[md]** Явное предупреждение о `CREATE OR REPLACE`: это полная перезапись таблицы с потерей предыдущего состояния и snapshot-истории. Для учебного silver это осознанный компромисс ради идемпотентности (повторный запуск ноутбука пересоздаёт таблицу с чистого листа). В рабочих сценариях это разрушительная операция — в Модуле 7 студент увидит, почему нужен другой подход. Параллель: `DROP TABLE + CREATE TABLE AS SELECT` в PostgreSQL.
|
||||
- **[md]** Явное предупреждение о `CREATE OR REPLACE`: операция атомарно заменяет текущее содержимое, схему и partition spec, но сохраняет прежние snapshot-ы для time travel. Новый snapshot не имеет `parent_id`, поэтому старые snapshot-ы перестают быть предками текущего состояния. Для учебного silver это осознанный компромисс ради воспроизводимого результата без дублирования строк. В Модуле 7 студент подробно сравнит замену и rollback.
|
||||
- **[code]** `CREATE NAMESPACE IF NOT EXISTS lakehouse.silver`. Регистрация temp view. CTAS с явным списком демо-схемы: 24 колонки (19 оригинальных + 5 новых). Это каноническая схема, на которую опираются Модули 6 и 8.
|
||||
- **[md]** Предупреждение о времени выполнения (2-4 мин на 3 мес данных).
|
||||
- **[code]** Верификация: count, printSchema.
|
||||
@@ -276,7 +276,7 @@ speed_mph DOUBLE (E4: новая, trip_distance / (tr
|
||||
- **Время CTAS.** JOIN + трансформации — на 30-50% медленнее bronze CTAS. На 3 мес (~7 млн строк): 2-4 мин. Extended: до 5-10 мин. Предупреждение в markdown.
|
||||
- **Column case sensitivity.** Новые колонки (lowercase) рядом с оригинальными (mixed case: VendorID, PULocationID). Нормально для Iceberg, но добавить пояснение.
|
||||
- **trip_duration_minutes отрицательные.** Если dropoff < pickup — длительность отрицательна. Упомянуть как наблюдение, не фильтровать в демо (потенциально — для самостоятельного задания или будущего правила).
|
||||
- **Идемпотентность.** `CREATE OR REPLACE TABLE ... AS SELECT` (CTAS) для повторных запусков — тот же паттерн, что в Модуле 4. Пересоздаёт таблицу целиком.
|
||||
- **Повторный запуск.** `CREATE OR REPLACE TABLE ... AS SELECT` (CTAS) — тот же паттерн, что в Модуле 4. Атомарно заменяет текущее содержимое, не дублирует строки и добавляет новый snapshot, сохраняя прежние для time travel.
|
||||
|
||||
## Out of Scope
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Модуль 7. Безопасная работа с таблицами: schema evolution и time travel
|
||||
|
||||
**Статус:** `Ready for validation`
|
||||
**Последнее обновление:** `2026-03-08`
|
||||
**Последнее обновление:** `2026-09-15`
|
||||
|
||||
## Цель
|
||||
|
||||
@@ -69,9 +69,10 @@
|
||||
|
||||
Iceberg предоставляет stored procedure `CALL system.rollback_to_snapshot(table, snapshot_id)`. Это:
|
||||
|
||||
- создаёт НОВЫЙ snapshot, указывающий на данные старого — история не теряется;
|
||||
- переключает текущее состояние на существующий snapshot-предок и не создаёт новый snapshot;
|
||||
- добавляет запись в `.history` о смене текущего состояния, а прежние snapshot-ы остаются доступными;
|
||||
- старые data files не удаляются — они остаются до процедуры `expire_snapshots` / vacuum (Модуль 8);
|
||||
- семантически аналогично `git revert` (новый коммит, отменяющий изменение), а не `git reset --hard` (удаление истории).
|
||||
- отдалённо напоминает перемещение указателя ветки в git, но snapshot-ы Iceberg остаются доступными до явного `expire_snapshots`.
|
||||
|
||||
Точный синтаксис stored procedure зависит от версии Iceberg и Spark — нужна проверка на стенде при реализации.
|
||||
|
||||
@@ -171,11 +172,12 @@ except Exception as e:
|
||||
### Секция 6: Подготовка демо-таблицы с несколькими snapshot-ами
|
||||
|
||||
- **[md]** Для экспериментов с time travel и восстановлением создаём отдельную таблицу. Silver не трогаем — он нужен для Модуля 8. Создаём таблицу с небольшим подмножеством silver и наращиваем snapshot-историю через `INSERT INTO`.
|
||||
- **[code]** Создание `lakehouse.default.taxi_changes_demo` через CTAS (1000 строк из silver):
|
||||
- **[code]** Удаление одноразовой демо-таблицы через `DROP TABLE IF EXISTS ... PURGE`, затем создание `lakehouse.default.taxi_changes_demo` через CTAS (1000 строк из silver). Явное удаление гарантирует один начальный snapshot при повторном запуске:
|
||||
|
||||
```python
|
||||
spark.sql("DROP TABLE IF EXISTS lakehouse.default.taxi_changes_demo PURGE")
|
||||
spark.sql("""
|
||||
CREATE OR REPLACE TABLE lakehouse.default.taxi_changes_demo
|
||||
CREATE TABLE lakehouse.default.taxi_changes_demo
|
||||
USING iceberg AS
|
||||
SELECT VendorID, tpep_pickup_datetime, tpep_dropoff_datetime,
|
||||
passenger_count, trip_distance, fare_amount, total_amount,
|
||||
@@ -185,7 +187,7 @@ spark.sql("""
|
||||
""")
|
||||
```
|
||||
|
||||
- **[code]** Проверка: count = 1000. Snapshot-ы: 1 snapshot (overwrite — от CTAS).
|
||||
- **[code]** Проверка: count = 1000. Snapshot-ы: 1 snapshot (`append` от CTAS).
|
||||
- **[code]** INSERT ещё 500 строк -> snapshot 2:
|
||||
|
||||
```python
|
||||
@@ -293,15 +295,17 @@ spark.sql(f"""
|
||||
|
||||
#### 8c: Что произошло со snapshot-историей
|
||||
|
||||
- **[code]** Snapshot-история: теперь 4 записи. Четвёртый snapshot — rollback, но его данные соответствуют snapshot 2.
|
||||
- **[md]** Rollback не удаляет историю и не удаляет файлы. Он создаёт новый snapshot, указывающий на данные предыдущего состояния. Аналогия:
|
||||
- **[code]** `.snapshots`: по-прежнему 3 snapshot-а. `.history`: появилась четвёртая запись, снова указывающая на snapshot 2; ошибочный snapshot помечен `is_current_ancestor = false`.
|
||||
- **[md]** Rollback не удаляет историю и файлы, но и не создаёт новый snapshot. Он переключает текущее состояние на существующий snapshot-предок. Сравнение:
|
||||
|
||||
| | git | Iceberg |
|
||||
| Свойство | `rollback_to_snapshot` | `CREATE OR REPLACE TABLE ... AS SELECT` |
|
||||
|---|---|---|
|
||||
| Откат с сохранением истории | `git revert` (новый коммит) | `rollback_to_snapshot` (новый snapshot) |
|
||||
| Откат с удалением истории | `git reset --hard` | `CREATE OR REPLACE` (уничтожает snapshot-ы) |
|
||||
| Создаёт новый snapshot | Нет | Да |
|
||||
| Что становится текущим | Существующий snapshot-предок | Новый snapshot с результатом `SELECT` |
|
||||
| Что происходит со старыми snapshot-ами | Сохраняются | Сохраняются для time travel |
|
||||
| Цепочка предков | Возвращаемся к предку | Новый snapshot не имеет `parent_id`; прежние snapshot-ы перестают быть предками текущего |
|
||||
|
||||
- **[md]** Старые data files (включая «плохие» строки) остаются в MinIO до процедуры `expire_snapshots` — это тема Модуля 8. Rollback не чистит storage, он только меняет указатель текущего snapshot-а.
|
||||
- **[md]** `CREATE OR REPLACE` не создаёт отдельную именованную ветку: текущей остаётся `main`. Старые data files сохраняются до процедуры `expire_snapshots` — это тема Модуля 8.
|
||||
|
||||
### Секция 9: RENAME COLUMN и влияние на downstream
|
||||
|
||||
@@ -325,7 +329,7 @@ except Exception as e:
|
||||
- **[md]** Три ошибки, на которые обращает внимание PRD курса:
|
||||
|
||||
**Ошибка 1: Слепой `CREATE OR REPLACE`.**
|
||||
В Модулях 4 и 5 мы использовали `CREATE OR REPLACE TABLE ... AS SELECT` для создания bronze и silver. Это был осознанный компромисс: первичная загрузка, нет предыдущей истории, которую нужно сохранять. Но в рабочих сценариях `CREATE OR REPLACE` — деструктивная операция: она уничтожает ВСЮ snapshot-историю таблицы. Если у таблицы было 100 snapshot-ов, после `CREATE OR REPLACE` останется один. Time travel к предыдущим состояниям станет невозможен.
|
||||
В Модулях 4 и 5 мы использовали `CREATE OR REPLACE TABLE ... AS SELECT` для воспроизводимой полной загрузки bronze и silver. В `SparkCatalog` операция атомарно создаёт новый snapshot и сохраняет прежние snapshot-ы для time travel. Но она заменяет текущее содержимое, схему и partition spec. Новый snapshot не имеет `parent_id`, поэтому прежние snapshot-ы перестают быть предками текущего состояния. Если нужно перезаписать данные, сохранив текущую схему и партиционирование, следует использовать `INSERT OVERWRITE`.
|
||||
|
||||
**Ошибка 2: Изменение схемы без проверки downstream.**
|
||||
`RENAME COLUMN` мы только что видели: данные целы, но запросы со старым именем ломаются. `ALTER COLUMN TYPE` (расширение INT -> BIGINT) безопасен для данных, но downstream-системы могут интерпретировать новый тип по-другому. Перед любым изменением схемы нужно понимать, кто читает эту таблицу.
|
||||
@@ -335,12 +339,13 @@ except Exception as e:
|
||||
|
||||
### Секция 11: Самостоятельное задание
|
||||
|
||||
- **[md]** Пересоздаём демо-таблицу с чистого листа. После демонстраций в Секциях 6-9 её схема изменена (RENAME), а snapshot-история содержит наши учебные эксперименты. Для самостоятельной работы нужна чистая таблица — студент сам построит snapshot-историю и выполнит rollback.
|
||||
- **[code]** Пересоздание демо-таблицы (тот же CTAS, что в Секции 6):
|
||||
- **[md]** Готовим демо-таблицу с чистого листа. После демонстраций в Секциях 6-9 её схема изменена (RENAME), а snapshot-история содержит учебные эксперименты. Поскольку `CREATE OR REPLACE` сохраняет snapshot-ы, одноразовую таблицу удаляем через `PURGE` и создаём заново.
|
||||
- **[code]** Сброс и новое создание демо-таблицы (тот же CTAS, что в Секции 6):
|
||||
|
||||
```python
|
||||
spark.sql(f"DROP TABLE IF EXISTS {DEMO_TABLE} PURGE")
|
||||
spark.sql("""
|
||||
CREATE OR REPLACE TABLE lakehouse.default.taxi_changes_demo
|
||||
CREATE TABLE lakehouse.default.taxi_changes_demo
|
||||
USING iceberg AS
|
||||
SELECT VendorID, tpep_pickup_datetime, tpep_dropoff_datetime,
|
||||
passenger_count, trip_distance, fare_amount, total_amount,
|
||||
@@ -361,7 +366,7 @@ print(f"Демо-таблица пересоздана: {spark.table(DEMO_TABLE)
|
||||
|
||||
**Задача 4 (recovery).** INSERT 100 строк с `fare_amount = -1` и `pickup_zone = 'STUDENT_ERROR'` в демо-таблицу. Убедись, что «плохие» строки появились (`SELECT count(*) WHERE fare_amount < 0`). Затем выполни `rollback_to_snapshot` к snapshot-у ДО этого INSERT-а. Проверь, что таблица восстановлена: `fare_amount < 0` -> 0 строк, общий count вернулся к предыдущему значению.
|
||||
|
||||
**Задача 5 (ответ).** Ответь в markdown-ячейке: чем `rollback_to_snapshot` отличается от `CREATE OR REPLACE TABLE ... AS SELECT * FROM table VERSION AS OF <snapshot_id>`? Что происходит с историей snapshot-ов в каждом случае?
|
||||
**Задача 5 (ответ).** Сравни `rollback_to_snapshot` и `CREATE OR REPLACE TABLE ... AS SELECT * FROM table VERSION AS OF <snapshot_id>`: какая операция создаёт новый snapshot, что становится текущим и как меняется цепочка `parent_id`?
|
||||
|
||||
- **[code]** Пустая ячейка: `# Ваш код: ALTER TABLE ADD COLUMNS (quality_flag)`
|
||||
- **[code]** Пустая ячейка: `# Ваш код: проверка из Spark и Trino`
|
||||
@@ -390,7 +395,7 @@ print(f"Демо-таблица пересоздана: {spark.table(DEMO_TABLE)
|
||||
### Секция 13: Завершение
|
||||
|
||||
- **[md]** Удаляем демо-таблицу — она больше не нужна. Silver остаётся для Модуля 8 (колонка `loaded_at` — демонстрационная; Модуль 8 не зависит от её наличия).
|
||||
- **[code]** `spark.sql("DROP TABLE IF EXISTS lakehouse.default.taxi_changes_demo")`.
|
||||
- **[code]** `spark.sql("DROP TABLE IF EXISTS lakehouse.default.taxi_changes_demo PURGE")`.
|
||||
- **[md]** Итог модуля:
|
||||
- Schema evolution (ADD COLUMN) — безопасная metadata-only операция, видна из всех движков.
|
||||
- Snapshot-ы — автоматическая история изменений данных, встроенная в Iceberg.
|
||||
@@ -433,13 +438,14 @@ print(f"Демо-таблица пересоздана: {spark.table(DEMO_TABLE)
|
||||
|
||||
## Риски
|
||||
|
||||
- **`rollback_to_snapshot` синтаксис.** Точный синтаксис stored procedure зависит от версии Iceberg и Spark. Варианты: именованные параметры (`table => '...', snapshot_id => ...`), позиционные (`'table', snapshot_id`). Нужна проверка на стенде. Fallback: `CREATE OR REPLACE TABLE ... AS SELECT * FROM table VERSION AS OF <snapshot_id>` (менее элегантно, уничтожает snapshot-историю, но работает).
|
||||
- **`rollback_to_snapshot` синтаксис.** Именованные параметры (`table => '...', snapshot_id => ...`) проверены на Spark 3.5.1 и Iceberg 1.10.0. `CREATE OR REPLACE TABLE ... AS SELECT * FROM table VERSION AS OF <snapshot_id>` не является эквивалентом rollback: он переписывает данные в новый snapshot без родителя и может заменить схему и partition spec.
|
||||
- **`ALTER TABLE ADD COLUMNS` vs `ADD COLUMN`.** В Spark SQL с Iceberg синтаксис — `ADD COLUMNS` (множественное число). Проверить на стенде.
|
||||
- **Количество snapshot-ов silver.** Если студент перезапускал Модуль 5 через `CREATE OR REPLACE`, у silver может быть только 1 snapshot. Это нормально — `ADD COLUMN` не создаёт snapshot, но демо-таблица наращивает snapshot-историю через INSERT.
|
||||
- **Time travel через Trino.** Синтаксис `FOR VERSION AS OF` может отличаться в зависимости от версии Trino. Проверить на стенде.
|
||||
- **Количество snapshot-ов silver.** После первого запуска Модуля 5 у silver будет 1 snapshot; каждый повторный `CREATE OR REPLACE` добавляет новый, сохраняя старые для time travel. `ADD COLUMN` не создаёт data snapshot, а демо-таблица наращивает snapshot-историю через INSERT.
|
||||
- **Time travel через Trino.** Синтаксис `FOR VERSION AS OF <snapshot_id>` проверен на Trino 478 с общим JDBC-каталогом и MinIO.
|
||||
- **Floating-point в snapshot_id.** Snapshot ID — длинное число (long). При передаче в f-string и SQL нужно убедиться, что Python не конвертирует его в float. Использовать `int(snapshot_id)`.
|
||||
- **RENAME COLUMN ошибка в Trino.** Trino-запрос со старым именем колонки должен упасть с ошибкой. Обернуть в try/except с пояснением в markdown.
|
||||
- **Идемпотентность.** Повторный запуск ноутбука: `ALTER TABLE ADD COLUMNS (loaded_at TIMESTAMP)` упадёт, если колонка уже существует. Решение зафиксировано: обернуть в try/except с проверкой `"already exists"` в тексте ошибки (см. Секцию 3). Это переносимый подход, не зависящий от поддержки `IF NOT EXISTS` конкретной версией Iceberg. Демо-таблица пересоздаётся через `CREATE OR REPLACE` — идемпотентна.
|
||||
- **Повторный запуск.** `ALTER TABLE ADD COLUMNS (loaded_at TIMESTAMP)` упадёт, если колонка уже существует. Решение зафиксировано: обернуть в try/except с проверкой `"already exists"` в тексте ошибки (см. Секцию 3). Одноразовая демо-таблица перед каждой исходной CTAS удаляется через `DROP TABLE ... PURGE`, чтобы проверки числа snapshot-ов были воспроизводимы.
|
||||
- **Сброс демо-таблицы.** Два последовательных цикла `DROP TABLE ... PURGE` + `CREATE TABLE ... AS SELECT` проверены на стенде: каждый цикл начинает историю ровно с одного snapshot-а.
|
||||
- **Влияние на Модуль 8.** Колонка `loaded_at` — демонстрационная. Модуль 8 не должен от неё зависеть. Если студент перезапустит Модуль 5 после Модуля 7, silver будет без `loaded_at` — Модуль 8 должен работать в обоих случаях.
|
||||
|
||||
## Out of Scope
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Модуль 8. Базовое обслуживание таблиц и финальная практика
|
||||
|
||||
**Статус:** `Ready for validation`
|
||||
**Последнее обновление:** `2026-03-08`
|
||||
**Последнее обновление:** `2026-09-15`
|
||||
|
||||
## Цель
|
||||
|
||||
@@ -166,11 +166,12 @@ Greenplum AppendOnly-таблицы имеют аналогичные пробл
|
||||
|
||||
- **[md]** Каждый `INSERT INTO` в Iceberg создаёт новые data files. Если вставлять данные мелкими порциями (частые микробатчи, ручные INSERT-ы, инкрементальные загрузки), таблица накапливает множество мелких файлов. Это замедляет чтение: движку приходится открывать и читать каждый файл отдельно. Проблема знакома в мире Hadoop/Hive — и в Iceberg она решается через compaction.
|
||||
|
||||
- **[code]** Создание демо-таблицы с 500 строками (CTAS из silver):
|
||||
- **[code]** Удаление одноразовой демо-таблицы через `DROP TABLE IF EXISTS ... PURGE`, затем создание с 500 строками (CTAS из silver). Это гарантирует один начальный snapshot и воспроизводимое число snapshot-ов при повторном запуске:
|
||||
|
||||
```python
|
||||
spark.sql(f"DROP TABLE IF EXISTS {DEMO_TABLE} PURGE")
|
||||
spark.sql(f"""
|
||||
CREATE OR REPLACE TABLE {DEMO_TABLE}
|
||||
CREATE TABLE {DEMO_TABLE}
|
||||
USING iceberg AS
|
||||
SELECT VendorID, tpep_pickup_datetime, tpep_dropoff_datetime,
|
||||
passenger_count, trip_distance, fare_amount, total_amount,
|
||||
@@ -378,7 +379,7 @@ except Exception as e:
|
||||
### Секция 8: Удаление демо-таблицы
|
||||
|
||||
- **[md]** Демо-таблица для обслуживания больше не нужна.
|
||||
- **[code]** `spark.sql(f"DROP TABLE IF EXISTS {DEMO_TABLE}")`. Вывод подтверждения.
|
||||
- **[code]** `spark.sql(f"DROP TABLE IF EXISTS {DEMO_TABLE} PURGE")`. Вывод подтверждения.
|
||||
|
||||
### Секция 9: Финальная практика — сквозной end-to-end сценарий
|
||||
|
||||
@@ -467,7 +468,7 @@ except Exception as e:
|
||||
- **Исполняемый формат:** Spark SQL для compaction и expire_snapshots (stored procedures). Trino-запросы через Python `trino` клиент для downstream-верификации в финальной практике.
|
||||
- **DBeaver-подсказки:** markdown-заметки рядом с Trino-ячейками (паттерн Модуля 6).
|
||||
- **Helper-код:** `trino_query(sql)` — переиспользование из Модуля 6. Файловая статистика через metadata-таблицы Iceberg (`.files`, `.snapshots`).
|
||||
- **Cleanup:** DROP демо-таблицы `taxi_maintenance_demo`. Финальная практика: DROP таблиц в namespace `final`, DROP namespace. Silver и bronze сохраняются.
|
||||
- **Cleanup:** `DROP TABLE ... PURGE` для одноразовой демо-таблицы `taxi_maintenance_demo`. Финальная практика: DROP таблиц в namespace `final`, DROP namespace. Silver и bronze сохраняются.
|
||||
- **Порядок:** проблема мелких файлов → compaction → expire_snapshots → Greenplum-параллель → ошибки → самостоятельное задание (обслуживание) → финальная практика (end-to-end) → checkpoint (модуль + курс) → итог курса.
|
||||
|
||||
## Checkpoint
|
||||
@@ -505,7 +506,7 @@ except Exception as e:
|
||||
- **Время выполнения compaction.** `rewrite_data_files` перечитывает и перезаписывает данные. На демо-таблице (~1300 строк) — секунды. На silver (миллионы строк) — потенциально минуты. Предупреждение в markdown.
|
||||
- **Финальная практика: путь к raw-данным.** Путь `s3a://lakehouse/raw/nyc_taxi/yellow_tripdata_*.parquet` соответствует маршруту загрузки из Модуля 3. Если студент по какой-то причине загрузил файлы по другому пути, он увидит ошибку при чтении. Добавить подсказку в markdown: «Если путь отличается от указанного, посмотри Модуль 3.»
|
||||
- **Финальная практика: объём INSERT-ов.** При LIMIT 5000 для bronze и 3 INSERT по 300 строк в silver — объём данных минимален. Compaction может быть no-op (файлы и так небольшие). Решение: в инструкции явно указать, что цель — увидеть изменение количества файлов и snapshot-ов, а не добиться значимого улучшения производительности.
|
||||
- **Идемпотентность.** `CREATE OR REPLACE TABLE` для демо-таблицы — идемпотентна. Финальная практика: если студент запускает повторно, namespace и таблицы могут уже существовать. `CREATE NAMESPACE IF NOT EXISTS` и `CREATE OR REPLACE TABLE` обеспечивают идемпотентность. DROP в конце — `IF EXISTS`.
|
||||
- **Повторный запуск.** Перед созданием одноразовой демо-таблицы выполняется `DROP TABLE ... PURGE`, иначе `CREATE OR REPLACE` сохранил бы прежние snapshot-ы и сломал ожидаемое число 9. В финальной практике студент должен учитывать уже существующие namespace и таблицы после прерванного запуска; cleanup в конце выполняется через `IF EXISTS`.
|
||||
- **Независимость от Модуля 7.** Ноутбук не должен ссылаться на колонку `loaded_at` или результаты Модуля 7 как на обязательные предусловия. Snapshot-концепции кратко повторяются в контексте expire_snapshots.
|
||||
|
||||
## Out of Scope
|
||||
|
||||
Reference in new issue
Block a user