Приветствую.

Цель стать: показать на практике как проходит insert запрос в Apache Iceberg. Все шаги можно и даже нужно повторить.

Что будет в статье: Мы поднимем локально, через Docker, Apache Iceberg и сделаем запрос через Apache Spark. Так же подробнее посмотрим как выполняется запрос и что он после себя оставляет. То есть как именно записывает данные.

Автор данной статьи предполагает, что вы немного в курсе за Apche Iceberg и пришли сюда, чтобы понять эта картинка с архитектурой(об этом чуть позже) работает. Если вам нужен полноценный курс по Apache Iceberg, то моя рекомендация книга Apache Iceberg: The Definitive Guide. Эта статья "вдохновлена" главой номер 3.

Глоссарий

Прежде чем идти дальше, хочется привести краткое определение некоторых терминов, которые я буду использовать. Это будет очень краткое, но достаточное, описание терминов, чтобы можно было понять основную часть статьи.

Hadoop - платформа для распределенной обработки данных. Именно, что платформа. Там множество элементов. В данной статье мы будем подразумевать под Hadoop, HDFS.

HDFS(Hadoop Distributed File System) - распределённая файловая система. Если уж ООООООЧЕНЬ сильно упрощать, то это файловая система с финтифлюшками. То есть просто место, где хранятся много файлов.

S3(Simple Storage Service) - объектное хранилище. Изначально это продукт AWS. И это очередное хранилище, но уже плоское. Предлагаю думать, об этом как место где лежат много файлов. Да, очень похоже на HDFS, но подход немного другой. На данный момент, S3 стало не продуктом, а стандартом. Если ты выполнишь определённые пункты, то ты можешь называться S3-подобным хранилищем. Одном из самых популярных сейчас решений - это MinIO.

MinIO - объектное хранилище данных. По факту, это другая версия S3 от AWS, но заявленное как opensource решение. Но вот на конец 2025 года у нас появилась новость, что разработчики приостановили поддержку ветки opensource. Подробнее на статье habr. Для целей данной статьи предлагаю вам думать о MinIO, как о сервисе, которая просто хранит данные.

Apache Spark - инструмент, который позволяет писать код на python, java, scala для больших данных. Так же он поддерживает sql, но этот sql нужно оформлять в какой-то код.

Trino - sql вдижок. Просто движок, который может выполнять sql запросы к данными. Если классические СУБД - это комбинация хранилища, где лежат данные, и движка, который работает над этими данными, то Trino - это только движок. Его можно подключить к разным СУБД или файловым системам.

Docker - реализация контейнерезации. Эта штука позволяет вам вместо поднятия виртуальной машины, поднимать контейнеры, которые позволяют вам работать локально.

Немного истории

Мы жили себе с нашими СУБД (Oracle, Postresql, Mysql ...) и грузили наши данные в рамках OLAP. Строили своию аналитику и показывали свои дэшборды бизнесу (на самом деле мы и сейчас так делаем).

Потом появился Hadoop и его HDFS. С приходом Hadoop начали говорить, что с Hadoop есть проблемы, но идея того, что данные можно записывать в файлы и обрабатывать их движком, который не закреплён вместе с данными, осталась. Эта идея переросла в концепцию Data Lake. Но и у этого подхода возникли свои проблемы (дублирование файлов, качество данных и т.д.). Потом появился Lake House. Но и у него есть свои проблемы. И вот чтобы решить новые проблемы, которые создали новые технологии, возник Apache Iceberg.

Iceberg не единственно решение, но кажется, что оно становиться стандартом. И именно его мы будем рассматривать сегодня.

Apаche Iceberg

Так, что такое Apache Iceberg?

На официальном сайте проекта сказано:

Iceberg is a high-performance format for huge analytic tables.

Переводится: Iceberg - высоко производительный формат для больших аналитических таблиц (простите за такой перевод, но мне показалось, что это наиболее прямой).

Как по мне это определение не даёт понимание, что всё таки такое Iceberg. Поэтому, предлагаю вам представить такую картину.

рис. 1. Верхнеуровневая схема
рис. 1. Верхнеуровневая схема

