Apache Iceberg. Упражнение 1: Первая таблица
Практическое погружение в мир Iceberg
Содержание
Обзор
Пора запачкать руки. В этом упражнении вы развернёте локальное окружение Iceberg, создадите свою первую таблицу и вставите данные.
Ледокол для данных: Если предыдущие уроки были теорией про айсберги, то сейчас мы спускаемся на ледокол и начинаем пробивать путь к реальным данным.
Цели обучения
К концу этого упражнения вы сможете:
- Запустить локальное окружение Iceberg со Spark, REST-каталогом и объектным хранилищем
- Создать пространство имён и таблицу с помощью Spark SQL
- Вставить данные и убедиться, что они корректно записались
- Изучить снапшоты и метаданные таблицы
Предварительные требования
- Docker и Docker Compose установлены
- Терминал
- Около 15 минут свободного времени
Шаг 1: Запуск окружения
Сначала создайте директорию, например iceberg-course-exercises.
Откройте терминал в этой директории и создайте файл docker-compose.yaml со следующим содержимым. Эта конфигурация идентична официальному примеру с сайта Apache Iceberg:
services: spark-iceberg: image: tabulario/spark-iceberg container_name: spark-iceberg build: spark/ networks: iceberg_net: depends_on: - rest - minio volumes: - ./warehouse:/home/iceberg/warehouse - ./notebooks:/home/iceberg/notebooks/notebooks environment: - AWS_ACCESS_KEY_ID=admin - AWS_SECRET_ACCESS_KEY=password - AWS_REGION=us-east-1 ports: - 8888:8888 - 8080:8080 - 10000:10000 - 10001:10001 rest: image: apache/iceberg-rest-fixture:1.10.1 container_name: iceberg-rest networks: iceberg_net: ports: - 8181:8181 environment: - AWS_ACCESS_KEY_ID=admin - AWS_SECRET_ACCESS_KEY=password - AWS_REGION=us-east-1 - CATALOG_WAREHOUSE=s3://warehouse/ - CATALOG_IO__IMPL=org.apache.iceberg.aws.s3.S3FileIO - CATALOG_S3_ENDPOINT=http://minio:9000 minio: image: minio/minio container_name: minio environment: - MINIO_ROOT_USER=admin - MINIO_ROOT_PASSWORD=password - MINIO_DOMAIN=minio networks: iceberg_net: aliases: - warehouse.minio ports: - 9001:9001 - 9000:9000 command: ["server", "/data", "--console-address", ":9001"] mc: depends_on: - minio image: minio/mc container_name: mc networks: iceberg_net: environment: - AWS_ACCESS_KEY_ID=admin - AWS_SECRET_ACCESS_KEY=password - AWS_REGION=us-east-1 entrypoint: | /bin/sh -c " until (/usr/bin/mc alias set minio http://minio:9000 admin password) do echo '...waiting...' && sleep 1; done; /usr/bin/mc rm -r --force minio/warehouse; /usr/bin/mc mb minio/warehouse; /usr/bin/mc policy set public minio/warehouse; tail -f /dev/null "networks: iceberg_net:
Микросервисный зоопарк: Обратите внимание, что у нас здесь целый зоопарк контейнеров — Spark для вычислений, REST-сервис для каталога, MinIO для хранения и даже утилита
mcдля настройки. Как дирижёр оркестра, Docker Compose управляет этим ансамблем.
Далее создайте файл spark-defaults.conf в той же директории со следующим содержимым:
# S3 Support via Maven packagesspark.jars.packages org.apache.hadoop:hadoop-aws:3.3.4 # Hadoop S3A Configuration for MinIOspark.hadoop.fs.s3a.endpoint http://minio:9000spark.hadoop.fs.s3a.access.key adminspark.hadoop.fs.s3a.secret.key passwordspark.hadoop.fs.s3a.path.style.access truespark.hadoop.fs.s3a.impl org.apache.hadoop.fs.s3a.S3AFileSystemspark.hadoop.fs.s3.impl org.apache.hadoop.fs.s3a.S3AFileSystemspark.hadoop.fs.s3a.connection.ssl.enabled false
Теперь запустите всё:
docker compose up -d
Подождите около 30 секунд, пока все сервисы инициализируются. Вы можете проверить, что всё работает:
docker compose ps
Вы должны увидеть четыре запущенных контейнера: iceberg-rest, minio, mc и spark-iceberg.
Шаг 2: Подключение к Spark SQL
Откройте сессию Spark SQL:
docker compose exec -it spark-iceberg spark-sql --conf "spark.hadoop.hive.cli.print.header=true"
Вы должны увидеть приглашение spark-sql>. Давайте проверим, что наш каталог Iceberg доступен:
SHOW CATALOGS;
Вы должны увидеть demo в списке. Для этого упражнения мы будем использовать настроенный REST-каталог Iceberg, к которому Spark обращается напрямую.
Шаг 3: Создание базы данных
В Iceberg таблицы живут внутри пространств имён (namespaces). Давайте создадим одно для наших e-commerce данных:
CREATE NAMESPACE demo.ecommerce;
Проверьте, что оно существует:
SHOW NAMESPACES;
Вы должны увидеть ecommerce в выводе.
Теперь установите его как текущую базу данных:
USE demo.ecommerce;
Шаг 4: Создание таблицы Orders
Теперь главное событие. Мы создадим простую таблицу заказов:
CREATE TABLE orders ( order_id BIGINT, customer_id BIGINT, order_date DATE, total_amount DECIMAL(10, 2), status STRING)USING icebergTBLPROPERTIES ('format-version'='2', 'write.format.default'='parquet');
Несколько важных моментов:
USING icebergуказывает Spark, что это таблица Iceberg- Мы храним данные в файлах Parquet (по умолчанию, но лучше быть явным)
format-version='2'включает операции на уровне строк (UPDATE, DELETE, MERGE)- Пока без партицирования — мы рассмотрим это в следующем упражнении
Проверьте, что таблица создана:
DESCRIBE orders;
Вы должны увидеть что-то вроде:
| col_name | data_type | comment |
|---|---|---|
| order_id | bigint | |
| customer_id | bigint | |
| order_date | date | |
| total_amount | decimal(10,2) | |
| status | string |
Шаг 5: Вставка данных
Давайте добавим несколько заказов. Ничего сложного — просто обычные INSERT-запросы:
INSERT INTO orders VALUES (1001, 42, CAST('2024-01-15' AS DATE), 299.99, 'completed'), (1002, 17, CAST('2024-01-15' AS DATE), 149.50, 'completed'), (1003, 42, CAST('2024-01-16' AS DATE), 89.00, 'pending');
Добавим ещё парочку:
INSERT INTO orders VALUES (1004, 88, CAST('2024-01-16' AS DATE), 1250.00, 'completed'), (1005, 17, CAST('2024-01-17' AS DATE), 45.00, 'cancelled');
Фотографии таблицы: Каждый INSERT создаёт новый снапшот. Представьте, что вы фотографируете таблицу после каждого изменения. Эти «фотографии» позволяют потом путешествовать во времени, но об этом позже.
Шаг 6: Запрос данных
Убедимся, что всё корректно записалось:
SELECT * FROM orders ORDER BY order_id;
Вы должны увидеть все пять заказов:
| order_id | customer_id | order_date | total_amount | status |
|---|---|---|---|---|
| 1001 | 42 | 2024-01-15 | 299.99 | completed |
| 1002 | 17 | 2024-01-15 | 149.50 | completed |
| 1003 | 42 | 2024-01-16 | 89.00 | pending |
| 1004 | 88 | 2024-01-16 | 1250.00 | completed |
| 1005 | 17 | 2024-01-17 | 45.00 | cancelled |
Попробуйте более интересный запрос — общая выручка по клиентам:
SELECT customer_id, COUNT(*) AS order_count, SUM(total_amount) AS total_spentFROM ordersWHERE status = 'completed'GROUP BY customer_idORDER BY total_spent DESC;
Вы должны получить такой результат:
| customer_id | order_count | total_spent |
|---|---|---|
| 88 | 1 | 1250.00 |
| 42 | 1 | 299.99 |
| 17 | 1 | 149.50 |
Шаг 7: Заглянем под капот
Вот где Iceberg становится по-настоящему интересным. Давайте посмотрим на созданные снапшоты, запросив метатаблицу снапшотов:
SELECT snapshot_id, committed_at, operationFROM demo.ecommerce.orders.snapshotsORDER BY committed_at;
Вот что я получил:
| snapshot_id | committed_at | operation |
|---|---|---|
| 472356398657053538 | 2026-01-08 15:27:50.182 | append |
| 3188905721270494811 | 2026-01-08 15:27:55.681 | append |
Вы должны увидеть два снапшота — по одному на каждый INSERT-запрос. Это основа time travel: каждая операция записи создаёт неизменяемый снапшот состояния таблицы.
Давайте также проверим, какие файлы данных были созданы:
SELECT file_path, record_count, file_size_in_bytesFROM demo.ecommerce.orders.files;
Вот результат на моей машине:
| file_path | record_count | file_size_in_bytes |
|---|---|---|
| s3://warehouse/ecommerce/orders/data/00000-3-255d1806-0a53-4703-aaf3-24403d97adf2-0-00001.parquet | 1 | 1601 |
| s3://warehouse/ecommerce/orders/data/00001-4-255d1806-0a53-4703-aaf3-24403d97adf2-0-00001.parquet | 1 | 1601 |
| s3://warehouse/ecommerce/orders/data/00000-0-67dbaf8a-0c02-40e3-9acd-971dcf9c5c34-0-00001.parquet | 1 | 1600 |
| s3://warehouse/ecommerce/orders/data/00001-1-67dbaf8a-0c02-40e3-9acd-971dcf9c5c34-0-00001.parquet | 1 | 1600 |
| s3://warehouse/ecommerce/orders/data/00002-2-67dbaf8a-0c02-40e3-9acd-971dcf9c5c34-0-00001.parquet | 1 | 1587 |
Шкаф с носками: Каждый INSERT создал кучу Parquet-файлов. В production-системе вы бы в конечном итоге сделали компактификацию этих файлов — но это тема для другого упражнения. Пока представьте, что у вас есть шкаф, где каждый носок (запись) лежит в отдельном ящике. å
Шаг 8: Очистка
Когда закончите исследование, выйдите из Spark SQL:
exit;
И остановите окружение:
docker compose down -v
Флаг -v удаляет тома, давая вам чистый лист для следующего упражнения.
Что вы узнали
- Таблицы Iceberg создаются через стандартный SQL DDL
- Каждая операция INSERT создаёт новый снапшот
- Метатаблицы (
.snapshots,.files) позволяют заглянуть под капот - REST-каталог управляет всей координацией между вашим движком запросов и хранилищем
Что дальше?
В следующем уроке мы изучим ACID‑транзакции — как Iceberg обеспечивает атомарность, изоляцию и консистентность при работе с несколькими писателями.
Продолжение следует: Теперь, когда вы создали свою первую таблицу, самое время понять, как Iceberg гарантирует целостность данных. В следующем уроке мы разберём механизмы транзакций и конкурентных записей.