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:
ddadmin committed 2026-09-15 07:35:39 -04:00
1 parent ff474f05f6
commit a085b2fc55
11 files changed
+106 -76

No files matched your search

+2 -2
View File
@@ -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
+7 -6
View File
@@ -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
+3 -3
View File
@@ -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
+28 -22
View File
@@ -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
+7 -6
View File
@@ -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