Skip to content

fix(core): record the legacy precombine key for tables below version 9 - #20346

Open
wombatu-kun wants to merge 4 commits into
apache:masterfrom
wombatu-kun:fix-precombine-field-below-v9
Open

wombatu-kun wants to merge 4 commits into
apache:masterfrom
wombatu-kun:fix-precombine-field-below-v9

Conversation

@wombatu-kun

@wombatu-kun wombatu-kun commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Describe the issue this Pull Request addresses

Closes #20345

Tables below version 9 recorded their ordering fields only as hoodie.table.ordering.fields, which Hudi 0.x (version 6) and 1.0.x (version 8) do not read, and ALTER TABLE RENAME dropped hoodie.table.precombine.field from tables that had it.

Summary and Changelog

  • HoodieTableConfig.create also records the ordering field of a table below version 9 under hoodie.table.precombine.field, the key its readers know and the one the 9 to 8 downgrade writes. Those readers take that key as one field name, so it is written only when a single ordering field is declared, with that field, whatever the payload. Table creation and every re-creation from the table's own properties go through it.
  • TestHoodieTableMetaClient pins both keys per table version, for a new table, for one ordered on two fields, for a MySqlDebeziumAvroPayload table, and for one re-created from properties that carry only the legacy key. A Spark SQL test does the same through CREATE TABLE and ALTER TABLE RENAME at versions 6 and 8.

Impact

Tables created or re-created below version 9 with a single ordering field carry hoodie.table.precombine.field next to hoodie.table.ordering.fields, with the same value, so 0.x and 1.0.x find their ordering field again and order records on the same field as 1.x. Tables ordered on several fields keep only the new key, as before, since 1.0.2 would otherwise fail MOR merges with Field: ts,seq does not exist in the table schema instead of warning. 1.x reads hoodie.table.ordering.fields first, so its behavior is unchanged, and the 8 to 9 upgrade still removes the legacy key. Tables created before this fix keep only the new key until they are re-created. #20276 pins the exact table configs of versions 6 and 8, so whichever of the two lands second adds the legacy key there.

Risk Level

low

The change adds one key below version 9, only next to a single declared ordering field and with the same value, and leaves every value 1.x reads as it was. Verified: the single-field tests fail on master with precombine key at version 6 ==> expected: <ts> but was: <null>, and Hudi 0.15.0 and 1.0.2 resolve getPreCombineField() to the ordering field on single-field tables written with this change (null without it).

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:M PR with lines of changes in (100, 300] label Oct 15, 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 also writes hoodie.table.precombine.field when a table below version 9 is created or re-created, so 0.x and 1.0.x readers can find the ordering field; one edge case with multi-field ordering is flagged inline. 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.

