Перейти к содержанию
Learning Platform
Глоссарий Troubleshooting
Урок 10.06 · 24 мин
Средний
icebergpartitioningtransformsschema-evolution

Партиционирование, hidden partitioning, transforms и schema evolution

В уроке 3 мы видели, что Trino отбрасывает ненужные манифесты по диапазонам партиционирующих колонок и ненужные файлы по статистике — это и есть pruning. Партиционирование — главный рычаг, которым инженер управляет этим отсевом. Но партиционирование в Iceberg устроено принципиально иначе, чем в старом Hive: оно скрытое. А ещё в Iceberg и схему, и саму схему партиционирования можно менять на живой таблице, не переписывая данные. Этот урок объясняет механику hidden partitioning, разбирает transforms и показывает, почему schema evolution в Iceberg безопасна.

Iceberg: hidden partitioning и partition evolution

Боль партиционирования в Hive и как Iceberg её убирает

Сначала — как было в Hive, потому что Iceberg проектировали именно против этих проблем. В Hive партиция — это физическая директория, названная по значению колонки: /orders/order_date=2026-05-19/. Чтобы партиционировать таблицу по дате, в неё добавляли отдельную партиционирующую колонку, и у этого было три болезненных следствия.

Первое: партиционирующая колонка видна в схеме, и запросы должны фильтровать именно по ней. Если таблица партиционирована по колонке order_day, а запрос фильтрует WHERE order_date = ... по другой колонке, partition pruning не сработает — Hive прочитает всю таблицу. Пользователь обязан знать схему партиционирования и подстраивать запросы.

Второе: партиционирующее значение нужно вычислять и записывать руками. Хотите партиции по дню от колонки-timestamp — при вставке надо самому посчитать date(order_ts) и положить в order_day. Ошибётесь — данные лягут не в ту партицию.

Третье, и самое тяжёлое: схема партиционирования в Hive фиксирована при создании таблицы. Поняли через год, что партиции по дню слишком мелкие и нужны по месяцу — нет способа изменить это, кроме как создать новую таблицу и переписать в неё всё.

Партиционирование: Hive против Iceberg
HiveПартиция — физическая директория, нужна отдельная колонка, значение пишется руками, запрос обязан фильтровать по партиционирующей колонке, схема партиционирования фиксирована навсегда
Iceberg убирает все три проблемы
IcebergПартиционирование описано в метаданных, отдельная колонка не нужна, значение вычисляет движок, запрос фильтрует по обычной колонке, схему партиционирования можно менять на живой таблице

Hidden partitioning: партиция в метаданных, а не в директории

В Iceberg партиция — это не директория, а запись в метаданных. В уроке 3 мы видели: manifest list хранит для каждого манифеста диапазоны партиционирующих значений, манифест — партицию каждого файла. Партиционирование живёт в этом слое метаданных, а не в путях файлов. Это и называется hidden partitioning — скрытое партиционирование.

Из этого вытекает ключевое: партиционирующая колонка не нужна. Вы партиционируете таблицу не по вспомогательной колонке, а напрямую по выражению от обычной колонки. Например, по дню от order_ts. Iceberg сам при каждой записи вычисляет значение партиции из order_ts и записывает его в метаданные. Инженеру не надо ни добавлять колонку, ни считать значение руками — движок делает это сам.

И главный выигрыш на чтении. Запрос фильтрует по обычной колонке — WHERE order_ts >= TIMESTAMP '2026-05-19 00:00:00'. Trino знает из метаданных, что таблица партиционирована по дню от order_ts, применяет ту же функцию к границам фильтра и отбрасывает манифесты и файлы ненужных дней. Pruning срабатывает без того, чтобы пользователь знал схему партиционирования. Запрос написан естественно, а отсев работает. В Hive это было невозможно.

-- Iceberg: партиционируем по выражению от обычной колонки order_ts.
-- Отдельная партиционирующая колонка НЕ нужна.
CREATE TABLE iceberg.sales.events (
  event_id  BIGINT,
  user_id   BIGINT,
  order_ts  TIMESTAMP(6),
  amount    DECIMAL(12,2)
)
WITH (
  format = 'PARQUET',
  partitioning = ARRAY['day(order_ts)']
);

INSERT INTO iceberg.sales.events VALUES
  (1, 100, TIMESTAMP '2026-05-19 09:14:00', DECIMAL '120.00'),
  (2, 101, TIMESTAMP '2026-05-20 10:02:00', DECIMAL '80.00');
-- INSERT: 2 rows