На схеме выглядит так, что Iceberg (здесь и далее я буду опускать Apache для экономии времени) - это какая-то программа или сервис. На самом деле это не так. Iceberg это прослойка между движком и данными. По факту это просто библиотека о том, как хранить данные. Не более. Не сервис. Не субд. Просто набор библиотек для каждого движка, который записывает данные.

Так, а что нам даёт Iceberg, чего нет без него? Давайте приведём пару пунктов:

  • поддержка ACID. Целостность данных страдает если мы используем только HDFS или S3

  • откат к предыдущему состоянию в таблице

  • schema evolution(не знаю как это нормально перевести). Изменение названия полей, таблицы ...

  • ...

Опять же все эти красивые слова, которые вы можете видеть на любых презентациях. Вопрос в том как Iceberg это делает. Но в данной статье мы рассмотрим только вставку данных.

Если очень коротко, то это работает так ():

  1. Мы делаем insert запрос через движок(всё равно что это Spark, Trino ...)

  2. Движок понимает, что это запрос в Iceberg (на пример, это делается при создании spark сессии)

  3. Движок подтягивает библиотеки для работы с Iceberg

  4. Движок записывает данные в хранилище (пускай будет S3, но можно и в hadoop)

  5. Движок записывает метаданные, о данных

Да, кажется что все так делают. Каждая СУБД. И да, это очень простой и абсолютно не полный путь, который проходит движок, чтобы записать данные. Но, что стоит отметить, в этом процессе так это метаданные. Это краеугольная часть Iceberg. На этих метаданных всё и держится. По сути именно в этих мета данных раскрывается Iceberg.

Iceberg и его метаданные

Мы уяснили, что эти метаданные очень важны. А что именно записывается в эти метаданные?

Во всех презентациях и видео вы увидите такую картинку с оф сайта:

рис. 2. Схема Iceberg
рис. 2. Схема Iceberg

Попробуем пройтись по основным частям(будто я на собесе и меня просят описать эту картинку).

Пойдём снизу вверх.

Первое, что у нас есть это data files. Это сами данные. По умолчанию это parquet файлы, но можно и другие форматы. Однако на практике, в бою, я встречал именно parquet.

Далее у нас metadata layer. Этот слой начинается с manifest file. В этом файле хранятся данные о данных в parquet(например мин. и макс. значения в столбцах). Так же вы можете видеть, что 1 manifest file может хранить данные о нескольких data files (связь один ко многим. Не зря я все эти слова учил. Пригодились гыыы)

После manifest file у нас идёт manifest list. Тут тоже 1 manifest list может хранить несколько manifest file. Тут есть важный момент. 1 manifest list - это 1 snapshot. ЕЩЁ РАЗ. 1 manifest list - это 1 snapshot. Это важное сведение. Супер важное. Теперь, у вас вопрос: А что такое snapshot?

snapshot - это состояние таблицы, которое нельзя изменить. Каждое изменения состояния таблицы, будь то вставка данных или добавление нового столбца, генерируется новый snapshot. ДА, на каждое изменение. Будьте внимательны.

Идём дальше по схеме. Далее у нас есть metadata file. В этом файле хранятся важная информация о таблице. Как таблица партиционированна, список snapshot-ов для данной таблицы. То есть metadata file - это и есть файл о таблице.

И самый последний слой - это catalog. Catalog - это внешний сервис(postgresql, rest catalog, hive metastore) в котором находится ссылка на metadata file и который является точкой входи для всех внешних движков. Если делается select запрос, то сначала движо�� идёт в catalog. Это входная точка для запроса.

Много буков, но мы тут не за этим. Предлагаю перейти к практике и посмотреть, как эта картинка будет работать на настоящем примере.

Поднимает локально необходимый инструментарий

На официальном сайте есть docker compose файл. Можно было бы им и воспользоваться, но для наших целей, нам нужно будет его немного доработать. Приведу весь файл, с доработкой и проставлю пару комментариев:

