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 из 0
Концептуальный
Вопрос 1 из 4. В чём суть hidden partitioning в Iceberg по сравнению с партиционированием в Hive?

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

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

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

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