docs(course): integrate Spark setup into the module flow
This commit is contained in:
1 parent
16f76d63fc
commit
9d5b0b716e
9 files changed
+122
-214
No files matched your search
@@ -7,16 +7,9 @@
|
||||
"source": [
|
||||
"# Модуль 3. Raw-данные и первое чтение в Spark\n",
|
||||
"\n",
|
||||
"В этом модуле мы разбираем полный первый цикл работы с учебным датасетом в Lakehouse:\n",
|
||||
"В прошлом модуле мы проследили, как Spark работает с каталогом и файлами в MinIO. Теперь загрузим настоящие данные: поездки NYC Taxi и справочник зон.\n",
|
||||
"\n",
|
||||
"- создаём Spark-сессию для кластера и проверяем её настройки;\n",
|
||||
"- проверяем заранее скачанный локальный data bundle;\n",
|
||||
"- загружаем исходные файлы в raw-зону `MinIO`;\n",
|
||||
"- читаем raw-данные через `Spark` по `s3a://` пути;\n",
|
||||
"- смотрим схему, типы, базовые метрики и простые аномалии;\n",
|
||||
"- фиксируем, почему raw-слой нужен как воспроизводимая неизменяемая точка входа.\n",
|
||||
"\n",
|
||||
"После этого ноутбука ты должен уметь объяснить, почему raw-данные не надо «чинить на месте» и почему их не стоит скачивать прямо из ноутбука."
|
||||
"Сначала подготовим Spark-сессию и проверим подключение. Затем перенесем заранее скачанные файлы в raw-зону MinIO, прочитаем их через Spark и посмотрим на схему, пропуски и аномалии. Эти файлы останутся источником для таблиц, которые мы построим в следующих модулях.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -26,21 +19,9 @@
|
||||
"source": [
|
||||
"## 0. Перед стартом\n",
|
||||
"\n",
|
||||
"Перед выполнением ноутбука:\n",
|
||||
"\n",
|
||||
"- подними стенд по `START_HERE.md`;\n",
|
||||
"- пройди `notebooks/01_environment_and_smoke_test.ipynb`;\n",
|
||||
"- пройди `notebooks/02_lakehouse_mental_model.ipynb`;\n",
|
||||
"- начни с нового ядра, чтобы настройки не зависели от предыдущих опытов;\n",
|
||||
"- заранее скачай data bundle в `./data/nyc_taxi` по инструкции из `START_HERE.md`.\n",
|
||||
"\n",
|
||||
"В классическом `PostgreSQL / Greenplum` данные часто уже лежат внутри системы. В Lakehouse путь более явный: сначала исходные файлы должны попасть в object storage, и только потом мы начинаем поверх них читать, профилировать и строить таблицы.\n",
|
||||
"\n",
|
||||
"| | Классический DWH | Lakehouse |\n",
|
||||
"| --- | --- | --- |\n",
|
||||
"| Где стартует работа | Данные уже внутри СУБД | Исходные файлы сначала попадают в storage |\n",
|
||||
"| Первая практическая задача | `SELECT` из готовой таблицы | Загрузить raw и прочитать raw |\n",
|
||||
"| Что важно не сломать | Таблицу / схему БД | Неизменяемый raw-слой |"
|
||||
"- Подними стенд по `START_HERE.md` и пройди модули 1 и 2.\n",
|
||||
"- Скачай data bundle в `./data/nyc_taxi` по инструкции из `START_HERE.md`.\n",
|
||||
"- Открой этот ноутбук в новом ядре, чтобы начать без сессии от предыдущих опытов.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -48,27 +29,21 @@
|
||||
"id": "session-intro",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 0.1. Как настроен Spark в нашем стенде\n",
|
||||
"## 1. Как настроен Spark в нашем стенде\n",
|
||||
"\n",
|
||||
"Отведи на эту практику 20–30 минут. Сначала разберём готовый пример, затем ты заполнишь шаблон и проверишь подключение. Файлы Taxi понадобятся после практики.\n",
|
||||
"До сих пор мы пользовались готовым объектом `spark`. Разберем, как его создать и куда он отправляет задания: это понадобится при чтении файлов Taxi.\n",
|
||||
"\n",
|
||||
"`SparkSession` — объект `spark`, через который мы читаем DataFrame и выполняем SQL. Внутри него `SparkContext` связывает приложение с вычислительными ресурсами. В обычном ядре нашего ноутбука используется один работающий контекст.\n",
|
||||
"`SparkSession` дает доступ к DataFrame и SQL. Его `SparkContext` связывает приложение с вычислительными ресурсами. В обычном ядре нашего ноутбука используется один работающий контекст.\n",
|
||||
"\n",
|
||||
"| Участник | Что делает в этом стенде |\n",
|
||||
"| Участник | Что делает в стенде |\n",
|
||||
"| --- | --- |\n",
|
||||
"| Driver | Работает в контейнере `jupyter`: выполняет Python-код ноутбука, строит план и раздаёт задания |\n",
|
||||
"| Driver | Работает в `jupyter`: выполняет Python-код, строит план и раздает задания |\n",
|
||||
"| Master | Принимает подключение по `spark://spark-master:7077` и выделяет ресурсы worker-ов |\n",
|
||||
"| Executors | Запускаются на `spark-worker-1` и `spark-worker-2`, читают данные и выполняют задания |\n",
|
||||
"| Executors | Работают на `spark-worker-1` и `spark-worker-2`, читают данные и выполняют задания |\n",
|
||||
"\n",
|
||||
"В режиме `local[*]` Spark выполняет задания локально, используя потоки на машине driver-а. В нашем курсе нужен кластер: успешный `count()` сам по себе ещё не доказывает, что подключение правильное.\n",
|
||||
"В режиме `local[*]` задания выполняются в потоках на машине driver-а. Нам нужен кластер, поэтому проверим фактический master вместе с результатом вычисления.\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)."
|
||||
"Создадим сессию. `.appName(...)` задает имя приложения в Spark UI, `.getOrCreate()` создает сессию или возвращает уже работающую. Адрес кластера пока возьмем из общего файла настроек.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -99,19 +74,23 @@
|
||||
"id": "session-config-explanation",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"`appName` — имя приложения, видимое в Spark UI. `getOrCreate()` создаёт сессию, если её ещё нет, или возвращает существующую. `.master(...)` выбирает вычислитель; в демонстрации он не указан, поэтому используется общий файл.\n",
|
||||
"### Откуда взялось подключение\n",
|
||||
"\n",
|
||||
"### Что уже подготовлено для Iceberg и MinIO\n",
|
||||
"Master мы не указали в коде: Spark прочитал его из `spark/spark-defaults.conf`. Docker подключает этот файл к Jupyter как `/opt/spark/conf/spark-defaults.conf`. Там же подготовлены каталог Iceberg и доступ к MinIO.\n",
|
||||
"\n",
|
||||
"| Настройка в `spark-defaults.conf` | Назначение |\n",
|
||||
"| Настройка в файле | Для чего нужна |\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.sql.catalog.lakehouse.warehouse` | Задает место для файлов таблиц: `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.` снимается."
|
||||
"При запуске через `spark-submit` настройки можно передать параметрами `--master`, `--name` и `--conf`. В коде для этого есть `.master(...)`, `.appName(...)` и `.config(...)`. Для нового приложения обычный приоритет: код -> параметры запуска -> файл defaults. Параметры запуска driver-а надо задать до его старта; уже работающий контекст новый builder не перестраивает.\n",
|
||||
"\n",
|
||||
"В нашем Jupyter дополнительные `--master` и `--conf` при запуске не передаются. Вне стенда потребуются также библиотеки Iceberg, S3A и JDBC: их состав и конфигурация описаны в [справочнике](../docs/stack_reference.md#spark).\n",
|
||||
"\n",
|
||||
"Посмотрим, какие значения получила сессия. В файле есть и учетные данные, поэтому выводим только выбранные несекретные настройки. Для SQL используем `spark.conf`; в Hadoop-настройках префикс `spark.hadoop.` уже снят.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -140,11 +119,11 @@
|
||||
"id": "session-live-explanation",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Что можно изменить во время работы\n",
|
||||
"### Изменение настройки в работающей сессии\n",
|
||||
"\n",
|
||||
"`spark.sql.session.timeZone` задаёт часовой пояс SQL-сессии. У одной и той же секунды Unix-времени будет разное отображение в UTC и Москве. Это настройка интерпретации времени, без изменения файлов данных.\n",
|
||||
"Некоторые настройки можно менять, пока приложение работает. Например, `spark.sql.session.timeZone` определяет часовой пояс SQL-сессии. Посмотрим, как одна и та же секунда Unix-времени отображается в UTC и Москве.\n",
|
||||
"\n",
|
||||
"Сохраним исходное значение и восстановим его в `finally`: оно вернётся даже при ошибке внутри опыта. Для Spark 3.5.1 эта настройка изменяется в работающей сессии."
|
||||
"В Spark 3.5.1 эта настройка изменяется через `spark.conf.set(...)`. Сохраним исходное значение и вернем его в `finally`, в том числе при ошибке. Файлы данных этот опыт не меняет.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -174,11 +153,9 @@
|
||||
"id": "session-reuse-explanation",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Для чего нужен новый запуск\n",
|
||||
"### Повторный вызов builder-а\n",
|
||||
"\n",
|
||||
"Проверим `appName`: это имя приложения, которое запущено через `SparkContext`. Попробуем задать другое имя через builder и посмотрим на **фактическое** `spark.sparkContext.appName`.\n",
|
||||
"\n",
|
||||
"Повторное выполнение ячейки не останавливает приложение. `getOrCreate()` использует ту же сессию и тот же контекст; это не способ переключиться на другой master или переименовать уже запущенное приложение."
|
||||
"Теперь попробуем сменить имя приложения на `module-03-raw-ingest-and-first-read`, с которым будем читать Taxi. Проверим имя у работающего контекста: `spark.sparkContext.appName`.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -206,13 +183,11 @@
|
||||
"id": "session-stop-explanation",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Теперь остановим демонстрационную сессию. В следующем задании новый контекст получит новое имя.\n",
|
||||
"Имя осталось прежним: `getOrCreate()` вернул ту же сессию с тем же контекстом. Повтор ячейки сохраняет и переменные Python, и работающее приложение.\n",
|
||||
"\n",
|
||||
"- **Повторный запуск ячейки** сохраняет переменные Python и может использовать уже работающий контекст.\n",
|
||||
"- **`spark.stop()`** останавливает сессию и её контекст. Переменные Python остаются, но старые Spark DataFrame больше нельзя использовать. Файлы MinIO и записи каталога сохраняются.\n",
|
||||
"- **Перезапуск ядра** заново запускает Python и связанный с ним Spark driver. Переменные исчезают; нужно повторить импорты, создание сессии и чтение DataFrame. Данные в MinIO и каталоге от этого не удаляются.\n",
|
||||
"Чтобы создать приложение с новым именем, вызовем `spark.stop()`. Этот вызов останавливает сессию и ее контекст. Переменные Python останутся, но старые Spark DataFrame использовать уже нельзя. Файлы MinIO и таблицы каталога сохранятся.\n",
|
||||
"\n",
|
||||
"Для смены master или конфигурации файла начинай с нового ядра. Остановка сессии без перезапуска ядра достаточна для нашего опыта с именем, но не заменяет новый процесс для всех стартовых настроек."
|
||||
"Для смены master или настроек в общем файле перезапускай ядро: одной остановки сессии недостаточно для всех параметров запуска. Перезапуск удалит переменные Python, и тогда придется повторить импорты, создание сессии и чтение DataFrame.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -230,17 +205,13 @@
|
||||
"id": "session-exercise-explanation",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Самостоятельно: создай сессию и проверь подключение\n",
|
||||
"## 2. Подключись к Spark самостоятельно\n",
|
||||
"\n",
|
||||
"Заполни три пропуска в следующей ячейке:\n",
|
||||
"Создай сессию, с которой продолжишь работу с Taxi. Заполни в следующей ячейке адрес кластера, имя `module-03-raw-ingest-and-first-read` и цепочку `.master(...)`, `.appName(...)`, `.getOrCreate()`.\n",
|
||||
"\n",
|
||||
"1. Адрес кластера внутри Docker-сети.\n",
|
||||
"2. Имя приложения `module-03-raw-ingest-and-first-read`.\n",
|
||||
"3. Вызов builder-а с `.master(...)`, `.appName(...)` и `.getOrCreate()`.\n",
|
||||
"Настройки каталога возьми из общего файла. Адрес кластера в этот раз задай явно через `.master(...)`; остальные ноутбуки берут его из того же файла.\n",
|
||||
"\n",
|
||||
"Используй подготовленный каталог из `spark-defaults.conf`: его параметры не нужно копировать в код. Явный `.master(...)` здесь нужен, чтобы ты самостоятельно указал подключение; остальные ноутбуки берут тот же адрес из файла.\n",
|
||||
"\n",
|
||||
"Шаблон остановится на `assert`, пока ты его не заполнишь. После заполнения выполни шаблон и ячейку проверки подключения дважды: имя и `applicationId` должны остаться теми же."
|
||||
"Пока пропуски не заполнены, шаблон останавливается на `assert`. Выполни заполненный шаблон и проверку подключения дважды: имя и `applicationId` должны остаться теми же.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -269,7 +240,7 @@
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<details>\n",
|
||||
"<summary>Контрольное решение — открой после своей попытки</summary>\n",
|
||||
"<summary>Контрольное решение (после своей попытки)</summary>\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"from pyspark.sql import SparkSession\n",
|
||||
@@ -307,13 +278,13 @@
|
||||
"id": "session-table-explanation",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Проверь весь путь: Spark → каталог → MinIO\n",
|
||||
"### Запись и чтение через каталог\n",
|
||||
"\n",
|
||||
"Одного правильного master недостаточно: проверим запись и чтение через каталог. Ячейка сама создаёт маленькую Iceberg-таблицу с числами `0` и `1`, читает её и удаляет в `finally`. Таблицы модуля 2 не требуются.\n",
|
||||
"Проверим, что сессия может работать с каталогом и MinIO. Следующая ячейка создаст собственную маленькую Iceberg-таблицу, прочитает из нее числа `0` и `1` и удалит ее в `finally`. Она работает и после удаления таблиц из модуля 2.\n",
|
||||
"\n",
|
||||
"У таблицы уникальное имя. `DROP TABLE … PURGE` удаляет именно её запись в каталоге, файлы данных и метаданных. В MinIO могут остаться пустые маркеры каталогов `data/` и `metadata/`; это не данные таблицы. Пока ячейка работает, не перезапускай ядро: при принудительном завершении процесса `finally` не выполнится. Если это произошло, сохрани напечатанное имя для удаления этой таблицы после нового подключения.\n",
|
||||
"У таблицы уникальное имя. `DROP TABLE ... PURGE` удаляет ее запись в каталоге, данные и метаданные; в MinIO могут остаться пустые маркеры `data/` и `metadata/`.\n",
|
||||
"\n",
|
||||
"Это только проверка подключения; создание рабочей таблицы поездок изучим в модуле 4."
|
||||
"Дождись завершения ячейки перед перезапуском ядра: при принудительной остановке процесса `finally` не выполнится. Если прервал ее, сохрани напечатанное имя, чтобы удалить эту таблицу после нового подключения.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -344,20 +315,13 @@
|
||||
"id": "session-checkpoint",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Checkpoint настройки\n",
|
||||
"### Проверь запуск в новом ядре\n",
|
||||
"\n",
|
||||
"Проверки выше должны подтвердить master `spark://spark-master:7077`, заданное имя приложения, строки `[0, 1]` и удаление временной таблицы. В опыте с часовым поясом исходное значение должно восстановиться.\n",
|
||||
"В выводе должны быть master `spark://spark-master:7077`, заданное имя приложения, строки `[0, 1]` и сообщение об удалении таблицы.\n",
|
||||
"\n",
|
||||
"Ответь своими словами:\n",
|
||||
"Перезапусти ядро и повтори заполненный шаблон, проверку подключения и ячейку с таблицей. `applicationId` будет новым, остальные результаты должны совпасть. Импорт `SparkSession` уже есть в шаблоне, поэтому демонстрацию повторять не нужно.\n",
|
||||
"\n",
|
||||
"1. Какие настройки ты задал в коде, а какие предоставил стенд? Где их найти?\n",
|
||||
"2. Чем SparkSession отличается от SparkContext? Где работают driver и executors?\n",
|
||||
"3. Почему повторный `getOrCreate()` не сменил имя демонстрационного приложения, а после `stop()` оно изменилось?\n",
|
||||
"4. Что останется после перезапуска ядра и что потребуется выполнить заново?\n",
|
||||
"\n",
|
||||
"Теперь, когда временная таблица удалена, **перезапусти ядро**. Выполни заполненный шаблон, проверку подключения и ячейку с временной таблицей. Они должны работать без демонстрации и без состояния прошлого ядра; `applicationId` будет новым. Импорт `SparkSession` уже есть в шаблоне.\n",
|
||||
"\n",
|
||||
"После этой проверки продолжи ниже: подготовь переменные и MinIO-клиент, затем переходи к raw-данным. Сессию пока не останавливай."
|
||||
"С этим подключением перейдем к файлам Taxi. Оставь сессию работать.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -365,9 +329,9 @@
|
||||
"id": "raw-preparation",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 0.2. Подготовка к raw-ingest\n",
|
||||
"## 3. Проверка data bundle\n",
|
||||
"\n",
|
||||
"Сессия из самостоятельного задания продолжает работать. Создадим MinIO-клиент и переменные для исходного сценария модуля."
|
||||
"Подготовим MinIO-клиент для загрузки файлов и пути к данным. Docker подключает каталог `data/` с хоста к Jupyter как `/opt/data/`.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -460,15 +424,7 @@
|
||||
"id": "d4d8f302",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 1. Проверка data bundle\n",
|
||||
"\n",
|
||||
"Сначала убеждаемся, что нужные файлы уже лежат внутри Jupyter-контейнера по пути `/opt/data/nyc_taxi`.\n",
|
||||
"\n",
|
||||
"Почему не скачиваем данные прямо здесь:\n",
|
||||
"\n",
|
||||
"- так модуль остаётся воспроизводимым и не зависит от сетевого доступа внутри ноутбука;\n",
|
||||
"- onboarding и подготовка окружения остаются в `START_HERE.md`, а практика в ноутбуке занимается только Lakehouse-операциями;\n",
|
||||
"- студент видит границу ответственности: сначала подготовить bundle, потом работать с raw-слоем.\n"
|
||||
"Файлы должны быть скачаны заранее по `START_HERE.md`. Здесь проверим состав набора, прежде чем отправлять его в MinIO. Так во время урока нам не понадобится внешняя сеть, а при повторном прохождении мы будем читать те же исходные файлы.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -523,7 +479,7 @@
|
||||
"id": "dabd90f2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 2. Загрузка в raw-зону MinIO\n",
|
||||
"## 4. Загрузка в raw-зону MinIO\n",
|
||||
"\n",
|
||||
"Raw-слой в Lakehouse похож на воспроизводимый входной `stg`-контур: мы сохраняем исходные файлы как есть, под оригинальными именами, не переписывая их содержимое.\n",
|
||||
"\n",
|
||||
@@ -621,7 +577,7 @@
|
||||
"id": "696d6adc",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 3. Первое чтение raw PARQUET через Spark\n",
|
||||
"## 5. Первое чтение raw PARQUET через Spark\n",
|
||||
"\n",
|
||||
"Теперь читаем raw-файлы напрямую из `MinIO` по `s3a://` пути. Это и есть первая важная практика Lakehouse: вычислитель (`Spark`) работает поверх файлов в object storage, а не только поверх локальной файловой системы или таблиц внутри одной СУБД.\n",
|
||||
"\n",
|
||||
@@ -630,7 +586,7 @@
|
||||
"- `spark.read.parquet(\"s3a://...\")` читает raw-файлы напрямую по физическому пути;\n",
|
||||
"- `spark.table(\"lakehouse....\")` читает уже зарегистрированную управляемую таблицу через каталог.\n",
|
||||
"\n",
|
||||
"Проверочную `Iceberg`-таблицу из практики настройки мы уже удалили. Поездки пока лежат отдельными raw-файлами, поэтому читаем их через `spark.read.parquet(...)`. Рабочую таблицу поездок создадим в Модуле 4."
|
||||
"Для проверки подключения мы читали маленькую таблицу по имени через `spark.table(...)`. Файлы Taxi пока не зарегистрированы в каталоге: прочитаем их по пути через `spark.read.parquet(...)`. Таблицу поездок создадим в модуле 4."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -697,7 +653,7 @@
|
||||
"id": "9701728a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 4. Схема, типы и null-значения\n",
|
||||
"## 6. Схема, типы и null-значения\n",
|
||||
"\n",
|
||||
"Следующий шаг после первого чтения: понять, какие колонки пришли, какие у них типы и где уже на raw-слое встречаются `null`.\n",
|
||||
"\n",
|
||||
@@ -760,7 +716,7 @@
|
||||
"id": "69e6b77e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 5. Базовые метрики и аномалии\n",
|
||||
"## 7. Базовые метрики и аномалии\n",
|
||||
"\n",
|
||||
"Теперь быстро профилируем данные и ищем несколько очевидных проблемных паттернов:\n",
|
||||
"\n",
|
||||
@@ -879,7 +835,7 @@
|
||||
"id": "095c9ac6",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 6. Самостоятельное задание\n",
|
||||
"## 8. Самостоятельное задание\n",
|
||||
"\n",
|
||||
"Используй raw-данные, которые уже лежат в `MinIO`:\n",
|
||||
"\n",
|
||||
@@ -937,7 +893,7 @@
|
||||
"id": "e23dfe47",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 7. Почему raw-слой важен\n",
|
||||
"## 9. Почему raw-слой важен\n",
|
||||
"\n",
|
||||
"Raw-слой полезен не потому, что в нём удобно жить постоянно, а потому что он даёт устойчивую исходную точку. Если завтра поменяются правила очистки, дедупликации или нормализации, мы не обязаны заново скачивать источник и гадать, что именно уже успели «подправить». Мы просто перечитываем raw и перестраиваем downstream-слои.\n",
|
||||
"\n",
|
||||
@@ -953,16 +909,17 @@
|
||||
"id": "8849c73f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 8. Checkpoint\n",
|
||||
"## 10. Checkpoint\n",
|
||||
"\n",
|
||||
"Проверь себя:\n",
|
||||
"\n",
|
||||
"1. Можешь ли ты показать файлы raw-зоны в `MinIO` через код или `MinIO Console`?\n",
|
||||
"2. Сколько колонок в схеме `Yellow Taxi` и какие типы там встречаются?\n",
|
||||
"3. Какие колонки содержат `null` и почему это не обязательно баг загрузки?\n",
|
||||
"4. Зачем нужен raw-слой, если дальше всё равно будут трансформации?\n",
|
||||
"5. Почему мы не скачиваем датасет прямо из ноутбука?\n",
|
||||
"6. Что произойдёт, если источник завтра поменяет формат файлов?\n"
|
||||
"1. К какому master подключена сессия? Какие настройки ты задал в коде, а какие она получила из общего файла?\n",
|
||||
"2. Чем SparkSession отличается от SparkContext? Где работают driver и executors?\n",
|
||||
"3. Почему часовой пояс изменился сразу, а для нового имени приложения понадобилось остановить сессию? Что придется повторить после перезапуска ядра?\n",
|
||||
"4. Можешь ли ты показать загруженные raw-файлы в MinIO и прочитать их через Spark?\n",
|
||||
"5. Сколько колонок в Yellow Taxi, какие у них типы и где встречаются `null`? Почему пропуски не обязательно означают ошибку загрузки?\n",
|
||||
"6. Зачем сохранять raw-слой без изменений, если дальше будут трансформации?\n",
|
||||
"7. Почему мы скачали датасет до запуска ноутбука? Что потребуется проверить, если источник изменит формат файлов?\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -970,7 +927,7 @@
|
||||
"id": "371c4c9d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 9. Завершение\n",
|
||||
"## 11. Завершение\n",
|
||||
"\n",
|
||||
"Мы намеренно не удаляем raw-данные из `MinIO`. В Модуле 4 они понадобятся для построения первого управляемого `bronze`-слоя на `Iceberg`.\n"
|
||||
]
|
||||
|
||||
Reference in new issue
Block a user