diff --git a/START_HERE.md b/START_HERE.md index ddb61d6..07b3da5 100644 --- a/START_HERE.md +++ b/START_HERE.md @@ -181,6 +181,14 @@ ls -lh data/nyc_taxi/ Если хочешь расширенный режим, можешь скачать все 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`; diff --git a/docs/stack_reference.md b/docs/stack_reference.md index b5c5154..c022fc2 100644 --- a/docs/stack_reference.md +++ b/docs/stack_reference.md @@ -80,6 +80,11 @@ SHOW SCHEMAS FROM lakehouse; SHOW TABLES FROM lakehouse.default; ``` +**Подключение через DBeaver:** +- Host: `localhost`, Port: `8090`, Catalog: `lakehouse`, User: любая строка, Password: нет. +- Driver: Trino (встроен в DBeaver). +- Проверка: `SHOW SCHEMAS FROM lakehouse;`. + ### MinIO - S3 endpoint: `http://localhost:9000` diff --git a/jupyter/Dockerfile b/jupyter/Dockerfile index 9bddc20..db381b9 100755 --- a/jupyter/Dockerfile +++ b/jupyter/Dockerfile @@ -8,7 +8,8 @@ ENV DEBIAN_FRONTEND=noninteractive RUN pip3 install --no-cache-dir \ "jupyterlab==4.2.5" \ "boto3>=1.35,<2" \ - "psycopg2-binary>=2.9,<3" + "psycopg2-binary>=2.9,<3" \ + "trino>=0.328" # Создаём непривилегированного пользователя ARG NB_USER=jovyan diff --git a/notebooks/06_spark_and_trino_on_same_table.ipynb b/notebooks/06_spark_and_trino_on_same_table.ipynb new file mode 100644 index 0000000..19c917c --- /dev/null +++ b/notebooks/06_spark_and_trino_on_same_table.ipynb @@ -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 +} diff --git a/plans/README.md b/plans/README.md index 2ba2197..7376721 100644 --- a/plans/README.md +++ b/plans/README.md @@ -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 | | 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 | -| 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 | | 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 | diff --git a/plans/module-06-spark-and-trino-on-same-table.md b/plans/module-06-spark-and-trino-on-same-table.md index 7c30898..140ec77 100644 --- a/plans/module-06-spark-and-trino-on-same-table.md +++ b/plans/module-06-spark-and-trino-on-same-table.md @@ -1,6 +1,6 @@ # Модуль 6. Одна таблица, два движка: Spark и Trino -**Статус:** `Draft` +**Статус:** `Ready for validation` **Последнее обновление:** `2026-03-07` ## Цель