Learning Platform
Глоссарий Troubleshooting
Урок 06.02 · 14 мин
Продвинутый
Z-orderingData SkippingMin/Max StatisticsColumn StatisticsPredicate Pushdown

Z-ordering и data skipping

Data skipping: пропускаем ненужные данные

Data skipping — это техника, при которой Spark не читает блоки данных, которые гарантированно не содержат нужных строк. Это работает благодаря min/max statistics, хранящимся в metadata каждого row group (Parquet) или stripe (ORC).

Как работают min/max statistics

Каждый row group в Parquet хранит минимальное и максимальное значение для каждого столбца:

Parquet: min/max statistics per Row Group
Row Group 150,000 строк
order_date:min=2024-01-01max=2024-01-15
amount:min=10.0max=5000.0
city:min=Екатеринбургmax=Москва
Row Group 250,000 строк
order_date:min=2024-01-16max=2024-01-31
amount:min=5.0max=8000.0
city:min=Казаньmax=Ярославль
Row Group 350,000 строк
order_date:min=2024-02-01max=2024-02-15
amount:min=15.0max=3000.0
city:min=Владивостокmax=Новосибирск

Predicate pushdown с min/max

Когда Spark выполняет запрос с фильтром, он проверяет min/max statistics перед чтением row group:

# Запрос: заказы за февраль 2024
df = spark.read.parquet("/data/orders/")
feb_orders = df.filter(col("order_date") >= "2024-02-01")

Spark проверяет каждый row group:

  • Row Group 1: max(order_date) = 2024-01-15 < 2024-02-01 → SKIP (не читаем!)
  • Row Group 2: max(order_date) = 2024-01-31 < 2024-02-01 → SKIP
  • Row Group 3: min(order_date) = 2024-02-01 >= 2024-02-01 → READ

Результат: Spark прочитал 1/3 данных вместо всего файла.

TIP

Data skipping эффективен, когда данные отсортированы. Если данные записаны в случайном порядке, min/max statistics охватывают весь диапазон значений в каждом row group, и пропустить блок невозможно. Сортировка перед записью — простейший способ включить data skipping.

Проблема: фильтрация по нескольким столбцам

Данные можно отсортировать только по одному столбцу (или по нескольким, но с жёсткой иерархией). Если вы отсортировали по order_date, то фильтрация по city не будет эффективной — значения города разбросаны по всем row groups.

# Данные отсортированы по order_date
# Запрос по дате -- data skipping работает отлично
df.filter(col("order_date") == "2024-02-01")  # читает ~3% данных

# Запрос по городу -- data skipping НЕ работает
df.filter(col("city") == "Москва")  # читает 100% данных!
# Потому что "Москва" есть в КАЖДОМ row group (min/max покрывает весь диапазон)

Z-ordering: многомерная кластеризация

Z-ordering решает эту проблему, организуя данные так, что строки с похожими значениями по нескольким столбцам оказываются в одних и тех же файлах/row groups.

Как работает Z-ordering

Z-ordering использует Z-кривую (кривую Мортона) для отображения многомерных данных в одномерное пространство. Вместо сортировки по одному столбцу, данные организуются по чередующимся битам нескольких столбцов:

Без Z-ordering vs С Z-ordering
Без Z-ordering (сортировка по date)
File 1: all cities, Jan
File 2: all cities, Feb
File 3: all cities, Mar
File 4: all cities, Apr
Запрос: city='Moscow' AND date='Feb'
Читает: File 2 (50% данных)
С Z-ordering (date + city)
File 1: Moscow, Jan-Feb
File 2: SPb, Jan-Feb
File 3: Moscow, Mar-Apr
File 4: SPb, Mar-Apr
Запрос: city='Moscow' AND date='Feb'
Читает: File 1 (25% данных)

Z-ordering обеспечивает data skipping по обоим столбцам одновременно.

Применение Z-ordering (Delta Lake)

Z-ordering — это прежде всего фича Delta Lake (и других lakehouse-форматов):

-- Delta Lake: Z-ordering при компактизации
OPTIMIZE orders
ZORDER BY (order_date, city)
# Через DeltaTable API
from delta.tables import DeltaTable

delta_table = DeltaTable.forPath(spark, "/data/orders/")
delta_table.optimize().executeZOrderBy("order_date", "city")
WARNING

