diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..34fea71 --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# Копировать в .env необязательно: это значения по умолчанию. +# Если .env уже есть, добавь нужную настройку в него, сохранив остальные. + +# GHCR — основной источник MinIO. Резерв того же автора и digest: docker.io. +MINIO_REGISTRY=ghcr.io + +# На Linux при необходимости укажи свой UID и GID (команды: id -u; id -g). +NB_UID=1000 +NB_GID=100 diff --git a/README.md b/README.md index 166b9c9..a638adf 100755 --- a/README.md +++ b/README.md @@ -17,14 +17,15 @@ ## Стек -Всё работает локально в Docker. Никаких облаков, внешних зависимостей и регистраций. +Всё работает локально в Docker. Для первой сборки и скачивания учебных данных +нужен интернет; облачный аккаунт не требуется. ```mermaid graph TB U[Student / engineer] subgraph Access - J[JupyterLab
notebooks / PySpark] + J[VS Code в браузере
Jupyter notebooks / PySpark] Q[Trino UI / CLI
ad hoc SQL] end @@ -54,41 +55,56 @@ graph TB | `PostgreSQL` | Catalog — метаданные JDBC-каталога `lakehouse` | | `Spark` | Compute — ETL, запись и чтение Iceberg-таблиц | | `Trino` | Compute — ad hoc SQL, чтение тех же таблиц | -| `JupyterLab` | Точка входа — практические ноутбуки курса | +| `code-server` | VS Code в браузере — ноутбуки курса, Python и терминал внутри Docker | +| `JupyterLab` | Запасной интерфейс для тех же ноутбуков | Ноутбуки подключаются к Spark-кластеру с [готовыми настройками стенда](./docs/stack_reference.md#spark). ## С чего начать -1. Открой [START_HERE.md](./START_HERE.md) — запуск стенда, диагностика, первый ноутбук. -2. Пройди `notebooks/01_environment_and_smoke_test.ipynb`. -3. Дальше по порядку: [docs/course_program.md](./docs/course_program.md). +1. Открой [START_HERE.md](./START_HERE.md) — подготовка, запуск и первый ноутбук + с ожидаемым результатом. +2. Продолжи по [маршруту из восьми модулей](./docs/course_program.md#маршрут-по-модулям). +3. Между занятиями используй [памятку учащегося](./docs/study_workflow.md): + остановка, продолжение работы и диагностика. -Краткий запуск из корня репозитория: +Для одного учащегося рекомендуем компьютер с 16 ГБ RAM, не менее 4 CPU и +ориентировочно 8 ГБ памяти для Docker, а также от 20 ГБ свободного места перед сборкой. +Trino ограничен 2 ГиБ памяти контейнера и 1 ГиБ Java heap. +Подробности и границы проверки — в [системных требованиях](./START_HERE.md#системные-требования). + +Краткий запуск из корня репозитория после подготовки по `START_HERE`: ```bash +docker compose pull minio minio-init postgres trino docker compose build docker compose up -d -docker compose ps +docker compose ps -a ``` -Зафиксированные исторические образы MinIO загружаются из официального Quay registry; подробности — в [техническом reference](./docs/stack_reference.md#minio). +MinIO скачивается готовым из GHCR, его версия и digest закреплены. Если какой-то +источник недоступен из твоей сети, начни с +[инструкции по доступу к репозиториям](./docs/network_access.md). После старта: | UI | Адрес | | --- | --- | -| JupyterLab | `http://localhost:8888` | +| VS Code в браузере | `http://localhost:8889` | +| JupyterLab (запасной интерфейс) | `http://localhost:8888` | | Spark Master | `http://localhost:8080` | | Trino | `http://localhost:8090` | | MinIO Console | `http://localhost:9001` | -JupyterLab опубликован только на loopback-интерфейсе хоста и рассчитан на локальный одно-пользовательский стенд. Подключение через VS Code Remote-SSH описано в [START_HERE.md](./START_HERE.md#как-работать-в-jupyter). +Открой ноутбук в браузерном VS Code и выбери Python внутри контейнера: локальный +Python и venv не нужны. Оба интерфейса доступны только с хоста стенда. +Первый запуск ноутбука описан в [START_HERE.md](./START_HERE.md#как-работать-с-ноутбуками). ## Документы для прохождения -- [START_HERE.md](./START_HERE.md) — первый маршрут: prerequisites, запуск, диагностика. +- [START_HERE.md](./START_HERE.md) — подготовка компьютера, запуск и первый результат. +- [docs/study_workflow.md](./docs/study_workflow.md) — памятка для каждого занятия и помощь при ошибках. - [docs/course_program.md](./docs/course_program.md) — модульная структура и состав учебных материалов. - [docs/stack_reference.md](./docs/stack_reference.md) — порты, команды, конфиги, шпаргалка. - [docs/glossary.md](./docs/glossary.md) — справочник терминов (storage, catalog, compute, snapshot и др.). diff --git a/START_HERE.md b/START_HERE.md index f4ad300..a9e8514 100644 --- a/START_HERE.md +++ b/START_HERE.md @@ -1,31 +1,88 @@ # START HERE -Этот документ нужен для первого входа в курс: поднять стенд, проверить сервисы, открыть интерфейсы, выполнить базовый smoke test и подготовить локальный data bundle для следующих модулей. +Здесь ты подготовишь окружение и выполнишь первое вычисление в Spark. +К концу запуска в ноутбуке появится `Spark cluster smoke test passed.`, а результат +сохранится в файле на твоем компьютере. Smoke test — короткая проверка, что +основные компоненты работают вместе. + +Если стенд уже собран, открой [памятку для следующего занятия](./docs/study_workflow.md). +Она объясняет, как продолжить работу, что сохраняется и куда смотреть при ошибках. ## Маршрут прохождения -1. Проверить prerequisites. -2. Собрать и поднять стенд. -3. Убедиться, что контейнеры живы. -4. Открыть основные UI. -5. Зайти в Jupyter и выполнить `notebooks/01_environment_and_smoke_test.ipynb`. -6. Перед Модулем 3 подготовить учебный датасет в `./data/nyc_taxi`. -7. При проблемах использовать логи и шаги диагностики из этого документа. +1. Подготовь Docker, ресурсы и локальную копию репозитория. +2. Скачай образы, собери окружение и проверь запуск сервисов. +3. Открой первый ноутбук в браузерном VS Code и проверь результат вычисления. +4. Продолжи по [программе курса](./docs/course_program.md#маршрут-по-модулям). -## Prerequisites +Датасет Taxi понадобится перед Модулем 3, DBeaver необязателен и понадобится +только перед Модулем 6. Для первого ноутбука их готовить не нужно. -Нужно заранее установить: + -- Docker; -- Docker Compose; -- современный браузер для UI; -- свободные порты на хосте. +## Что подготовить -Рекомендуемые ресурсы хоста: +- Docker с запущенным движком и Docker Compose 2.17+ с BuildKit. +- Современный браузер; основной интерфейс курса — VS Code на `localhost:8889`. +- Git для скачивания репозитория или его распакованную ZIP-копию. +- Свободные порты из таблицы ниже и доступ к источникам образов и зависимостей. -- не менее `4 vCPU`; -- не менее `10 GB RAM`, иначе `Spark`, `Trino` и `Jupyter` могут стартовать нестабильно; -- хотя бы `8-10 GB` свободного места под образы и контейнеры. +Команды ниже рассчитаны на Bash: терминал Linux/macOS или терминал Linux +в WSL2 на Windows с включенной интеграцией Docker Desktop. Выполняй их на +машине, где работает Docker. Терминал внутри браузерного VS Code для запуска +`docker compose` не подходит. + +### Системные требования + +Ориентир для одного учащегося и основного набора Taxi за три месяца: + +| Ресурс | Рекомендация | Что проверить | +| --- | --- | --- | +| Процессор | Не менее 4 CPU, доступных Docker | В Docker Desktop/WSL2 может стоять меньший лимит | +| Память Docker | Ориентир — 8 ГБ RAM; 10 ГБ дают дополнительный запас | Это память для среды Docker, а не общий объем памяти компьютера | +| Память компьютера | 16 ГБ RAM или больше | Оставь память ОС, браузеру и другим приложениям | +| Свободное место | Не менее 20 ГБ перед первой сборкой; 30 ГБ с запасом | На диске Docker и в папке репозитория: образы, кэш сборки, исходные данные и таблицы | + +Это рекомендуемый бюджет для одного активного ноутбука, а не измеренный минимум. +На Windows с WSL2 по умолчанию доступно 50% RAM компьютера: при 16 ГБ это 8 ГБ +для всей WSL2, включая Docker. Лимит можно настроить, это не аппаратное +ограничение. См. [настройки WSL2](https://learn.microsoft.com/en-us/windows/wsl/wsl-config#main-wsl-settings) +и [ресурсы Docker Desktop](https://docs.docker.com/desktop/settings-and-maintenance/settings/#resources). + +Trino ограничен 2 ГиБ памяти контейнера и 1 ГиБ Java heap. Проверка запросов +на данных курса описана в [справочнике](./docs/stack_reference.md#ресурсы-и-проверенная-среда). +Полный курс под общим жестким лимитом Docker 8 ГБ пока не проверен. В WSL2 часть +памяти также нужна самой Linux-среде; закрой другие тяжелые задачи на время урока. +Место внутри диска Docker и свободное место на диске компьютера — отдельные ограничения. + +С добавлением VS Code образ окружения вырос примерно на 1,2 ГБ относительно +Jupyter; базовые слои у них общие. MinIO скачивается готовым, компилировать его +не нужно. [Основания оценок и границы проверки](./docs/stack_reference.md#ресурсы-и-проверенная-среда). + +Python, Java, Jupyter и расширения VS Code устанавливаются при сборке образов. +На компьютере не нужно создавать venv или устанавливать Python-пакеты. + +Для первой сборки нужен интернет. Если ты работаешь из РФ или сети с +ограничениями, при ошибке скачивания используй +[инструкцию по доступу к репозиториям](./docs/network_access.md). Она различает +проблемы скачивания образов и зависимостей внутри сборки. + +### Проверка до сборки + +Открой терминал в папке репозитория — там, где лежит `docker-compose.yml`: + +```bash +docker version +docker compose version +docker info --format 'CPUs={{.NCPU}} MemoryBytes={{.MemTotal}}' +docker compose config --quiet +``` + +В `docker version` должны быть разделы Client и Server, версия Compose — не +ниже 2.17. `docker info` показывает ресурсы, которые видит Docker; память дана +в байтах (8 ГиБ — 8 589 934 592 байта). Последняя команда при успехе +ничего не печатает. Если Docker не отвечает или конфигурация не читается, +исправь эту ошибку до сборки. ## Порты стенда @@ -37,20 +94,46 @@ | Trino UI | `http://localhost:8090` | Проверка координатора Trino | | MinIO API | `http://localhost:9000` | S3-compatible endpoint | | MinIO Console | `http://localhost:9001` | Просмотр бакетов и файлов | -| JupyterLab | `http://localhost:8888` | Основная точка входа в практику | +| VS Code в браузере | `http://localhost:8889` | Основная точка входа в практику | +| JupyterLab | `http://localhost:8888` | Запасной интерфейс для ноутбуков | | PostgreSQL | `localhost:5432` | JDBC-каталог Iceberg, нужен для диагностики | ## Быстрый запуск Все команды выполняются из корня репозитория. -### 1. Собрать образы +На Linux и в WSL2 проверь владельца файлов: ```bash +id -u +id -g +``` + +Это UID и GID — числовые идентификаторы пользователя и группы. В контейнере по +умолчанию используются `1000` и `100`. Если значения отличаются, до сборки +создай `.env` в корне репозитория и укажи `NB_UID` и `NB_GID` со своими числами. +Например, для вывода `1001` и `1001`: + +```dotenv +NB_UID=1001 +NB_GID=1001 +``` + +Если `.env` уже есть, измени только эти строки, сохранив остальные настройки. +Файл остается между занятиями. Образец — [`.env.example`](./.env.example). +На macOS с Docker Desktop обычно подходят значения по умолчанию. + +### 1. Скачать готовые образы и собрать окружение + +```bash +docker compose pull minio minio-init postgres trino docker compose build ``` -Зафиксированные образы MinIO загружаются из `quay.io`; их версии и назначение описаны в [техническом reference](./docs/stack_reference.md#minio). +Первая команда проверяет загрузку готовых сервисов до сборки окружения. +MinIO уже собран; для Spark, Jupyter и IDE первая сборка скачивает зависимости +и зависит по времени от сети и компьютера. Дождись завершения без ошибок; +не переходи к запуску после неудачной сборки. Повторная сборка использует кэш Docker. ### 2. Поднять стенд @@ -61,33 +144,36 @@ docker compose up -d ### 3. Проверить статус контейнеров ```bash -docker compose ps +docker compose ps -a ``` Ожидаемое состояние: -- сервисы `spark-master`, `spark-worker-1`, `spark-worker-2`, `minio`, `postgres-iceberg`, `trino`, `jupyter` находятся в состоянии `Up`; -- однократные init-контейнеры вроде `minio-init` и `iceberg-catalog-init` могут завершиться после успешной инициализации. +- `spark-master`, `spark-worker-1`, `spark-worker-2`, `minio`, `postgres-iceberg`, + `trino`, `jupyter`, `ide` работают (`Up`); у `postgres-iceberg`, `trino` и `ide` + дождись `healthy`; +- `minio-init` и `iceberg-catalog-init` заканчивают работу со статусом `Exited (0)`. + Это успешная разовая подготовка хранилища и каталога. `Exited (1)` и другой + ненулевой код требуют проверки логов. + +Флаг `-a` показывает и завершившиеся контейнеры. Сам по себе статус `Up` еще +не доказывает, что вычисления работают: дальше проверим их в ноутбуке. ## Куда заходить после старта -Открой в браузере: +| Что открыть | Что должно быть видно | +| --- | --- | +| [Spark Master](http://localhost:8080) | Два worker-а в состоянии `ALIVE` | +| [MinIO Console](http://localhost:9001) | После входа `minioadmin` / `minioadmin` виден бакет `lakehouse` — контейнер для файлов курса | +| [VS Code](http://localhost:8889) | Слева папки **Ноутбуки**, **Примеры (только чтение)**, **Документация (только чтение)** | -- `http://localhost:8080` для `Spark Master UI`; -- `http://localhost:8090` для `Trino UI`; -- `http://localhost:9001` для `MinIO Console`; -- `http://localhost:8888` для `JupyterLab`. +В [Trino UI](http://localhost:8090) для входа достаточно имени, например `student`; +пароль в стенде не задан. Сейчас достаточно проверить, что интерфейс открывается. +Чтение общих таблиц через Trino разберем в Модуле 6. -Для входа в `MinIO Console` используй: - -- логин: `minioadmin`; -- пароль: `minioadmin`. - -Если интерфейсы открываются, переходи в Jupyter и запускай: - -```text -notebooks/01_environment_and_smoke_test.ipynb -``` +Этот маршрут предполагает Docker на твоем компьютере: `localhost` относится +к машине с браузером. Доступ к VS Code на удаленном сервере описан отдельно +в [инструкции по SSH-туннелю](./docs/stack_reference.md#vs-code-в-браузере). ## Роли сервисов в стенде @@ -97,68 +183,76 @@ notebooks/01_environment_and_smoke_test.ipynb | `PostgreSQL` | `catalog`: хранит метаданные JDBC-каталога Iceberg | | `Spark` | `compute`: выполняет PySpark и Spark SQL задания | | `Trino` | `compute`: читает те же таблицы через общий каталог | -| `Jupyter` | Точка входа в учебные ноутбуки | +| `VS Code / JupyterLab` | Интерфейсы для учебных ноутбуков | -## Как работать в Jupyter +## Как работать с ноутбуками -- JupyterLab доступен без логина только с хоста, на котором запущен стенд; -- при работе через VS Code Remote-SSH выбери `Existing Jupyter Server` и укажи `http://127.0.0.1:8888/`; -- token в адрес добавлять не нужно: аутентификация и XSRF-проверка отключены для локального одно-пользовательского сценария; -- `./notebooks` смонтирован в контейнер как `/opt/work`; -- `./src` смонтирован read-only как `/opt/src`; -- `./data` смонтирован read-only как `/opt/data`; -- `PYTHONPATH=/opt/src`, поэтому helper-скрипты из `src/` доступны для импорта в ноутбуках; -- первый ноутбук курса: `01_environment_and_smoke_test.ipynb`; -- локальный учебный data bundle для Модуля 3 должен быть доступен внутри Jupyter по пути `/opt/data/nyc_taxi`. +Ноутбук — файл с пояснениями, кодом и сохраненным выводом. Ядро — процесс Python, +который исполняет код и хранит переменные до остановки. -Ноутбуки берут адрес кластера из -[`spark/spark-defaults.conf`](./spark/spark-defaults.conf). Если изменил его, -перезапусти ядро и заново создай Spark-сессию. Повторный запуск ячейки с -`getOrCreate()` использует текущую сессию. При перезапуске ядра переменные Python -сбрасываются, а учебные таблицы и файлы сохраняются. +1. Открой `http://localhost:8889` в отдельной вкладке браузера. +2. Если сверху виден **Restricted Mode**, нажми **Manage → Trust** и закрой окно + Workspace Trust. Подтверди доверие только к своей копии этого учебного + репозитория: в Restricted Mode запуск ноутбуков ограничен. +3. В папке **Ноутбуки** открой `01_environment_and_smoke_test.ipynb`. +4. Нажми **Select Kernel → Python Environments** и выбери Python с путём + `/usr/bin/python3`. Он находится внутри контейнера `ide`, где уже есть + зависимости курса. Пункт **Existing Jupyter Server** здесь не нужен. +5. Запусти первую кодовую ячейку кнопкой слева или `Shift+Enter`, затем остальные + ячейки по порядку. Результат появится под ячейкой. Сохрани ноутбук через + **File → Save** (`Ctrl+S`; на macOS — `Cmd+S`). + +Терминал VS Code тоже работает внутри контейнера. Команды `docker compose` +выполняй в терминале хоста, откуда запускал стенд. + +### Как понять, что первый запуск прошел + +В третьей кодовой ячейке первого ноутбука появится отчет, содержащий: + +```text +Spark cluster smoke test passed. +master_url=spark://spark-master:7077 +result_sum=499999500000 +expected_sum=499999500000 +``` + +В отчете есть и другие строки; значения выше должны совпасть. Затем выполни +самостоятельное задание: замени `TODO` в словаре `service_roles` ролями сервисов. +Сам факт запуска ячейки с `TODO` не означает, что задание выполнено. + +В конце выполни ячейку `spark.stop()`, сохрани ноутбук и проверь, что его изменения +видны в `notebooks/01_environment_and_smoke_test.ipynb` на хосте. После повторного +открытия сохраненный вывод остается виден. Такой вывод — запись прошлого запуска; +он не означает, что ядро Python и Spark-сессия продолжают работать. + +Правила запуска ячеек, перехода между модулями и сохранения данных собраны в +[памятке учащегося](./docs/study_workflow.md). + +Запасной интерфейс — JupyterLab на `http://localhost:8888`. Файлы ноутбуков общие, +а ядра и переменные Python отдельные. Перед переходом сохрани ноутбук и заверши +Spark-сессию через `spark.stop()`; не редактируй один файл в двух интерфейсах +одновременно. ## Базовая диагностика -### Посмотреть список контейнеров +Начни с `docker compose ps -a` и логов проблемного сервиса, например: ```bash -docker compose ps +docker compose logs --tail=100 ide ``` -### Посмотреть логи конкретного сервиса - -```bash -docker compose logs -f spark-master -docker compose logs -f trino -docker compose logs -f minio -docker compose logs -f jupyter -``` - -### Типовые первые проверки - -- `Spark UI` не открывается: проверь `docker compose ps` и логи `spark-master`. -- `Trino UI` не открывается: проверь `docker compose ps` и логи `trino`. -- `JupyterLab` не открывается: проверь `docker compose ps` и логи `jupyter`. -- `MinIO Console` не открывается: проверь `docker compose ps` и логи `minio`. -- smoke test падает из ноутбука: сначала убедись, что `spark-master` и worker-ы видны в `Spark UI`. +Выбери свой симптом в [таблице диагностики](./docs/study_workflow.md#если-что-то-не-работает). +При ошибке скачивания сразу открой [диагностику сети](./docs/network_access.md). ## Restart и reset -### Мягкий перезапуск стенда +Для обычной паузы используй `docker compose stop`, на следующем занятии — +`docker compose up -d`. Порядок действий и последствия удаления контейнеров +описаны в [памятке](./docs/study_workflow.md#закончить-занятие). -```bash -docker compose down -docker compose up -d -``` - -### Полный reset с удалением данных - -```bash -docker compose down -v -docker compose up -d -``` - -Используй полный reset, если хочешь пройти практику заново с чистого состояния. +Полный сброс удаляет учебные таблицы и каталог. Его команда вынесена в +[отдельный раздел](./docs/study_workflow.md#начать-с-чистых-данных), чтобы не путать +с окончанием занятия. ## Подготовка учебного датасета (перед Модулем 3) @@ -167,6 +261,7 @@ docker compose up -d Скачай каноническое учебное подмножество `NYC TLC Yellow Taxi Trip Records`: ```bash +mkdir -p data/nyc_taxi curl -fLo data/nyc_taxi/yellow_tripdata_2024-01.parquet \ https://d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_2024-01.parquet curl -fLo data/nyc_taxi/yellow_tripdata_2024-02.parquet \ @@ -197,13 +292,12 @@ ls -lh data/nyc_taxi/ - Зачем: в Модуле 6 можно работать с Trino через DBeaver параллельно с ноутбуком — привычный SQL-интерфейс. - Предусловие: DBeaver установлен (ссылка на [dbeaver.io/download](https://dbeaver.io/download/)). Необязателен — ноутбук работает без DBeaver. - Шаги: New Database Connection -> Trino. Host: `localhost`. Port: `8090`. Database/Catalog: `lakehouse`. Username: любая строка (напр. `student`). Password: пусто. Test Connection -> Finish. -- Проверка: `SHOW SCHEMAS FROM lakehouse;`. Ожидаем: `bronze`, `default`, `information_schema`, `silver`. +- Проверка: `SHOW SCHEMAS FROM lakehouse;`. После модулей 4–5 должны быть видны `bronze` и `silver`; до их прохождения этих схем может еще не быть. - Troubleshooting: стенд поднят? контейнер `trino` Up? порт 8090 свободен? ## Что делать дальше -- пройти `notebooks/01_environment_and_smoke_test.ipynb`; -- пройти `notebooks/02_lakehouse_mental_model.ipynb`; -- подготовить data bundle по инструкции выше и пройти `notebooks/03_raw_ingest_and_first_read.ipynb`; -- свериться с программой курса в `docs/course_program.md`; -- после прохождения первых модулей переходить к следующим учебным материалам. +После первого ноутбука открой [маршрут по модулям](./docs/course_program.md#маршрут-по-модулям). +В нем есть ссылки на все восемь уроков и результат, который нужно получить +перед переходом дальше. Для следующего занятия сохрани ссылку на +[памятку учащегося](./docs/study_workflow.md). diff --git a/docker-compose.yml b/docker-compose.yml index 73bf191..8e04b70 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -121,7 +121,8 @@ services: - ./spark/spark-defaults.conf:/opt/spark/conf/spark-defaults.conf:ro minio: - image: quay.io/minio/minio:RELEASE.2024-10-02T17-50-41Z + # Один и тот же образ опубликован автором в GHCR и Docker Hub. + image: &minio-image ${MINIO_REGISTRY:-ghcr.io}/coollabsio/minio:RELEASE.2025-04-22T22-12-26Z@sha256:a4938f37f1be1841b8e7b627ad0207b265345fd0d063e42d7410c78af0e63e68 container_name: minio hostname: minio networks: @@ -137,7 +138,7 @@ services: - minio-data:/data minio-init: - image: quay.io/minio/mc:RELEASE.2024-06-24T19-40-33Z-cpuv1 + image: *minio-image container_name: minio-init hostname: minio-init networks: @@ -145,13 +146,13 @@ services: depends_on: - minio entrypoint: > - /bin/sh -c " + /bin/sh -ec " echo 'Waiting for MinIO...'; until mc alias set minio http://minio:9000 minioadmin minioadmin 2>/dev/null; do sleep 1; done; echo 'MinIO is up, creating bucket...'; - mc mb -p minio/lakehouse || true; + mc mb --ignore-existing minio/lakehouse; echo 'Bucket lakehouse is ready'; exit 0; " @@ -160,6 +161,8 @@ services: jupyter: build: context: ./jupyter + additional_contexts: + spark-runtime: service:spark-master args: NB_UID: ${NB_UID:-1000} NB_GID: ${NB_GID:-100} @@ -192,10 +195,45 @@ services: - ./src:/opt/src:ro - ./data:/opt/data:ro + ide: + build: + context: ./ide + additional_contexts: + jupyter-runtime: service:jupyter + container_name: ide + hostname: ide + depends_on: + - spark-master + - minio + - iceberg-catalog-init + networks: + - spark-net + environment: + - AWS_ACCESS_KEY_ID=minioadmin + - AWS_SECRET_ACCESS_KEY=minioadmin + - PYTHONPATH=/opt/src + ports: + - "127.0.0.1:8889:8889" + volumes: + - ./spark/spark-defaults.conf:/opt/spark/conf/spark-defaults.conf:ro + - ./notebooks:/opt/work + - ./src:/opt/src:ro + - ./data:/opt/data:ro + - .:/opt/course:ro + - ./notebooks:/opt/course/notebooks + healthcheck: + test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8889/healthz"] + interval: 10s + timeout: 5s + retries: 6 + trino: image: trinodb/trino:478 container_name: trino hostname: trino + # Heap JVM ограничен отдельно в trino/jvm.config; остальное — запас вне heap. + mem_limit: 2g + memswap_limit: 2g networks: - spark-net depends_on: @@ -210,6 +248,7 @@ services: - "8090:8080" # Web UI Trino volumes: - ./trino/catalog:/etc/trino/catalog:ro + - ./trino/jvm.config:/etc/trino/jvm.config:ro networks: spark-net: diff --git a/docs/course_prd.md b/docs/course_prd.md index 716c2e0..c9a0024 100644 --- a/docs/course_prd.md +++ b/docs/course_prd.md @@ -96,7 +96,7 @@ * snapshot/time travel на прикладном уровне; * базовый `compaction/vacuum` с параллелями к `Greenplum AppendOnly`; * набор практик в Jupyter Notebooks с обязательными заданиями и checkpoints; -* стартовая документация до первого ноутбука: prerequisites, запуск стенда, вход в Jupyter и базовый troubleshooting. +* стартовая документация до первого ноутбука: prerequisites, запуск стенда, вход в браузерный VS Code и базовый troubleshooting. Ориентировочная программа курса вынесена в отдельный документ: **[course_program.md](./course_program.md)**. @@ -152,7 +152,7 @@ **Acceptance Criteria для учебных материалов и результата:** -1. В составе курса есть стартовая документация до первого ноутбука, включая prerequisites, запуск стенда, вход в `Jupyter` и получение учебного data bundle. +1. В составе курса есть стартовая документация до первого ноутбука, включая prerequisites, запуск стенда, вход в браузерный VS Code и получение учебного data bundle. 2. Студент проходит обязательные `checkpoints` по урокам. 3. Студент самостоятельно выполняет набор практик на одном датасете, включая: * подключение к Spark и загрузку raw-данных; @@ -174,7 +174,7 @@ **В скоупе:** -* использование существующего `docker-compose.yml` (`Spark`, `Trino`, `MinIO`, `PostgreSQL`, `Jupyter`); +* использование существующего `docker-compose.yml` (`Spark`, `Trino`, `MinIO`, `PostgreSQL`, `code-server`, `JupyterLab`); * подготовка учебных датасетов и/или инструкций по их загрузке в репозиторий; * наличие воспроизводимого способа получить учебный data bundle без ручного поиска по внешним сайтам; * поддержка выполнения запросов к `Trino` из ноутбуков; diff --git a/docs/course_program.md b/docs/course_program.md index 9ce667f..6f9802c 100644 --- a/docs/course_program.md +++ b/docs/course_program.md @@ -1,6 +1,29 @@ # Программа учебного курса: Lakehouse без магии -Этот документ заменяет предыдущий черновой план и выравнивает программу с PRD из [course_prd.md](./course_prd.md). +Здесь собраны порядок модулей, ссылки на ноутбуки и результаты, по которым можно +проверить свою работу. Рамки курса и learning outcomes описаны в [course_prd.md](./course_prd.md). + +## Маршрут по модулям + +Перед первым уроком подготовь стенд по [START_HERE](../START_HERE.md). +Дальше проходи ноутбуки по порядку: файлы и таблицы из ранних модулей нужны в +следующих. Пауза между занятиями описана в [памятке учащегося](./study_workflow.md). + +| Модуль и ноутбук | Что понадобится | Результат перед переходом дальше | +| --- | --- | --- | +| [1. Вход в стенд](../notebooks/01_environment_and_smoke_test.ipynb) | Работающий стенд | Smoke test вернул `499999500000`; ты можешь показать два worker-а и объяснить роли сервисов | +| [2. Модель Lakehouse](../notebooks/02_lakehouse_mental_model.ipynb) | Модуль 1 | Ты нашел файлы демо-таблицы в MinIO и запись каталога в PostgreSQL; выполнил очистку демо по ноутбуку | +| [3. Исходные данные](../notebooks/03_raw_ingest_and_first_read.ipynb) | [Четыре файла Taxi](../START_HERE.md#подготовка-учебного-датасета-перед-модулем-3) на хосте | Файлы загружены в raw-зону MinIO, Spark читает их; проверены схема и основные метрики | +| [4. Bronze и Iceberg](../notebooks/04_bronze_with_iceberg.ipynb) | Raw-файлы из Модуля 3 | Созданы bronze-таблица поездок и таблица зон из самостоятельного задания; видны их данные и metadata-файлы | +| [5. Silver](../notebooks/05_silver_layer.ipynb) | Обе bronze-таблицы | Создана silver-таблица, проверены правила очистки и качество результата | +| [6. Spark и Trino](../notebooks/06_spark_and_trino_on_same_table.ipynb) | Silver из Модуля 5; DBeaver по желанию | Одна таблица читается обоими движками; ты можешь объяснить, какие компоненты у них общие | +| [7. Изменения таблиц](../notebooks/07_safe_table_changes.ipynb) | Результаты предыдущих модулей | На демо-таблице проверены изменение схемы, чтение прошлого состояния и rollback; silver сохранена | +| [8. Обслуживание и финальная практика](../notebooks/08_maintenance_and_final_lab.ipynb) | Raw, bronze и silver | Проверено обслуживание таблиц; самостоятельно пройден поток от исходных файлов до чтения и проверок результата | + +В каждом ноутбуке сначала прочитай объяснение, затем выполни демонстрацию и +самостоятельное задание. Checkpoint в конце урока — вопросы и проверки для +самопроверки. Сохраненный вывод помогает вернуться к результату, но не заменяет +собственный запуск и объяснение. ## 1. Рамка курса @@ -27,7 +50,7 @@ ## 3. Состав учебных материалов -Курс не должен состоять только из ноутбуков. Ноутбуки являются ядром практики, но вход в курс, запуск стенда и правила работы должны быть вынесены в обычную документацию, доступную до старта `Jupyter`. +Курс не должен состоять только из ноутбуков. Ноутбуки являются ядром практики, но вход в курс, запуск стенда и правила работы должны быть вынесены в обычную документацию, доступную до открытия IDE. ### 3.1. Стартовая документация до первого ноутбука @@ -38,15 +61,17 @@ * `START_HERE.md` или аналогичный стартовый документ с маршрутом прохождения курса; * инструкция по prerequisites: `Docker`, `Docker Compose`, свободные порты, базовые команды; * пошаговый гайд по запуску стенда: `build`, `up`, проверка сервисов, открытие UI; -* краткое объяснение, как пользоваться `Jupyter`, где лежат ноутбуки и как читать структуру урока; +* краткое объяснение, как запускать Jupyter-ноутбуки в браузерном VS Code, где лежат ноутбуки и как читать структуру урока; * troubleshooting по типовым проблемам старта; -* инструкция по reset/restart стенда для повторного прохождения практик. +* [памятка учащегося](./study_workflow.md): продолжение занятия, сохранение данных, + перезапуск ядра и диагностика; +* отдельная инструкция по сбросу учебных данных для повторного прохождения практик. **Что должен закрывать этот слой:** * студент понимает, что нужно установить и проверить до начала курса; * студент может поднять стенд без ментора; -* студент знает, как попасть в `Jupyter`, `MinIO`, `Trino UI`; +* студент знает, как попасть в браузерный VS Code, `MinIO`, `Trino UI`; * студент понимает, как устроен формат уроков и что от него ожидается. ### 3.2. Практическое ядро курса @@ -85,10 +110,10 @@ **Содержание:** -* запуск `docker compose build` и `docker compose up -d`; +* скачивание готовых образов, сборка окружения и запуск по `START_HERE.md`; * проверка сервисов и веб-интерфейсов; * базовый smoke test для Spark; -* первый обзор того, где в стенде `MinIO`, `PostgreSQL`, `Spark`, `Trino`, `Jupyter`. +* первый обзор того, где в стенде `MinIO`, `PostgreSQL`, `Spark`, `Trino` и IDE. **Практика студента:** diff --git a/docs/maintainer_guide.md b/docs/maintainer_guide.md index c0deab3..ee2d768 100644 --- a/docs/maintainer_guide.md +++ b/docs/maintainer_guide.md @@ -17,7 +17,7 @@ | Зона | Где лежит | Назначение | | --- | --- | --- | -| Runtime / stand | `docker-compose.yml`, `spark/`, `trino/`, `jupyter/` | Топология сервисов, образы, конфиги, порты | +| Runtime / stand | `docker-compose.yml`, `spark/`, `trino/`, `jupyter/`, `ide/` | Топология сервисов, образы, конфиги, порты | | Onboarding | `README.md`, `START_HERE.md` | Вход в репозиторий и первый пользовательский маршрут | | Runtime reference | `docs/stack_reference.md` | Технические детали стенда: порты, доступ, команды, smoke-тесты | | Course definition | `docs/course_prd.md`, `docs/course_program.md` | Границы курса, learning outcomes, структура модулей | @@ -31,9 +31,10 @@ | Файл или каталог | За что отвечает | Что не стоит туда складывать | | --- | --- | --- | | `docker-compose.yml` | Реальный состав сервисов, контейнеров, сетей, портов и зависимостей | Учебные пояснения, которые не нужны для запуска | -| `spark/`, `trino/`, `jupyter/` | Конкретные runtime-конфиги и образы | Описание программы курса | +| `spark/`, `trino/`, `jupyter/`, `ide/` | Конкретные runtime-конфиги и образы | Описание программы курса | | `README.md` | Верхнеуровневое объяснение репозитория, архитектуры и точек входа | Пошаговый student onboarding во всех деталях и low-level runtime reference | | `START_HERE.md` | Первый маршрут студента: prerequisites, запуск, первые UI, первый ноутбук, базовая диагностика | Полный PRD курса или подробный бэклог модулей | +| `docs/study_workflow.md` | Повседневный маршрут: продолжить и закончить занятие, сохранить результат, восстановиться после ошибки | Состав модулей, установка окружения с нуля | | `docs/stack_reference.md` | Технический reference стенда: сервисы, порты, доступ, reset, smoke-тесты | Роль основного onboarding-документа или описание всей программы курса | | `docs/course_prd.md` | Product scope, аудитория, learning outcomes, dataset strategy, out of scope | Технические мелочи запуска контейнеров | | `docs/course_program.md` | Модульная структура курса, состав материалов, checkpoints | Подробные docker-команды и legacy-лабы | @@ -75,6 +76,7 @@ - `START_HERE.md`; - `docs/course_program.md`; +- `docs/study_workflow.md`; - соответствующие `notebooks/`; - `README.md`, если меняется верхнеуровневый вход в репозиторий. diff --git a/docs/mentor_notes.md b/docs/mentor_notes.md index 45eb719..8df920c 100644 --- a/docs/mentor_notes.md +++ b/docs/mentor_notes.md @@ -17,9 +17,9 @@ | Модуль | Тема | Самостоятельная работа | Обсуждение с ментором | Комментарий | | --- | --- | --- | --- | --- | -| 1 | Вход в стенд | 30-40 мин | 10-15 мин | Если Docker знаком, идёт быстро | +| 1 | Вход в стенд | 30-40 мин | 10-15 мин | Без учета первой сборки: ее время зависит от сети и компьютера | | 2 | Ментальная модель | 40-60 мин | 15-20 мин | Ключевой модуль; убедись, что студент заходил в MinIO | -| 3 | Raw-данные | 40-50 мин | 10 мин | Зависит от скорости загрузки данных | +| 3 | Raw-данные | Около 2 часов | 10 мин | Включая 20–30 минут на практику со Spark-сессией; датасет скачивается заранее | | 4 | Bronze + Iceberg | 60-80 мин | 15-20 мин | Первая таблица — много новых концепций | | 5 | Silver | 50-70 мин | 15 мин | Трансформации + проверки качества | | 6 | Spark + Trino | 40-60 мин | 15 мин | Если DBeaver настроен заранее — быстрее | @@ -34,8 +34,9 @@ ### Модуль 1. Вход в стенд -- **Docker не хватает ресурсов.** Стенд требует ~4-6 ГБ RAM. На машинах с 8 ГБ бывают проблемы. Решение: увеличить лимиты Docker Desktop или закрыть лишние приложения. -- **Порты заняты.** 8080, 8888, 9000 — популярные порты. `docker compose ps` покажет, какой сервис не стартовал. Решение: остановить конфликтующий процесс или (крайний вариант) поменять порт в `docker-compose.yml`. +- **Docker не хватает ресурсов.** Сверься с [системными требованиями](../START_HERE.md#системные-требования). Проверь отдельно память компьютера и лимит памяти Docker Desktop/WSL2: контейнерам может быть доступна лишь часть RAM хоста. +- **После паузы виден старый вывод, но переменные пропали.** Разбери разницу между файлом ноутбука и ядром Python по [памятке учащегося](./study_workflow.md). Передай студенту ее до второго занятия; для обращения за помощью там есть список нужных сведений. +- **Порты заняты.** Проверь 8080, 8889, 9000 и остальные [порты стенда](../START_HERE.md#порты-стенда). `docker compose ps -a` покажет, какой сервис не стартовал. Решение: остановить конфликтующий процесс или (крайний вариант) поменять порт в `docker-compose.yml`. - **Студент не читает `START_HERE.md`.** Начинает с ноутбуков до поднятия стенда. Направь обратно к стартовому документу. ### Модуль 2. Ментальная модель diff --git a/docs/network_access.md b/docs/network_access.md new file mode 100644 index 0000000..a47b947 --- /dev/null +++ b/docs/network_access.md @@ -0,0 +1,74 @@ +# Если образы или зависимости не скачиваются + +Доступность репозиториев зависит от сети и провайдера, в том числе в РФ. +GHCR выбран основным источником MinIO, чтобы его загрузка не зависела от Docker +Hub. Это не гарантия доступности без VPN у каждого провайдера: у стенда остаются +другие внешние зависимости. Для первой сборки и скачивания датасета нужен интернет. + +## Сначала найди источник ошибки + +Запускай команды в терминале хоста, из корня репозитория: + +```bash +docker compose pull minio minio-init postgres trino +docker compose --progress plain build +``` + +Если первая команда завершилась с ошибкой, сначала реши проблему загрузки образа. +Если ошибка возникла во второй, найди имя сервиса и адрес, с которого скачивался +файл. Открывающийся в браузере сайт ещё не подтверждает доступ из Docker. + +| Где возникла ошибка | Что скачивается | Что проверить | +| --- | --- | --- | +| `ghcr.io` при загрузке `minio` | Готовые MinIO и `mc` | Резервный источник ниже | +| `docker.io`, `registry-1.docker.io`, `auth.docker.io` | PostgreSQL, Trino, базовый Spark; резерв MinIO | Доступ Docker к Docker Hub и настройки прокси | +| Ubuntu archive, PyPI, Maven Central при сборке `spark` или `jupyter` | Системные пакеты, Python-пакеты и JAR | Доступ к зависимостям из сборки | +| GitHub Releases, Open VSX при сборке `ide` | code-server и расширения | Доступ к зависимостям из сборки | + +## Резервный источник MinIO + +Автор образа публикует его в двух местах: GHCR и Docker Hub. В обоих закреплён +один digest, поэтому смена registry не меняет версию или содержимое образа. +Ссылка на сборку и версия приведены в [техническом reference](./stack_reference.md#minio). + +Если GHCR недоступен, создай файл `.env` в корне репозитория и добавь строку: + +```dotenv +MINIO_REGISTRY=docker.io +``` + +Если файл уже существует, измени только эту настройку. Другие значения, например +`NB_UID` и `NB_GID`, сохрани. Затем повтори: + +```bash +docker compose pull minio minio-init +``` + +Дальше используй обычные команды из [START_HERE](../START_HERE.md#быстрый-запуск). +Для возврата к основному источнику установи `MINIO_REGISTRY=ghcr.io`. +Оба источника публичные; регистрация для скачивания MinIO не требуется. +`.env.example` содержит пример настроек, а `.env` исключён из Git. + +## Если недоступны другие источники + +Переключение MinIO не меняет источник PostgreSQL, Trino, Spark или зависимостей +сборки. Если в твоей сети нужен VPN или HTTP-прокси, важно, чтобы им мог +пользоваться Docker, а не только браузер. + +- **Docker Desktop:** настрой прокси в разделе **Settings → Resources → Proxies**. + См. [официальную инструкцию](https://docs.docker.com/desktop/settings-and-maintenance/settings/#proxies). +- **Docker Engine на Linux:** скачиванием образов занимается демон Docker. + Для него есть [отдельная настройка прокси](https://docs.docker.com/engine/daemon/proxy/). +- **Команды внутри сборки:** `apt`, `pip`, `curl` и установка расширений требуют + доступа из build-контейнера. Docker описывает + [настройки прокси для сборок](https://docs.docker.com/engine/cli/proxy/#build-with-a-proxy-configuration) + отдельно от настроек демона. Прокси на `localhost` хоста не обязательно + доступен как `localhost` внутри контейнера. + +Ошибка `429` обычно означает лимит запросов, а `401`/`403` может быть связана +с доступом к registry. Сам по себе этот код не доказывает блокировку по стране. +Не меняй версии образов и не удаляй данные стенда для исправления сетевой ошибки. + +Если нужна помощь, передай ментору ОС, вывод `docker compose version`, команду, +имя сервиса и фрагмент ошибки с адресом источника. Пароли и адреса прокси +с учётными данными в сообщение не добавляй. diff --git a/docs/stack_reference.md b/docs/stack_reference.md index 3537a06..b845256 100644 --- a/docs/stack_reference.md +++ b/docs/stack_reference.md @@ -15,18 +15,22 @@ | MinIO API | `minio` | `9000` | S3-compatible endpoint | | MinIO Console | `minio` | `9001` | веб-консоль бакетов и объектов | | PostgreSQL | `postgres-iceberg` | `5432` | JDBC-каталог Iceberg | -| JupyterLab | `jupyter` | `8888` | практические ноутбуки | +| VS Code в браузере (code-server) | `ide` | `8889` | основной интерфейс для ноутбуков и терминала | +| JupyterLab | `jupyter` | `8888` | запасной интерфейс для тех же ноутбуков | ## Основные команды -Все команды выполняются из корня репозитория. +Все команды выполняются в терминале хоста из корня репозитория. +Для первого запуска и настройки прав используй [START_HERE](../START_HERE.md), +для продолжения работы — [памятку учащегося](./study_workflow.md). ### Сборка и запуск ```bash +docker compose pull minio minio-init postgres trino docker compose build docker compose up -d -docker compose ps +docker compose ps -a ``` ### Логи @@ -36,16 +40,50 @@ docker compose logs -f spark-master docker compose logs -f trino docker compose logs -f minio docker compose logs -f jupyter +docker compose logs -f ide ``` ### Остановка и reset -```bash -docker compose down -docker compose down -v -``` +| Действие | Команда | Что происходит с данными | +| --- | --- | --- | +| Пауза до следующего занятия | `docker compose stop` | Контейнеры остановлены, файлы и volumes сохранены | +| Продолжение | `docker compose up -d` | Сервисы запускаются с прежними данными | +| Удаление контейнеров и сети | `docker compose down` | Volumes и файлы на хосте сохраняются, состояние внутри удаленных контейнеров теряется | +| Полный сброс учебных данных | См. [отдельную инструкцию](./study_workflow.md#начать-с-чистых-данных) | Удаляются MinIO volume и каталог PostgreSQL | -`down -v` удаляет volumes и возвращает стенд в чистое состояние. +Переменные Python и живые Spark-сессии после остановки нужно создать заново. + +## Ресурсы и проверенная среда + +Актуальный бюджет для учащегося приведен в +[системных требованиях](../START_HERE.md#системные-требования). +Он учитывает одновременную работу двух Spark worker-ов, драйвера в IDE, Trino, +MinIO и PostgreSQL. У каждого worker-а в Compose указаны 2 CPU и 2 ГБ для +приложений Spark; это не предел потребления всей JVM или контейнера. +Потребление при простое не показывает пик во время урока. + +Проверка 19 октября 2026 года: Linux x86_64, Docker Engine, 6 CPU и около +11,1 ГиБ памяти, видимых Docker. Выполнены первый ноутбук в Chromium и проверка +Spark → Iceberg → Trino. Затем Trino проверен с лимитом контейнера 2 ГиБ и heap +1 ГиБ на трех месяцах Taxi: 9 554 778 строк bronze, 9 418 184 строк silver +и 265 зон. Запросы Модуля 6 и дополнительный JOIN со справочником прошли; +агрегаты и JOIN сверены со Spark. Пик памяти контейнера Trino по cgroup составил +около 1,25 ГиБ, событий OOM и перезапусков не было. + +Наблюдавшийся суммарный пик памяти контейнеров без неактивного файлового кэша +составил около 5,44 ГиБ. Это измерение выбранного сценария, а не проверка всей +среды под общим лимитом 8 ГБ: память Docker daemon, ядра и других процессов +Linux/WSL2 в сумму не входит. Прогона всех восьми модулей под таким лимитом +не было. Docker Desktop и ARM64 в этой проверке не запускались. +Методика и результаты сохранены в [отчете проверки](../plans/trino-memory-validation.md). + +Размеры локально собранных образов на этой машине: Spark — около 3,37 ГБ, +Jupyter — 3,72 ГБ, IDE — 4,90 ГБ. Jupyter включает слои Spark, IDE включает слои +Jupyter: складывать эти три числа для оценки места неправильно. IDE добавляет +около 1,18 ГБ. IDE вместе с отдельными образами Trino, PostgreSQL и MinIO дает +около 8,1 ГБ до учета кэша сборки, данных и особенностей хранилища Docker. +Поэтому перед первой сборкой нужен запас места сверх размера готовых образов. ## Доступ к сервисам @@ -72,13 +110,33 @@ docker compose down -v Пример запуска smoke-скрипта: ```bash -docker compose exec spark-master \ - /opt/spark/bin/spark-submit /opt/src/spark/cluster_smoke.py +docker compose exec ide python3 /opt/src/spark/cluster_smoke.py ``` ### Trino - Web UI: `http://localhost:8090` +- Контейнер ограничен 2 ГиБ RAM (`mem_limit: 2g`). `memswap_limit: 2g` + отключает swap для него: одинаковое значение задает общий предел RAM + swap. +- В [`trino/jvm.config`](../trino/jvm.config) heap Java начинается с 512 МиБ + и может вырасти до 1 ГиБ (`-Xms512M`, `-Xmx1G`). Остальная память контейнера + оставлена для потоков, служебных структур JVM и буферов вне heap. + +Эти ограничения относятся к Trino; у всего Compose-проекта общего лимита +памяти нет. Если контейнер достигнет жесткого предела, он может быть завершен +системой. Увеличение только heap без изменения бюджета контейнера уменьшает +запас для памяти вне heap. См. [лимиты Compose](https://docs.docker.com/reference/compose-file/services/#mem_limit) +и [настройку JVM Trino](https://trino.io/docs/current/installation/deployment.html#jvm-config). + +После обновления этих файлов примени настройки из терминала хоста: + +```bash +docker compose up -d --no-deps --force-recreate trino +docker compose ps -a trino +``` + +Дождись `healthy`. Эта операция пересоздает только Trino и применяет в том числе +изменения содержимого `jvm.config`; таблицы остаются в MinIO и PostgreSQL. CLI внутри контейнера: @@ -106,7 +164,20 @@ SHOW TABLES FROM lakehouse.default; - логин: `minioadmin` - пароль: `minioadmin` -Стенд использует зафиксированные исторические образы MinIO Server и `mc` из официального namespace `quay.io/minio`. Эти версии подходят для локального курса, но больше не получают обновлений upstream и не предназначены для production. +Стенд использует готовый образ `coollabsio/minio:RELEASE.2025-04-22T22-12-26Z`. +Это сторонняя [сборка из официальных исходников MinIO](https://github.com/coollabsio/minio), +которая содержит сервер и клиент `mc` версии `RELEASE.2025-08-13T08-35-41Z`. +Версия сервера предшествует [изменению встроенной консоли в мае 2025 года](https://github.com/minio/minio/releases/tag/RELEASE.2025-05-24T17-08-30Z). +Образ подходит для локального курса; это историческая +версия без текущих обновлений, не production-конфигурация. + +В Compose закреплён digest индекса для AMD64 и ARM64: +`sha256:a4938f37f1be1841b8e7b627ad0207b265345fd0d063e42d7410c78af0e63e68`. +Сервисы `minio` и `minio-init` используют один образ. Компиляция MinIO и установка +Go не нужны. По умолчанию образ скачивается из GHCR без авторизации. Автор +публикует тот же digest в Docker Hub; резервный источник выбирается через +`MINIO_REGISTRY=docker.io` в `.env`. Подробности — в +[инструкции по доступу к репозиториям](./network_access.md). Бакет `lakehouse` обычно создаётся автоматически контейнером `minio-init`. @@ -126,6 +197,44 @@ psql -h localhost -p 5432 -U iceberg -d iceberg docker compose run --rm iceberg-catalog-init ``` +### VS Code в браузере + +Открой `http://localhost:8889`. Сервис `ide` запускает code-server с установленными +расширениями Python и Jupyter. Python, ipykernel, Java, PySpark и зависимости курса +уже находятся в образе. Локальный Python или venv не нужен. + +В рабочей области доступны ноутбуки, примеры из `src/` и документация. Репозиторий +смонтирован в `/opt/course` только для чтения, а его каталог `notebooks/` — для +чтения и записи. Так относительные ссылки между документами и ноутбуками работают +и внутри IDE. Пути `/opt/work`, `/opt/src`, `/opt/data` также доступны примерам курса. +Открой +`01_environment_and_smoke_test.ipynb`, нажми **Select Kernel → Python Environments** +и выбери `/usr/bin/python3`. Это Python внутри контейнера IDE. Расширение Jupyter +запускает ядро там же; адрес отдельного Jupyter Server вводить не требуется. + +Порт IDE опубликован только на `127.0.0.1`, без пароля, для одного пользователя. +Открывай именно `localhost` или `127.0.0.1`: браузер считает их доверенным +контекстом даже по HTTP. Notebook webview использует service worker; при обращении +по обычному HTTP к IP сервера или имени в локальной сети этот механизм может +быть недоступен. Это ограничение secure context, отдельное от CORS и XSRF. +См. [secure contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Secure_Contexts) +и [webviews в code-server](https://coder.com/docs/code-server/FAQ#why-do-web-views-not-work). + +Для стенда на удалённой машине используй SSH-туннель: + +```bash +ssh -N -L 8889:127.0.0.1:8889 user@host +``` + +Затем открой `http://localhost:8889` на своём компьютере. Прямой доступ по +публичному адресу не входит в эту конфигурацию. + +Образы собираются в порядке `spark-master → jupyter → ide` через Compose +`additional_contexts` (нужен Docker Compose 2.17+ и BuildKit). Версии code-server +и расширений зафиксированы в `ide/Dockerfile`; расширения скачиваются при сборке. +Автоматическое обновление расширений отключено, чтобы повторный запуск использовал +проверенные версии. + ### JupyterLab - адрес: `http://localhost:8888` @@ -137,6 +246,10 @@ docker compose run --rm iceberg-catalog-init - `./data` смонтирован read-only как `/opt/data` - `PYTHONPATH=/opt/src` +JupyterLab остаётся запасным интерфейсом. Он редактирует те же файлы, но запускает +отдельные ядра: переменные Python между IDE и JupyterLab не общие. Перед сменой +интерфейса сохрани ноутбук и останови его ядро. + Первый ноутбук курса: `notebooks/01_environment_and_smoke_test.ipynb`. Для Модуля 3 локально скачанный data bundle должен лежать на хосте в `./data/nyc_taxi` и будет доступен внутри Jupyter по пути `/opt/data/nyc_taxi`. @@ -157,16 +270,39 @@ docker compose run --rm iceberg-catalog-init | `docker-compose.yml` | состав сервисов, порты, volumes, init-контейнеры | | `spark/spark-defaults.conf` | Spark master, catalog `lakehouse`, `s3a`, Iceberg extensions | | `trino/catalog/lakehouse.properties` | каталог Trino `lakehouse`, JDBC и S3-доступ | +| `trino/jvm.config` | JVM Trino: heap до 1 ГиБ внутри контейнера с лимитом 2 ГиБ | | `spark/Dockerfile` | образ Spark с Iceberg, S3A и Python-зависимостями | | `jupyter/Dockerfile` | образ JupyterLab на базе Spark | +| `ide/Dockerfile` | code-server и расширения на базе образа Jupyter | +| `ide/lakehouse.code-workspace` | папки рабочей области и настройки Python | +| `docs/network_access.md` | источники образов и зависимостей, резервный registry и диагностика сети | +| `docs/study_workflow.md` | повседневная работа, сохранение состояния и диагностика ошибок | ## Smoke-тесты +### Браузерная IDE и Jupyter-ноутбук + +На Linux с Docker Engine, после запуска стенда: + +```bash +bash tests/browser_ide.sh +``` + +Проверка запускает Chromium в отдельном контейнере и проходит путь студента: +доверие рабочей области → открытие копии Модуля 1 → выбор Python → Run All → +вывод Spark в webview → сохранение → повторное открытие. Затем проверяет, что +все кодовые ячейки выполнились без ошибок и результат сохранился. Исходный +ноутбук не меняется. Скриншот остаётся в каталоге `/tmp/lakehouse-browser-check.*`, +путь печатается перед запуском. Docker Desktop этим скриптом не проверяется: +он использует Linux host networking. + +Для проверки отдельного стенда можно задать `COMPOSE_FILE` и `IDE_URL`; по +умолчанию тест использует текущий Compose-проект и `http://localhost:8889`. + ### 1. Проверка, что Spark-кластер жив ```bash -docker compose exec spark-master \ - /opt/spark/bin/spark-submit /opt/src/spark/cluster_smoke.py +docker compose exec ide python3 /opt/src/spark/cluster_smoke.py ``` ### 2. Проверка Spark -> Trino через общий каталог @@ -259,6 +395,7 @@ SELECT * FROM FOR VERSION AS OF ; - `README.md` — чтобы понять, что это за репозиторий и куда идти дальше. - `START_HERE.md` — чтобы впервые поднять стенд и пройти Модуль 1. +- `study_workflow.md` — чтобы продолжить занятие, сохранить результат и разобраться с ошибкой. - `stack_reference.md` — чтобы быстро вспомнить порты, команды, точки доступа, шпаргалку и smoke-тесты. - `course_program.md` — чтобы понять учебную траекторию дальше первого модуля. - `glossary.md` — чтобы вернуться к определению термина (storage, catalog, compute, snapshot и др.). diff --git a/docs/study_workflow.md b/docs/study_workflow.md new file mode 100644 index 0000000..273b8d1 --- /dev/null +++ b/docs/study_workflow.md @@ -0,0 +1,153 @@ +# Как заниматься со стендом + +Для первой сборки используй [START_HERE](../START_HERE.md), для выбора урока — +[маршрут по модулям](./course_program.md#маршрут-по-модулям). Эта памятка нужна, +когда окружение уже готово. + +## Начать следующее занятие + +1. Запусти Docker и открой терминал хоста в той же папке репозитория. +2. Выполни: + + ```bash + docker compose up -d + docker compose ps -a + ``` + + Дождись `healthy` у `ide`, `trino` и `postgres-iceberg`. У двух init-контейнеров + нормальное состояние — `Exited (0)`. Повторять сборку перед каждым занятием + не нужно; она понадобится после изменения образов или зависимостей. +3. Открой [VS Code в браузере](http://localhost:8889) и нужный ноутбук. + При запросе ядра выбери **Select Kernel → Python Environments → /usr/bin/python3**. +4. После остановки стенда переменные Python нужно создать заново: выполни + импорты, создание Spark-сессии и подключений в начале текущего ноутбука. + Сохраненный вывод ячеек может быть виден еще до их запуска. +5. Продолжи с места, на котором остановился. Перед повтором ячеек с записью + данных, `INSERT`, `CREATE OR REPLACE`, `DROP` или очисткой истории прочитай + пояснение: повторный запуск может изменить таблицу или создать новый snapshot. + +Используй прежнюю папку репозитория: имя Compose-проекта определяет, какие +Docker volumes подключаются. После переименования папки или запуска с другим +`-p` можно получить новый пустой набор данных вместо предыдущего. + +## Как проходить ноутбук + +Ноутбук состоит из пояснений и кодовых ячеек. Ядро Python выполняет код и хранит +переменные в памяти. Spark-сессия связывает этот Python-процесс с кластером. + +- Прочитай пояснение, запусти ячейку через `Shift+Enter`, дождись окончания и + сравни результат с описанием. Следующая ячейка может использовать предыдущие + переменные, поэтому порядок важен. +- В самостоятельном задании заполни `TODO` и проверь результат. **Run All** + запускает код, но не выполняет задание за тебя. +- Checkpoint — вопросы и проверки в конце урока. Покажи результат в своем + стенде и объясни его своими словами перед переходом к следующему модулю. +- Сохраняй изменения через **File → Save** (`Ctrl+S`; на macOS — `Cmd+S`). + Файл ноутбука вместе с ответами и выводом записывается в `notebooks/` на хосте. +- Перед следующим модулем выполни завершающую ячейку `spark.stop()`, если она + есть; иначе вызови `spark.stop()` после окончания вычислений. Это освобождает + ресурсы Spark для следующего приложения. Закрытие вкладки браузера само по + себе не останавливает вычисления. + +Держи одну активную Spark-сессию: если несколько ноутбуков одновременно занимают +все ядра worker-ов, новое задание может ждать ресурсы. Для своих SQL/Python-опытов +можно создать дополнительный ноутбук в папке **Ноутбуки**. `src/`, документация +и конфиги в IDE доступны только для чтения; их меняют в копии репозитория на хосте. + +### Если нужно перезапустить ядро + +Сначала сохрани ноутбук. В верхней панели нажми **Restart** и подтверди перезапуск. +Затем заново выполни импорты и создание Spark-сессии. Переменные Python исчезнут; +файлы и записанные учебные таблицы сохранятся. + +Перезапуск нужен, например, после изменения адреса кластера в +`spark/spark-defaults.conf` или после остановки Spark-сессии, с которой хочешь +продолжить работу. Повторный `getOrCreate()` при живой сессии использует ее; +подробнее это разбирается в Модуле 3. + +## Закончить занятие + +Сохрани ноутбук, дождись завершения вычислений и запиши для себя номер модуля +и следующую ячейку. Затем в терминале хоста выполни: + +```bash +docker compose stop +``` + +Контейнеры остановятся и освободят занятую ими оперативную память. Файлы +ноутбуков, исходные данные и таблицы останутся. На следующем занятии используй +`docker compose up -d` из начала этой памятки. + +## Что и где сохраняется + +Docker volume — хранилище вне файловой системы контейнера. В этом стенде два +таких хранилища: `minio-data` с объектами и `postgres-data` с каталогом Iceberg. +Имена volumes в Docker обычно имеют префикс Compose-проекта. + +| Что | Где находится | После `stop` или `down` без `-v` | +| --- | --- | --- | +| Код, ответы и сохраненный вывод ноутбуков | `notebooks/` на хосте; `/opt/work` и `/opt/course/notebooks` в IDE | Сохраняются | +| Скачанные Parquet и CSV | `data/nyc_taxi/` на хосте; `/opt/data/nyc_taxi` в контейнере | Сохраняются | +| Raw-файлы, данные и metadata-файлы Iceberg | Бакет `lakehouse`, volume `minio-data` | Сохраняются | +| Записи каталога Iceberg | PostgreSQL, volume `postgres-data` | Сохраняются | +| Переменные Python и живая Spark-сессия | Память процессов | Теряются; создай заново | +| Настройки IDE, доверие рабочей области | Файловая система контейнера `ide` | При `stop` сохраняются; при удалении или пересоздании контейнера могут сброситься | + +`docker compose down` удаляет контейнеры и сеть проекта. Именованные volumes +остаются, поэтому после `up -d` таблицы доступны снова. Для обычного окончания +занятия достаточно `stop`. Поведение команд описано также в документации Docker: +[stop](https://docs.docker.com/reference/cli/docker/compose/stop/), +[down](https://docs.docker.com/reference/cli/docker/compose/down/). + +### Начать с чистых данных + +Этот шаг нужен только для осознанного повторного прохождения с пустым +хранилищем. Он удалит оба volumes: raw-данные в MinIO, таблицы, их историю +и записи каталога PostgreSQL. Сохраненные ноутбуки и скачанные файлы в `data/` +на хосте останутся. + +```bash +docker compose down -v +docker compose up -d +``` + +После сброса в ноутбуках еще может быть старый вывод. Заново создай raw в +Модуле 3, bronze в Модуле 4 и silver в Модуле 5, прежде чем продолжать модули, +которые используют эти данные. Сброс не является способом исправить сеть +или выбор ядра. + +## Если что-то не работает + +Все `docker compose` команды ниже выполняются в терминале хоста из корня +репозитория. Имя сервиса для логов берется из колонки `SERVICE` в `ps`: +например, `postgres`, хотя контейнер называется `postgres-iceberg`. + +| Симптом | Первая проверка | Что сделать дальше | +| --- | --- | --- | +| Docker не отвечает | `docker version`: есть ли раздел Server? | Запусти Docker; при `permission denied` проверь доступ своего пользователя к Docker | +| Образ или зависимость не скачивается | Найди адрес источника и сервис в ошибке | Пройди [диагностику сети](./network_access.md) | +| `port is already allocated` / `address already in use` | Какой порт занят, не запущена ли другая копия стенда? | Освободи нужный порт; [список портов](../START_HERE.md#порты-стенда) | +| Сервис завершился, `unhealthy` или долго `starting` | `docker compose ps -a`, затем `docker compose logs --tail=100 ` | Для `postgres-iceberg` используй сервис `postgres`; проверь первую ошибку в логах | +| VS Code не открывается | `docker compose ps -a ide`, логи `ide` | Дождись `healthy`, открой `http://localhost:8889`; для сервера используй [SSH-туннель](./stack_reference.md#vs-code-в-браузере) | +| Ошибка notebook webview / service worker | Адрес в браузере и отдельная вкладка | Открой `http://localhost:8889`, без IP удаленного сервера и без встраивания в другой сайт | +| Не выбирается ядро, нет `pyspark` | Рабочая область доверенная? Выбран `/usr/bin/python3`? | Повтори [выбор ядра](../START_HERE.md#как-работать-с-ноутбуками); детали в **View → Output → Jupyter** | +| `NameError`, потерялась переменная | Ядро перезапускалось или выбран другой ноутбук? | Выполни импорты и подготовительные ячейки по порядку | +| Spark долго ждет, `Initial job has not accepted any resources` | В Spark UI два worker-а `ALIVE`? Есть другие приложения в `Running Applications`? | Заверши свою предыдущую сессию через `spark.stop()`; проверь логи worker-ов | +| Ядро умерло, контейнер убит, подозрение на память | `docker stats --no-stream`, лимит памяти Docker/WSL2 | Сверь [системные требования](../START_HERE.md#системные-требования), закрой другие вычисления; одного кода 137 недостаточно для вывода об OOM | +| Ноутбук не сохраняется (`Permission denied`) | Это файл в папке **Ноутбуки**? Совпадает UID? | Проверь `.env` и [настройку прав перед сборкой](../START_HERE.md#быстрый-запуск); после изменения UID/GID повтори `docker compose build`, затем `docker compose up -d` | +| Нет файлов Taxi или таблицы из прошлого модуля | Файлы в `data/nyc_taxi`, предыдущие модули, прежняя папка проекта | Для raw нужен Модуль 3, для bronze — 4, для silver — 5; после сброса восстанови их по порядку | + +### Что передать ментору + +Укажи ОС, модуль, ячейку или команду, ожидаемый результат и полный текст ошибки. +Приложи вывод: + +```bash +docker compose version +docker compose ps -a +docker compose logs --tail=100 ide +``` + +В последней команде замени `ide` на проблемный сервис, если ошибка в другом компоненте. +Для ошибки ноутбука добавь выбранный путь Python и вывод ячейки. Пароли, токены +и адреса прокси с учетными данными в сообщение не включай. diff --git a/ide/Dockerfile b/ide/Dockerfile new file mode 100644 index 0000000..f4c2ca6 --- /dev/null +++ b/ide/Dockerfile @@ -0,0 +1,35 @@ +# Compose передаёт результат сборки jupyter через additional_contexts. +FROM jupyter-runtime + +USER root +ARG CODE_SERVER_VERSION=4.141.0 +ARG PYTHON_EXTENSION_VERSION=2026.4.0 +ARG JUPYTER_EXTENSION_VERSION=2025.9.1 + +# Контрольные суммы .deb из выпуска code-server v4.141.0. +RUN set -eux; \ + arch="$(dpkg --print-architecture)"; \ + case "$arch" in \ + amd64) checksum=f384beb4f3ba207e0bca27d0dbd36dd825d5aa909d78c6d4baa8703eaf6b77ba ;; \ + arm64) checksum=0bca33a9b01180ad88f0b8969a8e34ae1c76417df471697e7cd65c20ef5dd60d ;; \ + *) echo "Unsupported architecture: $arch" >&2; exit 1 ;; \ + esac; \ + curl -fL --retry 3 \ + "https://github.com/coder/code-server/releases/download/v${CODE_SERVER_VERSION}/code-server_${CODE_SERVER_VERSION}_${arch}.deb" \ + -o /tmp/code-server.deb; \ + echo "$checksum /tmp/code-server.deb" | sha256sum -c -; \ + apt-get update; \ + apt-get install -y --no-install-recommends /tmp/code-server.deb; \ + rm -rf /tmp/code-server.deb /var/lib/apt/lists/* + +USER jovyan +ENV HOME=/home/jovyan +RUN code-server --install-extension "ms-python.python@${PYTHON_EXTENSION_VERSION}" \ + && code-server --install-extension "ms-toolsai.jupyter@${JUPYTER_EXTENSION_VERSION}" + +COPY lakehouse.code-workspace /opt/lakehouse.code-workspace +COPY --chown=jovyan settings.json /home/jovyan/.local/share/code-server/User/settings.json +EXPOSE 8889 +CMD ["code-server", "--bind-addr", "0.0.0.0:8889", "--auth", "none", \ + "--disable-telemetry", "--disable-update-check", \ + "/opt/lakehouse.code-workspace"] diff --git a/ide/lakehouse.code-workspace b/ide/lakehouse.code-workspace new file mode 100644 index 0000000..4dcfda1 --- /dev/null +++ b/ide/lakehouse.code-workspace @@ -0,0 +1,10 @@ +{ + "folders": [ + { "name": "Ноутбуки", "path": "/opt/course/notebooks" }, + { "name": "Примеры (только чтение)", "path": "/opt/course/src" }, + { "name": "Документация (только чтение)", "path": "/opt/course/docs" } + ], + "settings": { + "python.defaultInterpreterPath": "/usr/bin/python3" + } +} diff --git a/ide/settings.json b/ide/settings.json new file mode 100644 index 0000000..dc87556 --- /dev/null +++ b/ide/settings.json @@ -0,0 +1,8 @@ +{ + "extensions.autoUpdate": false, + "extensions.autoCheckUpdates": false, + "telemetry.telemetryLevel": "off", + "workbench.startupEditor": "none", + "chat.disableAIFeatures": true, + "workbench.secondarySideBar.defaultVisibility": "hidden" +} diff --git a/jupyter/Dockerfile b/jupyter/Dockerfile index db381b9..8e6c18a 100755 --- a/jupyter/Dockerfile +++ b/jupyter/Dockerfile @@ -1,5 +1,5 @@ # Берем за основу контейнер, приготовленный для spark в spark/Dockerfile -FROM de-spark-iceberg:3.5.1 +FROM spark-runtime USER root ENV DEBIAN_FRONTEND=noninteractive diff --git a/notebooks/01_environment_and_smoke_test.ipynb b/notebooks/01_environment_and_smoke_test.ipynb index 024d0fd..8880453 100644 --- a/notebooks/01_environment_and_smoke_test.ipynb +++ b/notebooks/01_environment_and_smoke_test.ipynb @@ -12,10 +12,10 @@ "На выходе ты должен:\n", "\n", "- понимать, что стенд уже поднят на хосте;\n", - "- подключиться к Spark-кластеру из Jupyter;\n", + "- подключиться к Spark-кластеру из ноутбука;\n", "- выполнить базовый smoke test;\n", "- сопоставить сервисы стенда с их ролями;\n", - "- знать первый маршрут диагностики через `docker compose ps`, логи и UI.\n" + "- знать первый маршрут диагностики через `docker compose ps -a`, логи и UI.\n" ] }, { @@ -25,15 +25,16 @@ "source": [ "## Перед стартом\n", "\n", - "Перед запуском этого ноутбука пройди шаги из `START_HERE.md`.\n", + "Если стенд еще не готов, выполни подготовку и запуск по [START_HERE](../START_HERE.md#быстрый-запуск).\n", + "Если возвращаешься после паузы, используй [памятку учащегося](../docs/study_workflow.md).\n", "\n", - "Важно: `docker compose build`, `docker compose up -d`, `docker compose ps` и просмотр логов выполняются на хосте, а не внутри Jupyter.\n", + "Важно: `docker compose build`, `docker compose up -d`, `docker compose ps -a` и просмотр логов выполняются в терминале хоста; терминал браузерного VS Code работает внутри контейнера.\n", "\n", "Ожидаемое состояние перед началом:\n", "\n", - "- контейнеры `spark-master`, `spark-worker-1`, `spark-worker-2`, `minio`, `postgres-iceberg`, `trino`, `jupyter` уже подняты;\n", - "- открываются `Spark UI`, `Trino UI`, `MinIO Console` и `JupyterLab`;\n", - "- ты работаешь в Jupyter внутри контейнера `jupyter`.\n" + "- контейнеры `spark-master`, `spark-worker-1`, `spark-worker-2`, `minio`, `postgres-iceberg`, `trino`, `jupyter`, `ide` уже подняты;\n", + "- открываются `Spark UI`, `Trino UI`, `MinIO Console` и браузерный VS Code;\n", + "- ты выбрал ядро Python `/usr/bin/python3` внутри контейнера `ide` (Select Kernel → Python Environments).\n" ] }, { @@ -49,7 +50,7 @@ "| `PostgreSQL` | `catalog`: хранит метаданные JDBC-каталога Iceberg |\n", "| `Spark` | `compute`: выполняет PySpark и Spark SQL |\n", "| `Trino` | `compute`: читает те же таблицы через общий каталог |\n", - "| `Jupyter` | Точка входа в учебные ноутбуки |\n", + "| `VS Code / JupyterLab` | Точка входа в учебные ноутбуки |\n", "\n", "В этом модуле мы не строим пайплайн и не создаём Iceberg-таблицы. Задача только одна: убедиться, что среда рабочая и понятная.\n" ] @@ -120,7 +121,7 @@ "\n", "Если тест проходит, это означает минимум следующее:\n", "\n", - "- Jupyter может создать SparkSession;\n", + "- ядро ноутбука может создать SparkSession;\n", "- Spark подключён к кластерному master;\n", "- задание действительно выполняется, а не падает на первом действии.\n" ] @@ -146,10 +147,10 @@ "\n", "Сделай руками и проверь себя:\n", "\n", - "1. Открой `Spark UI`, `Trino UI`, `MinIO Console` и `JupyterLab`.\n", + "1. Открой `Spark UI`, `Trino UI`, `MinIO Console` и браузерный VS Code.\n", "2. Сопоставь каждый сервис с одной из ролей: `storage`, `catalog`, `compute`, `entrypoint`.\n", "3. Объясни, почему этот smoke test не должен работать в `local[*]` режиме.\n", - "4. На хосте выполни `docker compose ps` и посмотри, какие контейнеры должны быть в состоянии `Up`.\n", + "4. На хосте выполни `docker compose ps -a` и посмотри, какие контейнеры должны быть в состоянии `Up`.\n", "5. На хосте выполни `docker compose logs -f trino` или `docker compose logs -f spark-master`, чтобы посмотреть, где начинается первичная диагностика.\n" ] }, @@ -166,7 +167,7 @@ " \"PostgreSQL\": \"TODO\",\n", " \"Spark\": \"TODO\",\n", " \"Trino\": \"TODO\",\n", - " \"Jupyter\": \"TODO\",\n", + " \"IDE\": \"TODO\",\n", "}\n", "service_roles\n" ] @@ -181,10 +182,14 @@ "К концу модуля ты должен уметь подтвердить:\n", "\n", "- стенд поднят и основные контейнеры живы;\n", - "- `Spark UI`, `MinIO Console`, `Trino UI` и `JupyterLab` открываются;\n", - "- smoke test завершился успешно и вернул ожидаемый результат;\n", - "- ты понимаешь назначение `MinIO`, `PostgreSQL`, `Spark`, `Trino`, `Jupyter`;\n", - "- ты знаешь, что первый маршрут диагностики начинается с `docker compose ps`, `docker compose logs -f ` и проверки UI.\n" + "- `Spark UI`, `MinIO Console`, `Trino UI` и браузерный VS Code открываются;\n", + "- smoke test напечатал `Spark cluster smoke test passed.` и `result_sum=499999500000`;\n", + "- ты понимаешь назначение `MinIO`, `PostgreSQL`, `Spark`, `Trino` и IDE;\n", + "- ты знаешь, что первый маршрут диагностики начинается с `docker compose ps -a`, `docker compose logs -f ` и проверки UI.\n", + "\n", + "После последней ячейки `spark.stop()` сохрани ноутбук. Дальше — [Модуль 2](./02_lakehouse_mental_model.ipynb)\n", + "или [завершение занятия](../docs/study_workflow.md#закончить-занятие).\n", + "Все уроки собраны в [маршруте по модулям](../docs/course_program.md#маршрут-по-модулям)." ] }, { diff --git a/plans/trino-memory-validation.md b/plans/trino-memory-validation.md new file mode 100644 index 0000000..afff659 --- /dev/null +++ b/plans/trino-memory-validation.md @@ -0,0 +1,107 @@ +# Проверка лимита памяти Trino + +Дата: 19 октября 2026 года. Это отчет о проверке; актуальная конфигурация +находится в `docker-compose.yml` и `trino/jvm.config`, требования для учащегося — +в `START_HERE.md`. + +## Что проверяли + +Trino 478 в контейнере с пределом 2 ГиБ RAM, без swap, с Java heap от 512 МиБ +до 1 ГиБ. JVM сама подбирает размер регионов G1; вместо настройки образа +`G1HeapRegionSize=32M` для большого heap получен регион 1 МиБ. + +Среда: Linux x86_64, Docker Engine, cgroup v2, 6 CPU и 11,14 ГиБ памяти, +видимых Docker. Остальные сервисы стенда работали одновременно. Общего лимита +8 ГБ для проекта или виртуальной машины в этом прогоне не было. + +Исходные файлы основного маршрута курса: + +| Файл | Размер, байт | +| --- | ---: | +| `yellow_tripdata_2024-01.parquet` | 49 961 641 | +| `yellow_tripdata_2024-02.parquet` | 50 349 284 | +| `yellow_tripdata_2024-03.parquet` | 60 078 280 | +| `taxi_zone_lookup.csv` | 12 331 | + +Файлы взяты из уже скачанного учебного набора, без выборки или сокращения. + +## Сценарий + +1. Перед изменением проверены отсутствие активных Spark-приложений и запросов + Trino, чтение существующей таблицы `lakehouse.default.spark_trino_smoke`. +2. Пересоздан только Trino. Запуск до `SERVER STARTED` занял около 25 секунд, + healthcheck прошел. +3. Созданы отдельная временная схема `trino_memory_` и собственный префикс + `validation/trino_memory_/raw/` в MinIO. Таблицы курса не перезаписывались. +4. Spark подготовил bronze и справочник по SQL Модуля 4; фильтры, нормализации, + два JOIN со справочником и silver выполнены кодом Модуля 5. Проверки качества + этого модуля также прошли. Получены 9 554 778 строк bronze, 9 418 184 строк + silver и 265 строк справочника. +5. Через Python-клиент Trino выполнены 11 запросов из Модуля 6: просмотр схем, + таблиц и колонок, выборки, подсчет строк, общие метрики, агрегаты по районам + и топ-10 зон. Имена таблиц заменены на временные; SQL сохранен. +6. Дополнительно дважды выполнен JOIN всей bronze со справочником зон и + группировкой по району: + + ```sql + SELECT z.Borough, count(*) AS trips, avg(b.fare_amount) AS avg_fare + FROM b + LEFT JOIN z ON b.PULocationID = z.LocationID + GROUP BY z.Borough + ORDER BY trips DESC; + ``` + +7. Подсчет строк, общие метрики, агрегаты по районам, топ зон, чтение созданной + Spark таблицы итогов и оба JOIN сверены с результатами Spark. Для чисел с + плавающей точкой использован допуск `1e-8`. Ошибки Python-клиента Trino + пробрасывались наружу, без обработчика исключений из ноутбука. +8. Повторно прочитана прежняя smoke-таблица: результат `1 / from_spark` сохранен. + Всего завершились успешно 14 запросов. Временные таблицы удалены с `PURGE`, + затем удалены временная схема и собственный raw-префикс. После `PURGE` + отдельно удалены восемь пустых маркеров каталогов в собственном warehouse-префиксе. + Проверено отсутствие временных объектов; исходные Parquet/CSV на хосте сохранены. + +## Измерения + +| Показатель | Результат | +| --- | ---: | +| Лимит контейнера (`memory.max`) | 2 147 483 648 байт | +| Лимит swap (`memory.swap.max`) | 0 | +| Фактический `MaxHeapSize` через `jcmd 1 VM.flags` | 1 073 741 824 байта | +| Пик контейнера Trino (`memory.peak`) | 1 340 874 752 байта, около 1,25 ГиБ | +| События `oom`, `oom_kill`, `max` в `memory.events` | 0 | +| Перезапуски Trino | 0 | +| Пик суммы `memory.current` восьми контейнеров | около 5,89 ГиБ | +| Пик той же суммы за вычетом `inactive_file` | около 5,44 ГиБ | + +Суммарная память снималась раз в секунду, получено 165 отсчетов во время +подготовки таблиц, запросов и очистки. Пик Trino взят из счетчика cgroup, +который учитывает и запуск контейнера. Вычитание `inactive_file` приближает +сумму к показателю памяти в `docker stats`; файловый кэш показан отдельно. + +Агрегаты по silver обработали 9 418 184 строки каждый, что подтверждено +статистикой запросов Trino. `count(*)` по bronze использовал метаданные Iceberg; +проверкой чтения bronze служил также JOIN со справочником. + +## Границы вывода + +Бюджет Trino 2 ГиБ / heap 1 ГиБ достаточен для проверенных запросов на основном +наборе курса при одном учащемся. Измерение не подтверждает работу всех восьми +модулей под общим жестким лимитом 8 ГБ. В сумму контейнеров не входят Docker +daemon, ядро и другие процессы Linux/WSL2. Docker Desktop, Windows, ARM64, +параллельные занятия и расширенный набор Taxi за 12 месяцев не проверялись. + +Для повторной проверки подними стенд с этой конфигурацией и пройди модули 3–6 +на трех месяцах Taxi, добавив JOIN выше. Временная схема нужна, если важно +сохранить существующие учебные таблицы. Примененный лимит можно проверить +с хоста: + +```bash +docker inspect --format '{{.HostConfig.Memory}} {{.HostConfig.MemorySwap}}' trino +docker compose exec trino jcmd 1 VM.flags +docker compose exec trino cat /sys/fs/cgroup/memory.peak +docker compose exec trino cat /sys/fs/cgroup/memory.events +``` + +Последние два пути относятся к cgroup v2; счетчики накапливаются с запуска +контейнера. Числа нового прогона могут отличаться. diff --git a/tests/browser_ide.cjs b/tests/browser_ide.cjs new file mode 100644 index 0000000..42a1784 --- /dev/null +++ b/tests/browser_ide.cjs @@ -0,0 +1,100 @@ +// Запуск через browser_ide.sh: настоящий notebook editor, локальное ядро и webview. +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const { chromium } = require('playwright'); + +const notebookName = 'lakehouse-browser-smoke.ipynb'; +const notebook = JSON.parse(fs.readFileSync( + path.join(__dirname, '../notebooks/01_environment_and_smoke_test.ipynb'), 'utf8', +)); +const smokeCell = notebook.cells.findIndex(cell => + cell.source.join('').includes('smoke_report = run_cluster_smoke(spark)'), +); + +async function openNotebook(page) { + await page.keyboard.press('Control+p'); + await page.keyboard.type(`/tmp/${notebookName}`); + await page.getByRole('option').filter({ hasText: notebookName }).first().waitFor(); + await page.keyboard.press('Enter'); + await page.getByRole('list', { name: /Notebook Use/ }).waitFor(); +} + +async function checkOutput(page) { + // Notebook editor виртуализирует ячейки: сначала показываем нужную ячейку. + const list = page.getByRole('list', { name: /Notebook Use/ }); + await list.press('Control+Home'); + for (let i = 0; i < smokeCell; i++) await list.press('ArrowDown'); + await page.getByRole('button', { + name: `Cell ${smokeCell + 1} of ${notebook.cells.length}`, exact: true, + }).waitFor(); + + for (let attempt = 0; attempt < 30; attempt++) { + for (const frame of page.frames()) { + const output = frame.getByText('result_sum=499999500000', { exact: false }); + if (await output.count() && await output.first().isVisible()) return; + } + await page.waitForTimeout(500); + } + throw new Error('Notebook webview did not render the expected Spark result'); +} + +(async () => { + assert(smokeCell >= 0, 'Module 1 must contain the cluster smoke cell'); + const browser = await chromium.launch({ headless: true }); + const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } }); + page.setDefaultTimeout(30000); + const webviewErrors = []; + page.on('console', message => { + if (/Error loading webview|Could not register.*service.?worker/i.test(message.text())) { + webviewErrors.push(message.text()); + } + }); + try { + await page.goto(process.env.IDE_URL || 'http://localhost:8889', { + waitUntil: 'domcontentloaded', + }); + assert(await page.evaluate(() => isSecureContext && 'serviceWorker' in navigator)); + await page.getByRole('tree', { name: 'Files Explorer' }).waitFor(); + // Баннер появляется позже Explorer; при повторном тесте доверие уже сохранено. + const restricted = page.getByRole('banner').filter({ hasText: 'Restricted Mode' }); + await restricted.waitFor({ timeout: 10000 }).catch(error => { + if (error.name !== 'TimeoutError') throw error; + }); + if (await restricted.count()) { + await restricted.getByRole('button', { name: 'Manage', exact: true }).click(); + await page.getByRole('button', { name: 'Trust', exact: true }).click(); + await page.getByRole('button', { name: 'Close Modal Editor (Escape)' }).click(); + await restricted.waitFor({ state: 'hidden' }); + } + console.log('PASS: workspace trust enabled'); + + await openNotebook(page); + await page.getByRole('button', { name: 'Select Kernel', exact: true }).click(); + await page.getByRole('option', { name: 'Python Environments...' }).click(); + await page.getByRole('option').filter({ hasText: '/usr/bin/python3' }).click(); + console.log('PASS: notebook editor and local Python kernel selection'); + + await page.getByRole('button', { name: 'Run All', exact: true }).click(); + await page.getByRole('button', { name: 'Interrupt', exact: true }).waitFor(); + await page.getByRole('button', { name: 'Run All', exact: true }).waitFor({ timeout: 120000 }); + await checkOutput(page); + await page.keyboard.press('Control+s'); + console.log('PASS: Spark output rendered in notebook webview over HTTP localhost'); + + await page.getByRole('tab', { name: notebookName, exact: true }) + .getByRole('button', { name: 'Close (Ctrl+W)' }).click(); + await openNotebook(page); + await checkOutput(page); + assert.deepEqual(webviewErrors, []); + await page.screenshot({ path: '/artifacts/notebook.png' }); + await page.getByRole('tab', { name: notebookName, exact: true }) + .getByRole('button', { name: 'Close (Ctrl+W)' }).click(); + console.log('PASS: saved notebook reopened with its output'); + } catch (error) { + await page.screenshot({ path: '/artifacts/failure.png' }).catch(() => {}); + throw error; + } finally { + await browser.close(); + } +})().catch(error => { console.error(error); process.exitCode = 1; }); diff --git a/tests/browser_ide.sh b/tests/browser_ide.sh new file mode 100644 index 0000000..ccdb3f6 --- /dev/null +++ b/tests/browser_ide.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Проверка UI на Linux Docker Engine; зависимости браузера остаются в контейнере. +set -euo pipefail +cd "$(dirname "$0")/.." + +artifact_dir=$(mktemp -d /tmp/lakehouse-browser-check.XXXXXX) +echo "Browser artifacts: $artifact_dir" +docker compose exec -T ide cp /opt/work/01_environment_and_smoke_test.ipynb \ + /tmp/lakehouse-browser-smoke.ipynb + +docker run --rm --network host --shm-size=1g \ + -e IDE_URL="${IDE_URL:-http://localhost:8889}" \ + -v "$PWD:/repo:ro" -v "$artifact_dir:/artifacts" \ + mcr.microsoft.com/playwright:v1.56.1-noble \ + bash -c 'npm install --prefix /tmp/browser-test --no-audit --no-fund playwright@1.56.1 >/dev/null && + NODE_PATH=/tmp/browser-test/node_modules node /repo/tests/browser_ide.cjs' + +docker compose exec -T ide python3 - <<'PY' +import json +from pathlib import Path + +path = Path('/tmp/lakehouse-browser-smoke.ipynb') +notebook = json.loads(path.read_text()) +cells = [cell for cell in notebook['cells'] if cell['cell_type'] == 'code'] +assert all(cell['execution_count'] is not None for cell in cells) +assert not any(output['output_type'] == 'error' + for cell in cells for output in cell.get('outputs', [])) +output = '\n'.join(''.join(item.get('text', [])) + for cell in cells for item in cell.get('outputs', [])) +assert 'master_url=spark://spark-master:7077' in output +assert 'result_sum=499999500000' in output +path.unlink() +print(f'PASS: all {len(cells)} code cells executed and saved without errors') +PY diff --git a/trino/jvm.config b/trino/jvm.config new file mode 100644 index 0000000..d92a48d --- /dev/null +++ b/trino/jvm.config @@ -0,0 +1,17 @@ +# Параметры образа trinodb/trino:478 с бюджетом памяти для локального курса. +-server +-agentpath:/usr/lib/trino/bin/libjvmkill.so +-Xms512M +-Xmx1G +# Размер регионов G1 выбирает JVM с учетом небольшого heap. +-XX:+ExplicitGCInvokesConcurrent +-XX:+HeapDumpOnOutOfMemoryError +-XX:+ExitOnOutOfMemoryError +-XX:-OmitStackTraceInFastThrow +-XX:ReservedCodeCacheSize=256M +-XX:PerMethodRecompilationCutoff=10000 +-XX:PerBytecodeRecompilationCutoff=10000 +-Djdk.attach.allowAttachSelf=true +-Djdk.nio.maxCachedBufferSize=2000000 +-Dfile.encoding=UTF-8 +-XX:+EnableDynamicAgentLoading