Skip to content

[flink] add support for flink 2.3 - #3521

Open
sd4324530 wants to merge 13 commits into
apache:mainfrom
sd4324530:support-flink-2.3
Open

[flink] add support for flink 2.3#3521
sd4324530 wants to merge 13 commits into
apache:mainfrom
sd4324530:support-flink-2.3

Conversation

@sd4324530

@sd4324530 sd4324530 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Purpose

Linked issue: close #3520

This PR adds Flink 2.3 engine support to Apache Fluss by introducing a new fluss-flink-2.3 module. To accommodate the API changes in CatalogMaterializedTable / IntervalFreshness introduced in Flink 2.3, corresponding version adapters are introduced in the common module so that cross-version connector code can compile against multiple Flink majors.

Brief change log

1. New fluss-flink-2.3 module

  • Added fluss-flink-2.3 as a sub-module in fluss-flink/pom.xml to provide connector support for Flink 2.3-based deployments.
  • The module mirrors the structure of fluss-flink-2.2 and ships with complete source and test directories. Version-specific adapters (e.g., MultipleParameterToolAdapter, SchemaAdapter, SinkAdapter, TypeInformationAdapter) follow the existing pattern and are provided alongside the module.

2. New version adapters in the common module

To handle the API changes of CatalogMaterializedTable / IntervalFreshness in Flink 2.3, the following adapters are introduced under fluss-flink-common/src/main/java/org/apache/fluss/flink/adapter/:

  • CatalogMaterializedTableAdapter: wraps CatalogMaterializedTable.Builder to abstract away differences introduced in Flink 2.3 (such as the new originalQuery / expandedQuery fields), so the shared common code does not depend on a specific Flink version.

  • IntervalFreshnessAdapter: a new adapter for IntervalFreshness and its inner TimeUnit enum. In Flink 2.3 the IntervalFreshness.TimeUnit type has been reworked (moved/repackaged), so this adapter provides a unified way to parse and serialize time units — converting between String names and the version-specific TimeUnit enum (via the TimeUnitAdapter wrapper). The common module no longer needs to depend on a Flink-version-specific IntervalFreshness.TimeUnit class, and the existing MATERIALIZED_TABLE_INTERVAL_FRESHNESS_TIME_UNIT config can keep its stringType() form unchanged.

    ⚠️ Note on the diff: the deletion of ResolvedCatalogMaterializedTableAdapter and the addition of IntervalFreshnessAdapter may show up in the GitHub PR diff as a rename (51% similarity). This is a false positive: their Apache 2.0 license headers happen to push the file-level similarity above Git's 50% rename-detection threshold, but the two classes have independent responsibilities and share no business logic. The change is in reality a pure add + delete:

    • Removed (fluss-flink-common/src/test/java/.../adapter/ResolvedCatalogMaterializedTableAdapter.java): the old test-only static helper that faked a 2-arg ResolvedCatalogMaterializedTable constructor call as a workaround for https://issues.apache.org/jira/browse/FLINK-38532. It is not removed because FLINK-38532 has been fixed — rather, the test code has been refactored to use a template-method pattern (see point 5 below), so the workaround is no longer referenced anywhere and the helper becomes dead code.
    • Added (fluss-flink-common/src/main/java/.../adapter/IntervalFreshnessAdapter.java): a fresh public adapter for IntervalFreshness.TimeUnit parsing/serialization, unrelated to the removed helper.

    Reviewers can confirm this by running git show 21c65a6c --no-renames --name-status, which reveals the real A + D pair.

3. Configuration and serialization compatibility

  • FlinkConnectorOptions.MATERIALIZED_TABLE_INTERVAL_FRESHNESS_TIME_UNIT is changed from enumType(IntervalFreshness.TimeUnit.class) to stringType(), avoiding a hard dependency on a Flink-version-specific enum class in the common module; concrete enum parsing is delegated to IntervalFreshnessAdapter.
  • FlinkConversions is updated to use the new adapters for materialization-table serialization/deserialization, ensuring consistent semantics across versions.

4. Back-port to fluss-flink-2.2

CatalogMaterializedTableAdapter is also added to fluss-flink-2.2, and Flink22CatalogTest is updated accordingly, so that the 2.2 module continues to share the same common code after the new adapter is introduced.

Same git rename false positive applies here: the deletion of fluss-flink-2.2/src/test/.../ResolvedCatalogMaterializedTableAdapter.java and the addition of fluss-flink-2.2/src/main/.../CatalogMaterializedTableAdapter.java are also displayed as a 50%-similarity rename for the same license-header reason. They are independent changes.

5. Test refactor: template-method pattern for version-specific constructors

The old ResolvedCatalogMaterializedTableAdapter.create() helper masked the Flink-version-specific ResolvedCatalogMaterializedTable constructor signature with a 2-arg fake. To support Flink 2.3's new 5-arg constructor (which adds StartMode), the catalog test hierarchy is refactored to a template-method pattern:

  • The parent FlinkCatalogTest (in fluss-flink-common) now exposes a protected createResolvedCatalogMaterializedTable(...) method with a default 2-arg-constructor implementation.
  • Flink22CatalogTest overrides it to use the 4-arg constructor (origin, resolvedSchema, refreshMode, intervalFreshness).
  • Flink23CatalogTest overrides it to use the 5-arg constructor (additionally passing StartMode.of(StartMode.StartModeKind.FROM_BEGINNING)).

This lets each Flink version exercise its native constructor signature, removes the need for the static helper, and makes the test code self-documenting about which Flink version it targets. Note that the parent FlinkCatalogTest no longer imports ResolvedCatalogMaterializedTableAdapter.

6. Test coverage

  • A complete ITCase suite (Flink23*ITCase) is added under the fluss-flink-2.3 module, covering catalog, metrics, procedure, authorization, sink, source (including binlog/changelog virtual tables, delta join, failover), and tiering.
  • Flink23MultipleParameterToolTest is added to validate MultipleParameterToolAdapter behavior.
  • FlinkCatalogTest and Flink22CatalogTest are updated for the adapter-related cases.

7. Test compatibility fix: Flink 2.3 ON CONFLICT validation vs. Delta Join tests

While porting the Delta Join ITCases to Flink 2.3, an unexpected upstream planner behavior change was discovered. This PR works around it in the test code only; the broader impact on Fluss end-users upgrading to Flink 2.3 is left for community discussion.

  • What changed in Flink 2.3. Flink 2.3 introduces a new planner option ExecutionConfigOptions.TABLE_EXEC_SINK_REQUIRE_ON_CONFLICT (table.exec.sink.require-on-conflict, default true). Inside FlinkChangelogModeInferenceProgram.SatisfyUpdateKindTraitVisitor.analyzeUpsertMaterializeStrategy, Flink now throws ValidationException("The query has an upsert key that differs from the primary key of the sink table ...") whenever:

    • TABLE_EXEC_SINK_UPSERT_MATERIALIZE = AUTO (default), and
    • the sink table's primary key does not contain the query's upsert keys, and
    • no ON CONFLICT clause is supplied.

    In Flink 2.2 this validation did not exist.

  • Why this affects Fluss's Delta Join tests. Delta Join semantics define the upsert key from the join condition, not from the sink's primary key. In several Flink23DeltaJoinITCase scenarios (e.g., testDeltaJoinWithJoinKeyExceedsPrimaryKey with join condition c1=c2 AND d1=d2 AND e1=e2 into a sink whose PK is (c1, d1)), the upsert key (c1, d1, e1) legitimately exceeds the sink PK. Under Flink 2.2 these tests asserted that StreamPhysicalDeltaJoinForceValidator throws The current sql doesn't support to do delta join optimization. Under Flink 2.3 the new ON CONFLICT validator fires before the Delta Join validator is reached, so the original error message is never produced and the assertions fail.

  • Why this PR only touches the tests. The new Flink validation is the correct behavior in the general case — silently allowing mismatched upsert keys leads to non-deterministic results at the sink, which is exactly what Flink 2.3 is trying to prevent. Disabling it in the connector would silently regress that protection for all Fluss users. Whether/how Fluss should expose this knob (e.g., as a Fluss-level connector option, or by injecting a ConflictStrategy from FlinkTableSink when the underlying Fluss table has a last_row merge engine) is a product-level decision that should be discussed with the community and is out of scope for this PR.

    This is the minimal, scoped workaround. Flink22DeltaJoinITCase is untouched (the option did not exist in 2.2).

8. Test compatibility fix: Flink 2.3 ON CONFLICT validation vs. Table Sink partial-upsert tests

A second, distinct impact of the same Flink 2.3 validation was uncovered while running Flink23TableSinkITCase on CI: the Fluss partial-upsert test path is also blocked. Same planner option, same root cause, but a different surface — addressed with the same minimal-scoped pattern.

  • The fix. Flink23TableSinkITCase was previously an empty subclass of FlinkTableSinkITCase. This PR turns it into a proper subclass with a single @BeforeEach that disables TABLE_EXEC_SINK_REQUIRE_ON_CONFLICT on the streaming TableConfig, mirroring the precedent set in Section 7.

  • Why this PR only touches the tests. Same reasoning as Section 7: Flink 2.3's validation is the correct general-case behavior, and silently turning it off at the connector level would regress the protection for real users. The proper follow-up is for Fluss to participate in the new model — most naturally by having FlinkTableSink.applyOperations(...) (or getSinkRuntimeProvider) inject a ConflictStrategy (e.g., DEDUPLICATE, semantically equivalent to Fluss's current first-write-wins plus target-column overwrite) when the sink is a Fluss PK table, so partial upserts remain expressible from SQL. That is a product/API change and is explicitly out of scope for this engine-port PR.

    Flink22TableSinkITCase is untouched (the option did not exist in 2.2).

Tests

  • Unit tests:
    • Flink23MultipleParameterToolTest
    • Flink23CatalogTest
    • Flink23TieringCommitOperatorTest
    • FlinkCatalogTest, Flink22CatalogTest (adapter-related cases updated; Flink22/23CatalogTest now exercise their native ResolvedCatalogMaterializedTable constructors via the new template-method override)
  • Integration tests (ITCases, all under the fluss-flink-2.3 module):
    • Flink23CatalogITCase, Flink23MaterializedTableITCase
    • Flink23MetricsITCase
    • Flink23ProcedureITCase
    • Flink23AuthorizationITCase
    • Flink23ComplexTypeITCase, Flink23TableSinkITCase (now works around Flink 2.3's new ON CONFLICT planner validation for partial upserts, see Section 8), Flink23UndoRecoveryITCase
    • Flink23BinlogVirtualTableITCase, Flink23ChangelogVirtualTableITCase, Flink23DeltaJoinITCase (now works around Flink 2.3's new ON CONFLICT planner validation, see Section 7), Flink23TableSourceBatchITCase, Flink23TableSourceFailOverITCase, Flink23TableSourceITCase
    • Flink23TieringITCase

API and Format

No breaking changes to the public API.

Documentation

Generative AI disclosure

  • Yes — Claude Code(minimax-m3)

@sd4324530
sd4324530 force-pushed the support-flink-2.3 branch 5 times, most recently from bd9decf to 018112e Compare June 26, 2026 07:26
@sd4324530
sd4324530 force-pushed the support-flink-2.3 branch from 018112e to 5cb3dd2 Compare July 20, 2026 14:24
@sd4324530 sd4324530 closed this Jul 21, 2026
@sd4324530 sd4324530 reopened this Jul 21, 2026
@sd4324530
sd4324530 force-pushed the support-flink-2.3 branch from 9f3a6ba to 2856fc0 Compare July 21, 2026 14:16
@polyzos polyzos added this to the v1.0 milestone Aug 7, 2026
@sd4324530
sd4324530 force-pushed the support-flink-2.3 branch 8 times, most recently from eb1da25 to 3f05e3f Compare August 21, 2026 07:01
@polyzos

polyzos commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Hi @sd4324530 and thank you for all the great work 🙏 Overall the PR LGTM 👍 However there is one thing I would like to clarify.

Flink 2.3's new table.exec.sink.require-on-conflict=true throws a ValidationException for partial upserts and for delta joins whose upsert key exceeds the sink PK. The PR keeps its tests green by disabling that option in @BeforeEach (Flink23TableSinkITCase, Flink23DeltaJoinITCase). That means on Flink 2.3, real users doing partial-upsert or delta-join, which are big Fluss features, will hit this exception out of the box unless they manually flip the flag.

How do you think we should best handle this? Let me know your thoughts

@sd4324530

Copy link
Copy Markdown
Contributor Author

@polyzos
Thank you very much for your review.
Regarding the configuration option you mentioned, I don't have a very in-depth understanding of this mechanism; I've mainly relied on reading the community documentation.
Community description of this configuration: "When a query produces an updating table with an upsert key that differs from the sink table's primary key, multiple records with different upsert keys may map to the same primary key. The ON CONFLICT clause specifies how to resolve these primary key conflicts at the sink." [1]

I think this issue has always existed and isn't a new problem introduced with Flink 2.3. If the Fluss cluster's inherent mechanisms already prevent this problem, I think we should be able to disable this configuration by default.

What do you think?

[1] https://nightlies.apache.org/flink/flink-docs-release-2.3/docs/sql/reference/dml/insert/#on-conflict-clause

@polyzos

polyzos commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

@sd4324530 agreed 😄 @fresh-borzoni any thoughts since you are a heavy user? 😄

@fresh-borzoni

Copy link
Copy Markdown
Member

@polyzos @sd4324530 I'll take a look today, it's in my list to review 👍

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

Thanks @sd4324530 for the contribution, I left two comments

@Override
public SinkWriter<InputT> createWriter(WriterInitContext writerInitContext) throws IOException {
return createWriter(
writerInitContext.getMailboxExecutor(), writerInitContext.metricGroup());

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.

Would it make sense to keep the three-argument createWriter hook here and pass writerInitContext.getTaskInfo().getIndexOfThisSubtask(), as the Flink 2.2 adapter does? FlinkSink in the common module still overrides createWriter(MailboxExecutor, SinkWriterMetricGroup, int), while this adapter now invokes and declares only the two-argument method. When these classes are packaged together in the Flink 2.3 artifact, creating a writer can therefore fail with an AbstractMethodError.

Could we align the adapter signature with the common implementation? It may also be helpful to add a regression test that creates the sink writer through the final Flink 2.3 shaded artifact.

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.

This change originates from this commit:afc34bb
I'll fix it.

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.

fix:1c5bb53

private static void serializeMaterializedTableToCustomProperties(
CatalogMaterializedTable mt, Map<String, String> customProperties) {
// Serialize core materialized table properties
customProperties.put(MATERIALIZED_TABLE_DEFINITION_QUERY.key(), mt.getDefinitionQuery());

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.

Would it make sense to persist the original and expanded queries separately here? In Flink 2.3, getDefinitionQuery() represents the expanded query, while the restore path currently uses this single stored value for definitionQuery, originalQuery, and expandedQuery. If the original user SQL differs from the expanded query, a catalog round trip will therefore lose the original SQL.

Could we introduce dedicated properties for the original and expanded queries, while retaining the existing definition-query property as a compatibility fallback? It may also be helpful to add a round-trip test where the original and expanded queries are intentionally different.

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.

fix:a0a29b0

@sd4324530
sd4324530 force-pushed the support-flink-2.3 branch 2 times, most recently from a0a29b0 to 53ef1b9 Compare August 22, 2026 02:12
@fightBoxing

Copy link
Copy Markdown

🐛 Bug fix: AbstractMethodError in SinkAdapter

Hi @sd4324530 (and reviewers), thanks for the great work on Flink 2.3 support!

While testing this PR against a Fluss (K8s) + Flink 2.3 standalone cluster, every Flink job that writes to a Fluss table fails at runtime with:

java.lang.AbstractMethodError: Receiver class org.apache.fluss.flink.sink.FlinkSink
does not define or inherit an implementation of the resolved method
'createWriter(MailboxExecutor, SinkWriterMetricGroup)' of abstract class
org.apache.fluss.flink.adapter.SinkAdapter.
    at org.apache.fluss.flink.adapter.SinkAdapter.createWriter(SinkAdapter.java:37)
    at org.apache.flink.streaming.runtime.operators.sink.StatelessSinkWriterStateHandler.createWriter(...)
    ...

Root cause

The abstract createWriter in fluss-flink-2.3/SinkAdapter has signature:

protected abstract SinkWriter<InputT> createWriter(
        MailboxExecutor mailboxExecutor, SinkWriterMetricGroup metricGroup);   // 2 args

but fluss-flink-common/FlinkSink overrides it with 3 args:

protected SinkWriter<InputT> createWriter(
        MailboxExecutor mailboxExecutor, SinkWriterMetricGroup metricGroup, int subtaskIndex);

Because the signatures don't match, FlinkSink never truly implements the abstract method → AbstractMethodError at runtime.

For reference, fluss-flink-2.2/SinkAdapter already has the 3-arg signature, so this looks like a copy-paste omission in the 2.3 adapter.

Fix

Align SinkAdapter in fluss-flink-2.3 with fluss-flink-2.2 by propagating subtaskIndex from WriterInitContext.getTaskInfo().getIndexOfThisSubtask().

@Override
public SinkWriter<InputT> createWriter(WriterInitContext writerInitContext) throws IOException {
    return createWriter(
            writerInitContext.getMailboxExecutor(),
            writerInitContext.metricGroup(),
            writerInitContext.getTaskInfo().getIndexOfThisSubtask());
}

protected abstract SinkWriter<InputT> createWriter(
        MailboxExecutor mailboxExecutor,
        SinkWriterMetricGroup metricGroup,
        int subtaskIndex);

Verification

With this fix applied on top of the current PR head (24a219484), I ran three end-to-end SQL demos on Fluss (K8s, 1 coordinator + 3 tablet-servers) + Flink 2.3 standalone:

Demo Description Result
1 Lookup Join: datagen events LEFT JOIN Fluss pk-table dim ✅ 20/20 rows correctly enriched
2 Delta Join: two Fluss pk-tables INNER JOIN ✅ Planner emits DeltaJoin operator; 5/5 rows correct, no join state
3 Cascaded Delta Join: three-level INNER JOIN ✅ Planner emits two DeltaJoin operators; 5/5 rows correct

Job graph excerpt (Demo 3):

Source: FlussSource-sales -> DropUpdateBefore
Source: FlussSource-sale_items -> DropUpdateBefore
Source: FlussSource-sale_item_addons -> DropUpdateBefore
DeltaJoin[7] -> Calc[8]
DeltaJoin[13] -> Calc[14] -> ConstraintEnforcer[15]
Sink(sale_item_addons_enriched): Writer

Patch

Happy to open this as a follow-up PR against your support-flink-2.3 branch if you'd like — just let me know your preference. 🙏

@fresh-borzoni fresh-borzoni left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@sd4324530 Thank you for the PR, looks good overall, left a couple of comments, PTAL

Map<String, String> options) {
// Validate required materialized table options first
String definitionQuery = options.get(MATERIALIZED_TABLE_DEFINITION_QUERY.key());
String originalQuery = options.get(MATERIALIZED_TABLE_ORIGINAL_QUERY.key());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Tables created before this PR only have definition-query, so both of these come back null and Flink 2.3 rejects them with checkNotNull therefore reading any existing materialized table fails with NullPointerException: Original query must not be null.
Should we fall back to definitionQuery when the new keys are absent?

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.

Hi @fresh-borzoni , I've read the design document for this feature(FLIP-546), and it should be compatible with existing materialized tables?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@sd4324530
The case that's missing is upgrade, not new tables.
Someone creates a materialized table on Fluss today. Later they move to Flink 2.3 with this connector. That table was written before original-query and expanded-query existed, so it only has definition-query and every read of it now fails with NullPointerException: Original query must not be null.

Tables created after this PR are fine, since we write all three. It's the ones already out there that break. Try it :)

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:3fa056c

* CatalogMaterializedTableAdapter} for Flink 1.20/2.2 treats original/expanded as no-ops, so
* the assertion deliberately targets only what the common adapter exposes: the consumed keys
* are absent from {@code getOptions()} and the {@code definitionQuery} matches the input.
* Verifying distinct original/expanded values survive a roundtrip is left to the Flink 2.3

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This says the roundtrip is covered in the 2.3 module, but I couldn't find that test there.
Could we add it, plus the case where neither key is present?


assertThatThrownBy(() -> tEnv.explainSql(sql))
.isInstanceOf(ValidationException.class)
.hasMessageContaining("doesn't support to do delta join optimization");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Cascaded delta join works on 2.3. The same 3-way join with the sink PK aligned to the join key plans as nested DeltaJoin for me.
This test still throws only because its sink PK (c1, c2, c3) forces upsertMaterialize=[true].
Should we invert it?

| Fluss Connector Versions | Supported Flink Versions |
|--------------------------|--------------------------|
| $FLUSS_VERSION_SHORT$ | 1.18, 1.19, 1.20 |
| $FLUSS_VERSION_SHORT$ | 1.18, 1.19, 1.20, 2.2, 2.3 |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

TY, 2.2 was missing here. delta-joins.mdx still lists cascade joins as a 2.2 limitation and multi-way joins under "Future Plan", worth a Flink 2.3 section.
And fluss-flink-common/README.md's version table doesn't list 2.3.
WDYT?

@sd4324530 sd4324530 Aug 22, 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.

done:491ae5b

* <p>Restore the pre-2.3 behaviour by disabling the option in the SQL Gateway session so the
* refresh job can be scheduled and reach RUNNING.
*/
@BeforeAll

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I removed this whole @BeforeAll and reverted the protected widening in the common class and 2/2 pass on 2.3. It also hides the parent's static setUp rather than extending it, so it will drift.

Can we drop it, or was there a failure that prompted it?

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:e7bb9b1

// StreamPhysicalDeltaJoinForceValidator runs. Disable it here so the existing
// delta-join "doesn't support to do delta join optimization" error remains
// reachable from these tests.
tEnv.getConfig().set(ExecutionConfigOptions.TABLE_EXEC_SINK_REQUIRE_ON_CONFLICT, false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Only the negative tests need this as they assert on an error message that Flink 2.3 changed.
The positive ones pass without it. Turning the validation off for the whole class to keep those assertions green also hides it from every other test here.
Could we update the assertions instead?

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:7b9f789

@fresh-borzoni

Copy link
Copy Markdown
Member

@sd4324530 agreed 😄 @fresh-borzoni any thoughts since you are a heavy user? 😄
@polyzos it's real and fairly common, but it isn't a Fluss thing.

Partial upsert and delta join both plan fine on 2.3, I checked. What throws is re-keying
and regular joins, where the sink PK doesn't cover the query's upsert key.

That's FLIP-558 at the planner level though as it applies to every upsert sink on 2.3, not just Fluss. Our sink is a plain upsert sink and there's no connector hook to opt out, so there's nothing for us to fix.
Users should add ON CONFLICT DO DEDUPLICATE or set table.exec.sink.require-on-conflict=false.

So I'd just document it in the docs.

@fresh-borzoni

Copy link
Copy Markdown
Member

If I understand this correctly, the only difference between 2.2 module is the materialized table adapters, the rest is identical
Mb it would be a good followup to factor out the generic part?

cc @loserwang1024 as I saw there was some discussion about 2.1 and 2.2 and it ended up with dropping 2.1, so there might be some context that I miss :)

@sd4324530

Copy link
Copy Markdown
Contributor Author

🐛 Bug fix: AbstractMethodError in SinkAdapter

Hi @sd4324530 (and reviewers), thanks for the great work on Flink 2.3 support!

While testing this PR against a Fluss (K8s) + Flink 2.3 standalone cluster, every Flink job that writes to a Fluss table fails at runtime with:

java.lang.AbstractMethodError: Receiver class org.apache.fluss.flink.sink.FlinkSink
does not define or inherit an implementation of the resolved method
'createWriter(MailboxExecutor, SinkWriterMetricGroup)' of abstract class
org.apache.fluss.flink.adapter.SinkAdapter.
    at org.apache.fluss.flink.adapter.SinkAdapter.createWriter(SinkAdapter.java:37)
    at org.apache.flink.streaming.runtime.operators.sink.StatelessSinkWriterStateHandler.createWriter(...)
    ...

Root cause

The abstract createWriter in fluss-flink-2.3/SinkAdapter has signature:

protected abstract SinkWriter<InputT> createWriter(
        MailboxExecutor mailboxExecutor, SinkWriterMetricGroup metricGroup);   // 2 args

but fluss-flink-common/FlinkSink overrides it with 3 args:

protected SinkWriter<InputT> createWriter(
        MailboxExecutor mailboxExecutor, SinkWriterMetricGroup metricGroup, int subtaskIndex);

Because the signatures don't match, FlinkSink never truly implements the abstract method → AbstractMethodError at runtime.

For reference, fluss-flink-2.2/SinkAdapter already has the 3-arg signature, so this looks like a copy-paste omission in the 2.3 adapter.

Fix

Align SinkAdapter in fluss-flink-2.3 with fluss-flink-2.2 by propagating subtaskIndex from WriterInitContext.getTaskInfo().getIndexOfThisSubtask().

@Override
public SinkWriter<InputT> createWriter(WriterInitContext writerInitContext) throws IOException {
    return createWriter(
            writerInitContext.getMailboxExecutor(),
            writerInitContext.metricGroup(),
            writerInitContext.getTaskInfo().getIndexOfThisSubtask());
}

protected abstract SinkWriter<InputT> createWriter(
        MailboxExecutor mailboxExecutor,
        SinkWriterMetricGroup metricGroup,
        int subtaskIndex);

Verification

With this fix applied on top of the current PR head (24a219484), I ran three end-to-end SQL demos on Fluss (K8s, 1 coordinator + 3 tablet-servers) + Flink 2.3 standalone:

Demo Description Result
1 Lookup Join: datagen events LEFT JOIN Fluss pk-table dim ✅ 20/20 rows correctly enriched
2 Delta Join: two Fluss pk-tables INNER JOIN ✅ Planner emits DeltaJoin operator; 5/5 rows correct, no join state
3 Cascaded Delta Join: three-level INNER JOIN ✅ Planner emits two DeltaJoin operators; 5/5 rows correct
Job graph excerpt (Demo 3):

Source: FlussSource-sales -> DropUpdateBefore
Source: FlussSource-sale_items -> DropUpdateBefore
Source: FlussSource-sale_item_addons -> DropUpdateBefore
DeltaJoin[7] -> Calc[8]
DeltaJoin[13] -> Calc[14] -> ConstraintEnforcer[15]
Sink(sale_item_addons_enriched): Writer

Patch

Happy to open this as a follow-up PR against your support-flink-2.3 branch if you'd like — just let me know your preference. 🙏

@fightBoxing Thank you for your review!
This issue has been fixed in 1c5bb53
Please verify it again.

Signed-off-by: Pei Yu <125331682@qq.com>
…Case

Flink 2.3 introduces ExecutionConfigOptions.TABLE_EXEC_SINK_REQUIRE_ON_CONFLICT
(table.exec.sink.require-on-conflict), defaulting to true. In
FlinkChangelogModeInferenceProgram, this triggers a ValidationException
("upsert key differs from primary key") before the
StreamPhysicalDeltaJoinForceValidator runs, so the Delta Join ITCases can no
longer reach the original "doesn't support to do delta join optimization"
error path.

Disable the option in Flink23DeltaJoinITCase#beforeEach so the existing
assertions remain valid. Production-side impact (real Fluss users hitting
this on multi-table joins / group-by + insert) is left to community
discussion.

Signed-off-by: Pei Yu <125331682@qq.com>
…Case

Flink 2.3 introduces ExecutionConfigOptions.TABLE_EXEC_SINK_REQUIRE_ON_CONFLICT
(table.exec.sink.require-on-conflict), defaulting to true. In
FlinkChangelogModeInferenceProgram, this triggers a ValidationException
("upsert key differs from primary key") before the partial-update handling
in FlinkTableSink#getSinkRuntimeProvider runs, so the partial upsert ITCases
(testPartialUpsert and testPartialUpsertDuringAddColumn) in
FlinkTableSinkITCase can no longer reach the Fluss sink layer.

Disable the option in Flink23TableSinkITCase#beforeEach so the existing
partial-upsert assertions remain valid.

Signed-off-by: Pei Yu <125331682@qq.com>
Signed-off-by: Pei Yu <125331682@qq.com>
Signed-off-by: Pei Yu <125331682@qq.com>
Signed-off-by: Pei Yu <125331682@qq.com>
Signed-off-by: Pei Yu <125331682@qq.com>
Signed-off-by: Pei Yu <125331682@qq.com>
Signed-off-by: Pei Yu <125331682@qq.com>
Signed-off-by: Pei Yu <125331682@qq.com>
Signed-off-by: Pei Yu <125331682@qq.com>
Signed-off-by: Pei Yu <125331682@qq.com>
…RE_ON_CONFLICT`.

Signed-off-by: Pei Yu <125331682@qq.com>
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.

[flink] support flink 2.3

5 participants