Z-ordering — это фича Delta Lake / Iceberg / Hudi, а не чистого Spark. Стандартный Spark с Parquet не поддерживает Z-ordering напрямую. Для достижения похожего эффекта в чистом Spark можно использовать repartitionByRange() + sortWithinPartitions(), но это менее эффективно.

Подробнее о Delta Lake — в модуле Lakehouse (Phase 66).

Эффект Z-ordering: Before vs After

Before Z-ordering (стандартная запись):

Запрос: WHERE city = 'Москва' AND order_date = '2024-02-01'
Файлов в таблице: 1000
Файлов прочитано: 950 (95%)  ← data skipping почти не работает
Данных прочитано: 9.5 GB из 10 GB
Время выполнения: ~45 секунд

After Z-ordering (по city + order_date):

Запрос: WHERE city = 'Москва' AND order_date = '2024-02-01'
Файлов в таблице: 1000
Файлов прочитано: 50 (5%)   ← data skipping отсекает 95%
Данных прочитано: 0.5 GB из 10 GB
Время выполнения: ~2 секунды

20x ускорение за счёт чтения 5% данных вместо 95%.

Практические рекомендации

Выбор столбцов для Z-ordering

КритерийХороший кандидатПлохой кандидат
КардинальностьСредняя (100-10000 значений)Уникальные значения (user_id)
Использование в фильтрахЧастое (WHERE, JOIN key)Редкое
Количество столбцов2-4 столбца10+ столбцов

Оптимально: 2-4 столбца, которые чаще всего используются в WHERE и JOIN.

Альтернатива Z-ordering в чистом Spark

# Приближение Z-ordering через сортировку
df.repartitionByRange(100, col("city"), col("order_date")) \
  .sortWithinPartitions(col("city"), col("order_date")) \
  .write.parquet("/data/orders_sorted/")

Это обеспечит хороший data skipping по city (первый столбец) и умеренный по order_date (второй столбец), но не настоящий Z-ordering.

Анти-паттерн: Z-ordering по высококардинальным уникальным столбцам

-- ПЛОХО: Z-ordering по уникальному столбцу
OPTIMIZE orders
ZORDER BY (user_id)  -- миллионы уникальных значений

-- Каждый row group содержит уникальные user_id
-- min/max statistics покрывают весь диапазон
-- Data skipping не работает!

Z-ordering эффективен для столбцов со средней кардинальностью (сотни-тысячи уникальных значений). Для столбцов с миллионами уникальных значений используйте bloom filters (следующий урок).

Проверка знанийKnowledge check
Таблица orders содержит 1TB данных в 1000 Parquet-файлов. Запрос WHERE order_date = '2024-02-01' AND region = 'Moscow' читает 950 файлов. Как Z-ordering поможет?
ОтветAnswer
Z-ordering по (order_date, region) реорганизует данные так, что строки с похожими комбинациями date+region окажутся в одних файлах. После Z-ordering min/max statistics для обоих столбцов будут иметь узкие диапазоны в каждом файле. Запрос сможет пропустить файлы, где min(order_date) > '2024-02-01' или max(order_date) < '2024-02-01' ИЛИ min(region) > 'Moscow' или max(region) < 'Moscow'. Вместо 950 файлов будет прочитано ~50-100, ускорение в 10-20x.
Проверка знанийKnowledge check
Почему сортировка по одному столбцу не решает проблему data skipping для запросов по нескольким столбцам?
ОтветAnswer
Сортировка по столбцу A группирует данные только по этому столбцу. Значения столбца B оказываются случайно распределены по row groups. Для запроса WHERE A = x AND B = y data skipping работает только по A (min/max узкие), но не по B (min/max охватывают весь диапазон в каждом row group). Z-ordering решает это, чередуя биты обоих столбцов, что обеспечивает кластеризацию данных одновременно по нескольким измерениям.
TIP

Для углублённого изучения encoding-техник (dictionary, RLE, delta) и Parquet metadata (page index, column index) см. курс Storage Formats Deep-Dive — модуль encoding и модуль Parquet.

Что дальше?

В следующем уроке мы изучим bloom filters — вероятностные структуры данных, которые дополняют min/max statistics для высококардинальных столбцов (user_id, session_id), где Z-ordering неэффективен.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. Как min/max statistics в Parquet row groups обеспечивают data skipping?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5