Skip to content

feat(flink): add new RocksDBPartitionedIndexBackend - #19765

Open
HuangZhenQiu wants to merge 3 commits into
apache:masterfrom
HuangZhenQiu:RocksDB-PartitionedIndexBackend
Open

feat(flink): add new RocksDBPartitionedIndexBackend#19765
HuangZhenQiu wants to merge 3 commits into
apache:masterfrom
HuangZhenQiu:RocksDB-PartitionedIndexBackend

Conversation

@HuangZhenQiu

@HuangZhenQiu HuangZhenQiu commented Aug 27, 2026

Copy link
Copy Markdown
Member

Describe the issue this Pull Request addresses

add new RocksDBPartitionedIndexBackend that can manages record-key-to-fileId mapping for each partition in column family of RocksDB.

closes #19601

Summary and Changelog

  1. Add RocksDBPartitionedIndexBackend, a RocksDB-based implementation of PartitionedIndexBackend that stores each data partition's record-key-to-fileId mapping in its own column family, enabling per-partition eviction.
  2. Add PartitionedIndexBackendFactory to centralize selection between the dummy, record-level, and new RocksDB-backed index backends based on FlinkOptions.INDEX_RLI_BACKEND_TYPE.
  3. Update DynamicBucketAssignFunction to use the new factory instead of inlining backend construction.
  4. Add unit tests for the new factory and RocksDB backend.

Impact

none

Risk Level

none

Documentation Update

none

Contributor's checklist

  • Read through contributor's guide
  • Enough context is provided in the sections above
  • Adequate tests were added if applicable

@github-actions github-actions Bot added the size:L PR with lines of changes in (300, 1000] label Aug 27, 2026

@hudi-agent hudi-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 🤖 This review was generated by an AI agent and may contain mistakes. Please verify any suggestions before applying.

Thanks for working on this! This PR adds a RocksDB-backed PartitionedIndexBackend that stores each partition's record-key→fileId mapping in its own column family, plus a PartitionedIndexBackendFactory to select between the dummy, MDT-backed, and RocksDB backends via index.rli.backend.type. The core RocksDB mechanics (column-family-per-partition, registry-then-visible ordering, close/cleanup) look sound; the main thing worth double-checking is that the RocksDB backend never consults the metadata table, so selecting it on a non-empty table or after a restart could route existing records as inserts — see the inline comments. Please take a look at any inline comments, and this should be ready for a Hudi committer or PMC member to take it from here. A couple of minor naming inconsistencies worth tidying up.

}
String backendType = conf.get(FlinkOptions.INDEX_RLI_BACKEND_TYPE);
if (ROCKSDB_BACKEND_TYPE.equalsIgnoreCase(backendType)) {
return new RocksDBPartitionedIndexBackend(conf.get(FlinkOptions.INDEX_RLI_CACHE_ROCKSDB_BASE_PATH));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 With index.rli.backend.type=rocksdb, get() only sees keys written via update() in the current run (RocksDBDAO wipes its dir on startup) and never consults the MDT RLI. On a non-empty table or after a restart, existing keys miss and get routed as inserts in DynamicBucketAssignFunction (L130-136), which can create a second file group for a key that already exists. The index.rli.cache.rocksdb.base.path description says this cache sits "in front of the metadata table" — is the MDT bootstrap/fallback still TODO before this backend is selectable? @danny0405 might want to weigh in.

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

*
* @param partitionPath the partition path to delete
*/
public void deletePartition(String partitionPath) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 deletePartition drops the column family before removing the registry entry — the reverse of the register-after-create ordering update() uses to keep get() safe. If a get() ever observed the intermediate state it would see the partition still registered but the CF handle already removed from RocksDBDAO's managedHandlesMap (null handle passed to RocksDB.get). Harmless while the assign operator is single-threaded, but would it be safer to unregister first, then drop the CF, to match the documented visibility invariant?

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

this.rocksDBDAO = new RocksDBDAO("hudi-partitioned-index-backend", rocksDbBasePath, new ConcurrentHashMap<>(), true);
}

@Override

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 nit: the parameter rocksDbBasePath uses lowercase Db while the class name spells it RocksDB (uppercase). Could you align to rocksDBBasePath for consistency?

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

public class PartitionedIndexBackendFactory {
private static final String ROCKSDB_BACKEND_TYPE = "rocksdb";

/**

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 nit: the string "rocksdb" is defined here but the valid values for FlinkOptions.INDEX_RLI_BACKEND_TYPE aren't visible at the call site. It might be clearer to keep this constant (or an enum) in FlinkOptions alongside the config key itself, so the two can't drift independently.

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

@codecov-commenter

codecov-commenter commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.15%. Comparing base (18ae8c3) to head (5c2cafe).
⚠️ Report is 20 commits behind head on master.

Files with missing lines Patch % Lines
...titioner/index/PartitionedIndexBackendFactory.java 87.50% 1 Missing ⚠️
...titioner/index/RocksDBPartitionedIndexBackend.java 96.66% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19765      +/-   ##
============================================
+ Coverage     77.97%   78.15%   +0.18%     
- Complexity    33464    33714     +250     
============================================
  Files          2539     2542       +3     
  Lines        140974   141465     +491     
  Branches      17013    17380     +367     
============================================
+ Hits         109928   110567     +639     
+ Misses        23382    23200     -182     
- Partials       7664     7698      +34     
Components Coverage Δ
hudi-common 83.57% <ø> (+0.08%) ⬆️
hudi-client 83.14% <ø> (+0.06%) ⬆️
hudi-flink 85.67% <95.00%> (+0.05%) ⬆️
hudi-spark-datasource 72.69% <ø> (+0.30%) ⬆️
hudi-utilities 74.58% <ø> (+0.26%) ⬆️
hudi-cli 15.06% <ø> (ø)
hudi-hadoop 70.10% <ø> (+0.85%) ⬆️
hudi-sync 75.56% <ø> (+0.02%) ⬆️
hudi-io 79.90% <ø> (+0.04%) ⬆️
hudi-timeline-service 83.44% <ø> (-0.30%) ⬇️
hudi-cloud 65.81% <ø> (+1.53%) ⬆️
hudi-kafka-connect 53.20% <ø> (ø)
Flag Coverage Δ
common-and-other-modules 51.49% <92.50%> (+0.43%) ⬆️
flink-integration-tests 48.89% <95.00%> (-0.10%) ⬇️
hadoop-mr-java-client 44.01% <ø> (+0.28%) ⬆️
integration-tests 13.53% <0.00%> (-0.04%) ⬇️
spark-client-hadoop-common 50.38% <ø> (-0.16%) ⬇️
spark-java-tests 52.15% <ø> (+0.13%) ⬆️
spark-scala-tests 46.73% <ø> (+0.08%) ⬆️
utilities 36.32% <ø> (-0.09%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...va/org/apache/hudi/configuration/FlinkOptions.java 99.79% <ø> (ø)
.../sink/partitioner/DynamicBucketAssignFunction.java 97.87% <100.00%> (-0.05%) ⬇️
...titioner/index/PartitionedIndexBackendFactory.java 87.50% <87.50%> (ø)
...titioner/index/RocksDBPartitionedIndexBackend.java 96.66% <96.66%> (ø)

... and 97 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

return new DummyPartitionedIndexBackend();
}
String backendType = conf.get(FlinkOptions.INDEX_RLI_BACKEND_TYPE);
if (ROCKSDB_BACKEND_TYPE.equalsIgnoreCase(backendType)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Selecting rocksdb here bypasses the MDT-backed RecordLevelIndexBackend, but this backend starts empty and neither bootstraps from nor falls back to the authoritative MDT RLI. On a non-empty table or after task/job restart, committed keys therefore miss in get() and DynamicBucketAssignFunction routes them as inserts, potentially assigning a second file group to an existing key.

In addition, the first update() registers the column family after loading only one key, which violates RFC-107's partition-completeness invariant. Please keep this backend non-selectable until partition bootstrap/on-demand MDT loading is implemented, or compose it with the MDT backend and register a partition only after its complete RLI state has been loaded.

@HuangZhenQiu HuangZhenQiu Aug 28, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for calling it out. Make the rocksdb option unselectable now.

@hudi-bot

Copy link
Copy Markdown
Collaborator

CI report:

Bot commands @hudi-bot supports the following commands:
  • @hudi-bot run azure re-run the last Azure build

@cshuo

cshuo commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Is RocksDBPartitionedIndexBackend intended to be a complete backend owned by BucketAssigner, or only a low-level store?
Currently, the RecordLevelIndexBackend owns bootstrap and cleanup itself. Will the RocksDB bootstrap and cleanup logic also live in BucketAssigner, or in a separate component such as RLIBootstrapOperator? If external, how will it share the backend’s privately owned RocksDB instance? Could you clarify the final ownership and responsibility boundary?

@HuangZhenQiu

Copy link
Copy Markdown
Member Author

It is a good question. As we described in the RFC, we probably need to decouple the dynamic partitioned record index load and final lookup into two operators. When a record of old partition comes, it will trigger the partition record index load, these record index should be keyed by and shuttle to all of other task managers. In this way, the RocksDB in each of task managers only needs to store the state it required, and one partition need to load once.

The RocksDBPartitionedIndexBackend will be a configurable backend owned by BucketAssignFunction and the new function PreLookup. The RocksDB Dao will be initialized in singleton and shared by both BucketAssignFunction and PreLookup. How do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L PR with lines of changes in (300, 1000]

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add new RocksDBPartitionedIndexBackend

5 participants