При работе с Delta Lake оптимизируем через кластеризацию, а не через сегментирование

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Сегментирование в стиле Hive применяется для оптимизации физического диска и более качественной компоновки данных, о чём подробнее рассказано в статье Organizing Data: Partition by Disk to Hive-style Partitioning. Но при работе с облачными объектными хранилищами сегментирование — не лучшая стратегия, и нам следует переосмыслить наш подход к организации данных. В этой статье обсудим, как путём пропуска данных (data skipping) и использования Z-порядка сократить объём тех данных, которые приходится просматривать. В результате можно значительно повысить производительность при обработке запросов и удешевить эти операции за счёт использования приёмов кластеризации.

Повышение производительности путём пропуска данных

Традиционно при запрашивании информации из озера данных движок запросов сначала выстраивает список релевантных файлов, а затем запрашивает их. В таких облачных объектных хранилищах как S3 подобное действие listFrom получается очень ресурсозатратным. Когда данные хранятся в форматах из категории «lakehouse», например, Delta Lake, удаётся совместить наилучшие черты озёр данных и хранилищ данных. Обеспечивается высокая надёжность данных, как в хранилище, и соответствие набору ACID, тогда как гибкость и масштабируемость при работе такая, как будто мы работаем с озером данных. В Delta Lake этого удаётся добиться благодаря использованию журнала транзакций, как показано на следующей схеме. Здесь клиент rust-lang запрашивает таблицу Delta. 

Клиент на Rust запрашивает таблицу Delta Lake через журнал транзакций Delta
Клиент на Rust запрашивает таблицу Delta Lake через журнал транзакций Delta

Delta Lake предоставляет движок для работы с запросами (напр., Rust Lang содержащийся в пакете delta-rs), прописывая точные пути к тем файлам, которые слагают таблицу. Следовательно, при работе с Delta Lake удаётся в значительной степени устранить проблемы с производительностью, возникающие при листинге файлов (напр., listFrom).

Клиент Rust получает меньше данных (т.e., меньше файлов), сокращение достигается путём пропуска данных в журнале транзакций Delta.
Клиент Rust получает меньше данных (т.e., меньше файлов), сокращение достигается путём пропуска данных в журнале транзакций Delta.

Обсудим, как Delta повышает производительность запросов путём пропуска данных, опираясь на статистику столбцов (минимальные и максимальные значения), сохранённую в журнале Delta. Например, движок запросов отсеет через фильтр все файлы данных, не относящиеся к 2023 году (*.parquet), исходя из максимальных и минимальных значений в журнале транзакций. Следовательно, пропуская данные таким образом, движок будет читать меньше информации, а значит — работать быстрее.

Свёртка многомерных точек до одномерных  значений

Пропуск данных получается ещё эффективнее, если кластеризовать данные так, чтобы диапазоны значений min-max получались узкими и не пересекались. Например, вот какова структура файла данных, показанного на предыдущей картинке.

# Конфигурация кластеров в предыдущем файле
1.parquet {min: 2023, max: 2023}
2.parquet {min: 1999, max: 2020}
3.parquet {min: 2009, max: 2018}

Если в данном примере мы запросим данные за 2010 год, то нам потребуется прочитать как 2.parquet, так и 3.parquet. Напротив, было бы более оправданно выполнить конфигурацию по следующему принципу.

# Оптимальная конфигурация кластеров данных по годам
1.parquet {min: 2019, max: 2023}
2.parquet {min: 2014, max: 2018}
3.parquet {min: 2009, max: 2013}

Этот упрощённый пример отлично работает с одним столбцом. Но всё значительно усложняется, когда требуется выполнить кластеризацию по множеству столбцов. Такая проблема распространена при работе с реляционными базами данных, когда мы пытаемся присваивать одному файлу множество значений из столбцов, по которым обычно запрашиваются/фильтруются/группируются данные. С математической точки зрения мы хотим сохранить локальность данных, отображая многомерные точки на одномерные данные. Следовательно, хотим использовать поддерживающие такую локальность кривые Пеано. Наиболее распространенными из них являются кривые Мортона и кривые Гильберта.

Отличный пост с разбором этой темы написал Адриан Ионеску. Он называется Processing Petabytes of Data in Seconds with Databricks Delta.

Азы кластеризации на кривых Мортона

