Learning Platform
Глоссарий Troubleshooting
Урок 14.04 · 40 мин
Продвинутый
Apache HudiConcurrency ControlACIDOCCNBCCLock ProviderMulti-WriterConflict ResolutionTable Version

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’ов работают параллельно без блокировок на уровне таблицы.

NOTE

Delta Lake использует OCC через атомарный rename _delta_log/ файлов (или Coordinated Commits в V7). Iceberg использует OCC через атомарную замену metadata pointer в каталоге. Hudi — единственный из трёх форматов, который реализовал полноценный non-blocking протокол для параллельных writer’ов на уровне файловых групп.

ACID-гарантии Hudi

Hudi обеспечивает полные ACID-свойства через timeline + атомарную публикацию instant’ов:

ACID-свойства в Hudi
AtomicityКаждая операция (commit/deltacommit) либо полностью завершена (completed instant), либо полностью отменена (rollback). Inflight instant невидим для reader'ов — публикация происходит при переходе в completed.
ConsistencyTimeline — single source of truth. Reader видит только completed instants. Схема валидируется при каждом commit. Precombine field разрешает дубликаты детерминистически.
IsolationSnapshot isolation: reader видит snapshot на момент последнего completed instant. Writer'ы работают со своим view timeline. Конфликты разрешаются при commit (OCC) или на уровне FileGroup (NBCC).
DurabilityДанные записаны в storage (HDFS/S3/GCS) до публикации completed instant. Timeline instants хранятся рядом с данными — нет внешних зависимостей для восстановления состояния таблицы.

Ключевое отличие от 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-провайдера:

OCC-протокол: два writer'а

Writer A: begin

Writer A начинает операцию. Читает текущий timeline, получает snapshot view таблицы. Вычисляет plan: какие FileGroup затронуть, какие записи insert/update.
read timeline

Запись данных

Writer A записывает данные в FileGroup. На этом этапе lock не нужен — данные пишутся в новые файлы, которые ещё не видны reader'ам.
acquire lock

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 (или более свежий).
read timeline

Запись данных

Writer B тоже записывает данные. Если A и B затронули разные FileGroup — конфликта не будет. Если одни и те же — один из writer'ов проиграет при conflict check.
acquire lock

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
WARNING

OCC не масштабируется для high-throughput сценариев: каждый retry — это полная перезапись данных. Если 10 writer’ов пишут в перекрывающиеся партиции, cascading retries могут занять больше времени, чем последовательная запись. Именно это ограничение привело к разработке NBCC.

Lock-провайдеры

Для OCC Hudi требует внешний lock-провайдер — компонент, обеспечивающий атомарный захват блокировки между распределёнными writer’ами:

Lock-провайдеры Hudi
ZookeeperBasedLockProviderApache Zookeeper ephemeral nodes. Самый надёжный для production: автоматическое освобождение lock при падении writer'а (ephemeral node удаляется при потере сессии). Требует Zookeeper-кластер.
DynamoDBBasedLockProviderAWS DynamoDB conditional writes. Идеален для S3-based Hudi-таблиц: serverless, нет инфраструктуры для управления. TTL на lock записи — автоматическая очистка. Ограничение: только AWS.
FileSystemBasedLockProviderФайл-маркер в storage (HDFS/S3). Простейший вариант — не требует внешних сервисов. Ограничения: S3 не поддерживает атомарный rename → возможны race conditions. Подходит только для HDFS с atomic rename.
InProcessLockProviderJVM-уровневый lock (ReentrantReadWriteLock). Работает только внутри одного JVM-процесса — для тестирования и single-writer сценариев. Бесполезен для multi-writer на разных машинах.
TIP

На 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:

NBCC: Non-Blocking Concurrency Control

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

OCC vs NBCC: сравнение протоколов
OCC (до 1.0)Optimistic Concurrency Control: writer'ы записывают данные оптимистично, затем при commit захватывают table-level lock и проверяют конфликты. При конфликте — полный retry.
NBCC (1.0+)Non-Blocking Concurrency Control: конфликты разрешаются на уровне FileGroup. Writer'ы, работающие с разными FileGroup, коммитят параллельно без блокировок. Конфликт только при пересечении FileGroup.

Как NBCC разрешает конфликты

Когда два writer’а всё-таки затрагивают один и тот же FileGroup, NBCC использует precombine field для детерминистического разрешения:

NBCC: разрешение конфликтов при пересечении FileGroup

Writer A: order_id=100, updated_at=T1 → fg-002

Writer A записал запись с order_id=100, updated_at=T1 в FileGroup fg-002. Commit успешен — instant опубликован на timeline.
A committed

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.
conflict detected

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 всё ещё может использоваться.

NOTE

