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:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user