Files
mini-lakehouse-lab/plans/module-03-raw-ingest-and-first-read.md
T
ddadmin 60079dd1c8 docs(plan): добавлен план модуля 3 по raw-ingest
- Зачем:
  - нужен согласованный 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-проверка стенда и ноутбука не выполнялась.
2026-03-07 11:43:38 +03:00

18 KiB
Raw Blame History

Модуль 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 (добавить ./data mount);
  • обновление индекса планов в 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/

План работ

  1. Добавить /data/ в .gitignore.
  2. Добавить volume mount ./data:/opt/data:ro в docker-compose.yml, секция jupyter.volumes.
  3. Добавить в START_HERE.md секцию «Подготовка учебного датасета (перед Модулем 3)» с воспроизводимыми командами скачивания NYC Taxi Yellow Tripdata PARQUET (3 месяца + taxi zone lookup). Обновить маршрут прохождения (добавить шаг подготовки данных). Обновить секцию «Как работать в Jupyter» — добавить упоминание ./data mount как /opt/data. Обновить секцию «Что делать дальше», чтобы маршрут включал Модуль 3.
  4. Обновить docs/stack_reference.md — в секции JupyterLab добавить строку ./data смонтирован read-only как /opt/data.
  5. Создать notebooks/03_raw_ingest_and_first_read.ipynb по ячеечной структуре, описанной ниже.
  6. Обновить plans/README.md — добавить строку Модуля 3 в таблицу оглавления.
  7. Валидация: убедиться, что 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-файлы) vs spark.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 содержит ./data mount.
  • 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-слой.
  • Автоматизированный скрипт скачивания (может быть добавлен позже, не является блокером).