-- Фильтр по обычной колонке order_ts. Pruning сработает:
-- Trino применит day() к границам и отбросит файлы ненужных дней.
SELECT count(*) FROM iceberg.sales.events
WHERE order_ts >= TIMESTAMP '2026-05-20 00:00:00';
--  _col0
-- -------
--      1
TIP

Проверить, что pruning сработал, можно через EXPLAIN ANALYZE: в строке table scan для Iceberg видно число прочитанных файлов. Если запрос с фильтром по партиционирующему выражению читает все файлы — partitioning подобран неудачно или фильтр не сводится к границам партиции.

Transforms: какие бывают функции партиционирования

Выражение в partitioning — это partition transform, функция, превращающая значение колонки в значение партиции. Iceberg даёт фиксированный набор transforms, и выбор между ними — это инженерное решение о гранулярности партиций.

TransformЧто делаетКогда уместен
identity (просто имя колонки)Партиция = само значение колонкиКолонка с небольшим числом значений: страна, регион, тип события
year(col)Партиция по годуОчень крупные исторические таблицы
month(col)Партиция по месяцуТаблицы с многолетней историей и помесячным доступом
day(col)Партиция по днюСамый частый выбор для событийных и фактовых таблиц
hour(col)Партиция по часуСтриминг с большим объёмом в час
bucket(col, N)Хеш-партиция в одну из N корзинКолонка с высокой кардинальностью: user_id, order_id
truncate(col, W)Партиция по усечённому значению (числа или строки)Группировка близких значений: префиксы строк, диапазоны чисел

Главная ошибка партиционирования — неверная гранулярность. Слишком мелкие партиции (hour там, где хватило бы day) дают много мелких файлов и раздутые метаданные — ту самую деградацию из урока 5. Слишком крупные (year для таблицы с ежедневными запросами) — partition pruning почти не отсекает данные, потому что нужный день всё равно внутри огромной годовой партиции. Цель — чтобы типичный запрос затрагивал немного партиций, а каждая партиция была достаточно крупной для эффективных файлов.

Отдельно про bucket. identity на колонке высокой кардинальности вроде user_id создал бы миллионы крошечных партиций — катастрофа. bucket(user_id, 64) хеширует user_id в одну из 64 корзин: число партиций ограничено и предсказуемо, а запрос WHERE user_id = 12345 всё равно прунится — Trino вычислит, в какой корзине лежит этот хеш, и прочитает только её.

Можно комбинировать transforms: партиционировать сразу по нескольким выражениям.

-- Партиционирование сразу по дню и по корзине user_id
CREATE TABLE iceberg.sales.events_v2 (
  event_id BIGINT,
  user_id  BIGINT,
  order_ts TIMESTAMP(6),
  amount   DECIMAL(12,2)
)
WITH (
  format = 'PARQUET',
  partitioning = ARRAY['day(order_ts)', 'bucket(user_id, 64)']
);

Schema evolution: менять схему без переписывания

Iceberg позволяет менять схему таблицы безопасно и без переписывания файлов. Причина — в уроке 3: каждая колонка в Iceberg имеет внутренний стабильный числовой идентификатор, а имя — лишь подпись поверх id. Файлы данных хранят значения по id колонки. Поэтому переименование колонки меняет только запись в metadata-файле; ни один Parquet-файл не трогается.

Поддерживаемые операции ALTER TABLE:

-- Добавить колонку. Старые файлы её не содержат — для их строк будет NULL.
ALTER TABLE iceberg.sales.events ADD COLUMN channel VARCHAR;

-- Переименовать колонку. Меняется только метаданные: id колонки прежний.
ALTER TABLE iceberg.sales.events RENAME COLUMN amount TO order_amount;

-- Удалить колонку. Данные в файлах остаются, но колонка скрыта из схемы.
ALTER TABLE iceberg.sales.events DROP COLUMN channel;

Также поддерживается type widening — безопасное расширение типа: INTEGER -> BIGINT, REAL -> DOUBLE, увеличение precision у DECIMAL. Эти преобразования безопасны, потому что любое старое значение валидно в широком типе — BIGINT вмещает любой INTEGER. Обратное (BIGINT -> INTEGER) Iceberg не разрешает: значение могло бы не влезть, это потеря данных.

Почему «добавить колонку» безопасно для старых файлов? Старый Parquet-файл просто не содержит данных новой колонки. Iceberg видит по id, что в файле такой колонки нет, и подставляет для его строк NULL. Никакого переписывания. В Hive добавление колонки было хрупким и часто требовало осторожности именно потому, что там колонки сопоставлялись по позиции, а не по стабильному id.