Кривая Мортона — это сохраняющая локальность разновидность кривых Пеано. Кривая Мортона позволяет, применяя фильтры, пропускать данные во множестве столбцов. Концепция такова: если любой прямоугольник размером 4×1 при запрашивании данных будет трактоваться как отдельный файл, то движок будет последовательно обрабатывать файл за файлом в процессе линейных проходов.

Instead of querying data linearly, organize the data in quadrants and visit each quadrant in z-order
Можно запрашивать данные не линейно, а организовать их в виде квадрантов и посещать каждый квадрант, обходя его в таком порядке, в котором рисуется буква z.

Но, если организовать данные в таком Z-порядке, то концептуально каждый файл будет представлять собой квадрант 2×2. Файл просматривается в порядке “Z” (0, 1, 2, 3), то есть, рекурсивно. Допустим, у вас есть следующий SQL-запрос, в котором данные представляются в двух измерениях, то есть, откладываются по осям (x, y).

Looking at data in two dimensions, the optimal path would be to only read at x=2, y = 2.
Если рассматривать данные в двух измерениях, то оптимальный путь строится так: читаем значения, расположенные лишь по x=2, y = 2.

В зелёных кружках с жёлтым фоном — те данные, которые мы хотим прочитать. Красные данные мы собираемся пропустить. Напомню, что в соответствии с Z-порядком читается квадрат 2×2 (прерывистая линия), и таким квадратом представлен каждый файл.  

Линейное сканирование

Но при запрашивании данных в линейном порядке каждому файлу соответствует прямоугольник 4×1 (прерывистая линия). При линейном сканировании приходится прочитать девять (9) файлов (жёлтый фон, прямоугольники, очерченные прерывистой линией). Также получается 21 ложноположительный результат, таким результатам соответствуют красные кружки на жёлтом фоне.

Scanning the data linearly results in 9 files read and 21 false positives
При линейном сканировании получается 9 операций чтения файлов и 21 ложноположительный результат

Сканирование в Z-порядке

С другой стороны, кластеризация в Z-порядке позволяет сократить количество как файлов, так и ложноположительных результатов. Каждый файл представлен квадратом 2×2 (прерывистая линия) и сканируется так, как рисуется буква Z (светло-голубая линия). Но связь между точками в каждом Z (т.e., в каждом файле) меняется и зависит от конкретных данных.  

Using Z-Order clustering, only 7 files are read with 13 false positives
При кластеризации данных в Z-порядке мы читаем всего 7 файлов, и остаётся 13 ложноположительных результатов

В данном примере движок запросов сканирует всего 7 файлов (а не 9). Кроме того, вместо 21 остаётся 13 ложноположительных результатов. 

Обсуждение

Выше мы рассмотрели упрощённый двумерный пример (одно измерение — один столбец). Но кластеризация такого типа применима и со множеством столбцов. Здесь действует такое эмпирическое правило: данная техника наиболее эффективна при работе с 5-8 столбцами. Мы можем не ограничиваться несколькими столбцами, как при сегментировании, а приобрести гораздо более значительную гибкость, если будем кластеризовать данные для обхода в Z-порядке.

Источник: https://habr.com/ru/articles/1098390/


Интересные статьи

Интересные статьи

Часто слышу в IT:PHP устарелBitrix - монолит из 2000-хНа Bitrix невозможно писать нормальную архитектуруС Bitrix24 работаю больше шести лет и за это время поработал над проектами: от простых внедрений...
Когда клиент откладывает покупку, ожидая скидки, важно не потерять его интерес. Уведомления о снижении цен на избранные товары помогают вовремя сообщать пользователям о выгодных предложениях, возвраща...
Всем привет, случалось такое, что вам надо поставить кучу агентов битрикса на крон, а потом сидеть и разбираться - сколько они отрабатывают, отрабатывают ли вообще, когда падают или зависают? Ну конеч...
В прошлой статье, посвящённой изучению кодирования на HEVC в FFmpeg, мы разобрали большинство функций работы с видео и научились эффективно сжимать видео или ускорять процесс кодирования для различн...
Вчера на них никто не обращал внимание, а сегодня они повсюду. Человейники атакуют! Официально они именуются ЖК эконом-класса, а по сути являются гигантскими бетонными коробками из дешевы...