NBCC доступен начиная с table version 8 (Hudi 1.0+). Таблицы, созданные в более ранних версиях, используют OCC по умолчанию. Миграция на NBCC требует обновления table version — подробнее в разделе Table Version Protocol.

Multi-Writer сценарии

На практике multi-writer возникает в нескольких типичных архитектурах:

Типичные multi-writer сценарии
Сценарий 1: Streaming + BatchFlink-job непрерывно пишет real-time события (deltacommit каждые 30 секунд). Spark-job запускается ежечасно для bulk backfill или корректировок. Оба пишут в одну таблицу.
Сценарий 2: Parallel IngestionНесколько Spark-job'ов параллельно инжестят данные из разных источников в одну таблицу. Каждый job работает со своим набором partition path. При NBCC — масштабируется линейно.
Сценарий 3: Table ServicesInline-сервисы (cleaning, compaction) выполняются внутри writer-процесса. Async-сервисы (кластеризация) запускаются отдельным процессом. Async-процесс конкурирует с основным writer за FileGroup.
Сценарий 4: Multi-Table TransactionОбновление нескольких связанных таблиц в рамках одной логической транзакции. Требует внешней координации — Hudi не имеет встроенных cross-table transactions. Используется lock provider для сериализации.

Конфигурация 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
TIP

При миграции с OCC на NBCC важно убедиться, что все writer’ы обновлены до Hudi 1.0+. Смешивание writer’ов разных версий на одной таблице приведёт к непредсказуемому поведению — старый writer не понимает NBCC-протокол.

Table Version Protocol

Hudi использует table version в hoodie.properties для управления совместимостью. Это аналог minReaderVersion/minWriterVersion в Delta Lake и format-version в Iceberg:

Эволюция Table Version
Version 0–5Ранние версии Hudi. Базовая функциональность: COW/MOR, timeline, Bloom index. Формат instant'ов — plain text. Нет multi-writer support.
Version 6Hudi 0.10–0.14. OCC support через lock providers. Timeline instants хранят план как Avro. Metadata Table для ускорения file listing. Log block format v2.
Version 8Hudi 1.0+. NBCC по умолчанию. LSM-style archived timeline. Secondary indexes. Expression indexes. Record-level index с HFile. Partial updates. Managed timeline server.

Совместимость версий

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 таблицу
WARNING

Обновление table version — одностороннее: переход с version 6 на 8 возможен, обратно — нет. После обновления все writer’ы должны использовать Hudi 1.0+. Планируйте обновление как миграцию всего pipeline, а не постепенный rollout.

Conflict Resolution Strategies

Hudi предлагает несколько стратегий разрешения конфликтов, настраиваемых через hoodie.write.conflict.resolution.strategy:

Стратегии разрешения конфликтов
SimpleConcurrencyOptimisticConflictResolutionСтратегия по умолчанию для OCC. При конфликте на FileGroup — fail текущего writer'а с исключением. Writer должен обработать исключение и retry на уровне приложения. Простая, но требует retry-логики в коде.
BucketIndexConcurrencyConflictResolutionСпециализированная стратегия для Bucket Index. Использует bucket assignment для детерминистического разделения writer'ов по FileGroup. Если два writer'а попадают в разные bucket — конфликта нет.
PreferWriterConflictResolutionСтратегия, предпочитающая текущий writer. При конфликте — перезаписывает данные предыдущего writer'а. Подходит для CDC-pipeline, где последняя запись = истина.
NBCC ResolutionВ NBCC конфликты разрешаются через precombine field автоматически. Запись с более поздним значением precombine побеждает. Compaction сливает результаты обоих writer'ов. Нет необходимости в retry.

Сравнение с Delta Lake и Iceberg

Concurrency Control: Hudi vs Delta Lake vs Iceberg
Delta LakeOCC через atomic rename (HDFS) или Coordinated Commits (S3/V7). Конфликт: retry всей транзакции. ConcurrentModificationException при пересечении файлов. Isolation: Serializable (Write) + Snapshot (Read).
Apache IcebergOCC через atomic swap metadata pointer в каталоге (Hive Metastore/REST/Nessie). Retry с автоматическим rebase (если нет пересечения файлов). Серийные snapshot ID. Каталог — single point of serialization.
Apache HudiOCC (до 1.0) через lock providers или NBCC (1.0+) через FileGroup-level isolation. NBCC — единственный non-blocking протокол среди трёх форматов. Разрешение через precombine, без полного retry.

Практические рекомендации

TIP

Для новых таблиц (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.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. Hudi-таблица (table version 6) на S3. Два Spark-job'а одновременно выполняют upsert. Lock provider — DynamoDBBasedLockProvider. Job A закоммитил, Job B обнаружил конфликт на FileGroup fg-005. Что произойдёт с Job B?

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

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

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

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