Partition evolution: менять само партиционирование

Iceberg умеет уникальное — менять схему партиционирования на живой таблице. Это та проблема Hive, которая там вообще не имела решения.

-- Год назад партиционировали по дню. Партиции стали мелкими.
-- Меняем на месяц — БЕЗ переписывания старых данных.
ALTER TABLE iceberg.sales.events
  SET PROPERTIES partitioning = ARRAY['month(order_ts)'];

Механика тонкая. Старые файлы данных остаются партиционированными по-старому (по дню) — их никто не переписывает. Новые записи партиционируются по-новому (по месяцу). В таблице сосуществуют две схемы партиционирования, и Iceberg хранит в метаданных обе. Для запроса это прозрачно: Trino применяет к старым файлам старую схему партиционирования, к новым — новую, и pruning корректно работает над обеими. Старые данные при желании можно постепенно перевести на новую раскладку через OPTIMIZE, но это не обязательно для корректности.

Partition evolution: две схемы партиционирования в одной таблице
Старые файлыЗаписаны до изменения, партиционированы по дню; никто их не переписывает, в метаданных хранится их прежняя схема партиционирования
ALTER TABLE SET PROPERTIES partitioning
Новые файлыЗаписаны после изменения, партиционированы по месяцу; Iceberg хранит обе схемы и применяет к каждому файлу свою
запрос видит обе схемы прозрачно
Запрос TrinoTrino применяет к старым файлам старую схему партиционирования, к новым — новую; pruning корректно работает над обеими
WARNING

Partition evolution не переписывает прошлое автоматически. Если старые данные критичны для производительности и старая раскладка плоха, запустите OPTIMIZE с WHERE по старым партициям — он перепишет их уже по новой схеме. Без этого старые файлы останутся в прежней раскладке: корректность не пострадает, но выигрыш от новой схемы партиционирования получат только новые данные.

Попробуй сам

В песочнице создайте Iceberg-таблицу events с колонкой order_ts TIMESTAMP(6), партиционированную по day(order_ts). Загрузите данные за несколько разных дней. Упражнение первое: выполните SELECT с фильтром по order_ts за один день, оберните в EXPLAIN ANALYZE и найдите в table scan число прочитанных файлов — убедитесь, что прочитаны не все. Упражнение второе на schema evolution: выполните ADD COLUMN region VARCHAR, затем SELECT region FROM events по строкам, вставленным до добавления колонки — какое значение вернулось и почему. Затем RENAME COLUMN и проверьте, что данные на месте. Упражнение третье на partition evolution: смените партиционирование на month(order_ts) через SET PROPERTIES, вставьте новые строки, посмотрите events$partitions. Письменно объясните, почему в таблице теперь сосуществуют партиции двух гранулярностей и почему это не сломало запросы.


Проверка знанийKnowledge check
Что такое hidden partitioning в Iceberg, чем оно отличается от партиционирования в Hive, и почему Iceberg позволяет менять схему партиционирования на живой таблице без переписывания старых данных?
ОтветAnswer
Hidden partitioning — скрытое партиционирование: в Iceberg партиция это не физическая директория, а запись в слое метаданных. Manifest list хранит диапазоны партиционирующих значений по манифестам, манифест — партицию каждого файла. Из этого вытекает главное отличие от Hive. В Hive партиция была директорией, и для партиционирования в таблицу добавляли отдельную партиционирующую колонку: её значение приходилось вычислять и записывать руками, запросы были обязаны фильтровать именно по ней, иначе partition pruning не срабатывал. В Iceberg отдельная колонка не нужна — таблица партиционируется по выражению от обычной колонки, partition transform. Движок сам вычисляет значение партиции при каждой записи, а запрос фильтрует по обычной колонке: Trino знает схему партиционирования из метаданных, применяет ту же функцию к границам фильтра и отсекает ненужные файлы — pruning работает, даже если пользователь не знает схему партиционирования. Менять схему партиционирования на живой таблице Iceberg позволяет потому, что партиционирование описано в метаданных, а не зашито в пути файлов. При смене старые файлы остаются партиционированными по-старому, их никто не переписывает; новые записи партиционируются по-новому. Iceberg хранит обе схемы в метаданных и применяет к каждому файлу свою, поэтому для запроса сосуществование двух схем прозрачно и pruning корректно работает над обеими. Старые данные при желании переводят на новую раскладку через OPTIMIZE, но для корректности это не требуется.

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

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

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

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