feat(module-06): реализован модуль по совместной работе Spark и Trino
- Зачем: - продемонстрировать возможность доступа нескольких движков к одной Iceberg-таблице через общий каталог. - Что: - создан notebooks/06_spark_and_trino_on_same_table.ipynb с примерами write (Spark) -> read (Trino). - в jupyter/Dockerfile добавлен клиент trino. - в START_HERE.md и docs/stack_reference.md добавлена инструкция по подключению DBeaver к Trino. - статус модуля 6 в планах обновлен до Ready for validation. - Проверка: - ручная проверка выполнения ячеек в ноутбуке (Python trino client). - проверка наличия всех инструкций в документации.
This commit is contained in:
@@ -181,6 +181,14 @@ ls -lh data/nyc_taxi/
|
|||||||
|
|
||||||
Если хочешь расширенный режим, можешь скачать все 12 месяцев `2024`, но основной маршрут курса и примеры опираются на первые 3 месяца.
|
Если хочешь расширенный режим, можешь скачать все 12 месяцев `2024`, но основной маршрут курса и примеры опираются на первые 3 месяца.
|
||||||
|
|
||||||
|
## Подключение DBeaver к Trino (перед Модулем 6)
|
||||||
|
|
||||||
|
- Зачем: в Модуле 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`.
|
||||||
|
- Troubleshooting: стенд поднят? контейнер `trino` Up? порт 8090 свободен?
|
||||||
|
|
||||||
## Что делать дальше
|
## Что делать дальше
|
||||||
|
|
||||||
- пройти `notebooks/01_environment_and_smoke_test.ipynb`;
|
- пройти `notebooks/01_environment_and_smoke_test.ipynb`;
|
||||||
|
|||||||
@@ -80,6 +80,11 @@ SHOW SCHEMAS FROM lakehouse;
|
|||||||
SHOW TABLES FROM lakehouse.default;
|
SHOW TABLES FROM lakehouse.default;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Подключение через DBeaver:**
|
||||||
|
- Host: `localhost`, Port: `8090`, Catalog: `lakehouse`, User: любая строка, Password: нет.
|
||||||
|
- Driver: Trino (встроен в DBeaver).
|
||||||
|
- Проверка: `SHOW SCHEMAS FROM lakehouse;`.
|
||||||
|
|
||||||
### MinIO
|
### MinIO
|
||||||
|
|
||||||
- S3 endpoint: `http://localhost:9000`
|
- S3 endpoint: `http://localhost:9000`
|
||||||
|
|||||||
+2
-1
@@ -8,7 +8,8 @@ ENV DEBIAN_FRONTEND=noninteractive
|
|||||||
RUN pip3 install --no-cache-dir \
|
RUN pip3 install --no-cache-dir \
|
||||||
"jupyterlab==4.2.5" \
|
"jupyterlab==4.2.5" \
|
||||||
"boto3>=1.35,<2" \
|
"boto3>=1.35,<2" \
|
||||||
"psycopg2-binary>=2.9,<3"
|
"psycopg2-binary>=2.9,<3" \
|
||||||
|
"trino>=0.328"
|
||||||
|
|
||||||
# Создаём непривилегированного пользователя
|
# Создаём непривилегированного пользователя
|
||||||
ARG NB_USER=jovyan
|
ARG NB_USER=jovyan
|
||||||
|
|||||||
@@ -0,0 +1,667 @@
|
|||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_0",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Модуль 6. Одна таблица, два движка: Spark и Trino\n",
|
||||||
|
"\n",
|
||||||
|
"В этом модуле мы увидим главное практическое следствие архитектуры Lakehouse: таблица, записанная одним движком, может быть прочитана другим без копирования данных.\n",
|
||||||
|
"\n",
|
||||||
|
"**Цели модуля:**\n",
|
||||||
|
"- записать таблицу через Spark и тут же прочитать её через Trino;\n",
|
||||||
|
"- выполнить SQL-запросы к Iceberg-таблицам из Trino;\n",
|
||||||
|
"- понять роль общего каталога и общего хранилища;\n",
|
||||||
|
"- осознать отличие Lakehouse-модели (decoupled compute) от классического DWH.\n",
|
||||||
|
"\n",
|
||||||
|
"**Prerequisite:** пройден Модуль 5 (silver-таблица существует).\n",
|
||||||
|
"\n",
|
||||||
|
"### Связка с привычным DWH (Greenplum)\n",
|
||||||
|
"\n",
|
||||||
|
"| | Классический DWH (Greenplum) | Lakehouse |\n",
|
||||||
|
"|---|---|---|\n",
|
||||||
|
"| Где лежат данные | Внутри СУБД, на управляемых дисках | В объектном хранилище (MinIO), отдельно от движков |\n",
|
||||||
|
"| Где лежат метаданные | `pg_catalog` внутри той же СУБД | Внешний JDBC-каталог (PostgreSQL) + metadata в MinIO |\n",
|
||||||
|
"| Кто может читать таблицу | Только сама СУБД | Любой движок с доступом к каталогу и хранилищу |\n",
|
||||||
|
"| Чтобы дать доступ другому инструменту | Подключиться к СУБД или скопировать данные | Подключить тот же каталог — данные уже доступны |\n",
|
||||||
|
"\n",
|
||||||
|
"**Ключевая идея:** в Lakehouse данные не заперты в одном движке. Это называется *decoupled compute*. \n",
|
||||||
|
"\n",
|
||||||
|
"**Как устроен этот ноутбук:** Spark-код и Trino-запросы выполняются здесь. Для Trino используется Python-клиент `trino`. Если у вас установлен DBeaver (и подключен по инструкции из START_HERE.md) — каждый Trino-запрос можно выполнить и там (в markdown будут подсказки)."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_1_title",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## 1. Spark-сессия, Trino-клиент и проверка таблиц\n",
|
||||||
|
"\n",
|
||||||
|
"Инициализируем Spark и настраиваем helper для отправки запросов в Trino."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_1_code",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"import pandas as pd\n",
|
||||||
|
"from pyspark.sql import SparkSession\n",
|
||||||
|
"from trino.dbapi import connect\n",
|
||||||
|
"import trino\n",
|
||||||
|
"\n",
|
||||||
|
"CATALOG_NAME = \"lakehouse\"\n",
|
||||||
|
"BRONZE_TABLE = f\"{CATALOG_NAME}.bronze.nyc_taxi_yellow\"\n",
|
||||||
|
"SILVER_TABLE = f\"{CATALOG_NAME}.silver.nyc_taxi_yellow\"\n",
|
||||||
|
"\n",
|
||||||
|
"spark = SparkSession.builder \\\n",
|
||||||
|
" .appName(\"module-06-spark-and-trino\") \\\n",
|
||||||
|
" .getOrCreate()\n",
|
||||||
|
"\n",
|
||||||
|
"spark.sparkContext.setLogLevel(\"ERROR\")\n",
|
||||||
|
"\n",
|
||||||
|
"assert spark.catalog.tableExists(BRONZE_TABLE), f\"Таблица {BRONZE_TABLE} не найдена (пройди Модуль 4).\"\n",
|
||||||
|
"assert spark.catalog.tableExists(SILVER_TABLE), f\"Таблица {SILVER_TABLE} не найдена (пройди Модуль 5).\"\n",
|
||||||
|
"\n",
|
||||||
|
"def trino_query(sql: str) -> pd.DataFrame:\n",
|
||||||
|
" \"\"\"Выполняет SQL-запрос в Trino и возвращает результат как Pandas DataFrame.\"\"\"\n",
|
||||||
|
" conn = connect(\n",
|
||||||
|
" host=\"trino\",\n",
|
||||||
|
" port=8080,\n",
|
||||||
|
" user=\"jupyter\",\n",
|
||||||
|
" catalog=\"lakehouse\"\n",
|
||||||
|
" )\n",
|
||||||
|
" cur = conn.cursor()\n",
|
||||||
|
" try:\n",
|
||||||
|
" cur.execute(sql)\n",
|
||||||
|
" rows = cur.fetchall()\n",
|
||||||
|
" columns = [desc[0] for desc in cur.description]\n",
|
||||||
|
" return pd.DataFrame(rows, columns=columns)\n",
|
||||||
|
" except trino.exceptions.ProgrammingError as e:\n",
|
||||||
|
" if \"No nodes available to run query\" in str(e):\n",
|
||||||
|
" print(\"Ошибка: Trino ещё не готов. Подождите пару минут после старта контейнеров.\")\n",
|
||||||
|
" raise\n",
|
||||||
|
" # Запросы типа CREATE/DROP не возвращают строк\n",
|
||||||
|
" return pd.DataFrame([{\"status\": \"Success\"}])\n",
|
||||||
|
" finally:\n",
|
||||||
|
" conn.close()\n",
|
||||||
|
"\n",
|
||||||
|
"print(\"Spark готов.\")\n",
|
||||||
|
"print(f\"Версия Trino клиента: {trino.__version__}\")\n",
|
||||||
|
"print(\"Trino-клиент готов. Проверка...\")\n",
|
||||||
|
"display(trino_query(\"SELECT 1 AS test_col\"))"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_1_note",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"*Если ячейка выше упала с ImportError: trino — нужно пересобрать образ Jupyter (в терминале: `docker compose build jupyter && docker compose up -d`).*\n",
|
||||||
|
"\n",
|
||||||
|
"Обе таблицы на месте. Spark может их читать. Trino-клиент готов. Вопрос: может ли Trino прочитать те же таблицы?"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_2_title",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## 2. Читаем silver через Spark — фиксируем метрики\n",
|
||||||
|
"\n",
|
||||||
|
"Сначала получим числа из Spark. Потом сравним с Trino."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_2_code",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"spark_metrics = spark.sql(f\"\"\"\n",
|
||||||
|
" SELECT \n",
|
||||||
|
" count(*) AS row_count, \n",
|
||||||
|
" avg(fare_amount) AS avg_fare, \n",
|
||||||
|
" avg(trip_distance) AS avg_distance, \n",
|
||||||
|
" count(DISTINCT pickup_borough) AS boroughs \n",
|
||||||
|
" FROM {SILVER_TABLE}\n",
|
||||||
|
"\"\"\").toPandas()\n",
|
||||||
|
"\n",
|
||||||
|
"display(spark_metrics)\n",
|
||||||
|
"\n",
|
||||||
|
"spark.table(SILVER_TABLE).select(\"tpep_pickup_datetime\", \"fare_amount\", \"pickup_borough\").show(5)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_2_note",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Запомни эти числа. Сейчас выполним тот же запрос через Trino."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_3_title",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## 3. Навигация по каталогу через Trino\n",
|
||||||
|
"\n",
|
||||||
|
"*DBeaver: тот же запрос можно выполнить в DBeaver, если он подключён к Trino по инструкции из START_HERE.md.*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_3_schemas",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"trino_query(\"SHOW SCHEMAS FROM lakehouse\")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_3_tables",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"trino_query(\"SHOW TABLES FROM lakehouse.silver\")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_3_describe",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"trino_query(\"DESCRIBE lakehouse.silver.nyc_taxi_yellow\")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_3_note",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Trino видит те же namespace (bronze, silver, default) и ту же таблицу с не менее чем 24 колонками. Различия в нотации типов (varchar vs STRING, double vs DOUBLE) нормальны — это разница синтаксиса движков, а не данных."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_4_title",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## 4. «Aha-момент» — одни и те же данные\n",
|
||||||
|
"\n",
|
||||||
|
"Центральный момент модуля. Trino читает таблицу, записанную Spark в Модуле 5.\n",
|
||||||
|
"\n",
|
||||||
|
"*DBeaver: `SELECT * FROM lakehouse.silver.nyc_taxi_yellow LIMIT 10;`*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_4_select",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"trino_query(\"SELECT tpep_pickup_datetime, fare_amount, pickup_borough FROM lakehouse.silver.nyc_taxi_yellow LIMIT 5\")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_4_metrics",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"trino_metrics = trino_query(\"\"\"\n",
|
||||||
|
" SELECT \n",
|
||||||
|
" count(*) AS row_count, \n",
|
||||||
|
" avg(fare_amount) AS avg_fare, \n",
|
||||||
|
" avg(trip_distance) AS avg_distance, \n",
|
||||||
|
" count(DISTINCT pickup_borough) AS boroughs \n",
|
||||||
|
" FROM lakehouse.silver.nyc_taxi_yellow\n",
|
||||||
|
"\"\"\")\n",
|
||||||
|
"\n",
|
||||||
|
"print(\"Метрики Trino:\")\n",
|
||||||
|
"display(trino_metrics)\n",
|
||||||
|
"\n",
|
||||||
|
"print(\"Метрики Spark (для сравнения):\")\n",
|
||||||
|
"display(spark_metrics)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_4_note",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Строки совпадают. Оба движка читают одни и те же data files из MinIO (с точностью до floating-point округления в функции `avg`)."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_5_title",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## 5. Почему это работает — роль общего каталога\n",
|
||||||
|
"\n",
|
||||||
|
"**Общий каталог:** оба движка подключены к одному инстансу PostgreSQL (`postgres-iceberg:5432/iceberg`). Когда Spark создаёт таблицу, он записывает ссылку на неё в PostgreSQL. Trino читает тот же PostgreSQL и находит эту ссылку.\n",
|
||||||
|
"\n",
|
||||||
|
"**Общее хранилище:** data files (Parquet) и metadata-файлы лежат в MinIO (`s3://lakehouse/warehouse`). Оба движка имеют сетевой доступ к бакету и могут прочитать файлы.\n",
|
||||||
|
"\n",
|
||||||
|
"**Параллель с Greenplum:** В классическом DWH данные лежат внутри СУБД на её собственных дисках. Доступ возможен только через саму СУБД. В Lakehouse данные лежат снаружи, а движки взаимозаменяемы.\n",
|
||||||
|
"\n",
|
||||||
|
"**Decoupled compute:** Вычислительные ресурсы отделены от хранения. Если нам потребуется добавить третий движок (например, Apache Flink для streaming), нужно будет просто подключить его к тому же каталогу и хранилищу.\n",
|
||||||
|
"\n",
|
||||||
|
"```\n",
|
||||||
|
" ┌─────────────────┐ ┌──────────────────┐\n",
|
||||||
|
" │ Spark (Jupyter)│ │ Trino (DBeaver) │\n",
|
||||||
|
" └────────┬────────┘ └────────┬─────────┘\n",
|
||||||
|
" │ │\n",
|
||||||
|
" ▼ ▼\n",
|
||||||
|
" ┌──────────────────────────────────────────┐\n",
|
||||||
|
" │ PostgreSQL (JDBC catalog) │\n",
|
||||||
|
" │ metadata_location -> s3://... │\n",
|
||||||
|
" └────────────────────┬─────────────────────┘\n",
|
||||||
|
" ▼\n",
|
||||||
|
" ┌──────────────────────────────────────────┐\n",
|
||||||
|
" │ MinIO (S3-compatible storage) │\n",
|
||||||
|
" │ metadata/ + data/ (Parquet files) │\n",
|
||||||
|
" └──────────────────────────────────────────┘\n",
|
||||||
|
"```"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_6_title",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## 6. Spark записывает — Trino читает (live write → read)\n",
|
||||||
|
"\n",
|
||||||
|
"До этого мы читали таблицы, созданные в предыдущих модулях. Теперь — живой цикл: Spark создаёт таблицу прямо сейчас, и Trino её тут же видит. \n",
|
||||||
|
"\n",
|
||||||
|
"Создаём агрегатную таблицу `lakehouse.default.borough_summary` через Spark:"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_6_write",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"spark.sql(\"\"\"\n",
|
||||||
|
" CREATE OR REPLACE TABLE lakehouse.default.borough_summary\n",
|
||||||
|
" USING iceberg AS\n",
|
||||||
|
" SELECT pickup_borough,\n",
|
||||||
|
" count(*) AS trips,\n",
|
||||||
|
" avg(fare_amount) AS avg_fare,\n",
|
||||||
|
" avg(trip_distance) AS avg_distance\n",
|
||||||
|
" FROM lakehouse.silver.nyc_taxi_yellow\n",
|
||||||
|
" WHERE pickup_borough IS NOT NULL\n",
|
||||||
|
" GROUP BY pickup_borough\n",
|
||||||
|
"\"\"\")\n",
|
||||||
|
"\n",
|
||||||
|
"spark.table(\"lakehouse.default.borough_summary\").show()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_6_note_1",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Spark записал таблицу. Мы не делали никакой синхронизации. Видит ли её Trino?\n",
|
||||||
|
"\n",
|
||||||
|
"*DBeaver: `SELECT * FROM lakehouse.default.borough_summary ORDER BY trips DESC;`*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_6_read",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"trino_query(\"SELECT * FROM lakehouse.default.borough_summary ORDER BY trips DESC\")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_6_note_2",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Trino видит таблицу мгновенно. Spark записал metadata в PostgreSQL и data files в MinIO. Trino прочитал тот же каталог — увидел таблицу. Никакого копирования, никакой синхронизации.\n",
|
||||||
|
"\n",
|
||||||
|
"Это и есть decoupled compute: один движок создал, другой прочитал. В Greenplum для этого пришлось бы либо подключиться к тому же движку, либо скопировать данные."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_7_title",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## 7. Аналитические запросы в Trino\n",
|
||||||
|
"\n",
|
||||||
|
"Trino как аналитический SQL-движок. Выполним несколько аналитических запросов.\n",
|
||||||
|
"\n",
|
||||||
|
"*DBeaver: скопируй запрос ниже без обёртки trino_query().*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_7_query1",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"trino_query(\"\"\"\n",
|
||||||
|
" SELECT \n",
|
||||||
|
" pickup_borough, \n",
|
||||||
|
" count(*) AS trips, \n",
|
||||||
|
" avg(fare_amount) AS avg_fare, \n",
|
||||||
|
" avg(trip_distance) AS avg_distance, \n",
|
||||||
|
" avg(tip_amount) AS avg_tip \n",
|
||||||
|
" FROM lakehouse.silver.nyc_taxi_yellow \n",
|
||||||
|
" WHERE pickup_borough IS NOT NULL \n",
|
||||||
|
" GROUP BY pickup_borough \n",
|
||||||
|
" ORDER BY trips DESC\n",
|
||||||
|
"\"\"\")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_7_note_1",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Топ зон посадки:"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_7_query2_trino",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"trino_top_zones = trino_query(\"\"\"\n",
|
||||||
|
" SELECT \n",
|
||||||
|
" pickup_zone, \n",
|
||||||
|
" count(*) AS trips, \n",
|
||||||
|
" avg(total_amount) AS avg_total \n",
|
||||||
|
" FROM lakehouse.silver.nyc_taxi_yellow \n",
|
||||||
|
" WHERE pickup_zone IS NOT NULL \n",
|
||||||
|
" GROUP BY pickup_zone \n",
|
||||||
|
" ORDER BY trips DESC \n",
|
||||||
|
" LIMIT 10\n",
|
||||||
|
"\"\"\")\n",
|
||||||
|
"display(trino_top_zones)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_7_query2_spark",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"spark_top_zones = spark.sql(\"\"\"\n",
|
||||||
|
" SELECT \n",
|
||||||
|
" pickup_zone, \n",
|
||||||
|
" count(*) AS trips, \n",
|
||||||
|
" avg(total_amount) AS avg_total \n",
|
||||||
|
" FROM lakehouse.silver.nyc_taxi_yellow \n",
|
||||||
|
" WHERE pickup_zone IS NOT NULL \n",
|
||||||
|
" GROUP BY pickup_zone \n",
|
||||||
|
" ORDER BY trips DESC \n",
|
||||||
|
" LIMIT 10\n",
|
||||||
|
"\"\"\").toPandas()\n",
|
||||||
|
"\n",
|
||||||
|
"display(spark_top_zones)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_7_note_2",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Результаты совпадают (с точностью до floating-point). Два движка, одна таблица."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_8_title",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## 8. Навигация по bronze из Trino\n",
|
||||||
|
"\n",
|
||||||
|
"До этого bronze читали только из Spark. Проверим через Trino."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_8_code1",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"trino_query(\"SHOW TABLES FROM lakehouse.bronze\")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_8_code2",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"trino_query(\"SELECT count(*) AS row_count FROM lakehouse.bronze.nyc_taxi_yellow\")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_8_code3",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"trino_query(\"SELECT * FROM lakehouse.bronze.nyc_taxi_yellow LIMIT 5\")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_8_note",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Trino видит все таблицы из всех namespace каталога. Каталог — общий, хранилище — общее. \n",
|
||||||
|
"\n",
|
||||||
|
"*DBeaver: те же запросы работают аналогично.*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_9",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## 9. DBeaver — SQL-клиент для Trino (рекомендация)\n",
|
||||||
|
"\n",
|
||||||
|
"В этом ноутбуке мы работали с Trino через Python-клиент — для воспроизводимости. В реальной аналитической работе Trino чаще используют через SQL-клиенты: DBeaver, DataGrip, DbVisualizer. DBeaver — де-факто стандарт для SQL, знакомый всем, кто работал с PostgreSQL/Greenplum.\n",
|
||||||
|
"\n",
|
||||||
|
"Если DBeaver установлен и подключён (инструкция в START_HERE.md), попробуйте выполнить в нём любой запрос из этого модуля. Результат будет тот же — тот же протокол, тот же движок, та же таблица. Разница: DBeaver — для интерактивной SQL-работы, Python-клиент — для автоматизации и ноутбуков."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_10_title",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## 10. Самостоятельное задание\n",
|
||||||
|
"\n",
|
||||||
|
"Четыре задачи. Центральная — самостоятельный цикл write → read, как в Секции 6.\n",
|
||||||
|
"\n",
|
||||||
|
"**Задача 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).\n",
|
||||||
|
"\n",
|
||||||
|
"**Задача 2 (Trino → read).** Прочитай `lakehouse.default.zone_summary` через Trino (Python-клиент или DBeaver). Убедись, что Trino видит таблицу без синхронизации.\n",
|
||||||
|
"\n",
|
||||||
|
"**Задача 3 (сравнение).** Выполни тот же агрегатный запрос напрямую по silver через Trino (без промежуточной таблицы). Сравни результат с `zone_summary`. Числа должны совпасть.\n",
|
||||||
|
"\n",
|
||||||
|
"**Задача 4 (ответ).** Ответь в markdown-ячейке ниже: почему Trino увидел `zone_summary` сразу после создания через Spark? Какие три компонента стенда это обеспечивают? Что произошло бы, если бы у Trino был другой каталог?"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_10_task1",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Ваш код (Spark): CREATE TABLE zone_summary\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_10_task2",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Ваш код (Trino): чтение zone_summary\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_10_task3",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Ваш код (Trino): тот же агрегат напрямую по silver\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_10_task4",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Ваш ответ на Задачу 4: ...\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_10_cleanup",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"spark.sql(\"DROP TABLE IF EXISTS lakehouse.default.zone_summary\")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_10_optional",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Дополнительная задача (по желанию): аналитический запрос по bronze через Trino и через Spark, сравнение."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_10_opt_trino",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Ваш код (Trino): аналитический запрос по bronze\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_10_opt_spark",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Ваш код (Spark): тот же запрос по bronze\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_11",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## 11. Что мы НЕ сравниваем\n",
|
||||||
|
"\n",
|
||||||
|
"Ограничение модуля: мы не сравниваем Spark и Trino как движки. Не обсуждаем: какой быстрее, какой лучше, каковы их внутренние различия оптимизаторов. \n",
|
||||||
|
"\n",
|
||||||
|
"Цель — показать, что оба работают с одной таблицей через общий каталог. Это фундаментальное свойство архитектуры Lakehouse, а не конкретного движка."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_12",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## 12. Checkpoint\n",
|
||||||
|
"\n",
|
||||||
|
"Проверь себя:\n",
|
||||||
|
"1. Почему Trino видит таблицы, созданные Spark, без синхронизации?\n",
|
||||||
|
"2. Какие два компонента стенда общие для Spark и Trino?\n",
|
||||||
|
"3. Чем доступ к данным в Lakehouse отличается от Greenplum?\n",
|
||||||
|
"4. Нужно ли копировать данные, чтобы Trino прочитал таблицу Spark?\n",
|
||||||
|
"5. Что такое «decoupled compute» в одном предложении?\n",
|
||||||
|
"6. Если добавить третий движок (Flink), что нужно сделать, чтобы он увидел те же таблицы?\n",
|
||||||
|
"7. Что произошло, когда Spark создал `borough_summary`, а Trino её тут же прочитал?"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "section_13",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## 13. Завершение\n",
|
||||||
|
"\n",
|
||||||
|
"Удаляем демо-таблицу, она больше не нужна. Таблицы `bronze` и `silver` остаются для следующих модулей.\n",
|
||||||
|
"\n",
|
||||||
|
"В Модуле 7 — schema evolution и time travel. В Модуле 8 — финальная практика."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "section_13_stop",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"spark.sql(\"DROP TABLE IF EXISTS lakehouse.default.borough_summary\")\n",
|
||||||
|
"spark.stop()"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Python 3 (ipykernel)",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python3"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.10.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
||||||
+1
-1
@@ -18,6 +18,6 @@
|
|||||||
| 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 |
|
| 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 |
|
| 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) | Ready for validation | `notebooks/05_silver_layer.ipynb` | 2026-03-07 |
|
| 5. Слой silver и воспроизводимые трансформации | [module-05-silver-layer.md](./module-05-silver-layer.md) | Ready for validation | `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 |
|
| 6. Одна таблица, два движка: Spark и Trino | [module-06-spark-and-trino-on-same-table.md](./module-06-spark-and-trino-on-same-table.md) | Ready for validation | `jupyter/Dockerfile`, `START_HERE.md`, `docs/stack_reference.md`, `notebooks/06_spark_and_trino_on_same_table.ipynb` | 2026-03-07 |
|
||||||
| 7. Безопасная работа с таблицами: schema evolution и time travel | [module-07-safe-table-changes.md](./module-07-safe-table-changes.md) | Draft | `notebooks/07_safe_table_changes.ipynb` | 2026-03-07 |
|
| 7. Безопасная работа с таблицами: schema evolution и time travel | [module-07-safe-table-changes.md](./module-07-safe-table-changes.md) | Draft | `notebooks/07_safe_table_changes.ipynb` | 2026-03-07 |
|
||||||
| 8. Базовое обслуживание таблиц и финальная практика | [module-08-maintenance-and-final-lab.md](./module-08-maintenance-and-final-lab.md) | Draft | `notebooks/08_maintenance_and_final_lab.ipynb` | 2026-03-07 |
|
| 8. Базовое обслуживание таблиц и финальная практика | [module-08-maintenance-and-final-lab.md](./module-08-maintenance-and-final-lab.md) | Draft | `notebooks/08_maintenance_and_final_lab.ipynb` | 2026-03-07 |
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Модуль 6. Одна таблица, два движка: Spark и Trino
|
# Модуль 6. Одна таблица, два движка: Spark и Trino
|
||||||
|
|
||||||
**Статус:** `Draft`
|
**Статус:** `Ready for validation`
|
||||||
**Последнее обновление:** `2026-03-07`
|
**Последнее обновление:** `2026-03-07`
|
||||||
|
|
||||||
## Цель
|
## Цель
|
||||||
|
|||||||
Reference in New Issue
Block a user