String orderingFields = hoodieConfig.getProps().getProperty(ORDERING_FIELDS.key());
if (orderingFields != null && tableVersion.lesserThan(HoodieTableVersion.NINE)) {
// readers of tables below version 9 only know the deprecated precombine key, which the 9 to 8 downgrade writes too
hoodieConfig.setValue(PRECOMBINE_FIELD, orderingFields);

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.

🤖 It looks like a v6/v8 table created with several ordering fields (e.g. orderingFields = 'ts,seq') would now record hoodie.table.precombine.field=ts,seq, which 0.x and 1.0.x read as a single field name. On 1.0.2 the file group reader then throws Field: ts,seq does not exist in the table schema for MOR slices with log files, where today it only logs a warning and reads. Would it make sense to write the legacy key only when there is exactly one ordering field?

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done f42fb35

@codecov-commenter

codecov-commenter commented Oct 15, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.75%. Comparing base (0170359) to head (c27bc85).
⚠️ Report is 13 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master   #20346      +/-   ##
============================================
+ Coverage     80.73%   80.75%   +0.01%     
- Complexity    35169    35320     +151     
============================================
  Files          2553     2562       +9     
  Lines        143577   144146     +569     
  Branches      17486    17620     +134     
============================================
+ Hits         115924   116401     +477     
- Misses        19696    19772      +76     
- Partials       7957     7973      +16     
Components Coverage Δ
hudi-common 84.18% <100.00%> (+<0.01%) ⬆️
hudi-client 83.95% <ø> (-0.03%) ⬇️
hudi-flink 86.08% <ø> (+<0.01%) ⬆️
hudi-spark-datasource 74.19% <ø> (-0.01%) ⬇️
hudi-utilities 78.36% <ø> (+0.21%) ⬆️
hudi-cli 70.80% <ø> (ø)
hudi-hadoop 71.18% <ø> (+0.01%) ⬆️
hudi-sync 76.85% <ø> (+0.19%) ⬆️
hudi-io 81.48% <ø> (-0.05%) ⬇️
hudi-timeline-service 83.41% <ø> (ø)
hudi-cloud 81.01% <ø> (+0.01%) ⬆️
hudi-kafka-connect 57.90% <ø> (ø)
Flag Coverage Δ
common-and-other-modules 52.69% <100.00%> (+0.06%) ⬆️
flink-integration-tests 49.65% <100.00%> (+<0.01%) ⬆️
hadoop-mr-java-client 43.95% <33.33%> (-0.01%) ⬇️
integration-tests 13.40% <33.33%> (-0.05%) ⬇️
spark-client-hadoop-common 38.52% <100.00%> (-0.16%) ⬇️
spark-java-tests 52.58% <100.00%> (-0.23%) ⬇️
spark-scala-tests 47.55% <100.00%> (-0.21%) ⬇️
utilities 37.00% <100.00%> (+0.12%) ⬆️

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

Files with missing lines Coverage Δ
...rg/apache/hudi/common/table/HoodieTableConfig.java 94.84% <100.00%> (+0.02%) ⬆️

... and 41 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.

@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 records hoodie.table.precombine.field alongside hoodie.table.ordering.fields for tables below version 9 that have a single ordering field, both at creation and when a table is re-created from its own properties (rename, truncate), so 0.x and 1.0.x readers find the ordering field again. No issues flagged from this automated pass — a Hudi committer or PMC member can take it from here for a final review.

cc @yihua

if (orderingFields.size() == 1 && tableVersion.lesserThan(HoodieTableVersion.NINE)) {
// readers of tables below version 9 only know the deprecated precombine key, which the 9 to 8 downgrade writes too,
// and take its value as one field name
hoodieConfig.setValue(PRECOMBINE_FIELD, orderingFields.get(0));

@shangxinli shangxinli Oct 16, 2026 •

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.

major: NineToEightDowngradeHandler.getOrderingFieldsBelowVersionNine in #20348 resolves this to DebeziumConstants.ADDED_SEQ_COL_NAME when the legacy payload is MySqlDebeziumAvroPayload, so a v8 table created here and an identical one downgraded to v8 end up with different precombine keys. LEGACY_PAYLOAD_CLASS_NAME is reconciled before this point, so the same check fits here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done ecbffc4: below version 9 a MySqlDebeziumAvroPayload table now gets hoodie.table.precombine.field=_event_seq, the same as after the downgrade. The payload is detected through hoodie.compaction.payload.class, since hoodie.table.legacy.payload.class is not set below version 9.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reverted in c27bc85 after danny0405's comment: a table declared with _event_seq still gets the same precombine key as a downgraded one, and one declared with ts now keeps ts. With _event_seq next to hoodie.table.ordering.fields=ts, a 0.x or 1.0.x Spark writer would dedup on _event_seq while 1.x dedups on ts, or fail with Config conflict when passed ts.

@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 records the ordering field under the legacy hoodie.table.precombine.field key when a table below version 9 is created or re-created, using a single ordering field as-is and _event_seq for MySqlDebeziumAvroPayload, the same value the 9 to 8 downgrade writes. No correctness issues found. A few style/readability suggestions in the inline comments. Please take a look, and this should be ready for a Hudi committer or PMC member to take it from here. The code is clean; I have one small suggestion to pull the new legacy-precombine block out of create() into a named helper.

cc @yihua

hoodieConfig.setValue(POPULATE_META_FIELDS,
Boolean.toString(metaFieldsMode.toLegacyPopulateMetaFields()));
}
List<String> orderingFields = StringUtils.split(hoodieConfig.getProps().getProperty(ORDERING_FIELDS.key()), ",");

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: would it make sense to move this block into a small helper like setLegacyPrecombineFieldIfNeeded(hoodieConfig, tableVersion), so create() reads as a list of steps and the rule for versions below 9 lives in one named place?

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

With c27bc85 the block is back to one if, the size of the POPULATE_META_FIELDS step above it, so it stays inline.

// and take its value as one field name
if (MySqlDebeziumAvroPayload.class.getName().equals(hoodieConfig.getString(PAYLOAD_CLASS_NAME))) {
// below version 9 this payload orders on its sequence field, the value the downgrade writes for it
hoodieConfig.setValue(PRECOMBINE_FIELD, DebeziumConstants.ADDED_SEQ_COL_NAME);

@danny0405 danny0405 Oct 17, 2026 •

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.

So the user just not declare the ordering field? should we just follow the user config since it has no default value now. and why couple payload class with ordeging field, do we have this validation for the bindings already?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done c27bc85: the legacy key now repeats the declared ordering field whatever the payload, and stays unset when none is declared. There is no such validation: this payload's ordering fields are only rewritten at version 9 creation, by the 8 to 9 and 9 to 8 handlers, and by StreamSync on version 9 tables.

…he legacy precombine key whatever the payload

@shangxinli shangxinli 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.

Checked the revert in c27bc85 - the 9 to 8 downgrade drops hoodie.table.ordering.fields when it writes _event_seq, so a created table that keeps both keys is not the same case and repeating the declared ordering field is the right call here. My comment is settled.

This branch has not been deployed

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

Labels

size:M PR with lines of changes in (100, 300]

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Tables below version 9 lack hoodie.table.precombine.field, so 0.x and 1.0.x see no ordering field

5 participants