feat(course): add SparkSession setup practice to module 3

This commit is contained in:
ddadmin committed 2026-09-15 09:01:41 -04:00
1 parent 00942c255f
commit 7c5e976405
10 files changed
+523 -22

No files matched your search

+2
View File
@@ -15,6 +15,8 @@
Курс рассчитан на `~12-15 часов` самостоятельной работы. Каждый модуль: объяснение, демонстрация, самостоятельное задание, checkpoint. Курс рассчитан на `~12-15 часов` самостоятельной работы. Каждый модуль: объяснение, демонстрация, самостоятельное задание, checkpoint.
В начале [модуля 3](notebooks/03_raw_ingest_and_first_read.ipynb) ты настроишь Spark-сессию для кластера и проверишь доступ к Iceberg и MinIO. На эту практику отведено 20–30 минут; общая оценка курса пока предварительная.
## Стек ## Стек
Всё работает локально в Docker. Никаких облаков, внешних зависимостей и регистраций. Всё работает локально в Docker. Никаких облаков, внешних зависимостей и регистраций.
+1 -1
View File
@@ -204,6 +204,6 @@ ls -lh data/nyc_taxi/
- пройти `notebooks/01_environment_and_smoke_test.ipynb`; - пройти `notebooks/01_environment_and_smoke_test.ipynb`;
- пройти `notebooks/02_lakehouse_mental_model.ipynb`; - пройти `notebooks/02_lakehouse_mental_model.ipynb`;
- подготовить data bundle по инструкции выше и пройти `notebooks/03_raw_ingest_and_first_read.ipynb`; - подготовить data bundle по инструкции выше и пройти [модуль 3](notebooks/03_raw_ingest_and_first_read.ipynb): сначала настроить Spark-сессию и проверить подключение, затем загрузить и прочитать raw-данные;
- свериться с программой курса в `docs/course_program.md`; - свериться с программой курса в `docs/course_program.md`;
- после прохождения первых модулей переходить к следующим учебным материалам. - после прохождения первых модулей переходить к следующим учебным материалам.
+4
View File
@@ -55,6 +55,7 @@
5. Умеет реализовать простой поток `raw -> bronze -> silver` с базовыми трансформациями и сохранением воспроизводимости шагов. 5. Умеет реализовать простой поток `raw -> bronze -> silver` с базовыми трансформациями и сохранением воспроизводимости шагов.
6. Умеет читать одну и ту же таблицу из `Spark` и `Trino` и понимает, почему это возможно без копирования данных. 6. Умеет читать одну и ту же таблицу из `Spark` и `Trino` и понимает, почему это возможно без копирования данных.
7. Умеет выполнить базовые безопасные операции с таблицей: изменение схемы, просмотр snapshot-ов, чтение предыдущего состояния, базовый `compaction` и `vacuum`. 7. Умеет выполнить базовые безопасные операции с таблицей: изменение схемы, просмотр snapshot-ов, чтение предыдущего состояния, базовый `compaction` и `vacuum`.
8. Умеет создать `SparkSession` для кластера с подготовленным каталогом, проверить фактические настройки и объяснить, какие из них можно изменить в работающей сессии, а для каких нужен новый запуск.
Трассировка этих outcomes на модули и checkpoints фиксируется в [course_program.md](./course_program.md). Трассировка этих outcomes на модули и checkpoints фиксируется в [course_program.md](./course_program.md).
@@ -91,6 +92,7 @@
* работа с raw-данными; * работа с raw-данными;
* построение слоев `bronze` и `silver`; * построение слоев `bronze` и `silver`;
* чтение и запись через `Spark`; * чтение и запись через `Spark`;
* обязательная практика базовой настройки `SparkSession` в начале модуля 3: источники конфигурации, подключение к кластеру, проверка Iceberg/MinIO и жизненный цикл сессии;
* чтение через `Trino`; * чтение через `Trino`;
* базовое schema evolution; * базовое schema evolution;
* snapshot/time travel на прикладном уровне; * snapshot/time travel на прикладном уровне;
@@ -147,6 +149,7 @@
Это целевые ориентиры курса, но не blocking acceptance criteria для материалов. Это целевые ориентиры курса, но не blocking acceptance criteria для материалов.
1. Студент проходит курс за `~12-15 часов` вдумчивой работы. 1. Студент проходит курс за `~12-15 часов` вдумчивой работы.
Практика настройки сессии в модуле 3 рассчитана на 20–30 минут внутри этого диапазона. Это предварительная оценка: машинный прогон не измеряет время самостоятельной работы студента.
2. Студент после курса способен безопасно выполнить базовые операции в Lakehouse-стенде без постоянной внешней помощи. 2. Студент после курса способен безопасно выполнить базовые операции в Lakehouse-стенде без постоянной внешней помощи.
3. Студент может внятно объяснить ключевые архитектурные принципы Lakehouse на уровне junior/middle interview readiness. 3. Студент может внятно объяснить ключевые архитектурные принципы Lakehouse на уровне junior/middle interview readiness.
@@ -155,6 +158,7 @@
1. В составе курса есть стартовая документация до первого ноутбука, включая prerequisites, запуск стенда, вход в `Jupyter` и получение учебного data bundle. 1. В составе курса есть стартовая документация до первого ноутбука, включая prerequisites, запуск стенда, вход в `Jupyter` и получение учебного data bundle.
2. Студент проходит обязательные `checkpoints` по урокам. 2. Студент проходит обязательные `checkpoints` по урокам.
3. Студент самостоятельно выполняет набор практик на одном датасете, включая: 3. Студент самостоятельно выполняет набор практик на одном датасете, включая:
* создание Spark-сессии по шаблону и проверку подключения на отдельной маленькой Iceberg-таблице;
* загрузку raw-данных; * загрузку raw-данных;
* построение `bronze`; * построение `bronze`;
* построение `silver`; * построение `silver`;
+16 -2
View File
@@ -10,6 +10,8 @@
**Ориентир на модуль:** в среднем `~1.5 часа`, но финальная практика может занять дольше. **Ориентир на модуль:** в среднем `~1.5 часа`, но финальная практика может занять дольше.
В модуле 3 дополнительно предусмотрены 20–30 минут на настройку Spark-сессии; ориентир на весь модуль — около 2 часов. Общие `~12-15 часов` пока остаются предварительной оценкой: её нужно уточнить после самостоятельного прохождения студентом.
**Сквозной учебный кейс:** `NYC TLC Yellow Taxi Trip Records` + `Taxi Zone Lookup`. **Сквозной учебный кейс:** `NYC TLC Yellow Taxi Trip Records` + `Taxi Zone Lookup`.
**Методика каждого урока:** `объяснение -> демонстрация -> самостоятельное повторение -> checkpoint`. **Методика каждого урока:** `объяснение -> демонстрация -> самостоятельное повторение -> checkpoint`.
@@ -74,6 +76,7 @@
| `LO5`. Умение собрать поток `raw -> bronze -> silver` | 4, 5, 8 | checkpoints модулей 5 и 8 | | `LO5`. Умение собрать поток `raw -> bronze -> silver` | 4, 5, 8 | checkpoints модулей 5 и 8 |
| `LO6`. Умение читать одну таблицу из `Spark` и `Trino` | 6, 8 | checkpoint модуля 6 и финальная практика | | `LO6`. Умение читать одну таблицу из `Spark` и `Trino` | 6, 8 | checkpoint модуля 6 и финальная практика |
| `LO7`. Умение выполнять безопасные операции с таблицей | 7, 8 | checkpoints модулей 7 и 8 | | `LO7`. Умение выполнять безопасные операции с таблицей | 7, 8 | checkpoints модулей 7 и 8 |
| `LO8`. Умение настроить Spark-сессию и проверить подключение | 3 | checkpoint практики настройки в начале модуля 3 |
## 5. Верхнеуровневая структура практической части ## 5. Верхнеуровневая структура практической части
@@ -124,10 +127,15 @@
**Рабочее название ноутбука:** `03_raw_ingest_and_first_read.ipynb` **Рабочее название ноутбука:** `03_raw_ingest_and_first_read.ipynb`
**Зачем нужен:** показать, как raw-данные попадают в стенд и как с ними безопасно начать работать. **Зачем нужен:** научить создавать Spark-сессию для нашего кластера, затем загружать и читать raw-данные.
**Содержание:** **Содержание:**
* обязательная практика «Как настроен Spark в нашем стенде» после ментальной модели модуля 2, до raw-ingest (20–30 минут);
* роли `SparkSession`, `SparkContext`, driver и executors; `master`, `appName`, `getOrCreate`, отличие `local` от кластера;
* настройки из `spark-defaults.conf`, параметров запуска и кода; подготовленный каталог Iceberg и доступ к MinIO;
* проверка фактического master и выбранных несекретных настроек; повторная ячейка, остановка сессии и перезапуск ядра;
* изменение часового пояса с восстановлением исходного значения и смена имени приложения при новом запуске; без подбора памяти, executors, shuffle или GC;
* работа с заранее подготовленным локальным data bundle из onboarding-документации; * работа с заранее подготовленным локальным data bundle из onboarding-документации;
* явная загрузка исходных `PARQUET`-файлов в raw-зону; * явная загрузка исходных `PARQUET`-файлов в raw-зону;
* чтение raw-данных через Spark; * чтение raw-данных через Spark;
@@ -136,13 +144,18 @@
**Практика студента:** **Практика студента:**
* выполнить демонстрацию настройки сессии, затем заполнить шаблон с адресом кластера и именем приложения, используя подготовленный каталог;
* проверить подключение на собственной маленькой Iceberg-таблице, прочитать две строки и удалить только эту таблицу; данные модуля 2 не нужны;
* объяснить происхождение настроек и повторить создание сессии после перезапуска ядра;
* проверить состав локально полученного набора `NYC Taxi`; * проверить состав локально полученного набора `NYC Taxi`;
* загрузить его в raw-зону `MinIO`; * загрузить его в raw-зону `MinIO`;
* прочитать несколько месяцев `NYC Taxi`; * прочитать несколько месяцев `NYC Taxi`;
* проверить схему и посчитать базовые метрики; * проверить схему и посчитать базовые метрики;
* найти простые аномалии или проблемные значения. * найти простые аномалии или проблемные значения.
**Checkpoint:** студент умеет загрузить учебный набор в raw-зону, прочитать raw-данные, проверить схему и объяснить, почему raw-слой нужен как воспроизводимая точка входа. **Checkpoint настройки:** master равен `spark://spark-master:7077`, имя приложения совпадает с заданным, из собственной Iceberg-таблицы прочитаны строки `0` и `1`, временная таблица удалена. Часовой пояс восстановлен. Студент объясняет, что задал в коде, что взял из стенда, где выполняются вычисления и какие ячейки повторит после перезапуска ядра.
**Checkpoint raw-ingest:** студент умеет загрузить учебный набор в raw-зону, прочитать raw-данные, проверить схему и объяснить, почему raw-слой нужен как воспроизводимая точка входа.
### Модуль 4. Первая рабочая Iceberg-таблица и слой bronze ### Модуль 4. Первая рабочая Iceberg-таблица и слой bronze
@@ -261,6 +274,7 @@
5. Умеет построить простой поток `raw -> bronze -> silver`. 5. Умеет построить простой поток `raw -> bronze -> silver`.
6. Умеет читать одну и ту же таблицу из `Spark` и `Trino`. 6. Умеет читать одну и ту же таблицу из `Spark` и `Trino`.
7. Умеет выполнять базовые безопасные операции: schema evolution, snapshots/time travel, compaction, vacuum. 7. Умеет выполнять базовые безопасные операции: schema evolution, snapshots/time travel, compaction, vacuum.
8. Создаёт Spark-сессию для кластера, проверяет настройки и понимает, когда нужен новый запуск.
## 7. Что не входит в первую версию курса ## 7. Что не входит в первую версию курса
+16
View File
@@ -44,6 +44,22 @@
**Параллель с DWH:** в классическом DWH вычислитель один, поэтому проблема не возникает. **Параллель с DWH:** в классическом DWH вычислитель один, поэтому проблема не возникает.
**Где в курсе:** Модуль 6. **Где в курсе:** Модуль 6.
## Spark-сессия и выполнение
### SparkSession
Точка входа в DataFrame и Spark SQL: объект `spark` в ноутбуке. Builder задаёт параметры, а `getOrCreate()` создаёт сессию или возвращает уже работающую.
### SparkContext
Связывает Spark-приложение с вычислительными ресурсами. Через `spark.sparkContext` можно проверить фактические `master`, `appName` и `applicationId`. Повторный builder не заменяет работающий контекст.
### Driver и executors
Driver управляет приложением: выполняет код ноутбука, строит план и отправляет задания. Executors выполняют задания и читают данные. В нашем стенде driver работает в `jupyter`, executors запускаются на Spark worker-ах. Master выделяет приложению ресурсы worker-ов.
**Где в курсе:** [практика настройки в модуле 3](../notebooks/03_raw_ingest_and_first_read.ipynb).
## Структура Iceberg-таблицы ## Структура Iceberg-таблицы
### Namespace ### Namespace
+21
View File
@@ -64,6 +64,27 @@ docker compose down -v
или настройки в файле, перезапусти ядро ноутбука и снова выполни ячейки или настройки в файле, перезапусти ядро ноутбука и снова выполни ячейки
с импортами и созданием сессии. с импортами и созданием сессии.
Практика с объяснением, шаблоном и проверкой подключения находится в начале
[модуля 3](../notebooks/03_raw_ingest_and_first_read.ipynb).
В ней `spark.sparkContext.master` и `.appName` показывают фактический кластер
и имя приложения. Для SQL-настроек используется `spark.conf.get(...)`;
вывод ограничен конкретными несекретными ключами.
В Spark 3.5.1 `spark.sql.session.timeZone` можно менять через
`spark.conf.set(...)` во время работы, сохранив и восстановив исходное значение.
Повторный builder с другим `appName` использует прежний контекст и не меняет
его фактическое имя. После `spark.stop()` новый контекст получает новое имя;
при смене master или общего файла конфигурации перезапусти ядро.
`stop()` оставляет переменные Python, перезапуск ядра удаляет их.
Оба действия сохраняют таблицы в каталоге и файлы MinIO.
Параметры обычного нового приложения имеют приоритет: код → параметры
`spark-submit` → `spark-defaults.conf`. Настройки запуска driver-а нужно
передавать до его старта. Вне стенда также потребуются зависимости и конфигурация
Iceberg/S3A/JDBC из [`spark/Dockerfile`](../spark/Dockerfile) и общего файла.
Подробности: [конфигурация Spark 3.5.1](https://github.com/apache/spark/blob/v3.5.1/docs/configuration.md)
и [поведение SparkSession в этой версии](https://github.com/apache/spark/blob/v3.5.1/python/pyspark/sql/session.py).
Пример запуска smoke-скрипта: Пример запуска smoke-скрипта:
```bash ```bash
+334 -9
View File
@@ -9,13 +9,14 @@
"\n", "\n",
"В этом модуле мы разбираем полный первый цикл работы с учебным датасетом в Lakehouse:\n", "В этом модуле мы разбираем полный первый цикл работы с учебным датасетом в Lakehouse:\n",
"\n", "\n",
"- создаём Spark-сессию для кластера и проверяем её настройки;\n",
"- проверяем заранее скачанный локальный data bundle;\n", "- проверяем заранее скачанный локальный data bundle;\n",
"- загружаем исходные файлы в raw-зону `MinIO`;\n", "- загружаем исходные файлы в raw-зону `MinIO`;\n",
"- читаем raw-данные через `Spark` по `s3a://` пути;\n", "- читаем raw-данные через `Spark` по `s3a://` пути;\n",
"- смотрим схему, типы, базовые метрики и простые аномалии;\n", "- смотрим схему, типы, базовые метрики и простые аномалии;\n",
"- фиксируем, почему raw-слой нужен как воспроизводимая неизменяемая точка входа.\n", "- фиксируем, почему raw-слой нужен как воспроизводимая неизменяемая точка входа.\n",
"\n", "\n",
"После этого ноутбука ты должен уметь объяснить, почему raw-данные не надо «чинить на месте» и почему их не стоит скачивать прямо из ноутбука.\n" "После этого ноутбука ты должен уметь объяснить, почему raw-данные не надо «чинить на месте» и почему их не стоит скачивать прямо из ноутбука."
] ]
}, },
{ {
@@ -29,7 +30,8 @@
"\n", "\n",
"- подними стенд по `START_HERE.md`;\n", "- подними стенд по `START_HERE.md`;\n",
"- пройди `notebooks/01_environment_and_smoke_test.ipynb`;\n", "- пройди `notebooks/01_environment_and_smoke_test.ipynb`;\n",
"- желательно пройти `notebooks/02_lakehouse_mental_model.ipynb`;\n", "- пройди `notebooks/02_lakehouse_mental_model.ipynb`;\n",
"- начни с нового ядра, чтобы настройки не зависели от предыдущих опытов;\n",
"- заранее скачай data bundle в `./data/nyc_taxi` по инструкции из `START_HERE.md`.\n", "- заранее скачай data bundle в `./data/nyc_taxi` по инструкции из `START_HERE.md`.\n",
"\n", "\n",
"В классическом `PostgreSQL / Greenplum` данные часто уже лежат внутри системы. В Lakehouse путь более явный: сначала исходные файлы должны попасть в object storage, и только потом мы начинаем поверх них читать, профилировать и строить таблицы.\n", "В классическом `PostgreSQL / Greenplum` данные часто уже лежат внутри системы. В Lakehouse путь более явный: сначала исходные файлы должны попасть в object storage, и только потом мы начинаем поверх них читать, профилировать и строить таблицы.\n",
@@ -38,7 +40,334 @@
"| --- | --- | --- |\n", "| --- | --- | --- |\n",
"| Где стартует работа | Данные уже внутри СУБД | Исходные файлы сначала попадают в storage |\n", "| Где стартует работа | Данные уже внутри СУБД | Исходные файлы сначала попадают в storage |\n",
"| Первая практическая задача | `SELECT` из готовой таблицы | Загрузить raw и прочитать raw |\n", "| Первая практическая задача | `SELECT` из готовой таблицы | Загрузить raw и прочитать raw |\n",
"| Что важно не сломать | Таблицу / схему БД | Неизменяемый raw-слой |\n" "| Что важно не сломать | Таблицу / схему БД | Неизменяемый raw-слой |"
]
},
{
"cell_type": "markdown",
"id": "session-intro",
"metadata": {},
"source": [
"## 0.1. Как настроен Spark в нашем стенде\n",
"\n",
"Отведи на эту практику 20–30 минут. Сначала разберём готовый пример, затем ты заполнишь шаблон и проверишь подключение. Файлы Taxi понадобятся после практики.\n",
"\n",
"`SparkSession` — объект `spark`, через который мы читаем DataFrame и выполняем SQL. Внутри него `SparkContext` связывает приложение с вычислительными ресурсами. В обычном ядре нашего ноутбука используется один работающий контекст.\n",
"\n",
"| Участник | Что делает в этом стенде |\n",
"| --- | --- |\n",
"| Driver | Работает в контейнере `jupyter`: выполняет Python-код ноутбука, строит план и раздаёт задания |\n",
"| Master | Принимает подключение по `spark://spark-master:7077` и выделяет ресурсы worker-ов |\n",
"| Executors | Запускаются на `spark-worker-1` и `spark-worker-2`, читают данные и выполняют задания |\n",
"\n",
"В режиме `local[*]` Spark выполняет задания локально, используя потоки на машине driver-а. В нашем курсе нужен кластер: успешный `count()` сам по себе ещё не доказывает, что подключение правильное.\n",
"\n",
"### Откуда берутся настройки\n",
"\n",
"Для новой сессии стенд читает `spark/spark-defaults.conf`, смонтированный в Jupyter как `/opt/spark/conf/spark-defaults.conf`. В нём уже заданы master, каталог и доступ к MinIO.\n",
"\n",
"При запуске приложения через `spark-submit` можно передать `--master`, `--name` и `--conf`. В коде похожие настройки задают методы builder-а: `.master(...)`, `.appName(...)`, `.config(...)`. Обычно приоритет такой: код → параметры запуска → файл defaults. Но уже запущенный контекст не перестраивается от нового вызова builder-а, а параметры запуска driver-а надо передавать до его старта.\n",
"\n",
"В нашем Jupyter дополнительные `--master`/`--conf` при запуске не передаются. Готовый пример ниже берёт master из файла, а имя задаёт в коде. Вне стенда понадобятся также библиотеки Iceberg/S3A/JDBC и конфигурация каталога: одного адреса master недостаточно. Пути и состав образа описаны в [справочнике стенда](../docs/stack_reference.md#spark)."
]
},
{
"cell_type": "code",
"id": "session-demo",
"metadata": {},
"source": [
"from pyspark.sql import SparkSession\n",
"\n",
"spark = (\n",
" SparkSession.builder\n",
" .appName(\"module-03-session-demo\")\n",
" .getOrCreate()\n",
")\n",
"spark.sparkContext.setLogLevel(\"ERROR\")\n",
"\n",
"print(\"Spark:\", spark.version)\n",
"print(\"Master:\", spark.sparkContext.master)\n",
"print(\"Приложение:\", spark.sparkContext.appName)\n",
"assert spark.sparkContext.master == \"spark://spark-master:7077\", \"Нужен Spark-кластер стенда\"\n",
"print(\"Сумма 0…9:\", spark.range(10).groupBy().sum(\"id\").first()[0])"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "session-config-explanation",
"metadata": {},
"source": [
"`appName` — имя приложения, видимое в Spark UI. `getOrCreate()` создаёт сессию, если её ещё нет, или возвращает существующую. `.master(...)` выбирает вычислитель; в демонстрации он не указан, поэтому используется общий файл.\n",
"\n",
"### Что уже подготовлено для Iceberg и MinIO\n",
"\n",
"| Настройка в `spark-defaults.conf` | Назначение |\n",
"| --- | --- |\n",
"| `spark.sql.extensions` | Добавляет команды Iceberg к Spark SQL |\n",
"| `spark.sql.catalog.lakehouse` | Подключает Spark-каталог с именем `lakehouse` |\n",
"| `spark.sql.catalog.lakehouse.catalog-impl` и `.uri` | Выбирает JDBC-каталог в PostgreSQL, где зарегистрированы таблицы |\n",
"| `spark.sql.catalog.lakehouse.warehouse` | Задаёт место для файлов таблиц в MinIO: `s3a://lakehouse/warehouse` |\n",
"| `spark.hadoop.fs.s3a.endpoint` и `.path.style.access` | Направляют S3A-запросы в MinIO внутри Docker-сети |\n",
"\n",
"В том же файле находятся настройки доступа к PostgreSQL и MinIO. Выведем только выбранные несекретные значения. SQL-настройки читаем через `spark.conf`, master и имя — через `spark.sparkContext`. При передаче настроек в Hadoop префикс `spark.hadoop.` снимается."
]
},
{
"cell_type": "code",
"id": "session-config",
"metadata": {},
"source": [
"for key in (\n",
" \"spark.sql.extensions\",\n",
" \"spark.sql.catalog.lakehouse\",\n",
" \"spark.sql.catalog.lakehouse.catalog-impl\",\n",
" \"spark.sql.catalog.lakehouse.uri\",\n",
" \"spark.sql.catalog.lakehouse.warehouse\",\n",
"):\n",
" print(f\"{key} = {spark.conf.get(key)}\")\n",
"\n",
"hadoop_conf = spark.sparkContext._jsc.hadoopConfiguration()\n",
"for key in (\"fs.s3a.endpoint\", \"fs.s3a.path.style.access\"):\n",
" print(f\"{key} = {hadoop_conf.get(key)}\")"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "session-live-explanation",
"metadata": {},
"source": [
"### Что можно изменить во время работы\n",
"\n",
"`spark.sql.session.timeZone` задаёт часовой пояс SQL-сессии. У одной и той же секунды Unix-времени будет разное отображение в UTC и Москве. Это настройка интерпретации времени, без изменения файлов данных.\n",
"\n",
"Сохраним исходное значение и восстановим его в `finally`: оно вернётся даже при ошибке внутри опыта. Для Spark 3.5.1 эта настройка изменяется в работающей сессии."
]
},
{
"cell_type": "code",
"id": "session-live-config",
"metadata": {},
"source": [
"timezone_key = \"spark.sql.session.timeZone\"\n",
"original_timezone = spark.conf.get(timezone_key)\n",
"assert spark.conf.isModifiable(timezone_key)\n",
"try:\n",
" for timezone in (\"UTC\", \"Europe/Moscow\"):\n",
" spark.conf.set(timezone_key, timezone)\n",
" value = spark.sql(\"SELECT from_unixtime(0) AS displayed_time\").first()[0]\n",
" print(timezone, value)\n",
"finally:\n",
" spark.conf.set(timezone_key, original_timezone)\n",
"\n",
"assert spark.conf.get(timezone_key) == original_timezone\n",
"print(\"Исходный часовой пояс восстановлен:\", original_timezone)"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "session-reuse-explanation",
"metadata": {},
"source": [
"### Для чего нужен новый запуск\n",
"\n",
"Проверим `appName`: это имя приложения, которое запущено через `SparkContext`. Попробуем задать другое имя через builder и посмотрим на **фактическое** `spark.sparkContext.appName`.\n",
"\n",
"Повторное выполнение ячейки не останавливает приложение. `getOrCreate()` использует ту же сессию и тот же контекст; это не способ переключиться на другой master или переименовать уже запущенное приложение."
]
},
{
"cell_type": "code",
"id": "session-reuse",
"metadata": {},
"source": [
"demo_context = spark.sparkContext\n",
"demo_name = demo_context.appName\n",
"same_spark = (\n",
" SparkSession.builder\n",
" .appName(\"module-03-raw-ingest-and-first-read\")\n",
" .getOrCreate()\n",
")\n",
"assert same_spark is spark\n",
"assert same_spark.sparkContext is demo_context\n",
"assert same_spark.sparkContext.appName == demo_name\n",
"print(\"То же приложение:\", same_spark.sparkContext.appName)"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "session-stop-explanation",
"metadata": {},
"source": [
"Теперь остановим демонстрационную сессию. В следующем задании новый контекст получит новое имя.\n",
"\n",
"- **Повторный запуск ячейки** сохраняет переменные Python и может использовать уже работающий контекст.\n",
"- **`spark.stop()`** останавливает сессию и её контекст. Переменные Python остаются, но старые Spark DataFrame больше нельзя использовать. Файлы MinIO и записи каталога сохраняются.\n",
"- **Перезапуск ядра** заново запускает Python и связанный с ним Spark driver. Переменные исчезают; нужно повторить импорты, создание сессии и чтение DataFrame. Данные в MinIO и каталоге от этого не удаляются.\n",
"\n",
"Для смены master или конфигурации файла начинай с нового ядра. Остановка сессии без перезапуска ядра достаточна для нашего опыта с именем, но не заменяет новый процесс для всех стартовых настроек."
]
},
{
"cell_type": "code",
"id": "session-stop",
"metadata": {},
"source": [
"spark.stop()"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "session-exercise-explanation",
"metadata": {},
"source": [
"### Самостоятельно: создай сессию и проверь подключение\n",
"\n",
"Заполни три пропуска в следующей ячейке:\n",
"\n",
"1. Адрес кластера внутри Docker-сети.\n",
"2. Имя приложения `module-03-raw-ingest-and-first-read`.\n",
"3. Вызов builder-а с `.master(...)`, `.appName(...)` и `.getOrCreate()`.\n",
"\n",
"Используй подготовленный каталог из `spark-defaults.conf`: его параметры не нужно копировать в код. Явный `.master(...)` здесь нужен, чтобы ты самостоятельно указал подключение; остальные ноутбуки берут тот же адрес из файла.\n",
"\n",
"Шаблон остановится на `assert`, пока ты его не заполнишь. После заполнения выполни шаблон и ячейку проверки подключения дважды: имя и `applicationId` должны остаться теми же."
]
},
{
"cell_type": "code",
"id": "session-exercise",
"metadata": {},
"source": [
"from pyspark.sql import SparkSession\n",
"\n",
"CLUSTER_MASTER = ...\n",
"APP_NAME = ...\n",
"assert CLUSTER_MASTER == \"spark://spark-master:7077\", \"Впиши адрес кластера\"\n",
"assert APP_NAME == \"module-03-raw-ingest-and-first-read\", \"Впиши имя приложения\"\n",
"\n",
"# Замени ... цепочкой вызовов SparkSession.builder.\n",
"spark = ...\n",
"assert isinstance(spark, SparkSession), \"Создай SparkSession через builder\"\n",
"spark.sparkContext.setLogLevel(\"ERROR\")"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "session-solution",
"metadata": {},
"source": [
"<details>\n",
"<summary>Контрольное решение — открой после своей попытки</summary>\n",
"\n",
"```python\n",
"from pyspark.sql import SparkSession\n",
"\n",
"CLUSTER_MASTER = \"spark://spark-master:7077\"\n",
"APP_NAME = \"module-03-raw-ingest-and-first-read\"\n",
"spark = (\n",
" SparkSession.builder\n",
" .master(CLUSTER_MASTER)\n",
" .appName(APP_NAME)\n",
" .getOrCreate()\n",
")\n",
"spark.sparkContext.setLogLevel(\"ERROR\")\n",
"```\n",
"\n",
"</details>"
]
},
{
"cell_type": "code",
"id": "session-connection-check",
"metadata": {},
"source": [
"assert spark.sparkContext.master == CLUSTER_MASTER\n",
"assert spark.sparkContext.appName == APP_NAME\n",
"print(\"Master:\", spark.sparkContext.master)\n",
"print(\"Приложение:\", spark.sparkContext.appName)\n",
"print(\"ID приложения:\", spark.sparkContext.applicationId)"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "session-table-explanation",
"metadata": {},
"source": [
"### Проверь весь путь: Spark → каталог → MinIO\n",
"\n",
"Одного правильного master недостаточно: проверим запись и чтение через каталог. Ячейка сама создаёт маленькую Iceberg-таблицу с числами `0` и `1`, читает её и удаляет в `finally`. Таблицы модуля 2 не требуются.\n",
"\n",
"У таблицы уникальное имя. `DROP TABLE … PURGE` удаляет именно её запись в каталоге, файлы данных и метаданных. В MinIO могут остаться пустые маркеры каталогов `data/` и `metadata/`; это не данные таблицы. Пока ячейка работает, не перезапускай ядро: при принудительном завершении процесса `finally` не выполнится. Если это произошло, сохрани напечатанное имя для удаления этой таблицы после нового подключения.\n",
"\n",
"Это только проверка подключения; создание рабочей таблицы поездок изучим в модуле 4."
]
},
{
"cell_type": "code",
"id": "session-table-check",
"metadata": {},
"source": [
"from uuid import uuid4\n",
"\n",
"session_table = f\"lakehouse.default.session_practice_{uuid4().hex}\"\n",
"print(\"Временная таблица:\", session_table)\n",
"try:\n",
" spark.range(2).writeTo(session_table).using(\"iceberg\").create()\n",
" session_rows = [row.id for row in spark.table(session_table).orderBy(\"id\").collect()]\n",
" assert session_rows == [0, 1]\n",
" print(\"Прочитано через Iceberg:\", session_rows)\n",
"finally:\n",
" spark.sql(f\"DROP TABLE IF EXISTS {session_table} PURGE\")\n",
"\n",
"assert not spark.catalog.tableExists(session_table)\n",
"print(\"Временная таблица удалена\")"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "session-checkpoint",
"metadata": {},
"source": [
"### Checkpoint настройки\n",
"\n",
"Проверки выше должны подтвердить master `spark://spark-master:7077`, заданное имя приложения, строки `[0, 1]` и удаление временной таблицы. В опыте с часовым поясом исходное значение должно восстановиться.\n",
"\n",
"Ответь своими словами:\n",
"\n",
"1. Какие настройки ты задал в коде, а какие предоставил стенд? Где их найти?\n",
"2. Чем SparkSession отличается от SparkContext? Где работают driver и executors?\n",
"3. Почему повторный `getOrCreate()` не сменил имя демонстрационного приложения, а после `stop()` оно изменилось?\n",
"4. Что останется после перезапуска ядра и что потребуется выполнить заново?\n",
"\n",
"Теперь, когда временная таблица удалена, **перезапусти ядро**. Выполни заполненный шаблон, проверку подключения и ячейку с временной таблицей. Они должны работать без демонстрации и без состояния прошлого ядра; `applicationId` будет новым. Импорт `SparkSession` уже есть в шаблоне.\n",
"\n",
"После этой проверки продолжи ниже: подготовь переменные и MinIO-клиент, затем переходи к raw-данным. Сессию пока не останавливай."
]
},
{
"cell_type": "markdown",
"id": "raw-preparation",
"metadata": {},
"source": [
"## 0.2. Подготовка к raw-ingest\n",
"\n",
"Сессия из самостоятельного задания продолжает работать. Создадим MinIO-клиент и переменные для исходного сценария модуля."
] ]
}, },
{ {
@@ -63,10 +392,6 @@
"RAW_URI = f\"s3a://{BUCKET_NAME}/{RAW_PREFIX}\"\n", "RAW_URI = f\"s3a://{BUCKET_NAME}/{RAW_PREFIX}\"\n",
"ZONE_LOOKUP_RAW_URI = f\"{RAW_URI}taxi_zone_lookup.csv\"\n", "ZONE_LOOKUP_RAW_URI = f\"{RAW_URI}taxi_zone_lookup.csv\"\n",
"\n", "\n",
"spark = SparkSession.builder .appName(\"module-03-raw-ingest-and-first-read\") .getOrCreate()\n",
"\n",
"spark.sparkContext.setLogLevel(\"ERROR\")\n",
"\n",
"s3 = boto3.client(\n", "s3 = boto3.client(\n",
" \"s3\",\n", " \"s3\",\n",
" endpoint_url=\"http://minio:9000\",\n", " endpoint_url=\"http://minio:9000\",\n",
@@ -127,7 +452,7 @@
"\n", "\n",
"\n", "\n",
"print(\"Spark и MinIO-клиент готовы\")\n", "print(\"Spark и MinIO-клиент готовы\")\n",
"print(f\"RAW URI: {RAW_URI}\")\n" "print(f\"RAW URI: {RAW_URI}\")"
] ]
}, },
{ {
@@ -305,7 +630,7 @@
"- `spark.read.parquet(\"s3a://...\")` читает raw-файлы напрямую по физическому пути;\n", "- `spark.read.parquet(\"s3a://...\")` читает raw-файлы напрямую по физическому пути;\n",
"- `spark.table(\"lakehouse....\")` читает уже зарегистрированную управляемую таблицу через каталог.\n", "- `spark.table(\"lakehouse....\")` читает уже зарегистрированную управляемую таблицу через каталог.\n",
"\n", "\n",
"Сейчас мы ещё не создавали `Iceberg`-таблицу, поэтому работаем именно через `spark.read.parquet(...)`. К `spark.table(...)` вернёмся в Модуле 4.\n" "Проверочную `Iceberg`-таблицу из практики настройки мы уже удалили. Поездки пока лежат отдельными raw-файлами, поэтому читаем их через `spark.read.parquet(...)`. Рабочую таблицу поездок создадим в Модуле 4."
] ]
}, },
{ {
+4 -2
View File
@@ -1,6 +1,6 @@
# План развития курса «Lakehouse без магии» # План развития курса «Lakehouse без магии»
Статус: подключение Spark исправлено; следующая работа — практика настройки сессии. Статус: подключение Spark исправлено; практика настройки сессии по #2 подготовлена и проверена, ожидает ревью.
Последнее обновление: 2026-09-15. Последнее обновление: 2026-09-15.
Здесь собираем отзывы студентов, выбираем следующие улучшения и объясняем их порядок. Подробные задания, критерии приёмки и актуальный статус исполнения хранятся в [Gitea Issues](https://git.dementev.space/ddmitry/mini-lakehouse-lab/issues). Здесь собираем отзывы студентов, выбираем следующие улучшения и объясняем их порядок. Подробные задания, критерии приёмки и актуальный статус исполнения хранятся в [Gitea Issues](https://git.dementev.space/ddmitry/mini-lakehouse-lab/issues).
@@ -36,7 +36,9 @@ Iceberg-таблицы. Результаты проверок и их огран
### 2. Добавить практику «Как настроен Spark в нашем стенде» ### 2. Добавить практику «Как настроен Spark в нашем стенде»
**Статус:** запланировано; можно приступать после завершения #1. **Статус:** готово к ревью по #2; зависимость #1 закрыта, её итог прочитан. Практика добавлена в начало модуля 3, PRD, программа и справочные материалы обновлены.
**Результат:** проверены демонстрация, заполненный шаблон и контрольное решение, повторные ячейки, настоящий перезапуск ядра, временная Iceberg-таблица и весь raw-ingest. Машинный прогон — 4 минуты 10 секунд; время самостоятельного прохождения студентом ещё не измерено. Подробности и ограничения — в [отчёте проверки](issue-2-spark-session-validation.md).
**Задача:** [#2 — практика базовой настройки SparkSession](https://git.dementev.space/ddmitry/mini-lakehouse-lab/issues/2). **Задача:** [#2 — практика базовой настройки SparkSession](https://git.dementev.space/ddmitry/mini-lakehouse-lab/issues/2).
+91
View File
@@ -0,0 +1,91 @@
# Проверка практики SparkSession в модуле 3
Дата: 2026-09-15.
Задача: https://git.dementev.space/ddmitry/mini-lakehouse-lab/issues/2.
База: `00942c2`, результат закрытой [#1](https://git.dementev.space/ddmitry/mini-lakehouse-lab/issues/1) прочитан вместе с комментариями.
## Что проверялось
Фактические ячейки `notebooks/03_raw_ingest_and_first_read.ipynb` выполнялись
через `jupyter_client.KernelManager` в контейнере `jupyter`, Spark 3.5.1.
Менеджер запускал настоящее IPython-ядро и перезапускал его через
`restart_kernel(now=True)`. Проверка не подменяла создание сессии общим helper-ом.
Порядок воспроизведения:
1. Поднять стенд и получить стандартный bundle по `START_HERE.md`.
2. В новом ядре выполнить демонстрацию, вывод настроек, опыт с часовым поясом,
повторный builder и остановку сессии.
3. Заполнить три пропуска шаблона, выполнить его дважды и сравнить контекст
и applicationId. Выполнить опубликованное контрольное решение
(при проверке извлекалось из блока `session-solution`).
4. Выполнить проверку master/appName и дважды создать, прочитать и удалить
временную таблицу.
5. Перезапустить ядро. Убедиться, что переменные `spark` и `CLUSTER_MASTER`
отсутствуют; выполнить контрольное решение, проверку подключения и таблицы.
6. Продолжить исходный raw-ingest до `spark.stop()`, заполнив самостоятельные
упражнения с lookup, top-5 локаций и ещё одной аномалией.
7. Отдельно проверить заполненный шаблон в новом ядре и повторную загрузку
уже существующих raw-файлов.
## Результаты
| Проверка | Результат |
| --- | --- |
| Демонстрационная сессия | Master `spark://spark-master:7077`, appName `module-03-session-demo`, сумма 0…9 = 45 |
| Настройки каталога | `SparkCatalog`, `JdbcCatalog`, URI PostgreSQL и warehouse соответствуют общему файлу; Hadoop endpoint `http://minio:9000`, path-style `true` |
| Изменяемая настройка | `spark.sql.session.timeZone` допускает изменение; Unix-время 0 отображается как `1970-01-01 00:00:00` в UTC и `1970-01-01 03:00:00` в Москве |
| Восстановление | В `finally` восстановлено исходное `Etc/UTC`, проверено через assert |
| Повторный builder с другим appName | Тот же объект сессии и контекста, фактическое имя остаётся `module-03-session-demo` |
| Создание после stop | Новое имя `module-03-raw-ingest-and-first-read`, master кластерный |
| Повторение шаблона и контрольного решения | Контекст и applicationId не меняются |
| Заполненный шаблон в отдельном новом ядре | Master и appName верны, распределённая сумма 0…9 = 45 |
| Перезапуск ядра | Переменные исчезли, сессия создана заново; ID изменился с `app-20260915125347-0001` на `app-20260915125511-0002` |
| Iceberg до и после перезапуска | Три независимые таблицы с уникальными именами; каждый раз прочитаны `[0, 1]`, после PURGE таблица отсутствует в каталоге |
| Raw-ingest | Январь: 2 964 624 строки; январь–март: 9 554 778 строк, 19 колонок |
| Профилирование | Схема, null-доли, describe и четыре проверки аномалий выполнены без ошибок |
| Самостоятельное задание raw | Lookup: 265 строк; подсчёт зон по Borough и top-5 pickup с названиями зон выполнены; найдено 180 поездок с отрицательной длительностью |
| Повторная загрузка raw в отдельном ядре | Все четыре файла пропущены с `skipped_same_size`; снимки всех объектов и каталога до/после совпали |
Код исходного raw-ingest после ячейки подготовки не изменён, что проверено
сравнением ячеек с базовым коммитом. Из подготовки убрано только создание сессии:
она теперь приходит из новой практики.
## Время и ограничения
- Машинный прогон блока настройки: **179,27 с**. Сюда вошли дополнительные
повторы шаблона и таблицы, а также настоящий перезапуск ядра.
- Исходная часть модуля, включая заполненные самостоятельные упражнения:
**70,41 с**. Полный основной прогон: **250,02 с** (около 4 минут 10 секунд).
- Запуск контейнеров и скачивание bundle в замер не входят. Это время выполнения
кода; чтение объяснений, самостоятельное заполнение и ответы студента не измерялись.
- Ориентир новой практики **20–30 минут**, модуля целиком **около 2 часов**.
Общую оценку **12–15 часов** оставили предварительной; машинного замера
недостаточно для её уточнения. Нужен самостоятельный проход студента.
- Кнопки Jupyter/VS Code, отображение раскрывающегося решения и UI сервисов
не проверялись. Перезапуск выполнен менеджером ядра через API, а не кнопкой.
- Остальные семь ноутбуков и Trino smoke не выполнялись: runtime-конфигурация
не менялась, проверялся модуль 3.
## Сохранность данных
До основного прогона в каталоге было **0 таблиц**, в bucket — **4 объекта**.
После прогона исходные объекты сохранили ETag и размер, состав каталога
не изменился. Это проверка на имевшемся состоянии стенда, а не на копии
заполненного студентом курса.
Добавлены четыре исходных файла Taxi в `raw/nyc_taxi/`, как предусмотрено уроком;
они оставлены для модуля 4. Локальный bundle сохранён в игнорируемом `data/`.
Файлы данных и метаданных трёх временных таблиц удалены через PURGE.
Hadoop S3A оставил шесть нулевых маркеров `data/` и `metadata/`;
при завершении проверки удалены только эти шесть зафиксированных объектов.
В трёх префиксах практики осталось 0 объектов. Volumes не сбрасывались.
Поднятый для проверки стенд остановлен через `docker compose down` без `-v`.
## Статические проверки
- `git diff --check`;
- `docker compose config --quiet`;
- проверка ноутбука через `nbformat.validate` и синтаксиса Python-ячеек через AST;
- относительные ссылки изменённых документов и ноутбука;
- сохранение восьми модулей и маршрута README → START_HERE → программа → ноутбук.
+34 -8
View File
@@ -1,7 +1,11 @@
# Модуль 3. Raw-данные и первое чтение в Spark # Модуль 3. Raw-данные и первое чтение в Spark
**Статус:** `Ready for validation` **Статус:** `Ready for review` — код проверен на стенде, время самостоятельного прохождения студентом пока не измерено
**Последнее обновление:** `2026-03-07` **Последнее обновление:** `2026-09-15`
Дополнение: [#2 — практика настройки SparkSession](https://git.dementev.space/ddmitry/mini-lakehouse-lab/issues/2), после закрытой [#1](https://git.dementev.space/ddmitry/mini-lakehouse-lab/issues/1). Новая практика входит в модуль 3; структура курса остаётся из восьми модулей.
Результаты прогона и ограничения: [проверка #2](issue-2-spark-session-validation.md).
## Цель ## Цель
@@ -11,6 +15,9 @@
После прохождения модуля студент: После прохождения модуля студент:
- создаёт Spark-сессию для кластера, различает настройки из кода, параметров запуска и общего файла;
- проверяет фактический master, имя приложения, настройки каталога и чтение собственной временной Iceberg-таблицы;
- объясняет повторное использование контекста, остановку сессии и перезапуск ядра; меняет часовой пояс с восстановлением исходного значения;
- понимает, как локальный data bundle попадает в raw-зону MinIO; - понимает, как локальный data bundle попадает в raw-зону MinIO;
- умеет загружать PARQUET-файлы в MinIO программно через `boto3`; - умеет загружать PARQUET-файлы в MinIO программно через `boto3`;
- умеет читать raw-данные из MinIO через Spark (`spark.read.parquet` по `s3a://` пути); - умеет читать raw-данные из MinIO через Spark (`spark.read.parquet` по `s3a://` пути);
@@ -84,6 +91,20 @@ volumes:
### Секция 0: Введение ### Секция 0: Введение
- **[md]** Заголовок, цели модуля, prerequisite (data bundle из `START_HERE.md`). Таблица-сравнение: «Классический DWH: данные уже внутри СУБД» vs «Lakehouse: данные нужно явно доставить в storage». - **[md]** Заголовок, цели модуля, prerequisite (data bundle из `START_HERE.md`). Таблица-сравнение: «Классический DWH: данные уже внутри СУБД» vs «Lakehouse: данные нужно явно доставить в storage».
### Секция 0.1: Как настроен Spark в нашем стенде
- **Объяснение:** SparkSession, SparkContext, driver в Jupyter, executors на worker-ах; master, appName, getOrCreate; local и кластер. Источники настроек и подготовленный доступ к Iceberg/MinIO.
- **Демонстрация:** сессия с master из общего файла, вычисление суммы 0…9, вывод выбранных несекретных настроек. Изменение `spark.sql.session.timeZone` и восстановление в `finally`.
- **Новый запуск:** повторный builder с другим appName сохраняет фактическое имя контекста; после `stop()` новый контекст получает новое имя.
- **Самостоятельное повторение:** заполнить адрес кластера, имя приложения и builder; контрольное решение в раскрывающемся блоке. Повторить ячейку и проверить неизменный applicationId.
- **Проверка каталога:** создать собственную Iceberg-таблицу с уникальным именем, прочитать `[0, 1]`, удалить только её через `DROP TABLE … PURGE` в `finally`. Учебные таблицы других модулей не используются.
- **Checkpoint:** проверить master и appName, объяснить источники настроек и место вычислений. После удаления временной таблицы перезапустить ядро, повторить заполненный шаблон и проверку Iceberg, затем продолжить raw-ingest.
- **Длительность:** ориентир 20–30 минут; на модуль целиком около 2 часов. Замер машинного выполнения и ограничения проверки фиксируются отдельно от оценки времени студента.
### Секция 0.2: Подготовка к raw-ingest
- Импорты, MinIO-клиент, пути и вспомогательные функции исходного урока. Сессия продолжает работать после checkpoint настройки.
### Секция 1: Проверка data bundle ### Секция 1: Проверка data bundle
- **[md]** Зачем проверяем; почему не скачиваем из интернета в ноутбуке (воспроизводимость, сетевые ограничения, разделение ответственности onboarding vs практика). - **[md]** Зачем проверяем; почему не скачиваем из интернета в ноутбуке (воспроизводимость, сетевые ограничения, разделение ответственности onboarding vs практика).
- **[code]** `os.listdir("/opt/data/nyc_taxi")`, вывод файлов с размерами, assert на наличие PARQUET-файлов и `taxi_zone_lookup.csv` — оба нужны для полного прохождения модуля (включая самостоятельное задание в Секции 6). Понятное сообщение с отсылкой к `START_HERE.md` при отсутствии любого из компонентов. - **[code]** `os.listdir("/opt/data/nyc_taxi")`, вывод файлов с размерами, assert на наличие PARQUET-файлов и `taxi_zone_lookup.csv` — оба нужны для полного прохождения модуля (включая самостоятельное задание в Секции 6). Понятное сообщение с отсылкой к `START_HERE.md` при отсутствии любого из компонентов.
@@ -96,7 +117,7 @@ volumes:
### Секция 3: Первое чтение в Spark ### Секция 3: Первое чтение в Spark
- **[md]** Разница `spark.read.parquet("s3a://...")` (raw-файлы) vs `spark.table(...)` (управляемая Iceberg-таблица). Мы пока на этапе raw, Iceberg-таблица появится в Модуле 4. - **[md]** Разница `spark.read.parquet("s3a://...")` (raw-файлы) vs `spark.table(...)` (управляемая Iceberg-таблица). Мы пока на этапе raw, Iceberg-таблица появится в Модуле 4.
- **[code]** Создание SparkSession (паттерн из Module 2 — адрес кластера из `spark-defaults.conf`, `setLogLevel("ERROR")`). - **[code]** Чтение через сессию, созданную в секции 0.1; повторная инициализация не нужна.
- **[code]** Чтение одного файла: `spark.read.parquet("s3a://lakehouse/raw/nyc_taxi/yellow_tripdata_2024-01.parquet")`. - **[code]** Чтение одного файла: `spark.read.parquet("s3a://lakehouse/raw/nyc_taxi/yellow_tripdata_2024-01.parquet")`.
- **[code]** Чтение всех файлов wildcard: `yellow_tripdata_*.parquet` (без привязки к конкретному году в wildcard, чтобы код работал с любыми скачанными файлами). - **[code]** Чтение всех файлов wildcard: `yellow_tripdata_*.parquet` (без привязки к конкретному году в wildcard, чтобы код работал с любыми скачанными файлами).
@@ -130,12 +151,15 @@ volumes:
### Дизайн-решения по ноутбуку ### Дизайн-решения по ноутбуку
- **Helper-код:** весь код inline в ноутбуке. Отдельный модуль в `src/` не создаётся — студент должен видеть каждый шаг явно (`boto3.upload_file()`, `spark.read.parquet()`, `.printSchema()`). - **Helper-код:** весь код inline в ноутбуке. Отдельный модуль в `src/` не создаётся — студент должен видеть каждый шаг явно (`boto3.upload_file()`, `spark.read.parquet()`, `.printSchema()`).
- **Cleanup:** ноутбук НЕ удаляет raw-данные из MinIO — они нужны Модулю 4 (bronze). Spark-сессия останавливается. Явное пояснение в конце ноутбука. - **Cleanup:** практика настройки удаляет только свою временную таблицу. Ноутбук НЕ удаляет raw-данные из MinIO — они нужны Модулю 4 (bronze). Spark-сессия останавливается в конце.
## Checkpoint ## Checkpoint
Студент должен уметь: Студент должен уметь:
- создать сессию по заполненному шаблону, подтвердить master `spark://spark-master:7077` и заданное appName;
- прочитать `[0, 1]` из собственной Iceberg-таблицы и подтвердить её удаление;
- объяснить, какие настройки предоставил стенд, где выполняются задания и что повторить после перезапуска ядра;
- показать файлы в raw-зоне MinIO (через MinIO Console или через код); - показать файлы в raw-зоне MinIO (через MinIO Console или через код);
- прочитать raw PARQUET через Spark и вывести несколько строк; - прочитать raw PARQUET через Spark и вывести несколько строк;
- назвать схему Yellow Taxi (количество колонок, основные типы); - назвать схему Yellow Taxi (количество колонок, основные типы);
@@ -148,10 +172,11 @@ volumes:
- `docker-compose.yml` содержит volume mount `./data:/opt/data:ro` для контейнера `jupyter`, и `docker compose config` проходит без ошибок. - `docker-compose.yml` содержит volume mount `./data:/opt/data:ro` для контейнера `jupyter`, и `docker compose config` проходит без ошибок.
- `START_HERE.md` содержит воспроизводимую инструкцию по скачиванию NYC Taxi PARQUET (минимум 3 файла + taxi zone lookup) до запуска Модуля 3. - `START_HERE.md` содержит воспроизводимую инструкцию по скачиванию NYC Taxi PARQUET (минимум 3 файла + taxi zone lookup) до запуска Модуля 3.
- `.gitignore` содержит `/data/` для исключения датасета из git. - `.gitignore` содержит `/data/` для исключения датасета из git.
- `notebooks/03_raw_ingest_and_first_read.ipynb` можно выполнить сверху вниз в поднятом Jupyter-окружении при наличии скачанного data bundle. - `notebooks/03_raw_ingest_and_first_read.ipynb` можно пройти по порядку в поднятом Jupyter-окружении при наличии data bundle, заполнив шаблон и выполнив указанный перезапуск ядра.
- Ноутбук следует методике курса: `объяснение -> демонстрация -> самостоятельное повторение -> checkpoint`. - Ноутбук следует методике курса: `объяснение -> демонстрация -> самостоятельное повторение -> checkpoint`.
- Ноутбук использует паттерны из Модулей 1-2: `boto3` для MinIO, SparkSession с адресом кластера из `spark-defaults.conf`, `setLogLevel("ERROR")`. - Демонстрация берёт master из `spark-defaults.conf`; самостоятельное задание явно задаёт тот же адрес, сохраняя подготовленную конфигурацию каталога. Шаблон и контрольное решение проверяются в новых ядрах на Spark 3.5.1.
- Raw-данные остаются в MinIO после выполнения ноутбука (cleanup отсутствует намеренно). - Проверены повторные ячейки, перезапуск ядра, изменение и восстановление часового пояса, новое имя приложения после остановки контекста.
- Raw-данные остаются в MinIO после выполнения ноутбука; удаляется только временная таблица настройки.
- При отсутствии data bundle — понятный assert с отсылкой к `START_HERE.md`. - При отсутствии data bundle — понятный assert с отсылкой к `START_HERE.md`.
- `docs/stack_reference.md` обновлён: секция JupyterLab содержит `./data` mount. - `docs/stack_reference.md` обновлён: секция JupyterLab содержит `./data` mount.
- `plans/README.md` содержит строку Модуля 3 в оглавлении. - `plans/README.md` содержит строку Модуля 3 в оглавлении.
@@ -166,7 +191,8 @@ volumes:
## Out of Scope ## Out of Scope
- Создание Iceberg-таблиц — отложено до Модуля 4. - Создание рабочих Iceberg-таблиц из raw — в Модуле 4. В Модуле 3 есть только маленькая временная таблица для проверки подключения.
- Подбор памяти, числа executors, shuffle, GC и эксперимент с числом партиций.
- Трансформации и очистка данных — отложено до Модулей 4-5. - Трансформации и очистка данных — отложено до Модулей 4-5.
- Чтение raw-данных через Trino (Trino не умеет читать произвольные файлы без таблицы) — отложено до Модуля 6. - Чтение raw-данных через Trino (Trino не умеет читать произвольные файлы без таблицы) — отложено до Модуля 6.
- Партиционирование, MERGE, streaming, gold-слой. - Партиционирование, MERGE, streaming, gold-слой.