services:
  spark-iceberg: # подключаем spark
    image: tabulario/spark-iceberg
    container_name: spark-iceberg
    build: spark/
    networks:
      iceberg_net:
    depends_on:
      - rest
      - minio
    volumes:
      - ./spark/warehouse:/home/iceberg/warehouse
      - ./spark/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 # подключаем rest catalog
    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
    volumes:
      - ./rest/tmp:/tmp # Вот это добавлил
  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
    volumes:
      - ./minio/tmp:/data # Вот это добавлил
    command: ["server", "/data", "--console-address", ":9001"]
  mc: # интерфейс для подключения к MinIO, чтобы можно было в браузере смотреть
    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:

Я делаю всё под Windows. Так, что добавленный путь может отличатся под linux.

Добавилось 2 volumes для контейнеров rest и minIO. Они нужны, чтобы можно было посмотреть на сами файлы, которые мы генерируем, а так же, чтобы сохранить данные, которые будет использованы в контейнерах. То есть схема будет такой:

рис. 3. То что мы хотим сделать на локальном ПК
рис. 3. То что мы хотим сделать на локальном ПК

Быстрое описание

Apache Spark - будет движком. Он будет делать всю работу. Буду использовать pyspark.

MinIO - объектное хранилище(s3 подобное). Именно здесь будут лежать данные. В этом контейнере мы смонтировали папку, чтобы посмотреть на те данные, которые будут созданы.

rest catalog - собственно catalog из рисунка 2. Тут же будет смонтирована папочка, в которой лежит данные catalog. В этом контейнере мы смонтировали папку, чтобы посмотреть, что будет писаться в iceberg catalog. Так как rest catalog хранит данные в SQLite, то его можно будет открыть как файл.

Да, в docker compose есть ещё и mc контейнер, но для меня это интерфейс к minIO и чтобы не перегружать схему, я его не показал на схеме.

С теорией закончили. Пошли тыкаться.

Пошаговая инструкция

Создаём локально папочку. Всё равно где и как вы её назовёте. Я создал папку с названием "iceberg_tutorial"

Создаём в этой папочке файл с названием "docker-compose.yaml". В него копируем код, который был ранее в статье.

Рядом с этой папкой создаём папку ''tmp'. Эта папка будем смонтирована с папкой в doker контейнере.

Вам нужно скачать docker desktop вот отсюда и установить его. Я верю, что вы справитесь с установкой самостоятельно.

Как только вы установили deocker desktop, вам нужно его запустить. Просто найдите иконку с этим приложением на своём пк. А дальше вам нужно будет запустить docker compose.

Тут небольшое отступление. У меня возникли проблемы с запуском контейнеров через desktop приложение, поэтому я всё буду делать как привык, через консоль.

В терминале перейдите в папку "iceberg_tutorial". Давайте проверим, что docker работает. В этом же терминале введите:

docker ps

Если у вас вывелось:

CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

то docker запущен и можно продолжать работать.

Теперь вводим

docker compose up -d

Тут придётся немного подождать пока все образы загрузятся. Как только всё будет запущено, то в папке "iceberg_tutorial" вы увидите, что есть создались несколько папок:

  • minio

  • rest

  • spark

Создание этих папок прописано в docker compose файле. Каждый из них отвечает за свой контейнер.

Сейчас у вас должно уже всё работать. Проверим, что всё запустилось через командную строку

docker ps -a 

Все ваши 4 контейнера должны быть в статусе UP

Последние приготовления. Нужно подключить обозреватель для iceberg catalog. Я воспользуюсь dbeaver. Можно скачать тут. Я так же надеюсь, что вы справитесь с установкой.

Устанавливаем соединение с iceberg catalog. Для этого открываем dbeaver. Выбираем новое соединение:

рис. 5. Новое соединение
рис. 4. Новое соединение

В открывшемся окне нам нужно SqlLite

рис. 6. Выбираем нужное соединение
рис. 5. Выбираем нужное соединение

Откроется такое окно, в котором нужно выбрать кнопку открыть

рис. 7. Настраиваем соединение
рис. 6. Настраиваем соединение

Выбираем файл

iceberg_tutorial\rest\tmp\iceberg_catalog.db

И нам в dbeaver покажет, что у нас есть 2 таблицы

рис. 7. Обзор таблиц
рис. 7. Обзор таблиц

Нас будет интересовать таблица iceberg_tables. Давайте выполним запрос

select * from iceberg_tables;

Ну и ответ будет пустота

