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 гарантирует целостность данных. В следующем уроке мы разберём механизмы транзакций и конкурентных записей.

→ Вперёд к ACID‑транзакциям

← Назад к каталогам