Skip to content

dm: support FK causality for one-to-one routes - #12675

Merged
ti-chi-bot[bot] merged 6 commits into
pingcap:masterfrom
OliverS929:fk-route-causality
Jun 10, 2026
Merged

ti-chi-bot[bot] merged 6 commits into
pingcap:masterfrom
OliverS929:fk-route-causality

Conversation

@OliverS929

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: ref #12350

What is changed and how it works?

Support DM FK causality when foreign_key_checks=1 and syncer.worker-count > 1 with static one-to-one route rules.

This PR:

  • builds FK causality relations with source table identity after route validation
  • makes causality key generation follow the task case-sensitive setting, including routed FK causality keys
  • respects the task case-sensitive setting when matching FK metadata and checking route topology
  • rejects route rules that map multiple source tables in the task to the same target table
  • rejects compact and multiple-rows DML boundary options when foreign_key_checks=1
  • caches successful route topology checks and invalidates the cache after schema/config state changes
  • adds unit and integration test coverage for routed multi-worker FK causality

A follow-up PR will explicitly reject hot updates that can change route/filter/case-sensitive/FK causality mode.

Check List

Tests

  • Unit test
  • Integration test
  • Manual test (add detailed scripts or steps below)
  • No code

Questions

Will it cause performance regression or break compatibility?
Do you need to update user documentation, design documentation or monitoring documentation?

Release note

Support foreign key causality for static one-to-one routing in DM multi-worker sync.

@ti-chi-bot ti-chi-bot Bot added release-note Denotes a PR that will be considered when it comes time to generate release notes. needs-cherry-pick-release-8.5 Should cherry pick this PR to release-8.5 branch. area/dm Issues or PRs related to DM. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Jun 5, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces support for foreign key causality checks under multi-worker syncer configurations with routing rules, specifically handling case-insensitive table routing and rejecting DML boundary-changing options (like compact and multiple-rows) when foreign_key_checks is enabled. The feedback identifies three potential nil pointer dereference panics due to missing nil checks on filter.Table parameters in sameForeignKeyTableIdentity, foreignKeyRouteTopologyTableID, and causalityKeySourceTableNameForRowChange.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread dm/pkg/schema/tracker.go
Comment thread dm/syncer/syncer.go
Comment thread dm/syncer/dml.go
@OliverS929

Copy link
Copy Markdown
Contributor Author

/test pull-syncdiff-integration-test

@OliverS929

Copy link
Copy Markdown
Contributor Author

cc @joechenrh PTAL.

Comment thread dm/config/subtask.go Outdated
Comment thread dm/pkg/schema/tracker.go
Comment thread dm/pkg/schema/tracker.go
Comment thread dm/pkg/schema/tracker_test.go Outdated
Comment thread dm/syncer/dml_worker.go Outdated

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.

make the file name the same as the inner used db name fk_route_src

@OliverS929 OliverS929 Jun 9, 2026 •

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.

I used db1.prepare.sql to follow the existing DM integration naming convention for source-1 SQL files. The schema name itself is still fk_route_src in the SQL and task config.

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.

i guess existing are wrong, please remove the db1 prefix or rename using real schema name or source1

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.

Changed in 996dcf1.

Comment thread dm/tests/foreign_key_route_multi_worker/run.sh
Comment thread dm/syncer/dml.go
Comment thread dm/syncer/dml.go
return dmls, nil
}

func (s *Syncer) causalityKeySourceTableNameForRowChange(sourceTable *filter.Table) *cdcmodel.TableName {

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.

Suggested change
func (s *Syncer) causalityKeySourceTableNameForRowChange(sourceTable *filter.Table) *cdcmodel.TableName {
func (s *Syncer) getCausalityKeySourceTableName(sourceTable *filter.Table) *cdcmodel.TableName {

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.

The current name is meant to tie this helper to the RowChange.SetCausalityKeySourceTable hook. It only changes the source table used by CausalityKeys(), not the row change source table itself. If the shorter name is not a must-change for you, I'd prefer to keep this one.

Comment thread dm/tests/foreign_key_route_multi_worker/run.sh
Comment thread dm/syncer/syncer.go Outdated
})
p := parser.New()

execTrackedDDL(t, syncer.schemaTracker, p, "", "create database db")

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.

maybe abstract those common pattern a bit, such as prepareSimpleParentChildSchema

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.

My concern with a higher-level helper here is that the nearby cases vary in schema case, route rules, target table names, and downstream FK definitions. The shared lower-level pieces are already covered by execTrackedDDL and expectFetchAllDoTables.

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.

just let AI try to do it without hurting readibility

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.

Changed in ccd9638.

sourceTable *cdcmodel.TableName
targetTable *cdcmodel.TableName
// Optional source table name used only by CausalityKeys.
causalityKeySourceTable *cdcmodel.TableName

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.

can we set sourceTable to the right table directly, so no need to add a separate field

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.

I think we should keep these two identities separate here. sourceTable is still the real row-change source table and is used by GetSourceTable(), DML job metadata, validator/checkpoint paths, and logs. This field only overrides the table name used by CausalityKeys(), so the FK causality key can use the normalized source-domain identity without changing the row change's source table semantics.

Comment thread dm/syncer/syncer.go Outdated
return ok
}

