From 39b1a5618c6e80b48b67738b9e7ad57861b494a6 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Sat, 7 Mar 2026 21:30:26 +0300 Subject: [PATCH] =?UTF-8?q?docs(plan):=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D0=BF=D0=BB=D0=B0=D0=BD=20=D0=BC=D0=BE?= =?UTF-8?q?=D0=B4=D1=83=D0=BB=D1=8F=206=20=D0=BF=D0=BE=20Spark=20=D0=B8=20?= =?UTF-8?q?Trino=20=D0=BD=D0=B0=20=D0=BE=D0=B4=D0=BD=D0=BE=D0=B9=20=D1=82?= =?UTF-8?q?=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - нужно продемонстрировать возможность одновременной работы Spark и Trino с одной Iceberg-таблицей. - Что: - создан файл plans/module-06-spark-and-trino-on-same-table.md с планом модуля. - обновлен plans/README.md с записью о новом модуле в таблицу содержания. - Проверка: - проверка согласованности ссылок и структуры документации. --- plans/README.md | 1 + ...module-06-spark-and-trino-on-same-table.md | 318 ++++++++++++++++++ 2 files changed, 319 insertions(+) create mode 100644 plans/module-06-spark-and-trino-on-same-table.md diff --git a/plans/README.md b/plans/README.md index a1396db..df89af2 100644 --- a/plans/README.md +++ b/plans/README.md @@ -18,3 +18,4 @@ | 3. Raw-данные и первое чтение в Spark | [module-03-raw-ingest-and-first-read.md](./module-03-raw-ingest-and-first-read.md) | Ready for validation | `docker-compose.yml`, `.gitignore`, `START_HERE.md`, `docs/stack_reference.md`, `notebooks/03_raw_ingest_and_first_read.ipynb` | 2026-03-07 | | 4. Первая рабочая Iceberg-таблица и слой bronze | [module-04-bronze-with-iceberg.md](./module-04-bronze-with-iceberg.md) | Draft | `notebooks/04_bronze_with_iceberg.ipynb` | 2026-03-07 | | 5. Слой silver и воспроизводимые трансформации | [module-05-silver-layer.md](./module-05-silver-layer.md) | Draft | `notebooks/05_silver_layer.ipynb` | 2026-03-07 | +| 6. Одна таблица, два движка: Spark и Trino | [module-06-spark-and-trino-on-same-table.md](./module-06-spark-and-trino-on-same-table.md) | Draft | `jupyter/Dockerfile`, `START_HERE.md`, `docs/stack_reference.md`, `notebooks/06_spark_and_trino_on_same_table.ipynb` | 2026-03-07 | diff --git a/plans/module-06-spark-and-trino-on-same-table.md b/plans/module-06-spark-and-trino-on-same-table.md new file mode 100644 index 0000000..7c30898 --- /dev/null +++ b/plans/module-06-spark-and-trino-on-same-table.md @@ -0,0 +1,318 @@ +# Модуль 6. Одна таблица, два движка: Spark и Trino + +**Статус:** `Draft` +**Последнее обновление:** `2026-03-07` + +## Цель + +Показать студенту главное практическое следствие архитектуры Lakehouse: таблица, записанная одним движком, может быть прочитана другим без копирования данных. Студент записывает таблицу через Spark, читает её же через Trino, сравнивает результаты и объясняет, почему это работает через общий каталог и общее хранилище. + +## Результат для студента + +После прохождения модуля студент: + +- умеет записать таблицу через Spark и тут же прочитать её через Trino — без копирования данных; +- умеет выполнять SQL-запросы к Iceberg-таблицам из Trino (программно из ноутбука и через DBeaver); +- умеет сопоставить результаты одного и того же запроса из Spark и из Trino; +- понимает роль общего каталога (PostgreSQL JDBC) и общего хранилища (MinIO) в обеспечении доступа из разных движков; +- может объяснить отличие Lakehouse-модели (decoupled compute) от классического DWH (engine = storage). + +## Deliverables + +- `notebooks/06_spark_and_trino_on_same_table.ipynb` — основной ноутбук Модуля 6; +- обновление `jupyter/Dockerfile` — добавление `trino>=0.328` в pip install; +- обновление `START_HERE.md` — новая секция «Подключение DBeaver к Trino (перед Модулем 6)»; +- обновление `docs/stack_reference.md` — блок о подключении DBeaver к Trino; +- обновление `plans/README.md` — строка Модуля 6 в оглавлении. + +## Зависимости + +### Предусловия + +- Стенд поднят и работает (Модуль 1). +- Студент прошёл Модуль 5: таблица `lakehouse.silver.nyc_taxi_yellow` существует (не менее 24 колонок; если студент выполнил расширенное задание Модуля 5 — колонок может быть больше, это нормально). +- Студент прошёл Модуль 4: таблица `lakehouse.bronze.nyc_taxi_yellow` существует. +- Рекомендуется: DBeaver установлен на хосте студента (или аналогичный SQL-клиент). Не обязателен — ноутбук самодостаточен через Python `trino` клиент. + +### Что используют последующие модули + +- Модуль 7 работает со schema evolution и time travel, используя и Spark, и Trino. +- Модуль 8 использует silver в финальной практике с проверкой через Trino. + +## Дизайн-решения + +### Два пути к Trino: ноутбук (основной) и DBeaver (параллельный) + +PRD требует, чтобы ноутбук был самодостаточным учебником и тренажёром: демонстрации, самостоятельные задания и checkpoints — в ноутбуке ([course_prd.md:65-68](../docs/course_prd.md#L65)). Поэтому основной исполняемый путь к Trino — Python `trino` клиент в code cells. Ноутбук выполняется сверху вниз без внешних зависимостей. + +DBeaver — рекомендуемый параллельный инструмент для тех, кто хочет работать в привычном SQL-интерфейсе. Инструкция по подключению добавляется в `START_HERE.md`. В ноутбуке каждый Trino-запрос сопровождается markdown-заметкой: «Этот же запрос можно выполнить в DBeaver». Это: + +- подкрепляет идею decoupled compute: два разных клиента (Python, DBeaver) работают с одним движком; +- даёт студенту выбор привычного инструмента; +- не делает ноутбук зависимым от внешнего приложения. + +### Python `trino` клиент: основной исполняемый путь + +Пакет `trino>=0.328` добавляется в `jupyter/Dockerfile`. В начале ноутбука создаётся helper-функция `trino_query(sql)`, которая выполняет SQL через Trino и выводит результат. Все Trino-запросы в ноутбуке — исполняемые code cells через этот helper. Это обеспечивает воспроизводимость и проверяемость. + +### Структура «aha-момента»: write → read + +Два этапа: + +1. **Сравнение существующих таблиц.** Spark читает silver, Trino читает silver — метрики совпадают. Устанавливает факт: общий каталог работает. +2. **Живой цикл write → read.** Spark создаёт новую таблицу (`lakehouse.default.borough_summary`) через CTAS. Trino тут же её читает — без синхронизации, без копирования. Студент видит причинно-следственную связь: записал → увидел. Это соответствует программе курса: «записать таблицу через Spark; прочитать ту же таблицу через Trino» ([course_program.md:204-205](../docs/course_program.md#L204)). + +### Greenplum-параллель: decoupled vs monolithic + +В Greenplum данные и движок — единая система. Чтобы другой инструмент прочитал те же данные, нужно подключиться через Greenplum или скопировать. В Lakehouse данные лежат снаружи в object storage, и любой движок с доступом к каталогу и хранилищу может их прочитать. Данные не заперты в одном движке. + +### Cleanup: удаление демо-таблицы + +Таблицы bronze и silver остаются для Модулей 7 и 8. Демо-таблица `lakehouse.default.borough_summary` удаляется в конце модуля — она нужна только для демонстрации write → read. Spark-сессия останавливается. + +## План работ + +1. Обновить `jupyter/Dockerfile`: добавить `"trino>=0.328"` в строку `pip3 install`. +2. Обновить `START_HERE.md`: добавить секцию «Подключение DBeaver к Trino (перед Модулем 6)» после секции «Подготовка учебного датасета». +3. Обновить `docs/stack_reference.md`: добавить блок о подключении через DBeaver в секцию «Trino». +4. Создать `notebooks/06_spark_and_trino_on_same_table.ipynb` по ячеечной структуре ниже. +5. Обновить `plans/README.md` — добавить строку Модуля 6. +6. Валидация: выполнить ноутбук сверху вниз на поднятом стенде после Модуля 5. Опционально: проверить DBeaver-подсказки из markdown в DBeaver на `localhost:8090`. + +## Структура ноутбука + +### Секция 0: Введение +- **[md]** Заголовок, цели модуля, prerequisites (Модуль 5 пройден). Если установлен DBeaver — можно подключить его к Trino по инструкции из START_HERE.md (необязательно). Таблица-сравнение: + +| | Классический DWH (Greenplum) | Lakehouse | +|---|---|---| +| Где лежат данные | Внутри СУБД, на управляемых дисках | В объектном хранилище (MinIO), отдельно от движков | +| Где лежат метаданные | `pg_catalog` внутри той же СУБД | Внешний JDBC-каталог (PostgreSQL) + metadata в MinIO | +| Кто может читать таблицу | Только сама СУБД | Любой движок с доступом к каталогу и хранилищу | +| Чтобы дать доступ другому инструменту | Подключиться к СУБД или скопировать данные | Подключить тот же каталог — данные уже доступны | + +- **[md]** Ключевая идея: в Lakehouse данные не заперты в одном движке. Spark записал таблицу, Trino может прочитать без копирования — общий каталог и общее хранилище. Это decoupled compute. +- **[md]** Как устроен этот ноутбук: Spark-код и Trino-запросы выполняются здесь, в Jupyter. Для Trino используется Python-клиент `trino`. Если у вас установлен DBeaver — каждый Trino-запрос можно выполнить и там (в markdown будут подсказки). Два движка — одни данные. + +### Секция 1: Spark-сессия, Trino-клиент и проверка таблиц +- **[code]** SparkSession (паттерн Модулей 2-5). Константы: `SILVER_TABLE`, `BRONZE_TABLE`. +- **[code]** Assert: silver и bronze таблицы существуют и непусты. Отсылка к Модулям 4-5. +- **[code]** Helper-функция `trino_query(sql)`: подключается к Trino через Python `trino` клиент, выполняет SQL, выводит результат в табличном виде. Используется во всех последующих Trino-ячейках. Если падает с `ImportError` — нужно пересобрать образ (`docker compose build jupyter && docker compose up -d`). +- **[md]** Обе таблицы на месте. Spark может их читать. Trino-клиент готов. Вопрос: может ли Trino прочитать те же таблицы? + +### Секция 2: Читаем silver через Spark — фиксируем метрики +- **[md]** Сначала получим числа из Spark. Потом сравним с Trino. +- **[code]** Spark SQL: `SELECT count(*), avg(fare_amount), avg(trip_distance), count(DISTINCT pickup_borough)`. Сохранение в переменную `spark_metrics`. +- **[code]** `spark.table(SILVER_TABLE).show(10, truncate=False)` — первые строки для визуального сравнения. +- **[md]** Запомни эти числа. Сейчас выполним тот же запрос через Trino. + +### Секция 3: Навигация по каталогу через Trino +- **[code]** `trino_query("SHOW SCHEMAS FROM lakehouse")`. Trino видит те же namespace (bronze, silver, default), потому что читает тот же JDBC-каталог. +- **[md]** _DBeaver: тот же запрос можно выполнить в DBeaver, если он подключён к Trino по инструкции из START_HERE.md._ +- **[code]** `trino_query("SHOW TABLES FROM lakehouse.silver")`. Ожидаем `nyc_taxi_yellow`. +- **[code]** `trino_query("DESCRIBE lakehouse.silver.nyc_taxi_yellow")`. Сравнить со схемой из Модуля 5 (не менее 24 колонок; если выполнено расширенное задание — может быть больше). Различия в нотации типов (varchar vs STRING, double vs DOUBLE) нормальны — это разница синтаксиса движков, не данных. + +### Секция 4: «Aha-момент» — одни и те же данные +- **[md]** Центральный момент модуля. Trino читает таблицу, записанную Spark в Модуле 5. +- **[code]** `trino_query("SELECT * FROM lakehouse.silver.nyc_taxi_yellow LIMIT 10")`. +- **[md]** Данные, записанные Spark. Trino не копировал — прочитал из MinIO через metadata из каталога. _DBeaver: `SELECT * FROM lakehouse.silver.nyc_taxi_yellow LIMIT 10;`_ +- **[code]** Trino-метрики: `trino_query("SELECT count(*) AS row_count, avg(fare_amount) AS avg_fare, avg(trip_distance) AS avg_distance, count(DISTINCT pickup_borough) AS boroughs FROM lakehouse.silver.nyc_taxi_yellow")`. +- **[code]** Вывод сохранённых Spark-метрик для удобного сравнения. +- **[md]** Ожидание: row_count совпадает точно, avg — с точностью до floating-point округления. Оба движка читают одни и те же data files из MinIO. + +### Секция 5: Почему это работает — роль общего каталога +- **[md]** Мини-лекция (4-5 абзацев): + - Общий каталог: оба движка подключены к PostgreSQL (`postgres-iceberg:5432/iceberg`). Spark создаёт запись — Trino читает ту же запись. + - Общее хранилище: data files и metadata в MinIO (`s3://lakehouse/warehouse`). Оба движка имеют доступ к бакету. + - Параллель с Greenplum: данные внутри СУБД, доступ только через Greenplum. В Lakehouse — данные снаружи, движки взаимозаменяемы. + - Decoupled compute: добавить новый движок = подключить его к каталогу и хранилищу. + +- **[md]** ASCII-диаграмма: + +``` + ┌─────────────────┐ ┌──────────────────┐ + │ Spark (Jupyter) │ │ Trino (DBeaver) │ + └────────┬────────┘ └────────┬──────────┘ + │ │ + ▼ ▼ + ┌──────────────────────────────────────────┐ + │ PostgreSQL (JDBC catalog) │ + │ metadata_location -> s3://... │ + └────────────────────┬─────────────────────┘ + ▼ + ┌──────────────────────────────────────────┐ + │ MinIO (S3-compatible storage) │ + │ metadata/ + data/ (Parquet files) │ + └──────────────────────────────────────────┘ +``` + +### Секция 6: Spark записывает — Trino читает (live write → read) +- **[md]** До этого мы читали таблицы, созданные в предыдущих модулях. Теперь — живой цикл: Spark создаёт таблицу прямо сейчас, и Trino её тут же видит. Это ключевое действие модуля: «записать таблицу через Spark; прочитать ту же таблицу через Trino» (программа курса). +- **[code]** Spark CTAS: создаём агрегатную таблицу `lakehouse.default.borough_summary`: + +```python +spark.sql(""" + CREATE OR REPLACE TABLE lakehouse.default.borough_summary + USING iceberg AS + SELECT pickup_borough, + count(*) AS trips, + avg(fare_amount) AS avg_fare, + avg(trip_distance) AS avg_distance + FROM lakehouse.silver.nyc_taxi_yellow + WHERE pickup_borough IS NOT NULL + GROUP BY pickup_borough +""") +spark.table("lakehouse.default.borough_summary").show() +``` + +- **[md]** Spark записал таблицу. Мы не делали никакой синхронизации. Видит ли Trino? +- **[code]** `trino_query("SELECT * FROM lakehouse.default.borough_summary ORDER BY trips DESC")`. +- **[md]** Trino видит таблицу мгновенно. Spark записал metadata в PostgreSQL и data files в MinIO. Trino прочитал тот же каталог — увидел таблицу. Никакого копирования, никакой синхронизации. _DBeaver: `SELECT * FROM lakehouse.default.borough_summary ORDER BY trips DESC;`_ +- **[md]** Это и есть decoupled compute: один движок создал, другой прочитал. В Greenplum для этого пришлось бы либо подключиться к тому же движку, либо скопировать данные. + +### Секция 7: Аналитические запросы в Trino +- **[md]** Trino как аналитический SQL-движок. Выполним несколько аналитических запросов. +- **[code]** `trino_query("SELECT pickup_borough, count(*) AS trips, avg(fare_amount) AS avg_fare, avg(trip_distance) AS avg_distance, avg(tip_amount) AS avg_tip FROM lakehouse.silver.nyc_taxi_yellow WHERE pickup_borough IS NOT NULL GROUP BY pickup_borough ORDER BY trips DESC")`. +- **[md]** _DBeaver: тот же запрос._ +- **[code]** Топ зон посадки: `trino_query("SELECT pickup_zone, count(*) AS trips, avg(total_amount) AS avg_total FROM lakehouse.silver.nyc_taxi_yellow WHERE pickup_zone IS NOT NULL GROUP BY pickup_zone ORDER BY trips DESC LIMIT 10")`. +- **[code]** Тот же запрос (топ зон) через Spark SQL для сравнения. +- **[md]** Результаты совпадают (с точностью до floating-point). Два движка, одна таблица. + +### Секция 8: Навигация по bronze из Trino +- **[md]** До этого bronze читали только из Spark. Проверим через Trino. +- **[code]** `trino_query("SHOW TABLES FROM lakehouse.bronze")`. +- **[code]** `trino_query("SELECT count(*) AS row_count FROM lakehouse.bronze.nyc_taxi_yellow")`. +- **[code]** `trino_query("SELECT * FROM lakehouse.bronze.nyc_taxi_yellow LIMIT 5")`. +- **[md]** Trino видит все таблицы из всех namespace каталога. Каталог — общий, хранилище — общее. _DBeaver: те же запросы._ + +### Секция 9: DBeaver — SQL-клиент для Trino (рекомендация) +- **[md]** В этом ноутбуке мы работали с Trino через Python-клиент — для воспроизводимости. В реальной аналитической работе Trino чаще используют через SQL-клиенты: DBeaver, DataGrip, DbVisualizer. DBeaver — де-факто стандарт для SQL, знакомый всем, кто работал с PostgreSQL/Greenplum. +- **[md]** Если DBeaver установлен и подключён (инструкция в START_HERE.md), попробуйте выполнить в нём любой запрос из этого модуля. Результат будет тот же — тот же протокол, тот же движок, та же таблица. Разница: DBeaver — для интерактивной SQL-работы, Python-клиент — для автоматизации и ноутбуков. + +### Секция 10: Самостоятельное задание +- **[md]** Четыре задачи. Центральная — самостоятельный цикл write → read, как в Секции 6. + + **Задача 1 (Spark → write).** Создай через Spark новую агрегатную таблицу `lakehouse.default.zone_summary` с CTAS: для каждой `pickup_zone` посчитай `count(*)`, `avg(total_amount)`, `avg(tip_amount)` по silver-таблице (WHERE pickup_zone IS NOT NULL). + + **Задача 2 (Trino → read).** Прочитай `lakehouse.default.zone_summary` через Trino (Python-клиент или DBeaver). Убедись, что Trino видит таблицу без синхронизации. + + **Задача 3 (сравнение).** Выполни тот же агрегатный запрос напрямую по silver через Trino (без промежуточной таблицы). Сравни результат с `zone_summary`. Числа должны совпасть. + + **Задача 4 (ответ).** Ответь в markdown-ячейке: почему Trino увидел `zone_summary` сразу после создания через Spark? Какие три компонента стенда это обеспечивают? Что произошло бы, если бы у Trino был другой каталог? + +- **[code]** Пустая ячейка: `# Ваш код (Spark): CREATE TABLE zone_summary` +- **[code]** Пустая ячейка: `# Ваш код (Trino): чтение zone_summary` +- **[code]** Пустая ячейка: `# Ваш код (Trino): тот же агрегат напрямую по silver` +- **[md]** Пустая markdown-ячейка: `Ваш ответ на Задачу 4: ...` +- **[code]** Cleanup: `spark.sql("DROP TABLE IF EXISTS lakehouse.default.zone_summary")` + +- **[md]** Дополнительная задача (по желанию): аналитический запрос по bronze через Trino и через Spark, сравнение. +- **[code]** Пустая ячейка: `# Ваш код (Trino): аналитический запрос по bronze` +- **[code]** Пустая ячейка: `# Ваш код (Spark): тот же запрос по bronze` + +### Секция 11: Что мы НЕ сравниваем +- **[md]** Ограничение: модуль не сравнивает Spark и Trino как движки. Не обсуждаем: какой быстрее, какой лучше, внутренние различия. Цель — показать, что оба работают с одной таблицей через общий каталог. Это свойство архитектуры Lakehouse, а не конкретного движка. + +### Секция 12: Checkpoint +- **[md]** Вопросы: + 1. Почему Trino видит таблицы, созданные Spark, без синхронизации? + 2. Какие два компонента стенда общие для Spark и Trino? + 3. Чем доступ к данным в Lakehouse отличается от Greenplum? + 4. Нужно ли копировать данные, чтобы Trino прочитал таблицу Spark? + 5. Что такое «decoupled compute» в одном предложении? + 6. Если добавить третий движок (Flink), что нужно сделать, чтобы он увидел те же таблицы? + 7. Что произошло, когда Spark создал `borough_summary`, а Trino её тут же прочитал? + +### Секция 13: Завершение +- **[md]** Удаляем демо-таблицу, она больше не нужна. Таблицы bronze и silver остаются для Модулей 7 и 8. +- **[code]** `spark.sql("DROP TABLE IF EXISTS lakehouse.default.borough_summary")`. +- **[md]** «В Модуле 7 — schema evolution и time travel. В Модуле 8 — финальная практика.» +- **[code]** `spark.stop()`. + +### Дизайн-решения по ноутбуку + +- **Исполняемый формат:** Spark-код и Trino-запросы (через Python `trino` клиент) — в code cells. Ноутбук выполняется сверху вниз без внешних зависимостей. +- **DBeaver-подсказки:** markdown-заметки рядом с Trino-ячейками: «этот же запрос можно выполнить в DBeaver». +- **Helper-код:** `trino_query(sql)` — единственный helper. Ноутбук проще предыдущих — основная работа в SQL. +- **Cleanup:** DROP демо-таблицы `borough_summary`. +- **Порядок:** Spark (метрики) -> Trino (сравнение) -> объяснение WHY -> write → read -> аналитика -> bronze -> DBeaver-рекомендация -> самостоятельное задание. + +## Изменения инфраструктуры + +### jupyter/Dockerfile + +Добавить `"trino>=0.328"` в pip install (строка 8-11): + +```dockerfile +RUN pip3 install --no-cache-dir \ + "jupyterlab==4.2.5" \ + "boto3>=1.35,<2" \ + "psycopg2-binary>=2.9,<3" \ + "trino>=0.328" +``` + +Чистый Python-пакет, без системных зависимостей. Используется для программного доступа к Trino из ноутбука. + +### START_HERE.md + +Новая секция после «Подготовка учебного датасета (перед Модулем 3)», перед «Что делать дальше»: + +**«Подключение DBeaver к Trino (перед Модулем 6)»** + +- Зачем: в Модуле 6 можно работать с Trino через DBeaver параллельно с ноутбуком — привычный SQL-интерфейс. +- Предусловие: DBeaver установлен (ссылка на 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`. +- Troubleshooting: стенд поднят? контейнер `trino` Up? порт 8090 свободен? + +### docs/stack_reference.md + +В секцию «Trino» добавить подсекцию «Подключение через DBeaver»: +- Host: `localhost`, Port: `8090`, Catalog: `lakehouse`, User: любая строка, Password: нет. +- Driver: Trino (встроен в DBeaver). +- Проверка: `SHOW SCHEMAS FROM lakehouse;`. + +## Checkpoint + +Студент должен уметь: + +- записать таблицу через Spark и прочитать её через Trino (live write → read); +- прочитать одну и ту же таблицу из Spark и из Trino и показать совпадение метрик; +- объяснить, какие компоненты обеспечивают доступ из двух движков (PostgreSQL catalog + MinIO storage); +- объяснить, почему данные не нужно копировать между движками; +- объяснить разницу между monolithic (Greenplum) и decoupled (Lakehouse) подходом. + +## Acceptance Criteria + +- `notebooks/06_spark_and_trino_on_same_table.ipynb` выполняется сверху вниз после прохождения Модуля 5 без внешних зависимостей (DBeaver не обязателен). +- Ноутбук содержит живой цикл write → read: Spark создаёт таблицу, Trino читает. +- Методика курса: объяснение -> демонстрация -> самостоятельное повторение -> checkpoint. +- Паттерны Модулей 1-5: SparkSession без `.master()`, `setLogLevel("ERROR")`. +- Trino-запросы — в исполняемых code cells через Python `trino` клиент. DBeaver-подсказки — в markdown. +- При отсутствии silver — assert с отсылкой к Модулю 5. Схема silver: не менее 24 колонок (допускается расширенная схема после Модуля 5). +- Текст на русском с параллелями к PostgreSQL/Greenplum. +- `jupyter/Dockerfile` содержит `trino>=0.328`. +- `START_HERE.md` содержит инструкцию по подключению DBeaver к Trino (рекомендуемый параллельный инструмент). +- `docs/stack_reference.md` содержит блок о подключении DBeaver. +- `plans/README.md` содержит строку Модуля 6. +- Демо-таблица `borough_summary` удаляется в конце модуля. Таблицы bronze и silver остаются для Модулей 7 и 8. + +## Риски + +- **DBeaver не установлен.** Не блокер — ноутбук самодостаточен через Python `trino` клиент. DBeaver рекомендуется как параллельный инструмент. В START_HERE.md ссылка на установку. Альтернативные SQL-клиенты (DataGrip, DbVisualizer) — аналогичное подключение. Fallback: Trino CLI (`docker compose exec -it trino trino --catalog lakehouse`). +- **Trino не видит таблицы.** Контейнер не поднят, PostgreSQL/MinIO недоступны. Диагностика через `docker compose ps` и логи. Пояснение в ноутбуке. +- **Различия типов между Spark и Trino.** STRING vs varchar, DOUBLE vs double — косметическая разница в синтаксисе, не в данных. Упомянуть в секции 3. +- **Floating-point различия в агрегатах.** `avg()` может дать незначительно разные последние знаки. Упомянуть как нормальное поведение. +- **Пакет `trino` требует пересборки образа.** Нужен `docker compose build jupyter`. Без пакета ноутбук не выполняется (ImportError в секции 1). Пояснение в markdown с командой пересборки. +- **Порт 8090 занят.** Стандартная диагностика в START_HERE.md. + +## Out of Scope + +- Глубокое сравнение Spark и Trino (performance, оптимизаторы). +- Запись данных через Trino (в курсе Trino только читает). +- Trino CLI как основной инструмент. +- Настройка Trino connector. +- dbt, Airflow, оркестрация. +- Партиционирование и partition pruning. +- Schema evolution через Trino — Модуль 7. +- Trino security, users, roles.