Урок по треку Data Engineering 7 мин

Parquet, партиционирование и бакетинг

Колоночные форматы, row groups и статистики, сжатие и кодирование, партиционирование, бакетинг, мелкие файлы.

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

Строчные и колоночные форматы

  • CSV, JSON: текст, строка за строкой. Просто и универсально, но нет типов (или они угадываются), плохое сжатие, чтение одной колонки требует разбора всей строки.
  • Avro: бинарный строчный формат со схемой. Хорош для потоков и записи событий по одному (Kafka), поддерживает эволюцию схемы.
  • Parquet, ORC: бинарные колоночные форматы. Стандарт для аналитики и озёр данных.

Как устроен Parquet

Файл Parquet делится на:

  1. Row groups: горизонтальные куски по многу строк (часто десятки-сотни мегабайт).
  2. Внутри row group column chunks: данные каждой колонки отдельно.
  3. Внутри chunk pages: единицы кодирования и сжатия.
  4. Footer в конце файла: схема, расположение row groups и статистики по колонкам (min, max, число NULL).

Что это даёт:

  • Column pruning (projection): читаются только нужные колонки.
  • Predicate pushdown: по min и max в футере движок пропускает row groups, где условие заведомо ложно. Запрос WHERE order_date = '2025-09-01' не читает группы, у которых max(order_date) < '2025-09-01'. Работает хорошо, если данные отсортированы или сгруппированы по колонке фильтра; при случайном порядке у каждой группы широкий диапазон, и пропускать нечего.
  • Кодирование: словарное для колонок с небольшим числом значений (город, статус), RLE для повторов, дельта для возрастающих чисел и дат.
  • Сжатие поверх кодирования: Snappy (быстро), Zstd (лучше сжимает при разумной скорости), Gzip (сильно, но медленно).
  • Типы и вложенные структуры: схема хранится в файле, поддерживаются массивы и структуры.

Parquet неизменяем: обновить строку внутри файла нельзя, только переписать файл. Поэтому для UPDATE и DELETE поверх Parquet нужны табличные форматы (Iceberg, Delta, Hudi).

Партиционирование

Данные раскладываются по каталогам по значению колонки:

s3://dwh/events/event_date=2025-09-01/part-0001.parquet
s3://dwh/events/event_date=2025-09-02/part-0001.parquet

Это «hive-style» партиционирование. Запрос с фильтром по event_date читает только нужные каталоги: partition pruning.

Как выбрать ключ партиционирования:

  • Колонка, по которой почти всегда фильтруют: обычно дата события.
  • Низкая или умеренная кардинальность: сотни-тысячи партиций, а не миллионы. Партиционирование по user_id создаст миллионы крошечных каталогов.
  • Размер партиции: от сотен мегабайт до нескольких гигабайт. Если данных мало, партиция по месяцу вместо дня.
  • Фильтр должен быть по самой колонке партиции: условие WHERE date(event_ts) = ... при партиции по event_date может не сработать для pruning, если движок не связывает колонки.

Проблема мелких файлов

Тысячи файлов по несколько килобайт: частая беда стриминга и чрезмерного партиционирования.

  • Каждый файл это запрос к хранилищу, открытие, чтение футера: накладные расходы больше полезной работы.
  • Метаданных много, листинг каталогов медленный.
  • Сжатие и статистики работают хуже на маленьких файлах.

Решения: компакция (периодически переписывать мелкие файлы в крупные, табличные форматы делают это процедурами), запись меньшим числом крупных файлов (coalesce, repartition по колонке партиции перед записью), более крупные партиции.

Бакетинг

Бакетинг делит данные внутри таблицы на фиксированное число файлов-бакетов по хешу колонки: hash(user_id) % 64. Если две таблицы бакетированы по одному ключу с одинаковым числом бакетов, движок соединяет их бакет с бакетом без shuffle. Полезно для частых джойнов и агрегаций по ключу высокой кардинальности, где партиционирование невозможно.

Отличие: партиционирование по значению создаёт каталоги и даёт pruning по фильтру; бакетинг по хешу даёт фиксированное число файлов и помогает джойнам. Их часто сочетают: партиция по дате, внутри бакеты по user_id.

В табличных форматах развивают эту идею: скрытое партиционирование и трансформации (Iceberg: day(ts), bucket(16, id)), сортировка и z-order внутри файлов для лучшего пропуска данных по нескольким колонкам.

Частые ошибки

  • Партиционировать по колонке высокой кардинальности.
  • Оставлять тысячи мелких файлов после стриминга без компакции.
  • Хранить аналитику в CSV или JSON.
  • Ожидать predicate pushdown от неотсортированных данных.
  • Фильтровать не по колонке партиции, а по вычисленному выражению.

Что спросят на собесе

  • Почему Parquet быстрее CSV для аналитики, как он устроен внутри.
  • Что такое predicate pushdown и когда он не работает.
  • Как выбрать ключ партиционирования, чем плохи мелкие файлы.
  • Чем бакетинг отличается от партиционирования.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.