func (s *Syncer) markForeignKeyRouteTopologyChecked(cacheKey string) {

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.

the key is always the same, what's the purpose to make a map?
add comment to explain this, if for future extension, we can do it later

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.

The map/key came from an earlier version that cached checks per routed target set for the current FK graph, instead of one task-level check. We moved back to task-level checking because scoped checks need more support to catch parent-only DML cases early enough. With the current task-level check, there is only one checked state, so I simplified it to a single checked flag in d8c4449.

If we further extend this to scoped FK-graph checks later, the keyed cache can come back with the needed parent-side detection.

Comment thread dm/syncer/dml_worker.go Outdated
Comment on lines 66 to 68
func isForeignKeyChecksEnabled(session map[string]string) bool {
for key, value := range session {
if strings.EqualFold(key, "foreign_key_checks") {
trimmed := strings.Trim(value, " '\"")
return variable.TiDBOptOn(trimmed)
}
}
return false
return config.IsForeignKeyChecksEnabled(session)
}

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.

Why not removing this function

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.

I kept the wrapper initially to avoid moving the existing syncer-side coverage while moving the parsing helper into config. I’ve moved that coverage to the config package and removed the wrapper in d8c4449.

case job, ok := <-causalityCh:
require.True(t, ok)
return job
case <-time.After(3 * time.Second):

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.

Will this make the test flaky?

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.

I don't think this adds a flaky timing dependency. The timeout is meant as a fail-fast guard if the causality goroutine does not produce output. The test has already put both jobs into a buffered channel and closed it, so it is expected that the receive is waiting for local goroutine processing rather than an external timing condition. Without the timeout, a bug in the causality path could leave the test hanging.

parentDMLs, err := syncer.genAndFilterInsertDMLs(tcontext.Background(), &genDMLParam{
sourceTable: parentSource,
targetTable: parentTarget,
originalData: [][]interface{}{{10}},

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: replace interface{} with any

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.

Changed in d8c4449.

Comment thread dm/syncer/syncer.go Outdated
downstreamTableInfo *schema.DownstreamTableInfo,
) error {
if downstreamTableInfo == nil || downstreamTableInfo.TableInfo == nil {
const foreignKeyRouteTopologyTaskCacheKey = "task"

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.

Will we have other task keys in the future?

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.

No, not in the current design. The map/key came from an earlier version that cached checks per routed target set for the current FK graph, instead of one task-level check. We moved back to task-level checking because scoped checks need more support to catch parent-only DML cases early enough. With the current task-level check, there is only one checked state, so I simplified it to a single checked flag in d8c4449. If we further extend this to scoped FK-graph checks later, we can bring back keyed cache behavior together with the needed parent-side detection.

@ti-chi-bot

ti-chi-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

@joechenrh: adding LGTM is restricted to approvers and reviewers in OWNERS files.

Details

In response to this:

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@OliverS929
OliverS929 requested review from D3Hunter and joechenrh June 10, 2026 03:54

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

rest lgtm

})
p := parser.New()

execTrackedDDL(t, syncer.schemaTracker, p, "", "create database db")

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.

just let AI try to do it without hurting readibility

@OliverS929
OliverS929 requested a review from D3Hunter June 10, 2026 04:20
@OliverS929

Copy link
Copy Markdown
Contributor Author

/retest

@ti-chi-bot ti-chi-bot Bot added the needs-1-more-lgtm Indicates a PR needs 1 more LGTM. label Jun 10, 2026
@OliverS929

Copy link
Copy Markdown
Contributor Author

/test pull-dm-compatibility-test

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

LGTM

@ti-chi-bot ti-chi-bot Bot added lgtm and removed needs-1-more-lgtm Indicates a PR needs 1 more LGTM. labels Jun 10, 2026
@ti-chi-bot

ti-chi-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

[LGTM Timeline notifier]

Timeline:

  • 2026-06-10 04:30:56.65570283 +0000 UTC m=+934357.726020240: ☑️ agreed by D3Hunter.
  • 2026-06-10 05:06:01.25589413 +0000 UTC m=+936462.326211520: ☑️ agreed by Benjamin2037.

@OliverS929

Copy link
Copy Markdown
Contributor Author

/retest

1 similar comment
@OliverS929

Copy link
Copy Markdown
Contributor Author

/retest

@ti-chi-bot

ti-chi-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: Benjamin2037, D3Hunter, joechenrh, yudongusa

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@ti-chi-bot ti-chi-bot Bot added the approved label Jun 10, 2026
@ti-chi-bot
ti-chi-bot Bot merged commit 3450f5d into pingcap:master Jun 10, 2026
41 of 42 checks passed
@ti-chi-bot

Copy link
Copy Markdown
Member

In response to a cherrypick label: new pull request created to branch release-8.5: #12707.
But this PR has conflicts, please resolve them!

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

Labels

approved area/dm Issues or PRs related to DM. lgtm needs-cherry-pick-release-8.5 Should cherry pick this PR to release-8.5 branch. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants