refactor(course): перенесена загрузка taxi_zone_lookup из домашки в демо-секцию

- Зачем:
  - демо-ячейки последующих модулей не должны зависеть от домашних заданий предыдущих — модуль 5 использует lookup в демо-коде для JOIN.
- Что:
  - модуль 4: секция 8 из домашки (5 пустых ячеек) превращена в демо с заполненным кодом (чтение CSV, CTAS).
  - модуль 4: новая домашка (секция 9) — осмотр metadata lookup-таблицы, сравнение с основной.
  - модуль 4: секции перенумерованы (9→10, 10→11), завершение упоминает обе bronze-таблицы.
  - модуль 5: assert-сообщение для lookup обновлено (убрана ссылка на «самостоятельное задание»).
  - планы модулей 4 и 5: обновлены дизайн-решения, структура секций и раздел рисков.
- Проверка:
  - прогнать готовые ячейки модуля 4 → lookup-таблица создаётся автоматически → модуль 5 проходит без ошибок.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 23:47:05 +03:00
co-authored by Claude Opus 4.6
parent bcf7975227
commit 68c530c855
4 changed files with 28 additions and 76 deletions
+9 -48
View File
@@ -564,21 +564,7 @@
"cell_type": "markdown",
"id": "320c0199",
"metadata": {},
"source": [
"## 8. Самостоятельное задание\n",
"\n",
"Используй raw-файл `taxi_zone_lookup.csv`, который уже лежит в `MinIO`.\n",
"\n",
"Что нужно сделать:\n",
"\n",
"1. Прочитать `taxi_zone_lookup.csv` из raw-зоны через `Spark`.\n",
"2. Создать таблицу `lakehouse.bronze.taxi_zone_lookup` с колонками `LocationID INT`, `Borough STRING`, `Zone STRING`, `service_zone STRING`.\n",
"3. Загрузить туда данные.\n",
"4. Проверить результат через `spark.table(...)`.\n",
"5. Посмотреть `snapshots` для этой таблицы.\n",
"\n",
"Подсказка: файл лежит по пути `s3a://lakehouse/raw/nyc_taxi/taxi_zone_lookup.csv`.\n"
]
"source": "## 8. Вторая bronze-таблица: taxi_zone_lookup\n\nВ raw-зоне кроме Yellow Taxi лежит ещё один файл — `taxi_zone_lookup.csv`. Это справочник зон: каждому `LocationID` соответствует название района (`Borough`) и зоны (`Zone`).\n\nВ Модуле 5 мы будем обогащать silver-таблицу через `LEFT JOIN` с этим справочником. Сейчас загрузим его в bronze по тому же паттерну `CTAS`, но из CSV вместо Parquet."
},
{
"cell_type": "code",
@@ -586,9 +572,7 @@
"id": "e94508bf",
"metadata": {},
"outputs": [],
"source": [
"# Ваш код: прочитай taxi_zone_lookup.csv из raw-зоны через Spark\n"
]
"source": "raw_zone_df = (\n spark.read\n .option(\"header\", \"true\")\n .option(\"inferSchema\", \"true\")\n .csv(ZONE_LOOKUP_RAW_URI)\n)\n\nprint(f\"Строк в raw lookup: {raw_zone_df.count()}\")\nraw_zone_df.printSchema()\nraw_zone_df.show(5, truncate=False)"
},
{
"cell_type": "code",
@@ -596,19 +580,15 @@
"id": "8e058aeb",
"metadata": {},
"outputs": [],
"source": [
"# Ваш код: создай таблицу lakehouse.bronze.taxi_zone_lookup\n"
]
"source": "raw_zone_df.createOrReplaceTempView(\"raw_zone_lookup\")\n\nctas_lookup_sql = f\"\"\"\nCREATE OR REPLACE TABLE {BRONZE_LOOKUP_TABLE}\nUSING iceberg\nAS\nSELECT\n CAST(LocationID AS INT) AS LocationID,\n Borough,\n Zone,\n service_zone\nFROM raw_zone_lookup\n\"\"\"\n\nspark.sql(ctas_lookup_sql)\n\nlookup_df = spark.table(BRONZE_LOOKUP_TABLE)\nlookup_count = lookup_df.count()\n\nprint(f\"Строк в bronze lookup: {lookup_count}\")\nlookup_df.show(5, truncate=False)"
},
{
"cell_type": "code",
"cell_type": "markdown",
"execution_count": null,
"id": "41c2c79a",
"metadata": {},
"outputs": [],
"source": [
"# Ваш код: загрузи данные в bronze lookup-таблицу\n"
]
"source": "## 9. Самостоятельное задание\n\nПосмотри на внутреннюю структуру только что созданной `taxi_zone_lookup` — используй те же инструменты, что в Секциях 5 и 6.\n\n1. Выведи `snapshots` и `files` для `lakehouse.bronze.taxi_zone_lookup`.\n2. Сравни: сколько data files у lookup-таблицы и сколько у `nyc_taxi_yellow`? Почему такая разница?"
},
{
"cell_type": "code",
@@ -616,9 +596,7 @@
"id": "80b392e1",
"metadata": {},
"outputs": [],
"source": [
"# Ваш код: проверь результат через spark.table(...)\n"
]
"source": "# Ваш код: snapshots и files для taxi_zone_lookup\n"
},
{
"cell_type": "code",
@@ -626,36 +604,19 @@
"id": "61e90959",
"metadata": {},
"outputs": [],
"source": [
"# Ваш код: посмотри snapshots для lakehouse.bronze.taxi_zone_lookup\n"
]
"source": "# Ваш код: сравни количество data files в lookup и yellow taxi\n"
},
{
"cell_type": "markdown",
"id": "24c04d60",
"metadata": {},
"source": [
"## 9. Checkpoint\n",
"\n",
"Проверь себя:\n",
"\n",
"1. Чем отличается `spark.read.parquet(\"s3a://...\")` от `spark.table(\"lakehouse.bronze.nyc_taxi_yellow\")`?\n",
"2. Что хранит snapshot и зачем он нужен?\n",
"3. Где физически лежат данные таблицы и где лежат её метаданные?\n",
"4. Почему `bronze` это не то же самое, что `raw`?\n",
"5. Что произойдёт, если удалить один data file из `MinIO`, но metadata не поменять?\n",
"6. Можешь ли ты показать таблицу и через `Spark`, и через `MinIO Console`?\n"
]
"source": "## 10. Checkpoint\n\nПроверь себя:\n\n1. Чем отличается `spark.read.parquet(\"s3a://...\")` от `spark.table(\"lakehouse.bronze.nyc_taxi_yellow\")`?\n2. Что хранит snapshot и зачем он нужен?\n3. Где физически лежат данные таблицы и где лежат её метаданные?\n4. Почему `bronze` это не то же самое, что `raw`?\n5. Что произойдёт, если удалить один data file из `MinIO`, но metadata не поменять?\n6. Можешь ли ты показать таблицу и через `Spark`, и через `MinIO Console`?"
},
{
"cell_type": "markdown",
"id": "76d9061f",
"metadata": {},
"source": [
"## 10. Завершение\n",
"\n",
"Мы намеренно не удаляем `lakehouse.bronze.nyc_taxi_yellow`. Эта таблица понадобится в Модуле 5, где мы будем строить `silver`-слой.\n"
]
"source": "## 11. Завершение\n\nМы намеренно не удаляем bronze-таблицы (`nyc_taxi_yellow` и `taxi_zone_lookup`). Они понадобятся в Модуле 5, где мы будем строить silver-слой."
},
{
"cell_type": "code",
+2 -16
View File
@@ -77,21 +77,7 @@
"id": "section_1_asserts",
"metadata": {},
"outputs": [],
"source": [
"assert spark.catalog.tableExists(BRONZE_TABLE), (\n",
" f\"Таблица {BRONZE_TABLE} не найдена. Вернись в Модуль 4 и выполни загрузку в bronze.\"\n",
")\n",
"\n",
"assert spark.catalog.tableExists(BRONZE_LOOKUP_TABLE), (\n",
" f\"Таблица {BRONZE_LOOKUP_TABLE} не найдена. \"\n",
" \"Вернись в Модуль 4 и выполни самостоятельное задание (Секция 8).\"\n",
")\n",
"\n",
"bronze_count = spark.table(BRONZE_TABLE).count()\n",
"assert bronze_count > 0, f\"Таблица {BRONZE_TABLE} пуста.\"\n",
"\n",
"print(f\"Обе bronze-таблицы на месте. В основной таблице {bronze_count:,} строк. Теперь строим silver.\")"
]
"source": "assert spark.catalog.tableExists(BRONZE_TABLE), (\n f\"Таблица {BRONZE_TABLE} не найдена. Вернись в Модуль 4 и выполни загрузку в bronze.\"\n)\n\nassert spark.catalog.tableExists(BRONZE_LOOKUP_TABLE), (\n f\"Таблица {BRONZE_LOOKUP_TABLE} не найдена. \"\n \"Вернись в Модуль 4 и выполни Секцию 8.\"\n)\n\nbronze_count = spark.table(BRONZE_TABLE).count()\nassert bronze_count > 0, f\"Таблица {BRONZE_TABLE} пуста.\"\n\nprint(f\"Обе bronze-таблицы на месте. В основной таблице {bronze_count:,} строк. Теперь строим silver.\")"
},
{
"cell_type": "markdown",
@@ -624,4 +610,4 @@
},
"nbformat": 4,
"nbformat_minor": 5
}
}