Concurrency Control и ACID
В Уроке 01 мы видели, что каждая операция Hudi записывается как instant на timeline. В Уроке 03 — что индекс определяет маршрутизацию записей по FileGroup. Но что происходит, когда два writer’а одновременно пишут в одну таблицу?
Concurrency control — одна из областей, где Hudi прошёл наиболее радикальную эволюцию. До версии 1.0 — Optimistic Concurrency Control (OCC) с внешними lock-провайдерами. С 1.0 — Non-Blocking Concurrency Control (NBCC), флагман новой версии: несколько writer’ов работают параллельно без блокировок на уровне таблицы.
Delta Lake использует OCC через атомарный rename _delta_log/ файлов (или Coordinated Commits в V7). Iceberg использует OCC через атомарную замену metadata pointer в каталоге. Hudi — единственный из трёх форматов, который реализовал полноценный non-blocking протокол для параллельных writer’ов на уровне файловых групп.
ACID-гарантии Hudi
Hudi обеспечивает полные ACID-свойства через timeline + атомарную публикацию instant’ов:
Ключевое отличие от Delta Lake: Delta полагается на atomic rename JSON-файлов в _delta_log/ для атомарности. Hudi полагается на файловый lifecycle instant’ов: .requested → .inflight → completion (удаление суффикса). Оба подхода гарантируют ACID, но с разной механикой.
OCC: Optimistic Concurrency Control (до 1.0)
До Hudi 1.0 единственным механизмом конкурентного доступа был OCC — модель, похожая на Delta Lake, но с использованием внешнего lock-провайдера:
Writer A: begin
Writer A начинает операцию. Читает текущий timeline, получает snapshot view таблицы. Вычисляет plan: какие FileGroup затронуть, какие записи insert/update.Запись данных
Writer A записывает данные в FileGroup. На этом этапе lock не нужен — данные пишутся в новые файлы, которые ещё не видны reader'ам.Commit + conflict check
Writer A захватывает lock через lock provider (Zookeeper/DynamoDB/filesystem). Проверяет: не появился ли между begin и commit новый completed instant, конфликтующий с его FileGroup. Если нет конфликтов — публикует instant.Writer B: begin
Writer B начинает параллельно. Читает тот же timeline snapshot, что и Writer A (или более свежий).Запись данных
Writer B тоже записывает данные. Если A и B затронули разные FileGroup — конфликта не будет. Если одни и те же — один из writer'ов проиграет при conflict check.Conflict → RETRY
Writer B пытается захватить lock. Если A уже закоммитил и затронул те же FileGroup — B обнаруживает конфликт. OCC: B должен retry весь процесс (re-read timeline, re-write данные).Механика конфликтов в OCC
OCC разрешает конфликты на уровне FileGroup: если два writer’а одновременно модифицируют записи, попадающие в один FileGroup, при commit второй writer обнаруживает, что base file изменился. В отличие от Delta Lake (конфликт на уровне файлов), Hudi проверяет конфликт на уровне FileGroup — более гранулярная единица.
Writer A: upsert 1000 records → FileGroup fg-001, fg-002
Writer B: upsert 500 records → FileGroup fg-002, fg-003
A commits first (acquires lock, conflict check passes, publishes instant)
B tries to commit:
→ lock acquired
→ conflict check: fg-002 modified by A since B's begin timestamp
→ ConflictResolutionStrategy decides: RETRY or FAIL
OCC не масштабируется для high-throughput сценариев: каждый retry — это полная перезапись данных. Если 10 writer’ов пишут в перекрывающиеся партиции, cascading retries могут занять больше времени, чем последовательная запись. Именно это ограничение привело к разработке NBCC.
Lock-провайдеры
Для OCC Hudi требует внешний lock-провайдер — компонент, обеспечивающий атомарный захват блокировки между распределёнными writer’ами:
На S3 не используйте FileSystemBasedLockProvider — S3 не гарантирует атомарный rename. Для S3 + OCC рекомендуются DynamoDBBasedLockProvider (AWS) или ZookeeperBasedLockProvider (multi-cloud). Это аналогичная проблема, с которой столкнулся Delta Lake, решив её через LogStore / Coordinated Commits.
NBCC: Non-Blocking Concurrency Control (1.0+)
NBCC — флагманская фича Hudi 1.0, принципиально отличающаяся от OCC. Вместо блокировки на уровне таблицы, NBCC разрешает конфликты на уровне отдельных FileGroup:
Writer A: begin
Writer A начинает. Как и в OCC — читает timeline. Но вместо table-level lock, A работает только со своими FileGroup.Запись: fg-001, fg-002
Writer A записывает данные в fg-001 и fg-002. Данные пишутся как log-файлы (MOR) или новые base файлы (COW). На этом этапе lock не нужен.Commit +
Writer A коммитит. NBCC проверяет конфликты только по FileGroup, которые затронул A. Если fg-001 и fg-002 не менялись другими writer'ами — commit успешен без ожидания.Writer B: begin
Writer B начинает параллельно. Его записи затрагивают fg-003 и fg-004 — нет пересечения с Writer A.Запись: fg-003, fg-004
Writer B записывает данные в fg-003 и fg-004. Параллельно с Writer A, без блокировки.Commit +
Writer B коммитит. FileGroup fg-003 и fg-004 не пересекаются с A — commit успешен параллельно. Оба writer'а работают без взаимного ожидания.Ключевые отличия NBCC от OCC
Как NBCC разрешает конфликты
Когда два writer’а всё-таки затрагивают один и тот же FileGroup, NBCC использует precombine field для детерминистического разрешения:
Writer A: order_id=100, updated_at=T1 → fg-002
Writer A записал запись с order_id=100, updated_at=T1 в FileGroup fg-002. Commit успешен — instant опубликован на timeline.Writer B: order_id=100, updated_at=T2 → fg-002
Writer B тоже записал order_id=100 с updated_at=T2 (T2 > T1) в fg-002. При commit обнаруживает, что fg-002 изменён A.Precombine: T2 > T1 → B побеждает. Compaction plan для слияния.
NBCC разрешает конфликт через precombine: запись с более поздним updated_at (T2 > T1) побеждает. B создаёт compaction plan для слияния результатов A и B. Ни один writer не делает полный retry.NBCC не требует внешнего lock-провайдера для базового сценария — конфликты разрешаются на уровне timeline и precombine. Но для сложных сценариев (multi-table transactions) lock provider всё ещё может использоваться.
NBCC доступен начиная с table version 8 (Hudi 1.0+). Таблицы, созданные в более ранних версиях, используют OCC по умолчанию. Миграция на NBCC требует обновления table version — подробнее в разделе Table Version Protocol.
Multi-Writer сценарии
На практике multi-writer возникает в нескольких типичных архитектурах:
Конфигурация multi-writer
Для OCC (до 1.0) multi-writer требует явной конфигурации lock-провайдера:
# Включить multi-writer
hoodie.write.concurrency.mode=optimistic_concurrency_control
hoodie.cleaner.policy.failed.writes=LAZY
# Lock provider — Zookeeper
hoodie.write.lock.provider=org.apache.hudi.client.transaction.lock.ZookeeperBasedLockProvider
hoodie.write.lock.zookeeper.url=zk-host:2181
hoodie.write.lock.zookeeper.port=2181
hoodie.write.lock.zookeeper.lock_key=hudi_table_lock
hoodie.write.lock.zookeeper.base_path=/hudi-locks
Для NBCC (1.0+) — встроенная поддержка, конфигурация минимальна:
# NBCC включён по умолчанию для table version 8+
# Если таблица создана с Hudi 1.0, NBCC активен автоматически
hoodie.write.concurrency.mode=NON_BLOCKING_CONCURRENCY_CONTROL
При миграции с OCC на NBCC важно убедиться, что все writer’ы обновлены до Hudi 1.0+. Смешивание writer’ов разных версий на одной таблице приведёт к непредсказуемому поведению — старый writer не понимает NBCC-протокол.
Table Version Protocol
Hudi использует table version в hoodie.properties для управления совместимостью. Это аналог minReaderVersion/minWriterVersion в Delta Lake и format-version в Iceberg:
Совместимость версий
hoodie.table.version=8 ← Hudi 1.0+ reader/writer
hoodie.table.version=6 ← Hudi 0.10–0.14 reader/writer
# Writer 1.0 может читать version 6, но NOT наоборот:
# Writer 0.14 НЕ может читать/писать version 8 таблицу
Обновление table version — одностороннее: переход с version 6 на 8 возможен, обратно — нет. После обновления все writer’ы должны использовать Hudi 1.0+. Планируйте обновление как миграцию всего pipeline, а не постепенный rollout.
Conflict Resolution Strategies
Hudi предлагает несколько стратегий разрешения конфликтов, настраиваемых через hoodie.write.conflict.resolution.strategy:
Сравнение с Delta Lake и Iceberg
Практические рекомендации
Для новых таблиц (Hudi 1.0+): используйте NBCC по умолчанию (table version 8). Не конфигурируйте lock provider — NBCC справляется с большинством сценариев.
Для существующих таблиц (pre-1.0): оцените table version upgrade. Если pipeline допускает downtime — обновите до version 8. Если нет — продолжайте с OCC и lock provider, планируя миграцию.
Для cross-table consistency: используйте lock provider даже с NBCC. Hudi гарантирует ACID per-table, но cross-table atomicity — ответственность приложения.
Итоги
- OCC (до 1.0): table-level lock через внешний provider. При конфликте — полный retry writer’а. Масштабирование ограничено serialized commits.
- NBCC (1.0+): FileGroup-level isolation. Параллельные writer’ы, затрагивающие разные FG, коммитят без блокировок. Конфликты на одном FG — precombine merge.
- Lock providers: Zookeeper (production, ephemeral nodes), DynamoDB (AWS, serverless), filesystem (только HDFS), InProcess (тесты).
- Table version 8: включает NBCC, LSM timeline, secondary indexes. Upgrade одностороний — обратно нельзя.
- Hudi — единственный из трёх major форматов с non-blocking конкурентным доступом на уровне файловых групп.
В следующем уроке мы разберём 5 типов запросов Hudi — от snapshot до incremental CDC — и паттерн checkpoint-based ETL, который делает Hudi уникальным инструментом для инкрементальных pipeline.