Repository navigation
Conversation
JingsongLi
left a comment
There was a problem hiding this comment.
Requirement fit: SUPPORTED. Stable Paimon operator IDs address the documented HA checkpoint restore failure after upstream topology changes. Implementation: FINDINGS: the new cover-all option still leaves the public streaming CDC route partially unnamed. The graph below was built through CdcSinkBuilder with the actual option enabled, not a synthetic helper. Existing IDs and default behavior were also checked. Validation: 29 graph cases and 2 HA restore cases pass in the formal Flink 1 Maven run. Both explicit-path migration cases pass through a freshly built production Flink 1.20 bundle (including actual subsequent commits under the restored commitUser); the first flat-classpath run failed before migration, so it is not counted as passing. The normal production package build and current CI pass.
| .defaultValue(false) | ||
| .withDescription( | ||
| "If true, 'sink.operator-uid.suffix' also names every other operator a streaming " | ||
| + "write adds: the row conversions, the local merge, the compaction operators, " |
There was a problem hiding this comment.
[P2] Include the streaming CDC operators in cover-all coverage
The new option promises to name every operator a streaming write adds, but the public CdcSinkBuilder route still creates Side Output and Schema Evolution without a UID. On a fixed-bucket PK table with sink.operator-uid.suffix=stable and cover-all=true, an actual CDC StreamGraph has UID=null for both. Adding one explicitly uid-named map upstream changes their operator hashes (Side Output 5f5dbcb6... -> f2512467..., Schema Evolution bf7a1542... -> 75147ea0...). Building the same graph under pipeline.auto-generate-uids=false fails with no UID assigned to operator Side Output. Thus users enabling the new option for streaming CDC remain exposed to the same orphaned HA checkpoint entries it is intended to prevent. Apply the new assigner to the CDC parser/schema-evolution/conversion route and add a CDC topology case; if CDC is intentionally excluded, explicitly scope the option/documentation instead of guaranteeing every streaming write operator.
There was a problem hiding this comment.
Thanks for the review. I went with your second option and scoped the options instead of covering CDC.
Covering only the single-table CdcSinkBuilder route would still leave the option's promise false. --table_conf copies the keys into every table of a database sync, so users can turn it on there too. Covering the database sync needs a uid shape for operators that belong to no single table: the shared Side Output, the keyed Schema Evolution, the multiplexed Convert and, in combined mode, the Writer and Global Committer of FlinkCdcMultiTableSink. Those last two hold state and have no uid today. Turning the option on would drop their state on the first restore, which is the kind of migration you asked us not to recommend. I don't think that belongs in this PR.
So:
- The options are now
sink.operator-uid.cover-table-sink-operatorsandsource.operator-uid.cover-table-source-operators(c815cc4). Their descriptions and the guide say the CDC sync actions andBATCH-only operators are not covered. - The
*.operator-uid.suffixdescriptions now say the suffix alone does not keep ids stable, and point at the new options. All four link the migration guide, which is also shorter (781072b).
Two things I checked on the way (e2797de):
- Source state. The only source entry the migration drops is the split monitor's, on the exactly-once consumer route. That route always has a consumer ID, and after
flink stopthe consumer holds the same next snapshot as the monitor, so the restored reader neither skips nor re-reads anything. A new ITCase shows this on Flink 1.20 and 2.2. A bounded read with dedicated split generation would re-read its scan, so the guide says to let such a job finish first. - The
endsink for SQL users. For aDataStreamSinkProviderthe planner leaves Paimon's uids alone in everytable.exec.uid.generationmode. A SQL-level test pins this.
If CDC coverage is wanted later, it would go under a new key, so nobody who turned these options on sees ids move on upgrade.
I also rebased onto current master. Does the new name work for you?
There was a problem hiding this comment.
Correction to my last comment: the bounded dedicated-split route does not re-read because of the migration. It re-plans its scan on every restore, and the Paimon sink drops the repeated end-of-input commit. Its monitor entry does need allowNonRestoredState, like the exactly-once route. I removed the sentence from the guide (e7aee65).
I also ran the guide's steps on a real Flink 1.20.5 cluster for the append, PARTITION_DYNAMIC, HA-store recovery and exactly-once consumer routes: no lost or duplicated rows, and the log lines match the guide.
…operator
sink.operator-uid.suffix reaches the writer, the global committer and the
dynamic bucket assigner; source.operator-uid.suffix reaches the source.
Every other operator Paimon adds takes its id from the shape of the stream
graph, so a change elsewhere in the job orphans its checkpoint entry.
Recovery from the HA checkpoint store then fails with "There is no
operator for the state <id>".
Add sink.operator-uid.cover-all-operators and
source.operator-uid.cover-all-operators, off by default. With the option
and the matching suffix set, every operator a streaming read or write
adds gets ${prefix}_${table}_${suffix}. Existing uids are unchanged.
Batch-only topologies (clustering, sort compaction, postpone
merge-on-read) never restore from a checkpoint and are left alone.
Tests: a sink and a source graph matrix built under
pipeline.auto-generate-uids=false, so Flink itself rejects any uid-less
operator per topology shape; literal pins for every pre-existing and
new uid; an ITCase restoring from a retained checkpoint store after an
upstream topology change; an ITCase migrating from the old uid layout
through execution.state-recovery.path, including PARTITION_DYNAMIC.
Docs: a migration guide under docs/docs/flink/savepoint.md.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…urce Rename sink/source.operator-uid.cover-all-operators to sink.operator-uid.cover-table-sink-operators and source.operator-uid.cover-table-source-operators. The CDC sync actions' operators are not covered, so "all" overstated it. Point the suffix options at the new keys and document the CDC gap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
36c8ed4 to
c815cc4
Compare
…n guide Say which operators the cover options name (table sink/source, streaming only) and that CDC and BATCH-only operators are not covered. The suffix options now say the suffix alone does not keep ids stable. All four link the "Operator UIDs" guide. Shorten the guide: merge the three restart steps into one, and state what happens to the split monitor. On the exactly-once consumer route the consumer keeps the read position across flink stop; a bounded read with dedicated split generation re-reads its scan, so finish it first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A SQL INSERT keeps the Writer, Global Committer and end uids under table.exec.uid.generation PLAN_ONLY and ALWAYS. A streaming read with an exactly-once consumer, stopped with a savepoint and restored with the source cover option on, reads every row exactly once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Running the guide on a Flink 1.20.5 cluster showed: with allowNonRestoredState Flink logs every skipped entry, empty ones too; a bounded read with dedicated split generation re-reads its scan on any restore, not because of the migration, and its monitor entry needs the flag like the exactly-once route; recovery from the HA store uses the latest checkpoint, so the extra savepoint step only matters as a warning not to restore from pre-migration state. Add concrete checks for the sink ($snapshots) and the consumer ($consumers). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Purpose
Fixes #10107.
sink.operator-uid.suffixandsource.operator-uid.suffixreach only Writer, Global Committer, dynamic-bucket-assigner and Source. Every other operator the Paimon table sink or source adds takes its id from the stream graph shape, so any topology change orphans its checkpoint entry, and a restore from the HA checkpoint store fails withThere is no operator for the state <id>. Discussed on dev@: [DISCUSS] The operator-uid.suffix options do not cover every operator.This adds
sink.operator-uid.cover-table-sink-operatorsandsource.operator-uid.cover-table-source-operators, boolean, default false. With the option and the matching suffix set, every streaming operator the table sink or source adds gets<prefix>_<table>_<suffix>. Existing uids and the default graph are unchanged. The suffix options' descriptions now point at the new keys. Batch-only operators are out of scope: they never restore from a checkpoint.Things a reviewer should know:
Side Output,Schema EvolutionandCase-insensitive Convertoperators and the shared operators of a database sync keep generated ids, so such jobs still cannot run withpipeline.auto-generate-uids=false. The option names and the guide say so. Covering them would need a uid shape for table-less operators and, in combined mode, a migration that drops Writer and Committer state; any later CDC coverage would go under a new key, so nobody who enabled these options sees ids move on upgrade. The second commit is this scoping and the rename (review round 1).Collect Statistics(aCoordinatedOperatorFactory, present underpartition.sink-strategy = PARTITION_DYNAMIC) and the split monitor on the exactly-once consumer route, whose read position the consumer keeps acrossflink stop. On the explicit-path route those two needexecution.state-recovery.ignore-unclaimed-stateonce; a recovery from the HA checkpoint store cannot skip entries at all, so the migration goes through a savepoint or an explicit checkpoint path. The guide indocs/docs/flink/savepoint.md("Operator UIDs") spells this out and shows that writer and committer state stays mapped and the read position is kept.endsink is covered. The HA route rejects any unclaimed entry regardless of content,endacknowledges checkpoints and so has one, and SQL users have no way to uid it themselves. It only changes with the option on, and a DataStream user's own.uid()on the returned sink still wins. A SQL-level test shows the planner keeps this uid in everytable.exec.uid.generationmode.db1.ordersanddb2.orderswritten with one suffix collide onWriter_orders_s. This is pre-existing for the four old uids and now applies to about twenty; the guide tells users to use a distinct suffix per table.*_NAMEconstants, followingWRITER_NAME,GLOBAL_COMMITTER_NAMEandSOURCE_NAME. They are pinned literally in tests.Tests
OperatorUidSuffixTest(19) andSourceOperatorUidSuffixTest(12): 12 sink and 7 source topology shapes each built underpipeline.auto-generate-uids=false, so Flink itself rejects any uid-less operator; exact node-name guards per shape; literal pins for the four pre-existing uids and for every new one; option off and option-without-suffix leave today's uids only; operator ids survive an upstream topology change.OperatorUidSuffixRestoreITCase(2): restore from a retained checkpoint store (HA route,EmbeddedHaServicesWithLeadershipControl+PerJobCheckpointRecoveryFactory) after adding an upstream operator succeeds with the option on and fails withThere is no operator for the statewith it off.OperatorUidMigrationITCase(2): checkpoint taken without the option, restored with it throughexecution.state-recovery.path. Unaware-bucket succeeds. PARTITION_DYNAMIC fails withCannot map checkpoint/savepoint state for operator, then succeeds withexecution.state-recovery.ignore-unclaimed-state; the restored job commits under the samecommitUser(committer state mapped) and the dropped entries, read from the checkpoint metadata, are exactly the previously uid-less operators.MonitorSourceUidMigrationITCase(1): a streaming read with an exactly-once consumer, stopped with a savepoint and restored with the source cover option on, reads every row exactly once (Flink 1.20 and 2.2.0).OperatorUidSuffixTest.testSqlInsertKeepsTheUids(2): a SQL INSERT keeps the Writer, Global Committer andenduids undertable.exec.uid.generationPLAN_ONLY and ALWAYS.ed8ecdc231with the rename: the five uid classes (36) on Flink 1.20, the two new tests andOperatorUidSuffixTeston Flink 2.2.0, the docs site build,ConfigOptionsDocsCompletenessITCase,-DskipTests verifyand thepaimon-flink-cdcinstall. Before the rebase: fullpaimon-flink-commonunit phase on Flink 1.20.1 (1094 tests), the four classes above on Flink 2.2.0 (-Pflink2),paimon-flink-cdccompile,ConfigOptionsDocsCompletenessITCase, and-DskipTests verify(checkstyle, spotless, rat, enforcer). Not built: thepaimon-flink-1.16..2.1version modules andpaimon-flink-action.🤖 Generated with Claude Code