Hive ACID-таблицы, sync_partition_metadata и ограничения коннектора
В прошлом уроке мы установили модель Hive: данные в директориях, метаданные в Metastore до уровня партиции. Эта простая модель имеет фундаментальное следствие — обычная Hive-таблица не поддерживает row-level изменения. Удалить одну строку нельзя. Hive со временем добавил решение — ACID-таблицы, но их механика и ограничения сильно отличаются от того, к чему привыкли по Iceberg. Этот урок разбирает транзакционные таблицы Hive, проблему рассинхронизации Metastore с файловой системой и процедуру sync_partition_metadata — а заодно объясняет, почему для новых проектов выбирают не Hive ACID, а Iceberg или Delta.
Почему обычная Hive-таблица не умеет UPDATE и DELETE
Вспомним модель. Hive-таблица — это файлы в директориях. Metastore знает директории партиций, но не знает отдельных файлов. Теперь представьте DELETE FROM events WHERE user_id = 500. Строка с этим user_id лежит где-то внутри файла на миллион строк. Чтобы её удалить, надо переписать весь файл без этой строки. А в обычной Hive-модели нет ни механизма пометить строку удалённой, ни механизма атомарно подменить файл — Metastore оперирует директориями, файлы он не отслеживает.
Поэтому обычная Hive-таблица поддерживает по сути только две операции записи: добавить данные (INSERT — кладёт новые файлы в директорию) и перезаписать целиком (INSERT OVERWRITE партиции или таблицы). Точечные UPDATE и DELETE отдельных строк она не умеет. Это прямое следствие того, что метаданные не доходят до уровня файла.
Hive ACID: транзакционные таблицы
Чтобы дать Hive row-level изменения, в него добавили ACID-таблицы (транзакционные таблицы). Идея: вместо того чтобы переписывать большие файлы, фиксировать изменения отдельно и собирать актуальное состояние при чтении.
ACID-таблица Hive хранит данные не как один набор файлов, а как base-директории и delta-директории. Base — снимок данных. Delta — директория с изменениями (вставками и удалениями) одной транзакции. Каждая транзакция получает идентификатор и пишет свою delta-директорию. При чтении движок берёт base и накладывает поверх все delta по порядку транзакций — получается актуальное состояние. Удаление строки — это запись в delta-директорию пометки «такая-то строка удалена», а не переписывание base.
Заметили параллель? Это идейно то же, что delete files в Iceberg v2 из прошлого модуля: не трогать большие файлы, а фиксировать дельту отдельно. Но реализация Hive ACID жёстче. Во-первых, она требует формата ORC — base и delta завязаны на ORC, с Parquet ACID-таблицы Hive не работают. Во-вторых, состояние транзакций отслеживает HMS, и это делает HMS ещё более критичной точкой системы.
Два типа транзакционных таблиц различаются по поддерживаемым операциям. Insert-only транзакционные таблицы умеют только атомарный INSERT. Full ACID таблицы умеют и UPDATE, и DELETE отдельных строк — но именно они требуют ORC.
-- Создать full ACID транзакционную таблицу Hive (требует ORC)
CREATE TABLE hive.ops.accounts (
account_id BIGINT,
status VARCHAR,
balance DECIMAL(12,2)
)
WITH (
format = 'ORC',
transactional = true
);
INSERT INTO hive.ops.accounts VALUES
(1, 'active', DECIMAL '1000.00'),
(2, 'active', DECIMAL '500.00');
-- INSERT: 2 rows
-- Row-level UPDATE работает только на транзакционной ORC-таблице
UPDATE hive.ops.accounts SET status = 'frozen' WHERE account_id = 2;
-- UPDATE: 1 row
-- Row-level DELETE — тоже только на транзакционной ORC-таблице
DELETE FROM hive.ops.accounts WHERE status = 'frozen';
-- DELETE: 1 row
Row-level UPDATE и DELETE в Hive-коннекторе Trino работают только для транзакционных таблиц формата ORC. На обычной (не транзакционной) Hive-таблице или на транзакционной таблице в Parquet эти команды не выполнятся. Если нужны row-level изменения в Parquet — это сигнал, что таблице место в формате Iceberg или Delta, а не Hive.
Проблема рассинхронизации: Metastore против файловой системы
Теперь — характерная боль Hive, которой по своей природе нет у Iceberg. Она прямо вытекает из модели «метаданные отдельно от данных».
Список партиций живёт в Metastore. Данные живут в директориях object storage. Это два независимых хранилища, и ничто не гарантирует их согласованность. Если кто-то добавит файлы в object storage в обход Trino и Hive — например, Spark-джоб напишет данные прямо в новую директорию dt=2026-05-21, или файлы зальёт сторонний пайплайн, — то на диске партиция есть, а в Metastore записи о ней нет.
Последствие: Trino не увидит эти данные. Запрос SELECT ... WHERE dt = '2026-05-21' спросит Metastore про партицию, Metastore ответит «такой партиции нет», и Trino вернёт ноль строк — хотя файлы физически лежат в S3. Данные есть, но для Trino их не существует. Бывает и обратное: партиция удалена из object storage, а запись в Metastore осталась — тогда запрос обратится к несуществующей директории.
sync_partition_metadata: синхронизация партиций
Чинит рассинхронизацию процедура system.sync_partition_metadata. Она сверяет фактические директории партиций в object storage со списком партиций в Metastore и приводит Metastore в соответствие.
-- Синхронизировать партиции таблицы events с файловой системой
CALL hive.system.sync_partition_metadata(
schema_name => 'logs',
table_name => 'events',
mode => 'FULL'
);
Параметр mode задаёт направление синхронизации:
| Режим | Что делает |
|---|---|
ADD | Добавляет в Metastore партиции, чьи директории есть в object storage, но записи нет |
DROP | Удаляет из Metastore партиции, чьих директорий в object storage больше нет |
FULL | И добавляет недостающие, и удаляет лишние — полная синхронизация в обе стороны |
Типичное применение: пайплайн залил в Hive-таблицу новые данные в обход Trino, и шагом после загрузки вызывается sync_partition_metadata в режиме ADD — чтобы свежие партиции стали видны. Это рабочий приём, но обратите внимание, чего он стоит: его нужно не забыть вызвать. Забыли — данные молча невидимы. У Iceberg такой проблемы нет в принципе: там нет двух независимых хранилищ метаданных, любая запись через любой движок атомарно обновляет единый слой метаданных, и синхронизировать нечего.
Родственная процедура — system.flush_metadata_cache. Hive-коннектор кэширует ответы Metastore, чтобы не дёргать его на каждый запрос. Если метаданные изменили внешним инструментом, кэш может отдавать устаревшее. flush_metadata_cache сбрасывает кэш. Разница в сути: sync_partition_metadata чинит сам Metastore, flush_metadata_cache — лишь кэш Trino поверх Metastore.
Ограничения Hive-коннектора и почему выбирают Iceberg
Соберём ограничения Hive в одну картину — это и есть мотивация, по которой в 2026 году новые таблицы делают в Iceberg или Delta, а не в Hive.
Нет настоящих снапшотов и time travel — модель Hive не хранит историю состояний. Нет атомарной фиксации на уровне таблицы как у Iceberg-снапшота: INSERT OVERWRITE не атомарен так же надёжно. Row-level UPDATE и DELETE — только для транзакционных таблиц и только в ORC. Метаданные не доходят до уровня файла, поэтому каждый запрос делает list директорий, а pruning возможен только на уровне директорий партиций, не отдельных файлов по статистике. И главная эксплуатационная боль — рассинхронизация Metastore с файловой системой, которую приходится чинить руками через sync_partition_metadata.
| Возможность | Hive | Iceberg |
|---|---|---|
| Снапшоты и time travel | Нет | Есть |
| Атомарность записи уровня таблицы | Слабая | Есть (снапшот + каталог) |
| Row-level UPDATE/DELETE | Только ORC, транзакционные таблицы | Есть (v2, любой формат) |
| Метаданные уровня файла | Нет (только до партиции) | Есть (манифесты) |
| Hidden partitioning, partition evolution | Нет | Есть |
| Рассинхронизация с файловой системой | Да, нужен sync_partition_metadata | Нет в принципе |
Тогда зачем вообще нужен Hive-коннектор? По одной практической причине: Hive-таблиц в мире огромное количество — это десятилетие данных в продакшене. Trino должен уметь их читать. Поэтому hive — рабочий коннектор для существующих таблиц и для миграции. Но для новых таблиц выбор — Iceberg или Delta.
Попробуй сам
В песочнице создайте обычную Hive-таблицу events, партиционированную по dt. Упражнение первое: попробуйте выполнить DELETE FROM events WHERE ... — зафиксируйте, что произойдёт, и объясните почему. Затем создайте таблицу accounts с transactional = true и format = 'ORC', выполните UPDATE и DELETE отдельных строк — теперь это сработает. Упражнение второе на рассинхронизацию: в обычной Hive-таблице вручную создайте в MinIO новую директорию-партицию dt=2026-05-25 и положите в неё Parquet-файл (или сымитируйте загрузкой в обход Trino). Выполните SELECT ... WHERE dt = '2026-05-25' — сколько строк вернулось? Теперь вызовите sync_partition_metadata в режиме ADD и повторите SELECT. Письменно объясните: почему до синхронизации данные были невидимы, какого класса проблема за этим стоит, и почему у Iceberg-таблицы этой проблемы не возникает в принципе.