Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FLINK-20370][table] part1: Fix wrong results when sink primary key is not the same with query result's changelog upsert key #17699

Merged
merged 5 commits into from
Nov 29, 2021

Conversation

lincoln-lil
Copy link
Contributor

What is the purpose of the change

The root cause of this case is FlinkChangelogModeInferenceProgram didn't consider the case when sink's primary key differs from input's upsert keys when infer required ChangeLogMode from sink node.
So we should add releated logic to FlinkChangelogModeInferenceProgram and also update upsertMaterialization process in StreamPhysicalSink

Brief change log

update the logic of FlinkChangelogModeInferenceProgram and StreamPhysicalSink

Verifying this change

Streaming sql's RankTest, AggregateTest and JoinTest

Does this pull request potentially affect one of the following parts:

  • Dependencies (does it add or upgrade a dependency): (no)
  • The public API, i.e., is any changed class annotated with @public(Evolving): (no)
  • The serializers: (no )
  • The runtime per-record code paths (performance sensitive): (no)
  • Anything that affects deployment or recovery: JobManager (and its components), Checkpointing, Kubernetes/Yarn, ZooKeeper: (no)
  • The S3 file system connector: (no)

Documentation

  • Does this pull request introduce a new feature? (no)

@flinkbot
Copy link
Collaborator

flinkbot commented Nov 5, 2021

Thanks a lot for your contribution to the Apache Flink project. I'm the @flinkbot. I help the community
to review your pull request. We will use this comment to track the progress of the review.

Automated Checks

Last check on commit 0c17938 (Fri Nov 05 14:57:55 UTC 2021)

Warnings:

  • No documentation files were touched! Remember to keep the Flink docs up to date!

Mention the bot in a comment to re-run the automated checks.

Review Progress

  • ❓ 1. The [description] looks good.
  • ❓ 2. There is [consensus] that the contribution should go into to Flink.
  • ❓ 3. Needs [attention] from.
  • ❓ 4. The change fits into the overall [architecture].
  • ❓ 5. Overall code [quality] is good.

Please see the Pull Request Review Guide for a full explanation of the review process.


The Bot is tracking the review progress through labels. Labels are applied according to the order of the review items. For consensus, approval by a Flink committer of PMC member is required Bot commands
The @flinkbot bot supports the following commands:

  • @flinkbot approve description to approve one or more aspects (aspects: description, consensus, architecture and quality)
  • @flinkbot approve all to approve all aspects
  • @flinkbot approve-until architecture to approve everything until architecture
  • @flinkbot attention @username1 [@username2 ..] to require somebody's attention
  • @flinkbot disapprove architecture to remove an approval you gave earlier

@flinkbot
Copy link
Collaborator

flinkbot commented Nov 5, 2021

CI report:

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

@JingsongLi
Copy link
Contributor

Thanks @lincoln-lil
Note SinkUpsertMaterializer requires update before.
We should not separate the inference of change log mode from the sink upsert materializer, which will lead to bugs. We should put both the inference and sink upsert mode in one place.
I think we should introduce something like RankProcessStrategy of rank in FlinkChangelogModeInferenceProgram.

@lincoln-lil
Copy link
Contributor Author

@JingsongLi agree with you putting these logic together. I've updated the pr.

@lincoln-lil
Copy link
Contributor Author

@flinkbot run azure

1 similar comment
@zentol
Copy link
Contributor

zentol commented Nov 10, 2021

@flinkbot run azure

@@ -58,6 +58,12 @@
<td><p>Enum</p></td>
<td>The NOT NULL column constraint on a table enforces that null values can't be inserted into the table. Flink supports 'error' (default) and 'drop' enforcement behavior. By default, Flink will check values and throw runtime exception when null values writing into NOT NULL columns. Users can change the behavior to 'drop' to silently drop such records without throwing exception.<br /><br />Possible values:<ul><li>"ERROR"</li><li>"DROP"</li></ul></td>
</tr>
<tr>
<td><h5>table.exec.sink.pk-shuffle</h5><br> <span class="label label-primary">Streaming</span></td>
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we create separate PR for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, I thought the target issue is the same one. I'll create a separate pr.

