- Зачем: - нужен согласованный draft модуля про raw-данные, первое чтение в Spark и подготовку data bundle. - Что: - добавлен план `module-03-raw-ingest-and-first-read.md` с deliverables, структурой ноутбука и acceptance criteria. - зафиксированы требования к onboarding-документации, raw-ingest в MinIO и валидации data bundle. - Проверка: - план вручную сверен с `docs/course_program.md`, `docs/course_prd.md` и `docs/stack_reference.md`. - runtime-проверка стенда и ноутбука не выполнялась.
18 KiB
Модуль 3. Raw-данные и первое чтение в Spark
Статус: Draft
Последнее обновление: 2026-03-07
Цель
Научить студента полному циклу работы с raw-данными в Lakehouse: доставка заранее скачанного PARQUET-датасета в raw-зону MinIO, чтение raw-данных через Spark, проверка схемы, типов и базового качества. Студент должен понять, зачем нужен raw-слой и почему raw-данные не следует «чинить на месте».
Результат для студента
После прохождения модуля студент:
- понимает, как локальный data bundle попадает в raw-зону MinIO;
- умеет загружать PARQUET-файлы в MinIO программно через
boto3; - умеет читать raw-данные из MinIO через Spark (
spark.read.parquetпоs3a://пути); - умеет проверять схему DataFrame (
.printSchema(),.dtypes), считать базовые метрики (.describe(),.count(), null-доли); - может найти простые аномалии или проблемные значения в реальном датасете;
- понимает, почему raw-слой нужен как воспроизводимая неизменяемая точка входа;
- понимает, почему датасет не следует скачивать из интернета прямо в ноутбуке.
Deliverables
notebooks/03_raw_ingest_and_first_read.ipynb— основной ноутбук Модуля 3;- изменение
docker-compose.yml— volume mount./data:/opt/data:roв сервисjupyter; - изменение
.gitignore— добавление/data/; - изменение
START_HERE.md— секция «Подготовка учебного датасета (перед Модулем 3)» + обновление маршрута прохождения и секции «Как работать в Jupyter» (добавить упоминание/opt/data); - изменение
docs/stack_reference.md— обновление секции JupyterLab (добавить./datamount); - обновление индекса планов в
plans/README.md.
Зависимости от инфраструктуры
Доставка данных в контейнер
Добавить volume mount ./data:/opt/data:ro в секцию jupyter.volumes файла docker-compose.yml (аналогично ./src:/opt/src:ro).
Студент заранее скачивает PARQUET на хост по инструкции из START_HERE.md. В ноутбуке студент загружает файлы из /opt/data/ в MinIO через boto3. Это делает процесс наглядным и воспроизводимым.
# docker-compose.yml, секция jupyter.volumes
volumes:
- ./spark/spark-defaults.conf:/opt/spark/conf/spark-defaults.conf:ro
- ./notebooks:/opt/work
- ./src:/opt/src:ro
- ./data:/opt/data:ro # учебный data bundle (NYC Taxi)
Инструкция скачивания датасета
Добавить в START_HERE.md секцию после «Restart и reset», перед «Что делать дальше»:
mkdir -p data/nyc_taxi- 3 команды
curlдля Yellow Tripdata 2024-01..03 сhttps://d37ci6vzurychx.cloudfront.net/trip-data/ - 1 команда
curlдляtaxi_zone_lookup.csvсhttps://d37ci6vzurychx.cloudfront.net/misc/ - Проверочный
ls -lh data/nyc_taxi/ - Инструкция фиксирует конкретные файлы (2024-01, 2024-02, 2024-03) как каноническое учебное подмножество; расширенный режим — до 12 месяцев 2024 года
- Пометка: после изменения
docker-compose.yml(добавления volume) нуженdocker compose up -dдля пересоздания контейнераjupyter; после этого новые файлы в./dataвидны сразу без перезапуска
Структура raw-зоны в MinIO
Путь: s3a://lakehouse/raw/nyc_taxi/<original_filename>. Отдельный prefix raw/ не смешивается с warehouse/ (Iceberg-таблицы). Имена файлов сохраняются оригинальными, чтобы подчеркнуть принцип «raw не изменяется».
.gitignore
Добавить в конец:
# Dataset (downloaded by student, not stored in repo)
/data/
План работ
- Добавить
/data/в.gitignore. - Добавить volume mount
./data:/opt/data:roвdocker-compose.yml, секцияjupyter.volumes. - Добавить в
START_HERE.mdсекцию «Подготовка учебного датасета (перед Модулем 3)» с воспроизводимыми командами скачивания NYC Taxi Yellow Tripdata PARQUET (3 месяца + taxi zone lookup). Обновить маршрут прохождения (добавить шаг подготовки данных). Обновить секцию «Как работать в Jupyter» — добавить упоминание./datamount как/opt/data. Обновить секцию «Что делать дальше», чтобы маршрут включал Модуль 3. - Обновить
docs/stack_reference.md— в секции JupyterLab добавить строку./dataсмонтирован read-only как/opt/data. - Создать
notebooks/03_raw_ingest_and_first_read.ipynbпо ячеечной структуре, описанной ниже. - Обновить
plans/README.md— добавить строку Модуля 3 в таблицу оглавления. - Валидация: убедиться, что
docker compose configпроходит без ошибок; выполнить ноутбук сверху вниз на поднятом стенде с data bundle.
Структура ноутбука
Секция 0: Введение
- [md] Заголовок, цели модуля, prerequisite (data bundle из
START_HERE.md). Таблица-сравнение: «Классический DWH: данные уже внутри СУБД» vs «Lakehouse: данные нужно явно доставить в storage».
Секция 1: Проверка data bundle
- [md] Зачем проверяем; почему не скачиваем из интернета в ноутбуке (воспроизводимость, сетевые ограничения, разделение ответственности onboarding vs практика).
- [code]
os.listdir("/opt/data/nyc_taxi"), вывод файлов с размерами, assert на наличие PARQUET-файлов иtaxi_zone_lookup.csv— оба нужны для полного прохождения модуля (включая самостоятельное задание в Секции 6). Понятное сообщение с отсылкой кSTART_HERE.mdпри отсутствии любого из компонентов.
Секция 2: Загрузка в raw-зону MinIO
- [md] Что такое raw-зона, параллель с stg-слоем в Greenplum, конвенция путей, почему raw не чинится «на месте».
- [code] Демонстрация: перед загрузкой проверяем через
s3.head_object(), существует ли уже объект с таким ключом. Три ветки: (1) объект не существует — загружаем черезboto3.upload_file(); (2) объект существует и размер совпадает с локальным файлом — пропускаем с пометкой «already exists, skipping»; (3) объект существует, но размер отличается — fail fast с понятной ошибкой (raise), без перезаписи. Это обеспечивает идемпотентность и подкрепляет принцип «raw не перезаписывается». В markdown-пояснении перед ячейкой явно объяснить: повторный запуск безопасен, уже загруженные файлы не перетираются, а конфликт размеров сигнализирует о проблеме с данными. - [code] Верификация:
s3.list_objects_v2()— листинг raw-зоны. - [md] Данные теперь в storage. Spark может читать их напрямую по
s3a://пути; для Trino потребуется зарегистрированная таблица (это будет в Модулях 4 и 6).
Секция 3: Первое чтение в Spark
- [md] Разница
spark.read.parquet("s3a://...")(raw-файлы) vsspark.table(...)(управляемая Iceberg-таблица). Мы пока на этапе raw, Iceberg-таблица появится в Модуле 4. - [code] Создание SparkSession (паттерн из Module 2 — без
.master(),setLogLevel("ERROR")). - [code] Чтение одного файла:
spark.read.parquet("s3a://lakehouse/raw/nyc_taxi/yellow_tripdata_2024-01.parquet"). - [code] Чтение всех файлов wildcard:
yellow_tripdata_*.parquet(без привязки к конкретному году в wildcard, чтобы код работал с любыми скачанными файлами).
Секция 4: Проверка схемы
- [md] Зачем проверять схему: в классическом DWH схема задана DDL, в raw — вычитывается из файлов. Что может пойти не так: разные типы в разных файлах, неожиданные nullable-поля.
- [code]
.printSchema(). - [code] Колонки и типы в удобном формате (
.dtypes). - [code] Null-анализ ключевых колонок (
VendorID,tpep_pickup_datetime,passenger_count,trip_distance,total_amount) — количество и доля null в каждой. - [md] Обсуждение: null в raw — норма, не исправляем на месте. Очистка — задача bronze/silver слоев (Модули 4-5).
Секция 5: Базовые метрики и аномалии
- [md] Цель: быстро понять диапазоны значений, выявить очевидные проблемы.
- [code]
.describe()на числовых колонках. - [code] Поиск аномалий: отрицательные
fare_amount, нулевая дистанция с ненулевой суммой,total_amount > $1000, pickup за пределами ожидаемого диапазона дат (определяется динамически из имён файлов, а не хардкодится). - [md] Аномалии в реальных данных — норма, а не баг загрузки. Задача raw — сохранить как есть.
Секция 6: Самостоятельное задание
- [md] Инструкция: (1) прочитать
taxi_zone_lookup.csvиз raw-зоны через Spark, (2) вывести схему + 10 строк, (3) посчитать количество зон по borough, (4) найти топ-5 pickup locations и join с зонами, (5) найти ещё одну аномалию или интересный паттерн в данных. - [code] 4 пустых ячейки с
# Ваш код: ....
Секция 7: Почему raw-слой важен
- [md] Мини-лекция (3-5 абзацев): raw = неизменяемый архив; параллель с stg/ods в Greenplum; почему не скачиваем в ноутбуке; почему не чиним на месте (можно перестроить bronze/silver заново); в Lakehouse raw-файлы лежат в storage отдельно от compute — Spark читает их напрямую, а для Trino потребуется оформить данные в таблицу.
Секция 8: Checkpoint
- [md] Вопросы для самопроверки: (1) показать файлы в raw-зоне MinIO, (2) какая схема у Yellow Taxi — сколько колонок, (3) какие колонки содержат null — это баг или норма, (4) зачем raw-слой, если данные потом трансформируются, (5) почему не скачиваем из ноутбука, (6) что произойдёт, если NYC TLC изменит формат файла.
Секция 9: Завершение
- [md] «Мы намеренно не удаляем raw-данные из MinIO. В Модуле 4 мы будем использовать их для создания bronze-слоя.»
- [code]
spark.stop().
Дизайн-решения по ноутбуку
- Helper-код: весь код inline в ноутбуке. Отдельный модуль в
src/не создаётся — студент должен видеть каждый шаг явно (boto3.upload_file(),spark.read.parquet(),.printSchema()). - Cleanup: ноутбук НЕ удаляет raw-данные из MinIO — они нужны Модулю 4 (bronze). Spark-сессия останавливается. Явное пояснение в конце ноутбука.
Checkpoint
Студент должен уметь:
- показать файлы в raw-зоне MinIO (через MinIO Console или через код);
- прочитать raw PARQUET через Spark и вывести несколько строк;
- назвать схему Yellow Taxi (количество колонок, основные типы);
- показать, какие колонки содержат null-значения;
- объяснить, почему raw-слой нужен как воспроизводимая неизменяемая точка входа;
- объяснить, почему данные не скачиваются прямо из ноутбука.
Acceptance Criteria
docker-compose.ymlсодержит volume mount./data:/opt/data:roдля контейнераjupyter, иdocker compose configпроходит без ошибок.START_HERE.mdсодержит воспроизводимую инструкцию по скачиванию NYC Taxi PARQUET (минимум 3 файла + taxi zone lookup) до запуска Модуля 3..gitignoreсодержит/data/для исключения датасета из git.notebooks/03_raw_ingest_and_first_read.ipynbможно выполнить сверху вниз в поднятом Jupyter-окружении при наличии скачанного data bundle.- Ноутбук следует методике курса:
объяснение -> демонстрация -> самостоятельное повторение -> checkpoint. - Ноутбук использует паттерны из Модулей 1-2:
boto3для MinIO, SparkSession без.master(),setLogLevel("ERROR"). - Raw-данные остаются в MinIO после выполнения ноутбука (cleanup отсутствует намеренно).
- При отсутствии data bundle — понятный assert с отсылкой к
START_HERE.md. docs/stack_reference.mdобновлён: секция JupyterLab содержит./datamount.plans/README.mdсодержит строку Модуля 3 в оглавлении.
Риски
- Размер данных. 3 месяца Yellow Taxi — примерно 120-150 MB (~40-50 MB на файл). Комфортно для стенда с 10 GB RAM. Extended (12 месяцев, ~600 MB) может замедлить Spark, но не должен вызывать падения.
- Студент забыл скачать данные. Assert в Секции 1 с понятным сообщением и ссылкой на
START_HERE.md. - NYC TLC изменит формат/URL. Ноутбук использует wildcard
yellow_tripdata_*.parquetбез привязки к году. Инструкция вSTART_HERE.mdфиксирует конкретные файлы 2024-01..03, но если URL станут недоступны — потребуется обновление инструкции. - Директория
data/не существует. Docker volume mount создаст пустую директорию, assert в ноутбуке поймает отсутствие файлов. - Пересоздание контейнера. Добавление volume в
docker-compose.ymlтребует пересоздания контейнераjupyter(docker compose up -dдостаточно — Docker пересоздаст только изменённый сервис). После этого bind mount уже работает: новые файлы в./dataвидны сразу без дополнительных перезапусков. Указать явно в инструкции.
Out of Scope
- Создание Iceberg-таблиц — отложено до Модуля 4.
- Трансформации и очистка данных — отложено до Модулей 4-5.
- Чтение raw-данных через Trino (Trino не умеет читать произвольные файлы без таблицы) — отложено до Модуля 6.
- Партиционирование, MERGE, streaming, gold-слой.
- Автоматизированный скрипт скачивания (может быть добавлен позже, не является блокером).