Contents
- 1 Введение — Parquet, HDFS, Hive, Data Lake, Data LakeHouse
- 1.1 Что такое parquet? Как он устроен? Базовые понятия для работы с Apache Iceberg
- 1.1.1 Структура файла Parquet
- 1.1.2 Определения составляющих Parquet файла
- 1.1.3 Рекомендации по конфигурации
- 1.1.4 Метаданные Parquet файла
- 1.1.5 Архитектура Parquet файла
- 1.1.6 Принципы работы и оптимизации
- 1.1.7 Сериализация данных в Apache Parquet
- 1.1.8 Где дополнительно прочитать или посмотреть про Apache Parquet?
- 1.1.9 Partitioning vs Bucketing
- 1.1.10 Когда что следует использовать?
- 1.1.11 Как работает Apache Parquet: внутреннее устройство и основные характеристики
- 1.2 Что такое HDFS, Hive и Hive Metastore?
- 1.3 DWH -> Data Lake -> Data LakeHouse — Эволюция аналитических систем
- 1.1 Что такое parquet? Как он устроен? Базовые понятия для работы с Apache Iceberg
- 2 Что такое Apache Iceberg? Архитектура и внутреннее устройство таблиц
- 3 Методы оптимизации Apache Iceberg
- 4 Как установить Apache Iceberg локально на Docker?
- 5 Техническая архитектура Apache Iceberg
- 5.1 Введение в спецификацию таблиц Iceberg
- 5.1.1 Версионирование Iceberg
- 5.1.2 Таблицы Apache Iceberg
- 5.1.3 Структура snapshots Iceberg
- 5.1.4 Оптимистичная параллельность (Optimistic Concurrency)
- 5.1.5 Порядковые номера (Sequence Numbers)
- 5.1.6 Удаления на уровне строк
- 5.1.7 Операции файловой системы
- 5.1.8 Разбиение на разделы (Partitioning)
- 5.1.9 Сортировка (Sorting)
- 5.1 Введение в спецификацию таблиц Iceberg
- 6 Плюсы и минусы Apache Iceberg
- 7 Проблемы, возникающие при эксплуатации Apache Iceberg
- 8 Что такое Trino и как он работает с Iceberg?
- 9 Полезные ссылки (в т.ч. те, которые использовались при написании статьи)
Введение — Parquet, HDFS, Hive, Data Lake, Data LakeHouse
Прежде чем переходить к Iceberg — давайте посмотрим на другие технологии, с которыми нужно быть знакомыми, прежде чем начать работать с Iceberg.
Это позволит глубже осмыслить технологию и применять ее вдумчиво, а не потому что это модно и современно!
Что такое parquet? Как он устроен? Базовые понятия для работы с Apache Iceberg
Apache Parquet — это открытый, колонко-ориентированный формат хранения данных, оптимизированный для эффективного хранения и извлечения больших объемов данных. Он поддерживает высокоэффективные схемы сжатия и кодирования, что делает его особенно полезным для обработки сложных и объемных данных.
Структура файла Parquet
Файл Parquet организован иерархически:
- Row Groups: Горизонтальные разделы данных, содержащие строки.
- Column Chunks: Вертикальные разделы внутри каждого Row Group, содержащие данные одного столбца.
- Pages: Наименьшие единицы данных внутри Column Chunks, которые являются неделимыми с точки зрения сжатия и кодирования.
Такая структура обеспечивает эффективную параллельную обработку и выборочное чтение данных.
Определения составляющих Parquet файла
Row Groups — это большой фрагмент данных, содержащий данные столбцов для подмножества строк. Каждая группа строк содержит фрагменты столбцов для каждого столбца в наборе данных.
Каждый столбец в группе строк имеет минимальную/максимальную статистику, что позволяет механизмам запросов пропускать целые группы строк для определенных запросов, что приводит к значительному повышению производительности при чтении данных.
Column Chunks — Блок столбцов хранит данные для одного столбца в группе строк. Блоки столбцов делятся на страницы.
Pages — это наименьшая единица хранения данных в Parquet. Каждый фрагмент столбца делится на страницы, которые могут быть нескольких типов:
- Data Pages: хранят фактические данные столбцов.
- Dictionary Pages: хранят уникальные значения для кодирования словаря, представляющего собой метод сжатия столбцов, содержащих много повторяющихся значений.
- Index Pages: хранят информацию индекса для более быстрого извлечения данных.
Рекомендации по конфигурации
- Размер Row Group: Рекомендуется использовать большие Row Groups (512 МБ – 1 ГБ), чтобы они полностью помещались в один блок HDFS, что улучшает производительность при чтении.
- Размер Data Page: Рекомендуется использовать размер страниц данных около 8 КБ для балансировки между точечным доступом и эффективностью сжатия.
Метаданные Parquet файла
Файлы Parquet включают метаданные, которые играют решающую роль в понимании и эффективном доступе к данным, которые они содержат. Эти метаданные встроены в саму структуру файла и обычно появляются в нескольких ключевых местах:
- File Header (Заголовок файла): В начале файла Parquet метаданные предоставляют важную информацию о файле, такую как его схема, используемые алгоритмы сжатия и другие свойства на уровне файла. Этот заголовок помогает программным приложениям быстро интерпретировать и обрабатывать файл.
- Row Groups (Группы строк): данные организованы в группы строк в файле Parquet. Каждая группа строк включает свой собственный раздел метаданных, в котором указаны такие данные, как количество строк, статистика столбцов (минимальные/максимальные значения) и кодировка, используемая для каждого столбца. Эта информация помогает оптимизировать извлечение данных и производительность запросов.
- Page Metadata (Метаданные страницы): данные внутри группы строк далее делятся на страницы. Каждая страница данных включает метаданные, указывающие ее размер, тип сжатия и другие характеристики, необходимые для эффективного чтения и распаковки данных.
Архитектура Parquet файла
Header и Footer:
- Каждый файл начинается и заканчивается магической строкой «PAR1», что позволяет идентифицировать его как файл Parquet.
- В footer содержится важная метаинформация: схема данных, статистика по столбцам, смещения row groups и другая служебная информация.
Row Groups (группы строк):
- Данные разбиваются на независимые блоки — row groups, каждый из которых содержит подмножество строк.
- Каждый row group может обрабатываться параллельно, что повышает производительность при чтении и записи.
Column Chunks (фрагменты столбцов):
- Внутри каждого row group данные организуются по столбцам.
- Каждый столбец хранится отдельно, что позволяет эффективно считывать только необходимые столбцы при выполнении запросов.
Pages (страницы):
- Column chunks делятся на страницы, которые являются наименьшими единицами хранения данных.
- Существуют различные типы страниц, включая data pages и dictionary pages, каждая из которых может быть сжата и закодирована отдельно.
Parquet — это самоописанный формат файла, который содержит всю информацию, необходимую для приложения, потребляющего файл. Это позволяет программному обеспечению эффективно понимать и обрабатывать файл, не требуя внешней информации. Таким образом, метаданные являются важнейшей частью Parquet:
Принципы работы и оптимизации
Колонко-ориентированное хранение:
- Данные одного столбца хранятся вместе, что позволяет эффективно сжимать и кодировать данные, а также ускоряет выполнение аналитических запросов, которые часто обращаются только к подмножеству столбцов.
Сжатие и кодирование:
Parquet применяет различные методы сжатия, такие как Snappy, Gzip и другие.
Для кодирования данных используются техники, включая:
- Dictionary Encoding: эффективно для столбцов с ограниченным числом уникальных значений.
- Run-Length Encoding (RLE): подходит для столбцов с повторяющимися значениями.
- Bit Packing: оптимизирует хранение небольших целых чисел.
Метаинформация и статистика:
- Parquet хранит подробную метаинформацию, включая минимальные и максимальные значения для каждого столбца, количество null-значений и другую статистику.
- Эта информация используется для оптимизации выполнения запросов, позволяя, например, пропускать целые row groups, если они не соответствуют условиям запроса.
Поддержка вложенных структур:
- Parquet поддерживает сложные и вложенные структуры данных, включая списки и структуры, благодаря алгоритму «record shredding and assembly», описанному в работе Google Dremel.
Сериализация данных в Apache Parquet
Apache Parquet использует бинарную сериализацию, основанную на Google Protocol Buffers (в ранней версии) и собственном наборе бинарных структур, описанных в Thrift (Apache Thrift IDL — Interface Definition Language).
Ключевые особенности сериализации в Parquet
Thrift-схема:
- Вся структура файла (footer, metadata, page headers) описана с помощью Apache Thrift.
- Это делает метаинформацию файла компактной и кросс-языковой.
Бинарный формат данных:
- Данные внутри pages сериализуются в бинарном виде с минимальным overhead’ом.
- Поддерживается эффективное сжатие и кодирование значений (dictionary encoding, run-length encoding, bit-packing и др.).
Сериализация вложенных структур:
- Используется концепция Dremel-style encoding («shredding and assembly») для представления вложенных (nested) и повторяющихся (repeated) полей — списков, структур и вложенных списков.
- Для этого сериализуются не только значения, но и их уровни повторения и определения (repetition level и definition level).
Колонко-ориентированное представление:
- Сериализация выполняется по колонкам, а не по строкам — что резко повышает эффективность сжатия и позволяет избирательно считывать данные.
Где дополнительно прочитать или посмотреть про Apache Parquet?
Статьи:
Видео:
- The Parquet Format and Performance Optimization Opportunities Boudewijn Braams (Databricks)
- An introduction to Apache Parquet
- ORC и Parquet. О форматах и их использовании на базе HDFS / Александр Маркачев (билайн)
Partitioning vs Bucketing
Для оптимизации работы с большими объемами данных применяются техники секционирования (partitioning) и бакетирования (bucketing).
Секционирование (Partitioning) — подразумевает разбиение данных на подкаталоги в файловой системе на основе значений одного или нескольких столбцов. Например, при секционировании по столбцам year и month, структура каталогов будет следующей:
1 2 3 |
/sales/year=2023/month=01/ /sales/year=2023/month=02/ ... |
Преимущества:
- Пропуск ненужных данных: При выполнении запросов с фильтрами по секционированным столбцам, движок может пропустить чтение ненужных разделов, что ускоряет выполнение запросов.
Medium - Уменьшение ввода-вывода: Считывается только необходимая часть данных, что снижает нагрузку на диск.
- Упорядоченность данных: Данные организованы по логическим разделам, что облегчает их управление и анализ.
- Когда использовать: Секционирование эффективно, когда часто выполняются фильтрации по столбцам с низкой кардинальностью (например, year, region).
Бакетирование (Bucketing) — делит данные на фиксированное количество «бакетов» на основе хеш-функции от значений одного или нескольких столбцов. В отличие от секционирования, бакеты не создают отдельные каталоги, а распределяют данные внутри файлов.
1 2 3 4 5 |
/warehouse/bucketed_users/ ├── part-00000-...snappy.parquet ├── part-00001-...snappy.parquet ├── part-00002-...snappy.parquet └── part-00003-...snappy.parquet |
Преимущества:
- Равномерное распределение данных: Бакетирование обеспечивает равномерное распределение данных по бакетам, предотвращая перекосы в данных.
- Оптимизация соединений: Если две таблицы бакетированы по одному и тому же столбцу, соединения между ними выполняются более эффективно, так как соответствующие данные уже сгруппированы вместе.
- Предсказуемая производительность: Равномерное распределение данных приводит к стабильной и предсказуемой производительности запросов.
Когда что следует использовать?
- Секционирование: Используйте для столбцов с небольшим количеством уникальных значений, по которым часто выполняются фильтрации.
- Бакетирование: Применяйте для столбцов с большим количеством уникальных значений, особенно если часто выполняются соединения или группировки по этим столбцам.
- Комбинирование: В некоторых случаях целесообразно комбинировать секционирование и бакетирование для достижения наилучшей производительности.
Как работает Apache Parquet: внутреннее устройство и основные характеристики
Реализация Apache Parquet во многом похожа на алгоритмы разметки и сборки, описанными в статье Dremel от Google. Эти алгоритмы имеют решающее значение для эффективного хранения и извлечения данных в столбчатом формате, оптимизации производительности и эффективности хранения.
Алгоритм чередования
В Apache Parquet алгоритм чередования столбцов является ключевым для организации данных в их столбчатую структуру. Этот алгоритм можно визуализировать как обход в глубину определенной схемой древовидной структуры, где каждый конечный узел соответствует столбцу примитивного типа.
Проиллюстрируем это на примере:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
message Document { required int64 DocId; optional group Links { repeated int64 Backward; repeated int64 Forward; } repeated group Name { repeated group Language { required string Code; optional string Country; } optional string Url; } } |
Для этой схемы Apache Parquet сгенерирует столбцы следующим образом:
1 2 3 4 5 6 |
DocId Links.Backward Links.Forward Name.Language.Code Name.Language.Country Name.Url |
Алгоритм чередования включает в себя:
- Сериализация записи путем обхода дерева от корня к листьям.
- Запись значений для каждого столбца на максимальном уровне определения (указывающем, что значение определено) и текущем уровне повторения (начиная с 0 в корне).
Например, сериализация записи может выглядеть так:
1 2 3 4 5 6 |
DocId: 10, R:0, D:0 Links.Backward: NULL, R:0, D:1 (no value defined, so D < 2) Links.Forward: 20, R:0, D:2 Name.Language.Code: 'en-us', R:0, D:2 Name.Language.Country: 'us', R:0, D:3 Name.Url: 'http://A', R:0, D:2 |
Такой подход эффективно кодирует данные с минимальными накладными расходами, используя уровни повторения и определения для эффективного управления вложенными и повторяющимися структурами.
Сборка записей в паркете: эффективная реконструкция данных
Процесс сборки записей в Apache Parquet обращает процесс сериализации. Он реконструирует записи, проходя по древовидной структуре на основе требуемых столбцов.
Этот процесс обеспечивает эффективный доступ к определенным полям без необходимости считывать ненужные данные, тем самым оптимизируя производительность запросов.
Уровни повторения (Repetition) и определения (Definition)
В Parquet уровни повторения и определения играют решающую роль в управлении вложенными и повторяющимися структурами данных:
- Уровень повторения (Repetition Level, R): Указывает глубину вложенности для повторяющихся полей. Он увеличивается при прохождении повторяющихся полей, что позволяет эффективно реконструировать повторяющиеся элементы.
- Уровень определения (Definition Level, D): Указывает, определено ли значение (присутствует) или не определено (нуль). Он увеличивается при прохождении необязательных полей, гарантируя, что обрабатываются только допустимые данные.
Эти уровни эффективно кодируются с использованием компактного представления, оптимизируя эффективность хранения и обработки.
Принятие компанией Apache Parquet концепций из статьи Dremel подчеркивает ее стремление предоставлять высокопроизводительные масштабируемые решения для хранения данных.
Эти основополагающие принципы не только расширяют возможности Parquet в озерах данных и аналитических платформах, но и способствуют его широкому внедрению в качестве предпочтительного формата хранения в современных архитектурах данных.
Что такое HDFS, Hive и Hive Metastore?
HDFS (Hadoop Distributed File System) — Это распределённая файловая система от Apache Hadoop. Она хранит огромные объемы данных, разбивая их на части и распределяя по множеству машин (узлов). Каждый файл делится на блоки (обычно по 128 МБ) и хранится с копиями на разных серверах. Например, при загрузке 1 ТБ данных в HDFS, система может разбить его на 8000 блоков по 128 МБ и разложить по 100 серверам с резервными копиями.
Apache Hive — Это система, которая позволяет писать SQL-подобные запросы к данным, хранящимся в HDFS (и не только). Она делает анализ «как в SQL», но работает с большими данными.
Например, есть запрос: SELECT * FROM sales WHERE year = 2023
Hive преобразует его во внутренний код (например, в MapReduce или Spark) и запускает обработку данных в HDFS. Возвращает результат как обычная СУБД.
Hive Metastore — Это метадата-сервис (обычно обычная база данных типа MySQL/PostgreSQL), который Hive использует для хранения следующей информации о таблицах:
- Названия и схемы таблиц.
- Где физически лежат файлы (пути в HDFS).
- Секционирование, бакетирование, формат файлов (Parquet и др.).
- Статистика таблиц (например, число строк, размеры и т.д.).
DWH -> Data Lake -> Data LakeHouse — Эволюция аналитических систем
Сначала данных мало — хватает монолитных решений — SMP (Symmetric Multi-Processing). С ростом объёма и числа пользователей переходят на масштабируемые DWH (Greenplum, Vertica и др.) с архитектурой shared-nothing (MPP, Massively Parallel Processing).
Но со временем растёт не только объём, но и разнообразие нагрузок, поэтому на смену классическим подходам приходит disaggregated storage — вычисления и хранение разделяются.
Такой подход реализуют Trino, Presto, Spark. Даже классические DWH адаптируются: Vertica Eon, ClickHouse с S3 и т.д.
Интегрированные архитектуры (Monolithic DWH) — Классическая архитектура (Coupled)
Хранилище данных и вычислительные ресурсы находятся в одном физическом пространстве или в одном программном решении (CPU, память и диск на одном сервере или кластере) — Greenplum, Teradata
Плюсы:
- Высокая производительность для специфических задач (в том числе за счёт отсутствия задержки из-за сетевых взаимодействий между компонентами)
- Упрощенная настройка и управление (по принципу “единого окна” + нет необходимости распыления в сторону освоения бОльшего технологического стека)
Минусы:
- Масштабирование требует закупки дорогого железа.
- Меньшая гибкость и отказоустойчивость.
Разделенные архитектуры (Decoupled DWH) — Современная архитектура
В разделенных архитектурах компоненты хранения (storage) и вычисления (compute) разделены, что позволяет масштабировать их независимо друг от друга (Redshift, BigQuery, DataBricks)
Хранение — дешёвые object storage (S3, GCS, Azure Blob).
Вычисления — Spark, Trino, Presto, Flink, Dask и др.
Плюсы:
- Возможность увеличивать хранилище без необходимости увеличения вычислительных мощностей, и наоборот
- Оптимизация затрат за счёт возможности интерактивного и оперативного конфигурирования слоёв обработки и хранения
- Множественные движки под разные задачи и разные нагрузки
- Меньше дублирования данных
Самым большим преимуществом Data Lakehouse на Iceberg может быть возможность дешево начать строить хранилище, и затем, при необходимости, масштабировать его.
Schema-on-read vs Schema-on-write
Schema-on-write — схема (структура данных) определяется перед записью данных. То есть данные не могут быть загружены, если они не соответствуют заранее определённой схеме.
Примеры: традиционные DWH, такие как Oracle, Teradata, Snowflake. В Apache Iceberg также используется Schema-on-write.
Плюсы:
- Жёсткий контроль качества данных.
- Упрощает аналитику (все данные уже «в правильном» формате).
- Предсказуемое поведение при запросах.
Минусы:
- Сложность работы с semi-structured или нестабильными источниками.
- Нужно поддерживать миграции схем.
- Замедляет ingestion-пайплайны (жёсткие проверки на этапе записи).
Schema-on-read — данные загружаются «как есть», без строгой схемы. Структура интерпретируется только в момент чтения. Используется в основном для Data Lake (озер данных).
Примеры: Data Lake на базе HDFS, S3, с файлами Parquet, JSON, Avro и чтением через Spark, Presto, Trino.
Плюсы:
- Высокая гибкость, особенно при ingest’е разнородных данных.
- Можно хранить данные «как есть», не преобразуя.
- Удобно для Data Science и ML, где нужна работа с «сырыми» данными.
Минусы:
- Отложенная валидация может привести к ошибкам на этапе анализа.
- Производительность запросов может страдать.
- Нет единообразной схемы = труднее обеспечивать качество и совместимость.
Что такое Apache Iceberg? Архитектура и внутреннее устройство таблиц
История создания Apache Iceberg
Apache Iceberg — это формат таблиц, созданный в 2017 году Райаном Блю и Даниэлем Уиксом из Netflix. Он возник из необходимости преодолеть проблемы с производительностью, согласованностью и другими трудностями, присущими формату таблиц Hive. В 2018 году проект был открыт и передан в Фонд программного обеспечения Apache, после чего к нему начали подключаться и другие организации, включая Apple, Dremio, AWS, Tencent, LinkedIn и Stripe. С тех пор множество других компаний внесли вклад в развитие проекта.
Netflix, создавая то, что в итоге стало форматом Apache Iceberg, пришёл к выводу, что многие проблемы формата Hive происходят из одного простого, но фундаментального изъяна: каждая таблица отслеживается в виде директорий и поддиректорий, что ограничивает уровень детализации, необходимый для обеспечения согласованности, лучшей поддержки параллельной обработки и ряда других возможностей, характерных для хранилищ данных.
Ключевые понятия и концепция устройства Apache Iceberg
В этом разделе будут описаны основные термины и понятия Apache Iceberg (мой краткий конспект после изучения темы, в основном использовал книгу «Apache Iceberg. Полное руководство» — очень рекомендую к покупке и к прочтению).
- Транзакционность обеспечивается в рамках 1 таблицы. Гарантии параллелизма обеспечиваются каталогом, поскольку обычно этот механизм имеет встроенные гарантии ACID.
- Эволюция разделов (Partition evolution) — изменение секционирования/партицирования в существующей таблице.
- Шаблоны обновления таблицы на уровне записей:
- Копирование при записи (Copy-On-Write, COW)
- Слияние при чтении (Merge-On-Read, MOR)
- Time Travel (Путешествие во времени): хранятся snapshots таблиц — то есть доступна информация об историческом состоянии таблицы. Можно выполнить запрос к snapshot в определенный момент времени в прошлом.
Time Travel в Apache Iceberg позволяет:- Point-in-time queries: извлечение данных в том виде, в котором они существовали в любой момент времени.
- Change tracking: сравнение версий исторических данных для анализа тенденций.
- Откат и восстановление (rollback & recovery): можно вернуться к более старому снимку, чтобы исправить ошибки).
- Audit and compliance: обеспечение целостности данных через запрос старых состояний.
- Schema evolution (эволюция схемы). Позволяет выполнять операции над колонками и таблицами:
- Add (new column or nested struct)
- Drop
- Rename
- Update
- Reorder
- Sort Order Evolution — обновление sort order в существующей таблице
Таблицы Apache Iceberg имеют три разных слоя:
- Слой каталога
- Слой метаданных
- Слой данных, который в свою очередь содержит:
- Файлы самих данных
- Файлы удаления
Файлы удаления (Delete Files)
В Data Lakes не поддерживается обновление записей в самих файлах (можно перезаписать только сами файлы).
В Iceberg вместо обновления записей в файлах производится запись в новый файл, который может быть копией старого файла с внесенными изменениями (copy-on-write, COW) или содержать только изменения (merge-on-read, MOR — слияние при чтении, когда файлы читаются механизмами обработки запросов и затем объединяются).
Существуют 2 типа файлов удаления:
- Файлы позиционного удаления, которые содержат:
- Путь к файлу
- Номер строки в этом файле
- Файлы удаления по равенству — подходит, когда есть первичный ключ (когда каждая запись имеет уникальный идентификатор).
Слой метаданных содержит в себе:
- Файлы манифестов
- Списки манифестов
- Файлы метаданных
Файлы манифестов — отслеживают файлы в слое данных (т.е. файлы данных и файлы удаления), а также хранят дополнительные сведения и статистику о каждом файле.
Один файл манифеста будет содержать информацию только о файлах данных или только о файлах удаления.
Каждый файл манифеста содержит информацию о подмножестве файлов данных, такую как:
- сведения о принадлежности к разделам
- о количестве записей
- о нижней и верхней границах значений в столбцах
Один файл манифеста хранит статистику для нескольких файлов данных и это существенно снижает необходимость открытия множества файлов данных и способствует повышению производительности.
Статистическая информация записывается во время операции записи в каждое подмножество файлов данных, отслеживаемых файлов манифеста.
В Iceberg файлы манифестов имеют формат AVRO.
Списки манифестов
Список манифестов — это snapshot таблицы Iceberg.
Для snapshot список манифестов содержит: перечень всех файлов манифестов (включая местоположение, разделы, верхние и нижние границы столбцов разделов).
Списки манифестов имеют формат AVRO.
Файлы метаданных
Списки манифестов отслеживаются с помощью файлов метаданных:
- Схема таблицы
- Сведения о partitions
- Сведения о snapshots
- Какой снимок является текущим
Каждый раз, когда в таблицу Iceberg вносятся изменения, создается новый файл метаданных, который атомарно регистрируется, как последняя версия файла метаданных через каталог.
Puffin-файлы
Хранят статистику и индексы данных в таблице, помогающие повысить производительность.
Фильтр Блума (Bloom Filter)
Это пробабилистическая структура данных, предназначенная для проверки принадлежности элемента множеству с возможностью ложноположительных ответов, но без ложноотрицательных. Фильтр запоминает много вещений, но не точно, чтобы занимать очень мало места.
Blob — это тип данных, который используется для хранения больших объемов двоичной информации.
Theta Sketch — структура, позволяющая вычислить приблизительное количество разных значений столбца в заданном множестве записей и тем самым ускорить вычисления и уменьшить потребление ресурсов. Используется в сценариях приблизительных вычислений.
Фильтры Блума позволяют узнать, присутствует ли значений в наборе данных. Это вероятность присутствия некоторого значения X в наборе данных. Помогают избежать ненужного сканирования данных. Чтобы сделать их более точными, можно увеличить чувствительность хеш-функций и размер битовой линейки.
Чем больше данных добавляется, тем большего размера требуется фильтру Блума для сохранения точности и тем больше места ему потребуется для хранения. Важно найти правильный баланс.
Каталог (Catalog)
В Apache Iceberg каталог (catalog) — это компонент, который управляет регистрацией и хранением метаданных таблиц.
Он отвечает за:
- Хранение метаинформации о таблицах — например, где находится текущий файл метаданных (
v3.metadata.json
). - Разрешение имени таблицы — превращает логическое имя таблицы (
db1.table1
) в путь к метаданным. - Операции управления таблицами — создание, удаление, обновление и поиск таблиц.
Каталоги могут быть на основе файлов (HDFS), либо на основе служб (все остальные).
Типы каталогов:
- REST
- Hive Metastore
- JDBC
- Nessie
- LakeFS
- AWS Glue Data Catalog
- Google Cloud Data Catalog
- Data Bricks Unity Catalog
- Azure Purview
Всякий раз, когда таблица Iceberg изменяется, генерируется новый файл metadata.json
:
- v1.metadata.json
- v2.metadata.json
- v3.metadata.json — текущий файл
Скрытое секционирование — в Iceberg не нужно добавлять явный столбец для секционирования таблицы. Слой метаданных позволяет механизму запросов создавать более оптимальные планы выполнения запросов.
Iceberg автоматически отслеживает секционирование, в отслеживании полагается не на физическое местоположение файлов, а на диапазон значений в разделах на уровне моментального снимка и манифеста.
К целевому столбцу для секционирования можно использовать встроенные преобразования:
- year
- month
- day
- hour
- truncate
- bucket
Пример того, как работает Apache Iceberg
Пример запроса SELECT:
- Чтение
metadata.json
→ находим активный snapshot - Чтение
manifest list
→ получаем список manifest-файлов - Чтение
manifest
→ знаем, какие data-файлы нужны - Чтение только нужных файлов (по фильтрам и partition pruning)
Пример INSERT:
- Данные пишутся в новые data-файлы
- Создается новый
manifest
с этими файлами - Создается новый
manifest list
- Появляется новый snapshot
- Обновляется
metadata.json
→ теперь он ссылается на новый snapshot
Наглядная схема, что происходит с таблицей Iceberg при операциях CRUD
Представленные схемы могут изменятся, в случае разных сценариев управления записями (COW/MOR).
CREATE TABLE
Создаётся структура таблицы, включая:
- Metadata file — основной файл метаданных, содержащий схему, список снапшотов и другие параметры.
- Таблица начинается без данных, но уже готова к записи.
INSERT
Добавление новых строк данных:
- Данные пишутся в Parquet.
- Создаётся новый manifest file (перечисляет добавленные data files).
- Обновляется metadata file, создавая новый snapshot таблицы.
- Старые снапшоты остаются — поддержка time travel.
MERGE INTO / UPSERT
Обновление или вставка:
- Iceberg не модифицирует файлы на месте.
- Выполняется как delete + insert:
- Удаляются старые записи (логически — через фильтрацию).
- Добавляются новые строки в новые файлы.
- Создаётся новый snapshot с обновлёнными манифестами и файлами.
Методы оптимизации Apache Iceberg
Существуют различные стратегии оптимизации производительности таблиц Iceberg:
- Compacting small files — Уплотнение и сортировка данных (rewriteDataFiles)
- Copy-On-Write / Merge-On-Read — в зависимости от задач и целей хранилища, можно выбирать разные стратегии
- Partitioning data/Скрытое секционирование
- Sorting data — сортировка данных
- Expiring old snapshots — Удаление старых снимков таблиц
- Removing orphaned files — Удаление файлов-«сирот» (то есть файлов, на которые не ссылаются файлы с метаданными). Такие файлы образуются из-за неудачных попыток завершить транзакцию.
- Слой метаданных помогает создавать более оптимальные планы выполнения запросов
- Сбор метрик
- Фильтры Блума
Уплотнение — метод rewriteDataFiles
Потоковое поглощение данных может создавать множество мелких файлов, которые замедляют запросы. Регулярное сжатие этих файлов в более крупные может значительно повысить производительность запросов и сократить накладные расходы на метаданные.
Три стратегии уплотнения:
- Binpack
- Sort
- zOrder
При уплотнении всегда учитывается текущая спецификация раздела, поэтому при перезаписи данных, записанных согласно старой спецификации, к ним будут применены новые правила секционирования.
Binpack — чистое уплотнение без применения каких-либо других действий. Самая быстрая стратегия уплотнения. BinPack переписывает содержимое файлов небольшого размера в файл большого, желаемого размера, тогда как стратегии sort и zOrder дополнительно предусматривают сортировку данных перед выделением групп файлов для записи.
Если в настройках таблицы Iceberg установлен порядок сортировки, то даже при использовании стратегии binpack этот порядок будет использоваться в рамках одной задачи для («локальной») сортировки данных. Использование стратегий Sort и zOrder позволит отсортировать данные до того, как механизм запросов распределит записи по различным задачам, оптимизируя кластеризацию данных по разным задачам.
Сортировка (Sort)
Сортировка («кластеризация») данных имеет особое значение для запросов: она помогает ограничить количество файлов, которые необходимо просканировать, чтобы получить данные, соответствующие критериям в запросе.
Сортировка позволяет сосредоточить данные с похожими значениями в меньшем количестве файлов, что позволяет эффективнее планировать запросы.
Стратегия уплотнения sort предусматривает сортировку данных по всем файлам, на которые воздействуют задание.
Центральным вопросом при выборе метода sort является: какие запросы формируют конечные пользователи к таблице Iceberg.
zOrder (z-упорядочение)
Когда сразу несколько полей оказываются одинаково важными (данные сортируются по нескольким точкам данных).
zOrder работает через когорты. Мы разбиваем наши данные на квадранты и тем самым сканируем файлы из этих квадрантов.
Когда использовать zOrder:
- Высокая кардинальность
- Частые фильтрации по 2 или более полям
- Большие объемы данных
Expire Snapshots — Устаревание старых снимков
Операция по удалению старых snapshot’ов, manifest’ов и data-файлов, если они больше не используются. Используется для очистки и уменьшения storage footprint.
Rewrite Manifests
rewrite manifests
— уплотняет manifest-файлы
COW (copy-on-write) & MOR (merge-on-read) — что и когда выбрать?
Существует три подхода к обработке изменений на уровне записей.
COW: Переписывает целые файлы данных для обновлений/удалений, обеспечивая высокую согласованность и быстрое чтение, но с более высокими затратами на запись и хранение.
То есть прошлый файл данных, например, File003.parquet копируется в новый файл File008.parquet. При этом старые снепшоты ссылаются на старый файл, а новый снимок таблицы уже использует измененный файл. То есть оба файла остаются в системе. Под перезаписью понимается, что перезаписывается копия файла.
MOR: не производится перезапись всего файла данных, а изменения в записях фиксируются в файле удаления.
Кейс «Слияние при чтении (Удаление)»
Кейс «Слияние при чтении (Обновление)»
Таблица «Стратегии обработки изменений на уровне записей в Apache Iceberg»
Стратегия обработки изменений | Скорость чтения | Скорость записи | Лучшая практика |
---|---|---|---|
Копирование при записи | Самое быстрое чтение | Самое медленное изменение/удаление | |
Слияние при чтении (позиционное удаление) | Быстрое чтение | Быстрое изменение/удаление | Регулярное уплотнение для минимизации стоимости чтения |
Слияние при чтении (удаление по равенству) | Медленное чтение | Самое быстрое изменение/удаление | Максимально частое уплотнение для минимизации стоимости чтения |
По умолчанию используется COW (copy-on-write).
Выбор типа файлов удаления обычно определяется в настройках механизма для конкретных случаев использования, а не в настройках таблицы.
В одних ситуациях выгоднее использовать один вариант, а в других — другой.
Свойства таблицы Iceberg, которые определяют выбор стратегии COW и MOR:
write.delete.mode
— транзакции удаленияwrite.update.mode
— транзакции измененияwrite.merge.mode
— транзакции слияния
Spark Engine реализуется в рамках проекта Apache Iceberg, поэтому он поддерживает все свойства таблиц Iceberg.
Работая с иными механизмами:
- свойства таблицы могут не поддерживаться. Реализация поддержки зависит от механизма.
- При использовании стратегии MOR, убедитесь, что используемые вами механизмы запросов способны читать файлы удаления.
Варианты использования COW & MOR
- Copy-on-Write (COW): Идеально подходит для сценариев с частыми операциями чтения и редкими обновлениями или удалениями. Примерами служат аналитические рабочие нагрузки с большим объемом чтения, где производительность запросов имеет решающее значение.
- Merge-on-Read (MOR): подходит для сценариев с частыми обновлениями или удалениями и менее частыми операциями чтения. Примерами служат транзакционные рабочие нагрузки с большим объемом записи, где производительность записи и эффективность хранения являются приоритетными.
- Гибридный подход: Хотя COW и MOR часто представляются как взаимоисключающие, некоторые реализации допускают гибридный подход, когда оба метода могут использоваться в одной таблице. Это может быть полезно в сценариях, где нам нужно сбалансировать производительность чтения и записи на основе конкретных вариантов использования.
Orphan files
Потерянные файлы — это файлы и артефакты, которые накапливаются в каталоге данных таблицы, но не отслеживаются в дереве метаданных, так как были созданы заданиями, которые завершились неудачей.
! Очень ресурсоемкий процесс, его не рекомендуется часто запускать.
Фильтры Блума
Включить запись фильтра Блума для определенного столбца в файлах Parquet можно в свойствах таблицы.
1 2 3 4 |
ALTER TABLE catalog.MyTable SET TBLPROPERTIES ( 'write.parquet.bloom-filter-enabled.column.col1' = true, 'write.parquet.bloom-filter-max-bytes' = 1048576 ); |
После этого механизмы обработки запросов смогут воспользоваться преимуществами фильтра Блума и ускорить чтение, пропуская файлы данных, в которых фильтры Блума четко указывают на отсутствие искомых значений.
Как установить Apache Iceberg локально на Docker?
Заходим на сайт iceberg.apache.org/spark-quickstart
Там инструкция на английском, как при помощи docker-compose развернуть iceberg.
Предварительная настройка
Source: https://iceberg.apache.org/spark-quickstart/ — отсюда берем файл docker-compose.yml
В него прописываем локальные креды (логин/пароль и т.д.)
Запускаем установку
sudo docker-compose up
Jupyter Server
http://localhost:8888/tree — Jupyter скачиваются сюда notebooks:
- Iceberg — An Introduction to the Iceberg Java API.ipynb
- Iceberg — Berlin Buzzwords 2023.ipynb
- Iceberg — Getting Started.ipynb
- Iceberg — Integrated Audits Demo.ipynb
- Iceberg — Table Maintenance Spark Procedures.ipynb
- Iceberg — View Support.ipynb
- Iceberg — Write-Audit-Publish (WAP) with Branches.ipynb
- PyIceberg — Getting Started.ipynb
- PyIceberg — Write support.ipynb
Minio
http://127.0.0.1:9001 — WebUI
В результате вы получите готовую среду для тестирования работы с Apache Iceberg и Apache Spark в виде следующих контейнеров:
tabulario/spark-iceberg
— здесь развернется Jupyter Notebooks с готовыми примерами- minIO:
minio/mc
— MinIO Client (CLI)minio/minio
— сервер (S3-совместимое хранилище)
apache/iceberg-rest-fixture
— REST-каталог Iceberg
Также свои эксперименты я буду выкладывать у себя на github iceberg-quickstart.
Техническая архитектура Apache Iceberg
Введение в спецификацию таблиц Iceberg
Версионирование Iceberg
Версии 1 и 2 спецификации Iceberg завершены и приняты сообществом.
Версия 3 находится в активной разработке и ещё не принята официально. Почитать подробно про новые фичи можно в блоге dremio What’s New in Apache Iceberg Format Version 3?
Плюс видео с митапа (YouTube): Apache Iceberg V3 Ahead
Версия 3 спецификации Iceberg расширяет типы данных и существующие структуры метаданных для добавления новых возможностей:
- Новые типы данных: временные метки с наносекундной точностью (с часовым поясом), неизвестный тип, вариант, геометрия, география
- Поддержка значений по умолчанию для столбцов
- Преобразования с несколькими аргументами для разбиения на разделы и сортировки
- Отслеживание родословной строк (Row Lineage tracking)
- Бинарные векторы удаления (Binary deletion vectors)
- Ключи шифрования таблиц (Table encryption keys)
Таблицы Apache Iceberg
- Serializable isolation (Сериализуемая изоляция) — чтения изолированы от параллельных записей и всегда используют зафиксированный снимок данных таблицы. Записи поддерживают удаление и добавление файлов в одной операции и никогда частично не видимы. Читатели не будут получать блокировки.
- Speed (Скорость) — операции используют O(1) удалённые вызовы для планирования файлов на сканирование, а не O(n), где n растёт с размером таблицы, например, числом разделов или файлов.
- Scale (Масштаб) — планирование заданий в основном выполняется на стороне клиентов и не блокируется централизованным хранилищем метаданных. Метаданные содержат информацию, необходимую для оптимизации на основе затрат.
- Evolution (Эволюция) — таблицы поддерживают полную эволюцию схемы и спецификации секционирования (partition). Эволюция схемы включает безопасное добавление, удаление, переупорядочивание и переименование столбцов, включая вложенные структуры.
- Dependable types (Надёжные типы) — таблицы предоставляют чётко определённую и надёжную поддержку основного набора типов.
- Storage separation (Разделение хранения) — секционирование будет конфигурацией таблицы. Чтения планируются по предикатам значений данных, а не значений разделов. Таблицы поддерживают эволюцию схем разбиения.
- Formats (Форматы) — базовые форматы файлов данных поддерживают идентичные правила эволюции схемы и типов. Доступны форматы, оптимизированные как для чтения, так и для записи.
Структура snapshots Iceberg
Этот формат таблиц отслеживает отдельные файлы данных в таблице, а не директории. Это позволяет записывающим процессам создавать файлы данных на месте и добавлять их в таблицу только при явной фиксации.
Состояние таблицы хранится в файлах метаданных. Все изменения состояния таблицы создают новый файл метаданных и заменяют старый атомарной подменой. Файл метаданных таблицы отслеживает схему таблицы, конфигурацию секционирования (партицирования), пользовательские свойства и снимки (snapshots) содержимого таблицы. Снимок представляет состояние таблицы на определённый момент времени и используется для доступа ко всему набору файлов данных таблицы.
Файлы данных в снимках отслеживаются одним или несколькими манифестными файлами, содержащими строку для каждого файла данных в таблице, данные разбиения файла и его метрики. Данные снимка — объединение всех файлов в его манифестах. Манифесты переиспользуются между снимками, чтобы избежать переписывания медленно изменяющихся метаданных. Манифесты могут отслеживать любые подмножества таблицы и не связаны с разделами.
Манифесты, составляющие снимок, хранятся в файле списка манифестов. Каждый список манифестов содержит метаданные о манифестах, включая статистику по разделам и количество файлов данных. Эти данные используются, чтобы избежать чтения ненужных манифестов при выполнении операции.
Оптимистичная параллельность (Optimistic Concurrency)
Атомарная замена одного файла метаданных таблицы на другой обеспечивает основу сериализуемой изоляции. Читатели используют снимок, актуальный на момент загрузки метаданных таблицы, и не затрагиваются изменениями, пока не обновят метаданные.
Записывающие процессы создают файлы метаданных таблицы оптимистично, предполагая, что текущая версия не изменится до их фиксации. После создания обновления процесс фиксирует его, заменяя указатель файла метаданных таблицы с базовой версии на новую.
Процесс записи выглядит следующим образом:
Если снимок, на основе которого создано обновление, больше не актуален, записывающий процесс должен повторить попытку, основываясь на новой текущей версии. Некоторые операции поддерживают повторную попытку путём повторного применения изменений метаданных и фиксации при определённых условиях. Например, изменение, которое переписывает файлы, может быть повторно применено к новому снимку таблицы, если все переписанные файлы по-прежнему присутствуют в таблице.
Условия, необходимые для успешной фиксации записи, определяют уровень изоляции. Записывающие процессы могут выбирать, что проверять, и обеспечивать разные гарантии изоляции.
Схема Optimistic Concurrency (когда возникает конфликт фиксации метаданных и как он разрешается)
Цель каталога — обеспечить атомарную фиксацию файла метаданных. Реализовано это через внешнюю транзакционную систему (каталог).
Когда автор пытается опубликовать новую версию, он выполняет операцию сравнения и обмена (CAS) над файлом metadata.json
.
Порядковые номера (Sequence Numbers)
Относительный возраст файлов данных и удаления зависит от порядкового номера, назначаемого каждому успешному коммиту. При создании снимка для фиксации ему оптимистично присваивается следующий порядковый номер, который записывается в метаданные снимка. Если коммит неудачен и требует повторной попытки, порядковый номер переназначается и записывается в новые метаданные снимка.
Все манифесты, файлы данных и удаления, созданные для снимка, наследуют порядковый номер снимка. Метаданные манифеста в списке манифестов хранят порядковый номер манифеста. Новые записи файлов данных и метаданных записываются с null вместо порядкового номера, который заменяется на номер манифеста при чтении. Когда файл данных или удаления записывается в новый манифест (как «существующий»), унаследованный порядковый номер записывается, чтобы он не изменился после первого наследования.
Наследование порядкового номера из метаданных манифеста позволяет один раз записать новый манифест и переиспользовать его при повторных попытках фиксации. Чтобы изменить порядковый номер при повторной попытке, необходимо переписать только список манифестов — что в любом случае произойдёт с последним набором манифестов.
Удаления на уровне строк
Удаления на уровне строк хранятся в файлах удаления.
Существует два способа кодирования удаления строки:
- Удаления по позиции (position deletes) отмечают строку как удалённую по пути к файлу данных и позиции строки в файле
- Удаления по равенству (equality deletes) отмечают строку как удалённую по одному или нескольким значениям столбцов, например, id = 5
Как и файлы данных, файлы удаления отслеживаются по разделам. В общем случае файл удаления должен применяться к более старым файлам данных с тем же разделом; подробности см. в разделе Планирование сканирования. Метрики столбцов можно использовать для определения, перекрываются ли строки файла удаления с содержимым файла данных или диапазоном сканирования.
Операции файловой системы
Iceberg требует от файловой системы поддержки следующих операций:
- Запись на месте — файлы не перемещаются и не изменяются после записи
- Чтения с возможностью seek — форматы файлов данных требуют поддержки seek
- Удаления — таблицы удаляют файлы, которые больше не используются
Эти требования совместимы с объектными хранилищами, такими как S3.
Таблицы не требуют произвольных записей с доступом по смещению. После записи файлы данных и метаданных неизменяемы до их удаления.
Переименование не требуется, за исключением таблиц, использующих атомарное переименование для реализации операции фиксации новых файлов метаданных.
Термины
- Схема (Schema) — Имена и типы полей в таблице.
- Спецификация разбиения (Partition spec) — Определение того, как значения разбиений выводятся из полей данных.
- Снимок (Snapshot) — Состояние таблицы в определённый момент времени, включая набор всех файлов данных.
- Список манифестов (Manifest list) — Файл, содержащий перечень файлов манифестов; один список на каждый снимок.
- Манифест (Manifest) — Файл, содержащий перечень файлов данных или файлов удаления; подмножество снимка.
- Файл данных (Data file) — Файл, содержащий строки таблицы.
- Файл удаления (Delete file) — Файл, кодирующий строки таблицы, удалённые по позиции или по значениям данных.
Разбиение на разделы (Partitioning)
Partitioning разделяет данные на более мелкие подмножества, что позволяет получить доступ только к тем данным, которые необходимы для запроса, вместо чтения всего набора данных.
Iceberg поддерживает множество стратегий разбиения, например:
- Range partitioning (Разбиение по диапазону): разделяет данные на основе диапазона значений в столбце разбиения (partition column), например, определенных дат, числовых значений или строковых записей.
- Hash partitioning (Разделение по хэшу): применяет хэш-функцию к ключу раздела для разделения данных.
- Truncate partitioning (Секционирование с помощью усечения): Усекает значения столбца секционирования и группирует данные. Например, усечение почтовых индексов 533405, 533404, 533689, 533098, 535209 и 535678 до 3 цифр группирует данные до «533» и «535».
- List partitioning (Секционирование по списку): сопоставляет значения ключей раздела со значениями в списке, разделяя данные соответствующим образом. Подходит для категориальных значений в столбце раздела. Например, разделение компаний-производителей ноутбуков на группы, такие как «Lenovo», «Apple» и «HP».
Файлы данных хранятся в манифестах вместе с кортежем значений разбиений, который используется при сканировании для фильтрации файлов, не содержащих записей, соответствующих предикату фильтра сканирования. Значения разбиения для файла данных должны быть одинаковыми для всех записей, содержащихся в этом файле. (Манифесты могут содержать файлы данных из любых разделов, при условии, что спецификация разбиения одинакова для этих файлов.)
Таблицы настраиваются с помощью спецификации разбиения, которая определяет, как формировать кортеж значений разбиения из записи. Спецификация разбиения содержит список полей, каждое из которых включает:
- Идентификатор исходного столбца или список идентификаторов исходных столбцов из схемы таблицы
- Идентификатор поля разбиения, который используется для идентификации поля разбиения и уникален в рамках одной спецификации разбиения. В метаданных таблиц версии 2 он уникален во всех спецификациях разбиения
- Преобразование, применяемое к исходному(ым) столбцу(ам) для получения значения разбиения
- Имя разбиения
Исходные столбцы, выбранные по идентификаторам, должны быть примитивного типа и не могут находиться внутри карты (map) или списка (list), но могут быть вложены в структуру (struct).
Спецификации разбиения фиксируют преобразование данных таблицы в значения разбиения. Это используется как для преобразования предикатов в предикаты разбиений, так и для преобразования самих значений данных. Получение предикатов разбиения из предикатов столбцов таблицы позволяет отделить логические запросы от физического хранения: схема разбиения может изменяться, при этом корректные фильтры разбиения всегда выводятся из предикатов столбцов. Это упрощает запросы, поскольку пользователю не нужно указывать как логические, так и разбиенческие предикаты.
Поля разбиения с неизвестным преобразованием могут быть прочитаны с игнорированием этих полей при фильтрации файлов данных во время планирования сканирования. В версиях 1 и 2 считыватели должны игнорировать поля с неизвестными преобразованиями при чтении; это поведение обязательно в версии 3. Записывающим процессам запрещено фиксировать данные с использованием спецификации разбиения, содержащей поле с неизвестным преобразованием.
Две спецификации разбиения считаются эквивалентными, если у них одинаковое количество полей, и для каждого соответствующего поля совпадают идентификаторы исходных столбцов, определение преобразования и имя разбиения. Записывающие процессы не должны создавать новую спецификацию разбиения, если уже существует совместимая спецификация, определённая в таблице.
Идентификаторы полей разбиения должны переиспользоваться, если в существующей спецификации разбиения уже имеется эквивалентное поле.
Преобразования для разбиений (Partition Transforms)
Название преобразования | Описание | Типы исходных данных | Тип результата |
---|---|---|---|
identity |
Исходное значение без изменений | Любые, кроме geometry , geography и variant |
Тип исходного значения |
bucket[N] |
Хэш от значения по модулю N (см. ниже) | int , long , decimal , date , time , timestamp , timestamptz , timestamp_ns , timestamptz_ns , string , uuid , fixed , binary |
int |
truncate[W] |
Значение, усечённое до ширины W (см. ниже) | int , long , decimal , string , binary |
Тип исходного значения |
year |
Извлечение года из даты или временной метки, в виде количества лет с 1970 года | date , timestamp , timestamptz , timestamp_ns , timestamptz_ns |
int |
month |
Извлечение месяца из даты или временной метки, в виде количества месяцев с 1970-01-01 | date , timestamp , timestamptz , timestamp_ns , timestamptz_ns |
int |
day |
Извлечение дня из даты или временной метки, в виде количества дней с 1970-01-01 | date , timestamp , timestamptz , timestamp_ns , timestamptz_ns |
int |
hour |
Извлечение часа из временной метки, в виде количества часов с 1970-01-01 00:00:00 | timestamp , timestamptz , timestamp_ns , timestamptz_ns |
int |
void |
Всегда возвращает null |
Любые | Тип исходного значения или int |
Все преобразования должны возвращать null, если входное значение — null.
Преобразование void может использоваться для замены преобразования в существующем поле разбиения, чтобы фактически исключить это поле в таблицах версии 1.
Подробности о преобразовании truncate (усечение)
Тип | Параметр конфигурации | Спецификация усечения | Примеры |
---|---|---|---|
int |
W, ширина | v - (v % W) (остаток должен быть положительным¹) |
W=10: 1 → 0, -1 → -10 |
long |
W, ширина | v - (v % W) (остаток должен быть положительным¹) |
W=10: 1 → 0, -1 → -10 |
decimal |
W, ширина (без масштаба) | scaled_W = decimal(W, scale(v)); v - (v % scaled_W) [¹,²] |
W=50, s=2: 10.65 → 10.50 |
string |
L, длина | Подстрока длины L: v.substring(0, L) [³] |
L=3: «iceberg» → «ice» |
binary |
L, длина | Подмассив длины L: v.subarray(0, L) [⁴] |
L=3: \x01\x02\x03\x04\x05 → \x01\x02\x03 |
Примечания:
- Остаток
v % W
должен быть положительным. Для языков, где%
может давать отрицательные значения, правильная функция усечения:v - (((v % W) + W) % W)
- Ширина
W
, используемая для усеченияdecimal
значений, применяется с использованием масштаба столбца типаdecimal
, чтобы избежать дополнительных (и потенциально конфликтующих) параметров. - Строки усекаются до корректной строки в кодировке UTF-8, содержащей не более L символов (code points).
- В отличие от строк, бинарные значения не имеют предполагаемой кодировки и усекаются до L байт.
- Эволюция секционирования (Partition Evolution)
Секционирование таблицы может изменяться со временем путём добавления, удаления, переименования или изменения порядка полей секционирования (partition spec fields).
- Изменение спецификации секционирования создаёт новую спецификацию, идентифицируемую уникальным ID, которая добавляется в список всех спецификаций таблицы и может быть установлена как спецификация по умолчанию.
- При эволюции спецификации ID полей секционирования не должны изменяться, так как эти ID используются в качестве идентификаторов полей кортежей секционирования в манифест-файлах.
Различия по версиям
В версии v2:
- ID полей секционирования должны отслеживаться явно для каждого поля.
- Новые ID присваиваются на основе последнего используемого ID в метаданных таблицы.
В версии v1:
- ID не отслеживались, а присваивались последовательно начиная с 1000 в эталонной реализации.
- Это привело к проблемам при чтении метаданных из манифестов разных спецификаций: поля с одинаковыми ID могли иметь разные типы данных.
Рекомендации для эволюции секционирования в v1:
- Не изменяйте порядок полей секционирования.
- Не удаляйте поля секционирования напрямую — вместо этого замените их преобразование на void (делает поле неактивным).
- Добавляйте новые поля только в конец предыдущей спецификации.
Сортировка (Sorting)
Пользователи могут сортировать данные внутри партиций по столбцам, чтобы повысить производительность. Информация о сортировке данных указывается для каждого data- или delete-файла через порядок сортировки (sort order).
Порядок сортировки (Sort Order) включает:
- ID порядка сортировки (sort order id)
- Список полей сортировки (sort fields), где порядок этих полей определяет порядок применения сортировки к данным.
Каждое поле сортировки содержит:
- ID исходного столбца (или список ID) из схемы таблицы
- Преобразование (transform), которое применяется к исходному столбцу для получения значений сортировки (используется тот же механизм, что и в partition transforms)
- Направление сортировки — только
asc
(по возрастанию) илиdesc
(по убыванию) - Порядок NULL-значений —
nulls-first
(NULL сначала) илиnulls-last
(NULL в конце)
Дополнительные сведения:
- ID 0 зарезервирован для «без сортировки» (unsorted).
- Для чисел с плавающей точкой сортировка должна соответствовать следующему порядку:
-NaN < -Infinity < -value < -0 < 0 < value < Infinity < NaN
Это поведение соответствует сравнению типов float в Java.
Связь с файлами:
- Каждый data- или delete-файл связан с порядком сортировки через ID порядка, указываемый в манифест-файле.
- Таблица должна объявить все доступные порядки сортировки, чтобы их можно было использовать по ID.
- Таблица может быть сконфигурирована с порядком сортировки по умолчанию, который рекомендуется использовать при записи новых данных. Однако это не обязательно, особенно если сортировка слишком затратна (например, при потоковой записи).
Плюсы и минусы Apache Iceberg
todo
Проблемы, возникающие при эксплуатации Apache Iceberg
todo
Что такое Trino и как он работает с Iceberg?
Trino — массивно-параллельный аналитический SQL-движок для обработки больших объемов данных из разных источников (MPP SQL). Он не хранит данные. Trino работает с Iceberg через Iceberg Connector. Таблицы создаются/читаются напрямую Trino через SQL.
Он масштабируемый и ориентирован на MPP (Massively Parallel Processing)
Архитектура (Компоненты Trino)
Trino: Coordinator + Workers (Coordinator — «мозг», Workers — «мышцы»), работает через Connector’ы.
Coordinator: Принимает SQL-запросы, планирует их выполнение, координирует исполнение.
Workers: Выполняют части плана запроса (scans, joins, агрегации и т.д.).
Connectors: Интерфейсы доступа к источникам данных (Iceberg, Hive, Kafka, JDBC…).
Как выполняется запрос
Пользователь отправляет SQL-запрос на Coordinator.
Coordinator:
- Парсит и анализирует SQL.
- Создаёт распределённый план выполнения.
- Делит план на stages и tasks.
- Workers получают задачи (tasks), читают данные (например, из S3 через Iceberg) и выполняют операции.
- Coordinator собирает и возвращает результат.
Поток данных (Data Flow)
Client → Coordinator → [ Plan ] → Workers → [ Parallel Scan & Process ] → Coordinator → Client
Пример конфигурации в catalog/iceberg.properties
1 2 3 4 |
connector.name=iceberg catalog.type=hive hive.metastore.uri=thrift://hive-metastore:9083 warehouse=s3a://your-bucket/path/ |
Оптимизация в Trino Iceberg Connector через ALTER TABLE EXECUTE
optimize
Команда optimize используется для перезаписи содержимого указанной таблицы, чтобы объединить ее в меньшее количество файлов, но большего размера. Если таблица partitioned, сжатие данных выполняется отдельно для каждого раздела, выбранного для оптимизации. Эта операция повышает производительность чтения.
optimize_manifests
Переписывает файлы манифеста для кластеризации их путем partitioning columns. Это может использоваться для оптимизации планирования сканирования, когда есть много небольших файлов манифеста или когда есть partition filters в запросах на чтение, но файлы манифеста не сгруппированы по разделам (grouped by partitions). Свойство таблицы айсберга commit.manifest.target-size-bytes
управляет максимальным размером файлов манифеста, создаваемых этой процедурой.
expire_snapshots
Команда expire_snapshots удаляет все снимки и все связанные метаданные и файлы данных. Рекомендуется регулярное устаревание снимков для удаления файлов данных, которые больше не нужны, и для поддержания небольшого размера метаданных таблицы. Процедура влияет на все снимки, которые старше периода времени, настроенного с помощью retention_threshold
параметра.
remove_orphan_files
Команда remove_orphan_files удаляет все файлы из каталога данных таблицы, которые не связаны с файлами метаданных и которые старше значения retention_threshold
параметра. Время от времени рекомендуется удалять файлы-сироты, чтобы держать размер каталога данных таблицы под контролем.
Метаданные и системные таблицы
Trino предоставляет доступ к различным системным таблицам для получения информации о таблицах Iceberg:
$properties
— Содержит все пользовательские свойства (key-value), заданные для таблицы Iceberg.
- key — имя свойства
- value — значение свойства
1 |
SELECT * FROM "test_table$properties"; |
$history
— Отображает историю снимков (snapshots), применявшихся к таблице — фактически журнал изменений.
- made_current_at — время применения snapshot
- snapshot_id — идентификатор snapshot
- parent_id — ID родительского snapshot
- is_current_ancestor — флаг, входит ли snapshot в текущую цепочку версий
1 |
SELECT * FROM "test_table$history"; |
$metadata_log_entries
— Показывает последовательность файлов метаданных, связанных с таблицей. Полезно для диагностики или аудита изменений таблицы.
- timestamp — метка времени
- file — путь к файлу метаданных
1 |
SELECT * FROM "test_table$metadata_log_entries"; |
$snapshots
— Представляет все снимки (snapshots) таблицы, включая их метаданные и статистику.
- snapshot_id
- parent_id
- operation — тип операции (append, overwrite, delete, и т.д.)
- manifest_list — путь к списку манифестов
- summary — JSON-структура с описанием изменений
- timestamp_ms — время snapshot в миллисекундах
1 |
SELECT * FROM "test_table$snapshots"; |
$manifests
и $all_manifests
Представляют файлы манифестов (manifest files), содержащих информацию о наборах data files.
$manifests
— только для последнего snapshot.$all_manifests
— для всех snapshot таблицы.
Содержимое:
- path — путь к manifest-файлу
- length — размер
- partition_spec_id — ID схемы партиционирования
- added_snapshot_id, deleted_snapshot_id — snapshot, добавивший или удаливший manifest
- partition_summaries — диапазоны значений партиций
- data_file_count, file_size_in_bytes — статистика по данным
1 |
SELECT * FROM "test_table$manifests"; |
$partitions
— Содержит агрегированную информацию по каждой партиции таблицы (Полезно для оценки распределения и плотности данных).
- partition — значения полей партиции
- record_count — количество записей
- file_count, total_size, column_summaries, и т.д.
1 |
SELECT * FROM "test_table$partitions"; |
$files
— Отображает индивидуальные физические файлы с данными (data files), из которых состоит таблица (Предоставляет полную информацию о каждом файле данных).
- file_path, file_format, record_count, file_size_in_bytes
- partition — значение партиции
- column_sizes, value_counts, null_value_counts, lower_bounds, upper_bounds
1 |
SELECT * FROM "test_table$files"; |
$entries
и $all_entries
Показывают строки из manifest-файлов, где описаны изменения: какие файлы были добавлены, удалены, модифицированы.
$entries
— для последнего snapshot$all_entries
— для всех snapshot
Содержимое:
- status — ADDED, EXISTING, DELETED
- snapshot_id, data_file, partition, sequence_number, и т.д.
1 |
SELECT * FROM "test_table$entries"; |
$refs
— Отображает именованные ссылки (refs) — это механизм, похожий на Git-ветки, позволяющий обращаться к snapshot по имени.
- name — имя ссылки
- snapshot_id — ID связанного snapshot
- type — branch, tag
- max_ref_age_ms, min_snapshots_to_keep, и т.п. — параметры управления жизненным циклом
1 |
SELECT * FROM "test_table$refs"; |
Полезные ссылки (в т.ч. те, которые использовались при написании статьи)
Статьи про Apache Iceberg
- https://www.datacamp.com/tutorial/apache-iceberg
- Understanding Apache Iceberg’s Consistency Model Part 1
- Understanding Apache Iceberg’s Consistency Model Part 2
- Understanding Apache Iceberg’s Consistency Model Part 3
- I spent 8 hours learning Parquet. Here’s what I discovered
- I spent 4 hours learning how Netflix operates Apache Iceberg at scale
- I spent 4 hours learning Apache Iceberg. Here’s what I found
- I spent 7 hours diving deep into Apache Iceberg
- How does Netflix ensure the data quality for thousands of Apache Iceberg tables?
- Iceberg 101: Ten Tips to Optimize Performance
- Copy-on-Write or Merge-on-Read? What, When, and How?
- Incremental Processing using Netflix Maestro and Apache Iceberg
Leave a Reply