первый commit
This commit is contained in:
Executable
+23
@@ -0,0 +1,23 @@
|
||||
.ipynb_checkpoints
|
||||
__pycache__/
|
||||
|
||||
# Python bytecode / build artifacts
|
||||
*.py[cod]
|
||||
*.pyo
|
||||
*.pyd
|
||||
*.egg-info/
|
||||
*.egg
|
||||
|
||||
# Virtual environments
|
||||
.venv/
|
||||
venv/
|
||||
|
||||
# Editor / IDE settings
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# Local config
|
||||
.env
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
@@ -0,0 +1,56 @@
|
||||
# Repository Guidelines
|
||||
|
||||
This repository contains a small teaching Lakehouse stack (Spark + Trino + Iceberg + MinIO) intended for demos and mentoring.
|
||||
|
||||
## Project Structure & Module Organization
|
||||
|
||||
- `docker-compose.yml`: orchestrates Spark, Trino, MinIO, PostgreSQL, and Jupyter.
|
||||
- `spark/`: Spark image (`Dockerfile`) and `spark-defaults.conf`.
|
||||
- `jupyter/`: Jupyter image (`Dockerfile`) based on the Spark image.
|
||||
- `trino/`: catalog config, e.g. `trino/catalog/lakehouse.properties`.
|
||||
- `src/`: PySpark and SQL examples split by engine (`src/spark/...`, `src/trino/...`).
|
||||
- `notebooks/`: demo notebooks (mounted into Jupyter at `/opt/work`).
|
||||
|
||||
Keep new examples in `src/` or `notebooks/`, and avoid mixing configuration and code.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
|
||||
Run from the repo root:
|
||||
|
||||
- `docker compose build`: build custom Spark and Jupyter images.
|
||||
- `docker compose up -d`: start the full stack in the background.
|
||||
- `docker compose ps`: check container status.
|
||||
- `docker compose down -v`: stop the stack and remove volumes (for a clean slate).
|
||||
|
||||
Use `docker compose logs -f <service>` when debugging (`spark-master`, `trino`, `minio`, etc.).
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
|
||||
- Python: PEP 8, 4-space indentation, snake_case for functions, lower_snake_case for files (e.g. `spark_join_demo.py`).
|
||||
- SQL: uppercase keywords, `schema.table` naming, short English identifiers; comments may be in Russian.
|
||||
- Compose/Docker: service names kebab-case (`spark-master`), env vars UPPER_SNAKE_CASE.
|
||||
- Prefer small, focused examples; reuse helpers from `src/` in notebooks where possible.
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
There is no formal automated test suite yet. Validate changes by:
|
||||
|
||||
- building and starting the stack, then
|
||||
- running `src/spark/cluster_smoke.py` or the SQL in `src/spark/iceberg_demo.sql`,
|
||||
- opening `notebooks/spark-basic-test.ipynb` in Jupyter and checking it runs end-to-end.
|
||||
|
||||
If you add tests, prefer `pytest` under `tests/` and mark slow, integration-heavy tests clearly.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
|
||||
- Commits: short, descriptive subject in Russian or English, present tense (e.g. `Спарк запускается, создаётся тестовая таблица`, `Add Iceberg join demo`).
|
||||
- Keep changes small and focused; update `README.md` when behavior, ports, or images change.
|
||||
- PRs should describe the problem, the solution, and any impact on local setup; include screenshots (Trino UI, MinIO, Jupyter) when UI changes are relevant.
|
||||
|
||||
## Agent-Specific Instructions
|
||||
|
||||
When editing as an automated agent:
|
||||
|
||||
- Respect this file and avoid large refactors without an explicit request.
|
||||
- Preserve Russian user-facing text unless the change is explicitly about translation.
|
||||
- Prefer minimal changes that keep the demo simple and robust for newcomers.
|
||||
@@ -0,0 +1,298 @@
|
||||
# HOWTO: Учебные лабораторные по Lakehouse-стенду
|
||||
|
||||
Этот файл описывает пошаговые учебные сценарии (лабораторные работы), которые можно выполнять поверх стенда из `docker-compose.yml`.
|
||||
|
||||
Перед началом работ смотри разделы «Сборка и запуск» и «Доступ к сервисам» в `README.md` — там описано, как поднять стенд и на каких портах доступны Trino, MinIO, Spark и Jupyter.
|
||||
|
||||
Лабораторки опираются на примеры в `src/`:
|
||||
|
||||
- `src/spark/cluster_smoke.py` — проверка, что Spark‑кластер жив.
|
||||
- `src/spark/iceberg_demo.sql` — первая Iceberg‑таблица в Spark.
|
||||
- `src/spark/iceberg_smoke.py` — Spark создаёт Iceberg‑таблицу и читает её.
|
||||
- `src/trino/iceberg_smoke.sql` — Trino читает таблицу, созданную в Spark.
|
||||
|
||||
Все команды ниже выполняются из корня репозитория.
|
||||
|
||||
Краткая карта лабораторных:
|
||||
|
||||
- **Лаба 0** — стенд поднят, Spark‑кластер жив.
|
||||
- **Лаба 1** — первая Iceberg‑таблица в Spark.
|
||||
- **Лаба 2** — общий каталог Spark ↔ Trino.
|
||||
- **Лаба 3** — партиционирование и эволюция схемы.
|
||||
- **Лаба 4** — мини‑ETL поверх Lakehouse (эскиз).
|
||||
|
||||
---
|
||||
|
||||
## Лаба 0. Стенд поднят, Spark‑кластер жив
|
||||
|
||||
**Цель**
|
||||
|
||||
- Убедиться, что все контейнеры поднялись.
|
||||
- Проверить, что Spark‑кластер (master + workers) работает.
|
||||
|
||||
**Предусловия**
|
||||
|
||||
- Установлены Docker и Docker Compose.
|
||||
- Репозиторий склонирован локально.
|
||||
|
||||
**Шаги**
|
||||
|
||||
1. Собрать и поднять стенд:
|
||||
|
||||
```bash
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
2. Проверить статус контейнеров:
|
||||
|
||||
```bash
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
Ожидаем, что `spark-master`, `spark-worker-1`, `spark-worker-2` в статусе `Up`.
|
||||
|
||||
3. Запустить smoke‑скрипт кластера из контейнера `spark-master`:
|
||||
|
||||
```bash
|
||||
docker compose exec spark-master \
|
||||
/opt/spark/bin/spark-submit /opt/src/spark/cluster_smoke.py
|
||||
```
|
||||
|
||||
4. Открыть Web UI Spark:
|
||||
|
||||
- `http://localhost:8080` — мастер.
|
||||
- `http://localhost:8081` и `http://localhost:8082` — воркеры.
|
||||
|
||||
**Ожидаемый результат**
|
||||
|
||||
- `cluster_smoke.py` выполняется без ошибок.
|
||||
- В Web UI видно приложение, прошедшее через кластер.
|
||||
|
||||
---
|
||||
|
||||
## Лаба 1. Первая Iceberg‑таблица из Spark
|
||||
|
||||
**Цель**
|
||||
|
||||
- Создать Iceberg‑таблицу с помощью Spark SQL.
|
||||
- Посмотреть файлы таблицы в MinIO (`warehouse/default/demo_tbl/...`).
|
||||
|
||||
**Предусловия**
|
||||
|
||||
- Стенд запущен (лаба 0 выполнена).
|
||||
|
||||
**Шаги**
|
||||
|
||||
1. Подключиться к Spark SQL в контейнере:
|
||||
|
||||
```bash
|
||||
docker compose exec -it spark-master \
|
||||
/opt/spark/bin/spark-sql
|
||||
```
|
||||
|
||||
2. Выполнить учебный SQL‑скрипт:
|
||||
|
||||
- Внутри интерактивной сессии `spark-sql`:
|
||||
|
||||
```sql
|
||||
:r /opt/src/spark/iceberg_demo.sql
|
||||
```
|
||||
|
||||
- Либо одним вызовом (без интерактивного режима):
|
||||
|
||||
```bash
|
||||
docker compose exec spark-master \
|
||||
/opt/spark/bin/spark-sql -f /opt/src/spark/iceberg_demo.sql
|
||||
```
|
||||
|
||||
3. Зайти в MinIO Console:
|
||||
|
||||
- Адрес: `http://localhost:9001`
|
||||
- Учётные данные по умолчанию: `minioadmin / minioadmin` (см. `docker-compose.yml`).
|
||||
|
||||
4. Найти файлы таблицы:
|
||||
|
||||
- Бакет `lakehouse`.
|
||||
- Префикс `warehouse/default/demo_tbl/`.
|
||||
- Обратить внимание на структуру Iceberg: каталоги `metadata/`, `data/` и т.д.
|
||||
|
||||
**Ожидаемый результат**
|
||||
|
||||
- В Spark запрос
|
||||
|
||||
```sql
|
||||
SELECT * FROM lakehouse.default.demo_tbl;
|
||||
```
|
||||
|
||||
возвращает данные.
|
||||
|
||||
- В MinIO видна структура Iceberg‑таблицы: служебные файлы и файлы данных.
|
||||
|
||||
---
|
||||
|
||||
## Лаба 2. Общий каталог Spark ↔ Trino
|
||||
|
||||
**Цель**
|
||||
|
||||
- Показать, что Spark и Trino используют общий Iceberg‑каталог (метаданные в Postgres, данные в MinIO).
|
||||
- Создать таблицу из Spark и прочитать её через Trino.
|
||||
|
||||
**Предусловия**
|
||||
|
||||
- Стенд запущен.
|
||||
- Лаба 1 не обязательна, но полезна для понимания структуры файлов.
|
||||
|
||||
**Шаги**
|
||||
|
||||
1. Создать таблицу и записать данные из Spark (PySpark‑скрипт):
|
||||
|
||||
```bash
|
||||
docker compose exec spark-master \
|
||||
/opt/spark/bin/spark-submit /opt/src/spark/iceberg_smoke.py
|
||||
```
|
||||
|
||||
2. Убедиться в Spark, что таблица существует:
|
||||
|
||||
```bash
|
||||
docker compose exec -it spark-master \
|
||||
/opt/spark/bin/spark-sql
|
||||
```
|
||||
|
||||
В интерактивной сессии:
|
||||
|
||||
```sql
|
||||
USE lakehouse.default;
|
||||
SHOW TABLES;
|
||||
SELECT * FROM spark_trino_smoke;
|
||||
```
|
||||
|
||||
3. Прочитать ту же таблицу из Trino:
|
||||
|
||||
- Вариант через заранее скопированный SQL‑файл (как в README):
|
||||
|
||||
```bash
|
||||
docker compose cp src/trino/iceberg_smoke.sql trino:/tmp/
|
||||
docker compose exec trino trino --file /tmp/iceberg_smoke.sql
|
||||
```
|
||||
|
||||
- Либо интерактивно внутри Trino CLI:
|
||||
|
||||
```bash
|
||||
docker compose exec -it trino trino --catalog lakehouse
|
||||
```
|
||||
|
||||
Внутри CLI:
|
||||
|
||||
```sql
|
||||
USE lakehouse.default;
|
||||
SHOW TABLES;
|
||||
SELECT * FROM spark_trino_smoke;
|
||||
```
|
||||
|
||||
**Ожидаемый результат**
|
||||
|
||||
- Таблица `spark_trino_smoke` видна и в Spark, и в Trino.
|
||||
- Данные совпадают (например, строка `1, from_spark`).
|
||||
|
||||
---
|
||||
|
||||
## Лаба 3. Партиционирование и эволюция схемы
|
||||
|
||||
**Цель**
|
||||
|
||||
- Показать, как Iceberg работает с партиционированием (фильтрация по partition key, уменьшение объёма чтения).
|
||||
- Показать, как Iceberg поддерживает эволюцию схемы без сложных миграций.
|
||||
|
||||
**Предусловия**
|
||||
|
||||
- Стенд запущен.
|
||||
- Желательно выполнить Лабы 1–2, чтобы уже была интуиция про Iceberg и общий каталог.
|
||||
|
||||
**Подготовленные примеры**
|
||||
|
||||
- `src/spark/partitioned_table_demo.sql` — создание партиционированной Iceberg‑таблицы и вставка данных.
|
||||
- `src/spark/schema_evolution_demo.sql` — демонстрация `ALTER TABLE` и добавления колонок.
|
||||
- `src/trino/schema_evolution_demo.sql` — чтение той же таблицы с эволюцией схемы из Trino.
|
||||
- `notebooks/03_partitioning_and_schema_evolution.ipynb` — интерактивный разбор тех же примеров в Jupyter.
|
||||
|
||||
**Вариант A: через Spark SQL и Trino CLI**
|
||||
|
||||
1. Создать партиционированную таблицу в Spark:
|
||||
|
||||
```bash
|
||||
docker compose exec spark-master \
|
||||
/opt/spark/bin/spark-sql -f /opt/src/spark/partitioned_table_demo.sql
|
||||
```
|
||||
|
||||
2. Проверить в Spark, какие данные записаны по датам:
|
||||
|
||||
```bash
|
||||
docker compose exec -it spark-master \
|
||||
/opt/spark/bin/spark-sql
|
||||
```
|
||||
|
||||
Внутри интерактивной сессии:
|
||||
|
||||
```sql
|
||||
USE lakehouse.default;
|
||||
SELECT event_date, COUNT(*) AS cnt, SUM(amount) AS total_amount
|
||||
FROM partition_demo
|
||||
GROUP BY event_date
|
||||
ORDER BY event_date;
|
||||
```
|
||||
|
||||
3. Подготовить таблицу с эволюцией схемы:
|
||||
|
||||
```bash
|
||||
docker compose exec spark-master \
|
||||
/opt/spark/bin/spark-sql -f /opt/src/spark/schema_evolution_demo.sql
|
||||
```
|
||||
|
||||
4. Посмотреть схему и данные в Spark:
|
||||
|
||||
```bash
|
||||
docker compose exec -it spark-master \
|
||||
/opt/spark/bin/spark-sql
|
||||
```
|
||||
|
||||
Внутри:
|
||||
|
||||
```sql
|
||||
USE lakehouse.default;
|
||||
DESCRIBE TABLE schema_evolution_demo;
|
||||
SELECT * FROM schema_evolution_demo ORDER BY id;
|
||||
```
|
||||
|
||||
5. Прочитать таблицу с эволюцией схемы из Trino:
|
||||
|
||||
```bash
|
||||
docker compose cp src/trino/schema_evolution_demo.sql trino:/tmp/
|
||||
docker compose exec trino trino --file /tmp/schema_evolution_demo.sql
|
||||
```
|
||||
|
||||
**Вариант B: через Jupyter‑ноутбук**
|
||||
|
||||
- Открыть `http://localhost:8888` и запустить ноутбук `03_partitioning_and_schema_evolution.ipynb`.
|
||||
- Ноутбук:
|
||||
- создаёт SparkSession, подключённый к кластеру;
|
||||
- выполняет скрипты `partitioned_table_demo.sql` и `schema_evolution_demo.sql`;
|
||||
- показывает агрегаты по партициям и данные до/после эволюции схемы в интерактивном виде.
|
||||
|
||||
---
|
||||
|
||||
## Лаба 4. Мини‑ETL поверх Lakehouse (эскиз)
|
||||
|
||||
Идея лабы — собрать end-to-end сценарий:
|
||||
|
||||
- есть сырые данные (CSV/JSON) в S3/MinIO;
|
||||
- Spark читает raw‑данные, чистит и пишет в Iceberg‑таблицу;
|
||||
- Trino делает поверх неё аналитику.
|
||||
|
||||
Планируемые компоненты:
|
||||
|
||||
- Папка с примерами сырых данных (например, `examples/raw/` в репозитории, затем загрузка в MinIO).
|
||||
- `src/spark/etl_raw_to_iceberg.py` — мини ETL, записывающий данные в `lakehouse.default.events` или аналогичную таблицу.
|
||||
- `src/trino/etl_analytics.sql` — несколько аналитических запросов поверх этой таблицы.
|
||||
|
||||
Детали реализации можно развивать по мере появления новых сценариев.
|
||||
@@ -0,0 +1,15 @@
|
||||
Creative Commons Attribution 4.0 International (CC BY 4.0)
|
||||
|
||||
This work is licensed under the Creative Commons Attribution 4.0 International License.
|
||||
|
||||
You are free to:
|
||||
- Share — copy and redistribute the material in any medium or format.
|
||||
- Adapt — remix, transform, and build upon the material for any purpose, even commercially.
|
||||
|
||||
Under the following terms:
|
||||
- Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made.
|
||||
- No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
|
||||
|
||||
The full text of the license is available at:
|
||||
https://creativecommons.org/licenses/by/4.0/legalcode
|
||||
|
||||
@@ -0,0 +1,390 @@
|
||||
# Учебный Lakehouse-стенд (Trino + Spark + Iceberg + MinIO)
|
||||
|
||||
Учебный стенд для демонстрации **Lakehouse-архитектуры** на одном ноутбуке:
|
||||
|
||||
- объектное хранилище S3-класса (MinIO),
|
||||
- движок запросов Trino,
|
||||
- Spark для batch/ETL и интерактивных экспериментов,
|
||||
- формат таблиц Iceberg,
|
||||
- JDBC-каталог (PostgreSQL) для метаданных Iceberg в Trino,
|
||||
- конфиг Spark, заточенный под работу с Iceberg + S3.
|
||||
|
||||
Стенд ориентирован на обучение менти и быструю демонстрацию концепции Lakehouse: разделение **storage / compute / catalog** без лишней обвязки (Hive Metastore, полноценный Hadoop-кластер и т.п.).
|
||||
|
||||
---
|
||||
|
||||
## Архитектура
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph Storage
|
||||
M[(MinIO<br/>S3-compatible)]
|
||||
end
|
||||
|
||||
subgraph Catalog
|
||||
P[(PostgreSQL<br/>Iceberg JDBC catalog)]
|
||||
end
|
||||
|
||||
subgraph Compute
|
||||
S[Spark 3.5.1<br/>PySpark / SQL]
|
||||
T[Trino 478<br/>Iceberg connector]
|
||||
end
|
||||
|
||||
S <--> M
|
||||
T <--> M
|
||||
T <--> P
|
||||
```
|
||||
|
||||
**Основные идеи:**
|
||||
|
||||
* **Данные** (Parquet-файлы + служебные каталоги Iceberg) лежат в бакете MinIO (`s3a://lakehouse/warehouse/...`).
|
||||
* **Метаданные** Iceberg хранятся в PostgreSQL (JDBC-каталог `lakehouse`).
|
||||
* **Spark и Trino** используют один и тот же JDBC-каталог: таблицы, созданные в Spark, доступны в Trino, и наоборот.
|
||||
|
||||
---
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
1. Установить Docker и Docker Compose (см. раздел «Требования» ниже).
|
||||
2. В корне репозитория выполнить:
|
||||
|
||||
```bash
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
3. Открыть основные интерфейсы:
|
||||
|
||||
- Trino Web UI: `http://localhost:8090`
|
||||
- MinIO Console: `http://localhost:9001`
|
||||
- JupyterLab (если включён): `http://localhost:8888`
|
||||
|
||||
4. Для пошаговых лабораторных работ см. файл `HOWTO.md`.
|
||||
|
||||
---
|
||||
|
||||
## Состав репозитория
|
||||
|
||||
* `docker-compose.yml` — описание всех сервисов стенда:
|
||||
|
||||
* Spark master / worker(ы) на базе кастомного образа `spark-iceberg`,
|
||||
* Trino,
|
||||
* MinIO (S3-совместимое хранилище),
|
||||
* PostgreSQL под Iceberg JDBC-каталог,
|
||||
* (опционально) Jupyter / notebook для PySpark.
|
||||
* `spark/Dockerfile` — сборка кастомного образа **Spark** с зависимостями:
|
||||
|
||||
* `hadoop-aws`, `aws-java-sdk`,
|
||||
* библиотека Iceberg нужной версии,
|
||||
* `pyspark`, `pyarrow` и базовый набор инструментов.
|
||||
* `jupyter/Dockerfile` — образ JupyterLab на базе Spark-образа.
|
||||
* `spark/spark-defaults.conf` — конфигурация Spark для работы с:
|
||||
|
||||
* Iceberg-каталогом `lakehouse` (тип `jdbc`, метаданные в Postgres),
|
||||
* MinIO через `s3a://`,
|
||||
* расширениями `IcebergSparkSessionExtensions`.
|
||||
* `src/` — учебные примеры для Spark и Trino:
|
||||
|
||||
* `src/spark/cluster_smoke.py` — проверка, что кластер жив (Spark master/worker).
|
||||
* `src/spark/iceberg_smoke.py` — Spark создаёт Iceberg-таблицу и читает её.
|
||||
* `src/spark/iceberg_demo.sql` — пример создания Iceberg-таблицы через Spark SQL.
|
||||
* `src/trino/iceberg_smoke.sql` — Trino читает таблицу, созданную в Spark.
|
||||
* `trino/catalog/lakehouse.properties` — конфиг каталога Trino `lakehouse`:
|
||||
|
||||
* коннектор `iceberg`,
|
||||
* `jdbc`-каталог (PostgreSQL),
|
||||
* доступ к MinIO как к S3-хранилищу.
|
||||
|
||||
---
|
||||
|
||||
## Требования
|
||||
|
||||
- Docker и Docker Compose.
|
||||
- Порты по умолчанию должны быть свободны (см. таблицу «Сервисы и порты» ниже).
|
||||
|
||||
### Сервисы и порты
|
||||
|
||||
| Сервис | Контейнер | Порт (host → container) | Назначение / UI |
|
||||
|------------------------|-------------------|-------------------------|-------------------------------------|
|
||||
| Trino | `trino` | `8090 → 8080` | Web UI Trino |
|
||||
| MinIO API | `minio` | `9000 → 9000` | S3 endpoint |
|
||||
| MinIO Console | `minio` | `9001 → 9001` | Веб-консоль MinIO |
|
||||
| PostgreSQL (каталог) | `postgres-iceberg`| `5432 → 5432` | Доступ для psql/DBeaver и т.п. |
|
||||
| Spark master UI | `spark-master` | `8080 → 8080` | Web UI мастера Spark |
|
||||
| Spark worker-1 UI | `spark-worker-1` | `8081 → 8081` | Web UI первого воркера |
|
||||
| Spark worker-2 UI | `spark-worker-2` | `8082 → 8081` | Web UI второго воркера (host 8082) |
|
||||
| JupyterLab | `jupyter` | `8888 → 8888` | JupyterLab с PySpark |
|
||||
|
||||
---
|
||||
|
||||
## Сборка и запуск
|
||||
|
||||
Все команды в этом разделе выполняются из корня репозитория.
|
||||
|
||||
### 1. Собрать образы
|
||||
|
||||
```bash
|
||||
docker compose build
|
||||
```
|
||||
|
||||
Будут собраны кастомные образы Spark/Jupyter с зависимостями Iceberg, S3A, JDBC-драйвером Postgres и Python-библиотеками.
|
||||
|
||||
### 2. Поднять стенд
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Что происходит при старте:
|
||||
|
||||
* MinIO поднимается с root-пользователем/паролем (по умолчанию смотри в `docker-compose.yml`, обычно `minioadmin/minioadmin`), init-контейнер создаёт бакет `lakehouse`.
|
||||
* PostgreSQL под каталог Iceberg создаёт БД и пользователя (значения — в `docker-compose.yml` / `.env`).
|
||||
* Trino стартует с каталогом `lakehouse`, описанным в `lakehouse.properties`.
|
||||
* Spark master/worker получают конфиг из `spark-defaults.conf` (общий JDBC-каталог Iceberg + MinIO).
|
||||
|
||||
Проверить статус:
|
||||
|
||||
```bash
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
### 3. Остановить стенд и очистить данные
|
||||
|
||||
Чтобы остановить все сервисы и удалить данные в MinIO/PostgreSQL (Docker volumes), можно выполнить:
|
||||
|
||||
```bash
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Доступ к сервисам
|
||||
|
||||
### Trino
|
||||
|
||||
Web UI (координатор):
|
||||
|
||||
```text
|
||||
http://localhost:8090
|
||||
```
|
||||
|
||||
(точный порт см. в `docker-compose.yml`).
|
||||
|
||||
Подключение **из контейнера trino**:
|
||||
|
||||
```bash
|
||||
docker exec -it trino trino \
|
||||
--server http://localhost:8080 \
|
||||
--catalog lakehouse
|
||||
```
|
||||
|
||||
Проверка:
|
||||
|
||||
```sql
|
||||
SHOW CATALOGS;
|
||||
SHOW SCHEMAS FROM lakehouse;
|
||||
```
|
||||
|
||||
### MinIO
|
||||
|
||||
* Консоль: `http://localhost:9001`
|
||||
* S3 endpoint: `http://localhost:9000`
|
||||
|
||||
Учётные данные — `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD` из `docker-compose.yml` или `.env`.
|
||||
|
||||
Создай в MinIO бакет `lakehouse` (если его нет) — данные Iceberg будут храниться именно там.
|
||||
|
||||
### PostgreSQL (каталог Iceberg для Trino)
|
||||
|
||||
Подключение (пример):
|
||||
|
||||
```bash
|
||||
psql -h localhost -p 5432 -U iceberg -d iceberg
|
||||
```
|
||||
|
||||
(имя пользователя, БД и порт уточняются в `docker-compose.yml`).
|
||||
|
||||
Служебные таблицы Iceberg JDBC-каталога (`iceberg_tables`, `iceberg_namespace_properties`) создаёт однократный контейнер `iceberg-catalog-init`. Если база уже запускалась без них, можно переинициализировать вручную:
|
||||
|
||||
```bash
|
||||
docker compose run --rm iceberg-catalog-init
|
||||
```
|
||||
|
||||
### Jupyter / notebooks
|
||||
|
||||
Веб-интерфейс: `http://localhost:8888` (по умолчанию без токена).
|
||||
|
||||
* В контейнере монтируется `./notebooks` в `/opt/work`.
|
||||
* `./src` доступен read-only в `/opt/src`, переменная `PYTHONPATH=/opt/src` уже установлена — можно импортировать функции из скриптов прямо в ноутбуках.
|
||||
|
||||
---
|
||||
|
||||
## Конфигурация Trino (каталог `lakehouse`)
|
||||
|
||||
Файл `lakehouse.properties` монтируется в `/etc/trino/catalog/lakehouse.properties`.
|
||||
|
||||
Ключевые параметры:
|
||||
|
||||
```properties
|
||||
connector.name=iceberg
|
||||
|
||||
# Каталог Iceberg типа JDBC (метаданные в PostgreSQL)
|
||||
iceberg.catalog.type=jdbc
|
||||
iceberg.jdbc-catalog.catalog-name=lakehouse
|
||||
iceberg.jdbc-catalog.driver-class=org.postgresql.Driver
|
||||
iceberg.jdbc-catalog.connection-url=jdbc:postgresql://postgres-iceberg:5432/iceberg
|
||||
iceberg.jdbc-catalog.connection-user=iceberg
|
||||
iceberg.jdbc-catalog.connection-password=iceberg
|
||||
iceberg.jdbc-catalog.schema-version=V1
|
||||
|
||||
# Хранилище файлов Iceberg – S3 (MinIO) через hadoop-клиент
|
||||
iceberg.file-system.type=hadoop
|
||||
fs.native-s3.enabled=true
|
||||
|
||||
s3.endpoint=http://minio:9000
|
||||
s3.region=us-east-1
|
||||
s3.path-style-access=true
|
||||
s3.aws-access-key=minioadmin
|
||||
s3.aws-secret-key=minioadmin
|
||||
```
|
||||
|
||||
**Что это даёт:**
|
||||
|
||||
* Trino хранит *метаданные* Iceberg в PostgreSQL (таблицы каталога, снапшоты, манифесты и т.п.).
|
||||
* *Файлы данных* лежат в MinIO, в бакете `lakehouse`, к которому Trino ходит по `s3://`/`s3a://` через S3-клиент.
|
||||
|
||||
Пример создания схемы и таблицы из Trino:
|
||||
|
||||
```sql
|
||||
-- Схема в каталоге lakehouse (метаданные в PostgreSQL)
|
||||
CREATE SCHEMA lakehouse.default;
|
||||
|
||||
-- Таблица Iceberg с данными в s3://lakehouse/default/test_table/
|
||||
CREATE TABLE lakehouse.default.test_table (
|
||||
id bigint,
|
||||
name varchar
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Конфигурация Spark (spark-defaults.conf)
|
||||
|
||||
`spark-defaults.conf` монтируется в `/opt/spark/conf/spark-defaults.conf` в контейнеры Spark.
|
||||
|
||||
Ключевые моменты:
|
||||
|
||||
```properties
|
||||
# Iceberg Spark extensions
|
||||
spark.sql.extensions=org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions
|
||||
|
||||
# Каталог Iceberg для Spark (JDBC, метаданные в Postgres)
|
||||
spark.sql.catalog.lakehouse=org.apache.iceberg.spark.SparkCatalog
|
||||
spark.sql.catalog.lakehouse.catalog-impl=org.apache.iceberg.jdbc.JdbcCatalog
|
||||
spark.sql.catalog.lakehouse.uri=jdbc:postgresql://postgres-iceberg:5432/iceberg
|
||||
spark.sql.catalog.lakehouse.jdbc.user=iceberg
|
||||
spark.sql.catalog.lakehouse.jdbc.password=iceberg
|
||||
spark.sql.catalog.lakehouse.jdbc.driver=org.postgresql.Driver
|
||||
spark.sql.catalog.lakehouse.warehouse=s3a://lakehouse/warehouse
|
||||
spark.sql.catalog.lakehouse.default-namespace=default
|
||||
|
||||
# S3/MinIO через s3a
|
||||
spark.hadoop.fs.s3a.endpoint=http://minio:9000
|
||||
spark.hadoop.fs.s3a.access.key=minioadmin
|
||||
spark.hadoop.fs.s3a.secret.key=minioadmin
|
||||
spark.hadoop.fs.s3a.path.style.access=true
|
||||
spark.hadoop.fs.s3a.impl=org.apache.hadoop.fs.s3a.S3AFileSystem
|
||||
spark.hadoop.fs.s3a.connection.ssl.enabled=false
|
||||
```
|
||||
|
||||
**Важно:** Spark и Trino используют единый JDBC-каталог `lakehouse`: метаданные лежат в Postgres, данные — в MinIO. Таблица, созданная в Spark, видна в Trino без дополнительной настройки.
|
||||
|
||||
Пример создания таблицы из Spark:
|
||||
|
||||
```python
|
||||
from pyspark.sql import SparkSession
|
||||
|
||||
spark = (SparkSession.builder
|
||||
.appName("lakehouse-demo")
|
||||
.getOrCreate())
|
||||
|
||||
# Каталог lakehouse указан явно
|
||||
spark.sql("""
|
||||
CREATE TABLE lakehouse.default.spark_table (
|
||||
id BIGINT,
|
||||
name STRING
|
||||
)
|
||||
USING iceberg
|
||||
""")
|
||||
|
||||
spark.sql("INSERT INTO lakehouse.default.spark_table VALUES (1, 'Alice'), (2, 'Bob')")
|
||||
```
|
||||
|
||||
Файлы окажутся в `s3a://lakehouse/warehouse/default/spark_table/`.
|
||||
|
||||
---
|
||||
|
||||
## Типовой учебный сценарий
|
||||
|
||||
1. **Поднять стенд** (`docker compose build`, затем `docker compose up -d`).
|
||||
2. **Создать бакет `lakehouse`** в MinIO Console (если ещё нет).
|
||||
3. **Создать таблицу из Spark**, записать туда данные, показать дерево файлов Iceberg в MinIO (data/manifest/metadata).
|
||||
4. **Прочитать ту же таблицу из Trino** (проверка общего каталога).
|
||||
5. **Создать таблицу из Trino** и прочитать её из Spark.
|
||||
6. Обсудить архитектуру: Postgres хранит метаданные Iceberg, MinIO — данные, Spark/Trino — compute.
|
||||
|
||||
---
|
||||
|
||||
## Smoke-тесты Spark → Trino
|
||||
|
||||
Быстрая проверка, что таблица, созданная в Spark, читается в Trino через общий JDBC-каталог.
|
||||
|
||||
1. Убедиться, что стенд запущен: `docker compose up -d`.
|
||||
2. Скопировать скрипты внутрь контейнеров:
|
||||
|
||||
```bash
|
||||
docker compose cp src/spark/iceberg_smoke.py spark-master:/tmp/
|
||||
docker compose cp src/trino/iceberg_smoke.sql trino:/tmp/
|
||||
```
|
||||
|
||||
3. Выполнить smoke из Spark:
|
||||
|
||||
```bash
|
||||
docker compose exec spark-master /opt/spark/bin/spark-submit /tmp/iceberg_smoke.py
|
||||
```
|
||||
|
||||
Скрипт создаст `lakehouse.default.spark_trino_smoke`, вставит строку `1, from_spark` и прочитает её.
|
||||
|
||||
4. Прочитать ту же таблицу из Trino:
|
||||
|
||||
```bash
|
||||
docker compose exec trino trino --file /tmp/iceberg_smoke.sql
|
||||
```
|
||||
|
||||
В выводе должны быть строки `spark_trino_smoke` в списке таблиц и `1, from_spark` в результате выборки.
|
||||
|
||||
5. Очистка (опционально):
|
||||
|
||||
```bash
|
||||
docker compose exec spark-master /opt/spark/bin/spark-sql -e "DROP TABLE IF EXISTS lakehouse.default.spark_trino_smoke"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Дальнейшее развитие
|
||||
|
||||
Планируемые/возможные расширения:
|
||||
|
||||
* Подключить **Airflow** и запускать Spark-job’ы поверх этого же Lakehouse.
|
||||
* Вынести настройки (`MINIO_ROOT_USER`, `ICEBERG_*`, `POSTGRES_*`) в `.env` с шаблоном для студентов.
|
||||
* Добавить отдельные каталоги Trino (например, `hive`, `tpch`) для демонстрации федеративных запросов.
|
||||
* Добавить пример интеграции с BI-инструментом (DBeaver/Metabase/Superset) поверх Trino.
|
||||
|
||||
Для пошаговых учебных сценариев (лабораторных работ) см. файл `HOWTO.md`.
|
||||
|
||||
---
|
||||
|
||||
## Лицензия
|
||||
|
||||
Материалы этого репозитория лицензированы на условиях Creative Commons Attribution 4.0 International (CC BY 4.0).
|
||||
См. файл `LICENSE` или <https://creativecommons.org/licenses/by/4.0/>.
|
||||
Executable
+209
@@ -0,0 +1,209 @@
|
||||
# version: "3.9"
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
container_name: postgres-iceberg
|
||||
environment:
|
||||
POSTGRES_DB: iceberg # БД под Iceberg catalog
|
||||
POSTGRES_USER: iceberg
|
||||
POSTGRES_PASSWORD: iceberg
|
||||
ports:
|
||||
- "5432:5432" # опционально, чтобы заходить с хоста через DBeaver/psql
|
||||
networks:
|
||||
- spark-net
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U iceberg -d iceberg || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
iceberg-catalog-init:
|
||||
image: postgres:16
|
||||
container_name: iceberg-catalog-init
|
||||
depends_on:
|
||||
- postgres
|
||||
networks:
|
||||
- spark-net
|
||||
environment:
|
||||
- PGPASSWORD=iceberg
|
||||
entrypoint: |
|
||||
/bin/sh -c "set -e
|
||||
echo 'Waiting for Postgres (iceberg catalog)...'
|
||||
until pg_isready -h postgres-iceberg -U iceberg -d iceberg; do
|
||||
sleep 1
|
||||
done
|
||||
cat <<'SQL' | psql -h postgres-iceberg -U iceberg -d iceberg
|
||||
CREATE TABLE IF NOT EXISTS iceberg_tables (
|
||||
catalog_name VARCHAR(255) NOT NULL,
|
||||
table_namespace VARCHAR(255) NOT NULL,
|
||||
table_name VARCHAR(255) NOT NULL,
|
||||
metadata_location VARCHAR(1000),
|
||||
previous_metadata_location VARCHAR(1000),
|
||||
iceberg_type VARCHAR(5),
|
||||
PRIMARY KEY (catalog_name, table_namespace, table_name)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS iceberg_namespace_properties (
|
||||
catalog_name VARCHAR(255) NOT NULL,
|
||||
namespace VARCHAR(255) NOT NULL,
|
||||
property_key VARCHAR(255),
|
||||
property_value VARCHAR(1000),
|
||||
PRIMARY KEY (catalog_name, namespace, property_key)
|
||||
);
|
||||
SQL
|
||||
echo 'Iceberg JDBC catalog tables are ready'
|
||||
"
|
||||
|
||||
spark-master:
|
||||
build:
|
||||
context: ./spark
|
||||
image: de-spark-iceberg:3.5.1
|
||||
container_name: spark-master
|
||||
hostname: spark-master
|
||||
depends_on:
|
||||
- postgres
|
||||
networks:
|
||||
- spark-net
|
||||
environment:
|
||||
- SPARK_NO_DAEMONIZE=true
|
||||
- AWS_ACCESS_KEY_ID=minioadmin
|
||||
- AWS_SECRET_ACCESS_KEY=minioadmin
|
||||
command: ["/opt/spark/sbin/start-master.sh"]
|
||||
ports:
|
||||
- "7077:7077" # Spark master
|
||||
- "8080:8080" # Web UI мастера
|
||||
volumes:
|
||||
- ./spark/spark-defaults.conf:/opt/spark/conf/spark-defaults.conf:ro
|
||||
|
||||
spark-worker-1:
|
||||
image: de-spark-iceberg:3.5.1
|
||||
# build:
|
||||
# context: ./spark
|
||||
container_name: spark-worker-1
|
||||
hostname: spark-worker-1
|
||||
networks:
|
||||
- spark-net
|
||||
environment:
|
||||
- SPARK_NO_DAEMONIZE=true
|
||||
- SPARK_WORKER_CORES=2
|
||||
- SPARK_WORKER_MEMORY=2g
|
||||
- AWS_ACCESS_KEY_ID=minioadmin
|
||||
- AWS_SECRET_ACCESS_KEY=minioadmin
|
||||
command: ["/opt/spark/sbin/start-worker.sh", "spark://spark-master:7077"]
|
||||
depends_on:
|
||||
- spark-master
|
||||
ports:
|
||||
- "8081:8081" # Web UI первого воркера
|
||||
volumes:
|
||||
- ./spark/spark-defaults.conf:/opt/spark/conf/spark-defaults.conf:ro
|
||||
|
||||
spark-worker-2:
|
||||
image: de-spark-iceberg:3.5.1
|
||||
# build:
|
||||
# context: ./spark
|
||||
container_name: spark-worker-2
|
||||
hostname: spark-worker-2
|
||||
networks:
|
||||
- spark-net
|
||||
environment:
|
||||
- SPARK_NO_DAEMONIZE=true
|
||||
- SPARK_WORKER_CORES=2
|
||||
- SPARK_WORKER_MEMORY=2g
|
||||
- AWS_ACCESS_KEY_ID=minioadmin
|
||||
- AWS_SECRET_ACCESS_KEY=minioadmin
|
||||
command: ["/opt/spark/sbin/start-worker.sh", "spark://spark-master:7077"]
|
||||
depends_on:
|
||||
- spark-master
|
||||
ports:
|
||||
- "8082:8081" # Web UI второго воркера (host 8082)
|
||||
volumes:
|
||||
- ./spark/spark-defaults.conf:/opt/spark/conf/spark-defaults.conf:ro
|
||||
|
||||
minio:
|
||||
image: minio/minio:RELEASE.2024-10-02T17-50-41Z
|
||||
container_name: minio
|
||||
hostname: minio
|
||||
networks:
|
||||
- spark-net
|
||||
command: server /data --console-address ":9001"
|
||||
environment:
|
||||
- MINIO_ROOT_USER=minioadmin
|
||||
- MINIO_ROOT_PASSWORD=minioadmin
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9001:9001"
|
||||
volumes:
|
||||
- minio-data:/data
|
||||
|
||||
minio-init:
|
||||
image: minio/mc:RELEASE.2024-06-24T19-40-33Z-cpuv1
|
||||
container_name: minio-init
|
||||
hostname: minio-init
|
||||
networks:
|
||||
- spark-net
|
||||
depends_on:
|
||||
- minio
|
||||
entrypoint: >
|
||||
/bin/sh -c "
|
||||
echo 'Waiting for MinIO...';
|
||||
until mc alias set minio http://minio:9000 minioadmin minioadmin 2>/dev/null; do
|
||||
sleep 1;
|
||||
done;
|
||||
echo 'MinIO is up, creating bucket...';
|
||||
mc mb -p minio/lakehouse || true;
|
||||
echo 'Bucket lakehouse is ready';
|
||||
exit 0;
|
||||
"
|
||||
|
||||
|
||||
jupyter:
|
||||
build:
|
||||
context: ./jupyter
|
||||
args:
|
||||
NB_UID: ${NB_UID:-1000}
|
||||
NB_GID: ${NB_GID:-100}
|
||||
# image: jupyter-spark-iceberg:3.5.1
|
||||
container_name: jupyter
|
||||
hostname: jupyter
|
||||
depends_on:
|
||||
- spark-master
|
||||
- minio
|
||||
networks:
|
||||
- spark-net
|
||||
environment:
|
||||
- AWS_ACCESS_KEY_ID=minioadmin
|
||||
- AWS_SECRET_ACCESS_KEY=minioadmin
|
||||
- PYTHONPATH=/opt/src
|
||||
ports:
|
||||
- "8888:8888"
|
||||
volumes:
|
||||
- ./spark/spark-defaults.conf:/opt/spark/conf/spark-defaults.conf:ro
|
||||
- ./notebooks:/opt/work
|
||||
- ./src:/opt/src:ro
|
||||
|
||||
trino:
|
||||
image: trinodb/trino:478
|
||||
container_name: trino
|
||||
hostname: trino
|
||||
networks:
|
||||
- spark-net
|
||||
depends_on:
|
||||
- minio
|
||||
- postgres
|
||||
- iceberg-catalog-init
|
||||
environment:
|
||||
- AWS_ACCESS_KEY_ID=minioadmin
|
||||
- AWS_SECRET_ACCESS_KEY=minioadmin
|
||||
- AWS_REGION=us-east-1
|
||||
ports:
|
||||
- "8090:8080" # Web UI Trino
|
||||
volumes:
|
||||
- ./trino/catalog:/etc/trino/catalog:ro
|
||||
|
||||
networks:
|
||||
spark-net:
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
minio-data:
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
# Берем за основу контейнер, приготовленный для spark в spark/Dockerfile
|
||||
FROM de-spark-iceberg:3.5.1
|
||||
|
||||
USER root
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Только JupyterLab, без дополнительных пакетов
|
||||
RUN pip3 install --no-cache-dir "jupyterlab==4.2.5"
|
||||
|
||||
# Создаём непривилегированного пользователя
|
||||
ARG NB_USER=jovyan
|
||||
ARG NB_UID=1000
|
||||
|
||||
RUN useradd --uid ${NB_UID} -m -s /bin/bash ${NB_USER} \
|
||||
&& mkdir -p /opt/work \
|
||||
&& chown -R ${NB_UID}:${NB_UID} /opt/work
|
||||
|
||||
USER ${NB_UID}
|
||||
WORKDIR /opt/work
|
||||
EXPOSE 8888
|
||||
|
||||
CMD ["jupyter", "lab", \
|
||||
"--ip=0.0.0.0", \
|
||||
"--port=8888", \
|
||||
"--no-browser", \
|
||||
"--NotebookApp.token=", \
|
||||
"--NotebookApp.password="]
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Лаба 3: партиционирование и эволюция схемы",
|
||||
"",
|
||||
"В этом ноутбуке мы посмотрим, как Iceberg работает с партиционированными таблицами и эволюцией схемы поверх общего каталога `lakehouse`.",
|
||||
"",
|
||||
"Перед началом убедись, что стенд запущен (`docker compose up -d`) и Spark-кластер доступен."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from pyspark.sql import SparkSession",
|
||||
"",
|
||||
"spark = (",
|
||||
" SparkSession.builder",
|
||||
" .appName(\"lab3-partitioning-schema-evolution\")",
|
||||
" .master(\"spark://spark-master:7077\")",
|
||||
" .getOrCreate()",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Часть 1. Партиционированная таблица",
|
||||
"",
|
||||
"Для начала создадим партиционированную Iceberg-таблицу `lakehouse.default.partition_demo` с помощью готового SQL-скрипта из `src/spark/partitioned_table_demo.sql`."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from pathlib import Path",
|
||||
"",
|
||||
"sql_path = Path(\"/opt/src/spark/partitioned_table_demo.sql\")",
|
||||
"sql_text = sql_path.read_text(encoding=\"utf-8\")",
|
||||
"",
|
||||
"for statement in sql_text.split(\";\"):",
|
||||
" stmt = statement.strip()",
|
||||
" if stmt:",
|
||||
" spark.sql(stmt)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Посмотрим, какие данные записаны по датам, и обсудим партиционирование по `event_date`."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"spark.sql(\"\"\"",
|
||||
"SELECT",
|
||||
" event_date,",
|
||||
" COUNT(*) AS cnt,",
|
||||
" SUM(amount) AS total_amount",
|
||||
"FROM lakehouse.default.partition_demo",
|
||||
"GROUP BY event_date",
|
||||
"ORDER BY event_date",
|
||||
"\"\"\").show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"spark.sql(\"\"\"",
|
||||
"SELECT *",
|
||||
"FROM lakehouse.default.partition_demo",
|
||||
"WHERE event_date = DATE '2024-01-01'",
|
||||
"ORDER BY user_id",
|
||||
"\"\"\").show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Часть 2. Эволюция схемы",
|
||||
"",
|
||||
"Теперь посмотрим на эволюцию схемы: создадим таблицу, добавим колонку и вставим новые строки с дополнительными данными.",
|
||||
"",
|
||||
"Для подготовки таблицы используем скрипт `src/spark/schema_evolution_demo.sql`."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sql_path = Path(\"/opt/src/spark/schema_evolution_demo.sql\")",
|
||||
"sql_text = sql_path.read_text(encoding=\"utf-8\")",
|
||||
"",
|
||||
"for statement in sql_text.split(\";\"):",
|
||||
" stmt = statement.strip()",
|
||||
" if stmt:",
|
||||
" spark.sql(stmt)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"spark.sql(\"DESCRIBE TABLE lakehouse.default.schema_evolution_demo\").show(truncate=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"spark.sql(\"\"\"",
|
||||
"SELECT *",
|
||||
"FROM lakehouse.default.schema_evolution_demo",
|
||||
"ORDER BY id",
|
||||
"\"\"\").show(truncate=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Обрати внимание, что старые строки имеют `NULL` в колонке `metadata`, а новые — заполненное значение.",
|
||||
"Iceberg хранит историю снапшотов и позволяет эволюцию схемы без сложных миграций."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "ca5e5822-18cb-405e-90f3-a0f2f8ad2260",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Setting default log level to \"WARN\".\n",
|
||||
"To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).\n",
|
||||
"25/12/03 09:08:48 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable\n",
|
||||
"25/12/03 09:08:52 WARN MetricsConfig: Cannot locate configuration: tried hadoop-metrics2-s3a-file-system.properties,hadoop-metrics2.properties\n",
|
||||
" \r"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"+---+---+\n",
|
||||
"| id|txt|\n",
|
||||
"+---+---+\n",
|
||||
"| 1| ok|\n",
|
||||
"+---+---+\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from pyspark.sql import SparkSession\n",
|
||||
"\n",
|
||||
"spark = (\n",
|
||||
" SparkSession.builder\n",
|
||||
" .appName(\"check\")\n",
|
||||
" .master(\"spark://spark-master:7077\")\n",
|
||||
" .getOrCreate()\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"spark.sql(\"\"\"\n",
|
||||
" CREATE TABLE IF NOT EXISTS lakehouse.default.demo_fix (\n",
|
||||
" id BIGINT,\n",
|
||||
" txt STRING\n",
|
||||
" )\n",
|
||||
" USING iceberg\n",
|
||||
"\"\"\")\n",
|
||||
"\n",
|
||||
"spark.sql(\"INSERT INTO lakehouse.default.demo_fix VALUES (1, 'ok')\")\n",
|
||||
"\n",
|
||||
"spark.sql(\"SELECT * FROM lakehouse.default.demo_fix\").show()\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "1e125075-feab-4974-baa2-f787e41b46fd",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"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
|
||||
}
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
FROM spark:3.5.1-scala2.12-java17-python3-ubuntu
|
||||
|
||||
USER root
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Версии зависимостей в одном месте
|
||||
ARG HADOOP_AWS_VERSION=3.3.4
|
||||
ARG AWS_SDK_VERSION=1.12.262
|
||||
ARG ICEBERG_VERSION=1.10.0
|
||||
ARG POSTGRES_VERSION=42.7.3
|
||||
ARG PYSPARK_VERSION=3.5.1
|
||||
|
||||
# Базовый Python/утилиты
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y python3-pip curl wget nano mc tmux\
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# JAR'ы для S3A и Iceberg
|
||||
RUN mkdir -p /opt/spark/jars \
|
||||
&& curl -L "https://repo1.maven.org/maven2/org/apache/hadoop/hadoop-aws/${HADOOP_AWS_VERSION}/hadoop-aws-${HADOOP_AWS_VERSION}.jar" \
|
||||
-o "/opt/spark/jars/hadoop-aws-${HADOOP_AWS_VERSION}.jar" \
|
||||
&& curl -L "https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-bundle/${AWS_SDK_VERSION}/aws-java-sdk-bundle-${AWS_SDK_VERSION}.jar" \
|
||||
-o "/opt/spark/jars/aws-java-sdk-bundle-${AWS_SDK_VERSION}.jar" \
|
||||
&& curl -L "https://repo1.maven.org/maven2/org/apache/iceberg/iceberg-spark-runtime-3.5_2.12/${ICEBERG_VERSION}/iceberg-spark-runtime-3.5_2.12-${ICEBERG_VERSION}.jar" \
|
||||
-o "/opt/spark/jars/iceberg-spark-runtime-3.5_2.12-${ICEBERG_VERSION}.jar" \
|
||||
&& curl -L "https://repo1.maven.org/maven2/org/postgresql/postgresql/${POSTGRES_VERSION}/postgresql-${POSTGRES_VERSION}.jar" \
|
||||
-o "/opt/spark/jars/postgresql-${POSTGRES_VERSION}.jar"
|
||||
|
||||
# Общие Python-пакеты для драйвера и executors
|
||||
RUN pip3 install --no-cache-dir \
|
||||
"pyspark==${PYSPARK_VERSION}" \
|
||||
pandas \
|
||||
numpy \
|
||||
pyarrow
|
||||
|
||||
ENV PYSPARK_PYTHON=python3
|
||||
ENV PYSPARK_DRIVER_PYTHON=python3
|
||||
|
||||
# Конфиг Spark (S3 + Iceberg + каталог lakehouse)
|
||||
RUN mkdir -p /opt/spark/conf
|
||||
# Конфиги подключим уже в docker-compose
|
||||
# COPY spark-defaults.conf /opt/spark/conf/spark-defaults.conf
|
||||
|
||||
# Возвращаемся к непривилегированному UID как в официальном образе
|
||||
# (комментарий отдельно, чтобы не ломать USER)
|
||||
# тот же пользователь, что в официальном spark-образе
|
||||
USER 185
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
# spark/spark-defaults.conf
|
||||
|
||||
# Включаем Iceberg extensions
|
||||
spark.sql.extensions org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions
|
||||
|
||||
# JDBC-каталог "lakehouse" (метаданные в Postgres, данные в MinIO)
|
||||
spark.sql.catalog.lakehouse org.apache.iceberg.spark.SparkCatalog
|
||||
spark.sql.catalog.lakehouse.catalog-impl org.apache.iceberg.jdbc.JdbcCatalog
|
||||
spark.sql.catalog.lakehouse.uri jdbc:postgresql://postgres-iceberg:5432/iceberg
|
||||
spark.sql.catalog.lakehouse.jdbc.user iceberg
|
||||
spark.sql.catalog.lakehouse.jdbc.password iceberg
|
||||
spark.sql.catalog.lakehouse.jdbc.driver org.postgresql.Driver
|
||||
spark.sql.catalog.lakehouse.warehouse s3a://lakehouse/warehouse
|
||||
|
||||
# Настройки S3A для MinIO
|
||||
spark.hadoop.fs.s3a.impl org.apache.hadoop.fs.s3a.S3AFileSystem
|
||||
spark.hadoop.fs.s3a.endpoint http://minio:9000
|
||||
spark.hadoop.fs.s3a.path.style.access true
|
||||
spark.hadoop.fs.s3a.connection.ssl.enabled false
|
||||
|
||||
# Креды MinIO (для учебного стенда ок в явном виде)
|
||||
spark.hadoop.fs.s3a.access.key minioadmin
|
||||
spark.hadoop.fs.s3a.secret.key minioadmin
|
||||
|
||||
# Чуть-чуть логики по умолчанию
|
||||
spark.sql.catalog.lakehouse.default-namespace default
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
from pyspark.sql import SparkSession
|
||||
|
||||
spark = (
|
||||
SparkSession.builder
|
||||
.appName("test-cluster")
|
||||
.master("spark://spark-master:7077") # важно: не local[*]
|
||||
.getOrCreate()
|
||||
)
|
||||
|
||||
spark.range(0, 1000000).groupBy().sum().show()
|
||||
|
||||
spark.stop()
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
-- создаём таблицу в каталоге lakehouse, в MinIO
|
||||
CREATE TABLE lakehouse.default.demo_tbl (
|
||||
id BIGINT,
|
||||
txt STRING
|
||||
)
|
||||
USING iceberg;
|
||||
|
||||
INSERT INTO lakehouse.default.demo_tbl VALUES (1, 'hello'), (2, 'world');
|
||||
|
||||
SELECT * FROM lakehouse.default.demo_tbl;
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
from pyspark.sql import SparkSession
|
||||
|
||||
|
||||
TABLE = "lakehouse.default.spark_trino_smoke"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
spark = SparkSession.builder.appName("iceberg-smoke").getOrCreate()
|
||||
|
||||
spark.sql("CREATE NAMESPACE IF NOT EXISTS lakehouse.default")
|
||||
spark.sql(f"DROP TABLE IF EXISTS {TABLE}")
|
||||
spark.sql(f"CREATE TABLE {TABLE} (id INT, payload STRING) USING iceberg")
|
||||
spark.sql(f"INSERT INTO {TABLE} VALUES (1, 'from_spark')")
|
||||
|
||||
result = spark.sql(f"SELECT * FROM {TABLE} ORDER BY id")
|
||||
result.show(truncate=False)
|
||||
print(f"✔ Spark записал и прочитал таблицу {TABLE}")
|
||||
|
||||
spark.stop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
-- Партиционированная Iceberg-таблица lakehouse.default.partition_demo
|
||||
|
||||
CREATE NAMESPACE IF NOT EXISTS lakehouse.default;
|
||||
|
||||
DROP TABLE IF EXISTS lakehouse.default.partition_demo;
|
||||
|
||||
CREATE TABLE lakehouse.default.partition_demo (
|
||||
user_id BIGINT,
|
||||
event_date DATE,
|
||||
amount DOUBLE
|
||||
)
|
||||
USING iceberg
|
||||
PARTITIONED BY (event_date);
|
||||
|
||||
INSERT INTO lakehouse.default.partition_demo VALUES
|
||||
(1, '2024-01-01', 10.0),
|
||||
(2, '2024-01-01', 20.0),
|
||||
(3, '2024-02-01', 30.0),
|
||||
(4, '2024-02-01', 40.0);
|
||||
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
-- Эволюция схемы Iceberg-таблицы lakehouse.default.schema_evolution_demo
|
||||
|
||||
CREATE NAMESPACE IF NOT EXISTS lakehouse.default;
|
||||
|
||||
DROP TABLE IF EXISTS lakehouse.default.schema_evolution_demo;
|
||||
|
||||
CREATE TABLE lakehouse.default.schema_evolution_demo (
|
||||
id BIGINT,
|
||||
value STRING
|
||||
)
|
||||
USING iceberg;
|
||||
|
||||
INSERT INTO lakehouse.default.schema_evolution_demo VALUES
|
||||
(1, 'old_row_1'),
|
||||
(2, 'old_row_2');
|
||||
|
||||
ALTER TABLE lakehouse.default.schema_evolution_demo
|
||||
ADD COLUMN metadata STRING;
|
||||
|
||||
INSERT INTO lakehouse.default.schema_evolution_demo VALUES
|
||||
(3, 'new_row_1', 'extra_info'),
|
||||
(4, 'new_row_2', 'extra_info');
|
||||
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
-- Проверка чтения данных, созданных в Spark
|
||||
USE lakehouse.default;
|
||||
|
||||
SHOW TABLES;
|
||||
SELECT * FROM spark_trino_smoke;
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
-- Проверка эволюции схемы в Trino для таблицы,
|
||||
-- созданной в Spark (lakehouse.default.schema_evolution_demo).
|
||||
|
||||
USE lakehouse.default;
|
||||
|
||||
SHOW TABLES LIKE 'schema_evolution_demo';
|
||||
|
||||
DESCRIBE schema_evolution_demo;
|
||||
|
||||
SELECT *
|
||||
FROM schema_evolution_demo
|
||||
ORDER BY id;
|
||||
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
connector.name=iceberg
|
||||
|
||||
# 1. Тип каталога — JDBC (метаданные в Postgres)
|
||||
iceberg.catalog.type=jdbc
|
||||
|
||||
# Подключение к Postgres с метаданными Iceberg
|
||||
iceberg.jdbc-catalog.connection-url=jdbc:postgresql://postgres-iceberg:5432/iceberg
|
||||
iceberg.jdbc-catalog.connection-user=iceberg
|
||||
iceberg.jdbc-catalog.connection-password=iceberg
|
||||
|
||||
# Обязательные поля JDBC-каталога
|
||||
iceberg.jdbc-catalog.driver-class=org.postgresql.Driver
|
||||
iceberg.jdbc-catalog.catalog-name=lakehouse
|
||||
iceberg.jdbc-catalog.schema-version=V1
|
||||
|
||||
# 2. Warehouse целиком в S3 (MinIO)
|
||||
# Здесь "lakehouse" — имя S3-бакета, "warehouse" — префикс внутри него.
|
||||
iceberg.jdbc-catalog.default-warehouse-dir=s3://lakehouse/warehouse
|
||||
|
||||
# 3. Включаем новый native S3 filesystem
|
||||
fs.hadoop.enabled=false
|
||||
fs.native-s3.enabled=true
|
||||
|
||||
# 4. Настройки доступа к MinIO
|
||||
s3.endpoint=http://minio:9000
|
||||
s3.region=us-east-1
|
||||
s3.path-style-access=true
|
||||
|
||||
# Статические ключи для MinIO (учебный стенд)
|
||||
s3.aws-access-key=minioadmin
|
||||
s3.aws-secret-key=minioadmin
|
||||
Reference in New Issue
Block a user