рис. 8. Вывод
рис. 8. Вывод

Дальше выполните запрос

select * from iceberg_namespace_properties;

И опять ничего не будет. А чёго ожидали то?

Мы выполнил�� запросы к iceberg catalog. Так как ничего мы ещё не создавали, то и ничего там нет. Но давайте наконец создадим нашу первую таблицу

Переходим по адресу:

http://localhost:8888

Мы если, что его прописывали в docker-compose файле, в разделе ports.

Нам открывается jupyter

рис. 9. Jupyter
рис. 9. Jupyter

Вы можете посмотреть примеры, которые идут с контейнером. Но предлагаю это сделать позже.

Нам нужно создать собственный notebook. Для удобства перейдём в папку notebook и нажмём New, а потом выберем Python3

рис. 10. Создание notebook
рис. 10. Создание notebook

Вас перебросит на новое окно. Файл, я сразу переименовал в test_iceberg.ipynb. Этот же файл у меня лежит теперь в моей папке на локальном компьютере

рис. 11. Обзор spark файлов
рис. 11. Обзор spark файлов

В этом ноутбуке пропишем

from pyspark.sql import SparkSession # подключаем библиотеку для работы со sparkSQL
spark = SparkSession.builder.appName("Jupyter").getOrCreate() #создаём spark сессию

spark #вызываем, чтобы проверить, что оно работает 

Если вам вылезет warning, то проигнорируйте его сейчас.

sparkSQL - модуль spark, который позволяет использовать sql для обработки данных

спарк сессия - это... как бы это коротко объяснить? Это точка входа. Грубо говоря в этом мести пробрасывается соединение.

Вы можете спросить: А где тут Iceberg? Тут ничего ни одного слова нет...

Будете правы, потому что все настройки лежат в конфиге. Можете выполнить в notebook команду:

spark.sparkContext._conf.getAll()

Там вы увидите куда мы ходим и т.д. и т.п. Мы не будем рассматривать конфиг, потому, что это не цель данной статьи. Поэтому продолжим.

И так, у нас есть соединение с Iceberg. Давайте создадим схему. Для этого в новой ячейки notebook напишем

%%sql # это для того, чтобы ячейка воспринималась как sql

CREATE DATABASE IF NOT EXISTS test_iceberg

Схему создали. Давайте переключимся на dbeaver и выполним запрос:

Select * from iceberg_namespace_properties inp 

И появились 2 строки. То есть в этой табличке отображаются схемы.

Теперь давайте создадим в spark таблицу:

%%sql
CREATE TABLE test_iceberg.super_pyper_table (
id BIGINT
)
USING iceberg

Да, одно поле. Нам больше и не надо.

Прежде чем пойдём дальше, давайте ещё раз посмотрим на архитектуру Iceberg.

рис. 12. Схема Iceberg, ещё раз
рис. 12. Схема Iceberg, ещё раз

Снова делаем запрос на Iceberg Catatlog через dbever

select * from iceberg_tables it;

И у нас появилась строка.

рис. 13. Вывод Iceberg Catalog
рис. 13. Вывод Iceberg Catalog

Получилось, мы зарегистрировали в Iceberg Catalog нашу таблицу. Сейчас нас интересует атрибут metadata_location. Его значение:

s3://warehouse/test_iceberg/super_puper_table/metadata/[много буков].metadata.json

И видим, что это полный путь до metadata file. Получается, что Iceberg Catalog хранит связку название таблицы и место где хранится metadata file для неё. Напомню, что metadata file это и есть репрезентация таблицы.

Так а давайте посмотрим на этот файл. Где его взять? Для этого нужно будет открыть MinIO. Напоминаю, что MnIO это объектное хранилище, где лежат наши данные. Давайте перейдём туда открыв:

http://localhost:9001/login

Login и password прописаны в docker-compose

- MINIO_ROOT_USER=admin
- MINIO_ROOT_PASSWORD=password

Нам откроется окно

рис. 14. MinIO схема
рис. 14. MinIO схема

переходим в test_iceberg и у нас открывается

рис. 15. MinIO обзор таблицы
рис. 15. MinIO обзор таблицы

проваливаемся в эту единственную папку, потом переходим в папку metadata и видим:

рис. 16. MinIO обзор metadata
рис. 16. MinIO обзор metadata

Его можно скачать, нажав на кнопку Download в MinIO. Да мы смонтировали папку для этих целей, но MinIO хранит этот файл в виде бинарника.

рис. 17. Metadata в локальном пк
рис. 17. Metadata в локальном пк
рис. 18. Как выглидт metadata
рис. 18. Как выглидт metadata

Так, что просто скачайте этот файл через кнопку Download

рис. 19. MinIO, как скачать файл
рис. 19. MinIO, как скачать файл

Открыв файл, мы увидим такое наполнение

{
	"format-version": 2,
	"table-uuid": "64efcc91-a858-4366-9169-3d849482c43c",
	"location": "s3://warehouse/test_iceberg/super_pyper_table",
	"last-sequence-number": 0,
	"last-updated-ms": 1769868969208,
	"last-column-id": 1,
	"current-schema-id": 0,
	"schemas": [{
		"type": "struct",
		"schema-id": 0,
		"fields": [{
			"id": 1,
			"name": "id",
			"required": false,
			"type": "long"
		}]
	}],
	"default-spec-id": 0,
	"partition-specs": [{
		"spec-id": 0,
		"fields": []
	}],
	"last-partition-id": 999,
	"default-sort-order-id": 0,
	"sort-orders": [{
		"order-id": 0,
		"fields": []
	}],
	"properties": {
		"owner": "root",
		"write.parquet.compression-codec": "zstd"
	},
	"current-snapshot-id": -1,
	"refs": {},
	"snapshots": [],
	"statistics": [],
	"partition-statistics": [],
	"snapshot-log": [],
	"metadata-log": []
}

Тут много инфы. Не будем всё объяснять. Сосредоточимся на как мне кажется, важном.

table-uuid - уникальный идентификатор. Позволяет не привязываться к названию таблицы.

schemas - то какие поля у таблицы. Заметьте, что у поля тоже есть свой id.

current-snapshot-id - номер snapshot, который на данный момент является активным. Напомню, что это номер версии состояния таблицы. Сейчас он -1, так как данных нет. Мы просто создали таблицу.

Больше в папке super_puper_table ничего нет. Нет никаго manifes list, manifest file. Так как данных нет. Но теперь давайте добавим данные. Вернёмся в Jupyter Hub и выполним запрос

%%sql
INSERT INTO test_iceberg.super_pyper_table VALUES (
123
)

Убедимся, что данные пришли и сделаем запрос:

%%sql
SELECT * FROM test_iceberg.super_pyper_table;

И данные пришли. Теперь самое важное для чего всё и затевалось. Посмотрим наша схема выглядит в реальности. Я буду идти очень медленно и с повторениями, чтобы было кристально понятно.

Первое что делаем идём в dbeaver, чтобы выполнить select запрос в iceberg catalog

select * from iceberg_tables it ;

Сейчас мы посмотрели в

рис. 20. Часть Iceberg Catalog
рис. 20. Часть Iceberg Catalog

Нам показывают одну строку, но теперь состояние поменялось. Поменялись 2 поля.

  • metadata_location

  • previous_metadata_location

Помните, я писал, что для каждого действия с таблицей, будь-то insert или update создаются собственные мета данные. Так вот. Мы вставили данные, поэтому нам создался новый metadata file. В iceberg catalog прошли изменения и для нашей таблицы записалось, где хранится актуальный metadata file и его предыдущая версия.

Теперь перейдём в MinIO. Если вы перейдёте по пути

warehouse/test_iceberg/super_pyper_table

то увидите, что создалась папка data.

рис. 21. MinIO, обзор таблицы
рис. 21. MinIO, обзор таблицы

Открыв её, вы увидите файл с вашими данными. Можете скачать его и посмотреть, но там просто parquet файл. Нас больше интересует metadata. Давайте перейдём туда.

рис. 22. MinIO, обзор таблицы
рис. 22. MinIO, обзор таблицы

У нас появилось 3 новых файла.

00001-9a682068-6cb7-4b19-9bed-df14bd6a5a13.metadata.json - новый metadata file.

То есть в схеме мы открыли:

рис. 23. Часть Iceberg Catalog
рис. 23. Часть Iceberg Catalog

Если его открыть, то увидите что он похож на предыдущий. Тут важные отличия от предыдущей весии. Заполнился раздел snapshot. В этом разделе указывается вся инфа про snapshot. И дополнительно указывается путь до manifest list:

snap-1374504624696218140-1-d11abf55-85f5-4312-8825-8eea7f5100d2.avro

Сейчас мы откроем слой:

рис. 24. Часть Iceberg Catalog
рис. 24. Часть Iceberg Catalog

Так как это avro формат, то его просто через редактор не открыть. Я нашёл в интеренет вот такой python скрипт:

import avro.schema
from avro.datafile import DataFileReader, DataFileWriter
from avro.io import DatumReader, DatumWriter

# Assume a file named "users.avro" exists for this example.
# If you need to create one first, you can use the code snippets in the search results.

# Open the Avro file in binary read mode ('rb')
try:
    # подставьте свой путь до файла
    reader = DataFileReader(open("snap-1374504624696218140-1-d11abf55-85f5-4312-8825-8eea7f5100d2.avro", "rb"), DatumReader())
    print("Contents of the Avro file:")
    # Iterate over each record in the file
    for user in reader:
        # Print the record, which is a Python dictionary
        print(user)
    
    # Close the file reader
    reader.close()

except FileNotFoundError:
    print("Error: 'users.avro' not found. Please create an Avro file first.")
except Exception as e:
    print(f"An error occurred: {e}")

Которым открыл файл и у меня вывелось:

{
	'manifest_path': 's3://warehouse/test_iceberg/super_pyper_table/metadata/d11abf55-85f5-4312-8825-8eea7f5100d2-m0.avro', 
	'manifest_length': 6963, 
	'partition_spec_id': 0, 
	'content': 0, 
	'sequence_number': 1, 
	'min_sequence_number': 1, 
	'added_snapshot_id': 1374504624696218140, 
	'added_files_count': 1, 
	'existing_files_count': 0, 
	'deleted_files_count': 0, 
	'added_rows_count': 1, 
	'existing_rows_count': 0, 
	'deleted_rows_count': 0, 
	'partitions': [], 
	'key_metadata': None
}

Тут куча информации, в которой можно покопаться. Самое сейчас важное для нас это:

'manifest_path': 's3://warehouse/test_iceberg/super_pyper_table/metadata/d11abf55-85f5-4312-8825-8eea7f5100d2-m0.avro',

Это путь указывает на наш следующий слой

рис. 25. Часть Iceberg Catalog
рис. 25. Часть Iceberg Catalog

Manifest file это тоже avro. Так, что если его открыть кодом выше, то мы увидем

{
	'status': 1, 
	'snapshot_id': 1374504624696218140, 
	'sequence_number': None, 
	'file_sequence_number': None, 
	'data_file': 
		{
			'content': 0, 
			'file_path': 's3://warehouse/test_iceberg/super_pyper_table/data/00000-0-a0dd32cd-829a-45b2-9351-4e1f4ad7bef7-0-00001.parquet', 
			'file_format': 'PARQUET', 
			'partition': {}, 
			'record_count': 1, 
			'file_size_in_bytes': 436, 
			'column_sizes': [{'key': 1, 
			'value': 43}], 
			'value_counts': [{'key': 1, 
			'value': 1}], 
			'null_value_counts': [{'key': 1, 
			'value': 0}], 
			'nan_value_counts': [], 
			'lower_bounds': [{'key': 1, 
			'value': b'{\x00\x00\x00\x00\x00\x00\x00'}], 
			'upper_bounds': 
			[
				{
					'key': 1, 
					'value': b'{\x00\x00\x00\x00\x00\x00\x00'
				}
			], 
			'key_metadata': None, 
			'split_offsets': [4], 
			'equality_ids': None, 'sort_order_id': 0, 
			'referenced_data_file': None
		}
	}

Нас тут больше всего интересует поле "file_path", которое и указывает на данные в parquet формате.

Это всё что я хотел показать. На самом деле тут ещё много полей для эксперимента, но оставлю это на ваше усмотрение. Можете продолжать экспериментировать.