// if sink's pk(s) are not exactly match input changeLogUpsertKeys then it will fallback
// to beforeAndAfter mode for the correctness
var shouldFallback: Boolean = false
val sinkDefinedPks = toScala(sink.catalogTable.getResolvedSchema
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe you just want getPrimaryKeyIndexes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point! I simply moved the code from StreamPhysicalSink to here, getPrimaryKeyIndexes is more simpler.

// Notice: even sink pk(s) contains input upsert key we cannot optimize to UA only,
// this differs from batch job's unique key inference
if (changeLogUpsertKeys == null || changeLogUpsertKeys.size() == 0
|| !changeLogUpsertKeys.exists {0 == _.compareTo(sinkPks)}) {
Copy link
Contributor

Choose a reason for hiding this comment

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

compareTo -> equals?

// fallback to beforeAndAfter.
// Notice: even sink pk(s) contains input upsert key we cannot optimize to UA only,
// this differs from batch job's unique key inference
if (changeLogUpsertKeys == null || changeLogUpsertKeys.size() == 0
Copy link
Contributor

Choose a reason for hiding this comment

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

remove changeLogUpsertKeys.size() == 0?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This should be reserved because the metadata query may return a empty changeLogUpsertKeys set.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think changeLogUpsertKeys.exists {_.equals(sinkPks)} should cover this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, I overlooked it.

val sinkRequiredTraits = if (sinkTrait.equals(ONLY_UPDATE_AFTER)) {
// if sink's pk(s) are not exactly match input changeLogUpsertKeys then it will fallback
// to beforeAndAfter mode for the correctness
var shouldFallback: Boolean = false
Copy link
Contributor

Choose a reason for hiding this comment

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

requireBeforeAndAfter?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, it's more clearly.

val inputChangelogMode = ChangelogPlanUtils.getChangelogMode(
sink.getInput.asInstanceOf[StreamPhysicalRel]).get
val catalogTable = sink.catalogTable
val primaryKeys = toScala(catalogTable.getResolvedSchema
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto

sinkRequiredTraits
}

private def analyzeUpsertMaterializeStrategy(sink: StreamPhysicalSink): Boolean = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we merge this method to inferSinkRequiredTraits? For example, inferSinkRequiredTraits returns (Seq[UpdateKindTrait], Boolean)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Initially I put the two methods together, but seems a little bit complex, and the two methods do the different things indeed, so I change to the current version.

Copy link
Contributor Author

@lincoln-lil lincoln-lil left a comment

Choose a reason for hiding this comment

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

@JingsongLi Thans for your comments!
I'll separate the pr into two.

@@ -58,6 +58,12 @@
<td><p>Enum</p></td>
<td>The NOT NULL column constraint on a table enforces that null values can't be inserted into the table. Flink supports 'error' (default) and 'drop' enforcement behavior. By default, Flink will check values and throw runtime exception when null values writing into NOT NULL columns. Users can change the behavior to 'drop' to silently drop such records without throwing exception.<br /><br />Possible values:<ul><li>"ERROR"</li><li>"DROP"</li></ul></td>
</tr>
<tr>
<td><h5>table.exec.sink.pk-shuffle</h5><br> <span class="label label-primary">Streaming</span></td>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, I thought the target issue is the same one. I'll create a separate pr.

val sinkRequiredTraits = if (sinkTrait.equals(ONLY_UPDATE_AFTER)) {
// if sink's pk(s) are not exactly match input changeLogUpsertKeys then it will fallback
// to beforeAndAfter mode for the correctness
var shouldFallback: Boolean = false
Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, it's more clearly.

// if sink's pk(s) are not exactly match input changeLogUpsertKeys then it will fallback
// to beforeAndAfter mode for the correctness
var shouldFallback: Boolean = false
val sinkDefinedPks = toScala(sink.catalogTable.getResolvedSchema
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point! I simply moved the code from StreamPhysicalSink to here, getPrimaryKeyIndexes is more simpler.

// fallback to beforeAndAfter.
// Notice: even sink pk(s) contains input upsert key we cannot optimize to UA only,
// this differs from batch job's unique key inference
if (changeLogUpsertKeys == null || changeLogUpsertKeys.size() == 0
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This should be reserved because the metadata query may return a empty changeLogUpsertKeys set.

sinkRequiredTraits
}

private def analyzeUpsertMaterializeStrategy(sink: StreamPhysicalSink): Boolean = {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Initially I put the two methods together, but seems a little bit complex, and the two methods do the different things indeed, so I change to the current version.

@lincoln-lil lincoln-lil changed the title [FLINK-20370][table] Fix wrong results when sink primary key is not the same with query result's changelog upsert key [FLINK-20370][table] part1: Fix wrong results when sink primary key is not the same with query result's changelog upsert key Nov 22, 2021
@@ -762,6 +757,81 @@ class FlinkChangelogModeInferenceProgram extends FlinkOptimizeProgram[StreamOpti
Some(sink.copy(sinkTrait, children.head).asInstanceOf[StreamPhysicalRel])
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add document here to explain inferSinkRequiredTraits and analyzeUpsertMaterializeStrategy?

  1. .... should ...
  2. .... should ...
  3. ...

@JingsongLi
Copy link
Contributor

I think this is almost good. Left two comments.

@lincoln-lil
Copy link
Contributor Author

@JingsongLi Thanks for your reviewing! I've updated the pr according to your comments.

Copy link
Contributor

@JingsongLi JingsongLi left a comment

Choose a reason for hiding this comment

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

Looks good to me!

@JingsongLi JingsongLi merged commit f8f6935 into apache:master Nov 29, 2021
lincoln-lil added a commit to lincoln-lil/flink that referenced this pull request Dec 8, 2021
…s not the same with query result's changelog upsert key

This closes apache#17699
niklassemmler pushed a commit to niklassemmler/flink that referenced this pull request Feb 3, 2022
…s not the same with query result's changelog upsert key

This closes apache#17699
jnh5y pushed a commit to jnh5y/flink that referenced this pull request Dec 18, 2023
…s not the same with query result's changelog upsert key

This closes apache#17699
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants