eskabetxe commented on PR #4516:
URL: https://github.com/apache/flink-cdc/pull/4516#issuecomment-5598266362
> Thanks for doing this — the one-minor-per-commit staging makes it
reviewable in a way a single 160-file diff would not have been, and the 2.7.4
stopping point with the Java 11 rationale matches what I measured independently.
>
> I built the same migration separately before finding this PR (same 1.9.8 →
2.7.4 target, same Java 11 reasoning), so rather than open a competing PR I
would rather hand over what I found. Four items, in severity order. The first
three I can reproduce; the fourth is a judgement call about scope.
>
> ## 1. The Java 11 guarantee does not survive a JDK 17 build machine
> This one undercuts the PR's own headline claim, which is why it is first.
>
> The root pom sets `target.java.version` to 11, but the `java-17-target`
profile auto-activates on `<jdk>[17,)</jdk>` and hard-codes the compiler plugin
directly:
>
> ```
> <profile>
> <id>java-17-target</id>
> <activation><jdk>[17,)</jdk></activation>
> <properties><java.version>17</java.version></properties>
> ... <source>17</source><target>17</target> ...
> </profile>
> ```
>
> So a release built on a JDK 17 toolchain emits class-file major 61
regardless of the declared property. I verified this is true on `master` today
and unchanged on this branch. It predates this PR — but this PR is the one
whose argument rests on "we stay on Java 11", so it seems worth pinning down
here or in a companion change.
>
> Fix that worked for me: have both profiles derive `<source>`/`<target>`
from `${source.java.version}`/`${target.java.version}` and carry only the
`--add-exports`/`--add-opens` arguments their JDK actually needs. Then the
declared property is authoritative, and `-Dtarget.java.version=17` still works
for anyone who wants it.
>
> I am happy to send that as a separate small PR so it does not enlarge this
one.
>
> ## 2. `specific-offset` on the legacy MySQL source cannot start on a
GTID-enabled server
> Reproducible, and it is a startup hang rather than a failure.
>
> Debezium 2.7 calls `client.setGtidSet("")` in the "No GTID stored in the
offset" branch — on this branch that is
`BinlogStreamingChangeEventSource.java:341` in the forked copy. Debezium 1.9.8
set filename and position and left the client's GTID state alone.
>
> In `com.zendesk:mysql-binlog-connector-java:0.29.2`, `setGtidSet("")` sets
`gtidEnabled = true` and, because the argument is the empty string
specifically, skips assigning `gtidSet` — leaving it null. `gtidEnabled` has
exactly two `putfield` sites (the constructor and that setter), so the client
cannot leave GTID mode once latched.
>
> Observed on an equivalent 2.7.4 branch, against `gtid_mode=ON`: a startup
`NullPointerException` from `DumpBinaryLogGtidCommand#toByteArray`, after which
the engine logs `"Can't start the connector, will retry later..."` every 10
seconds indefinitely — zero records, no checkpoint progress, and no fast
failure. Reproduced twice.
>
> The incremental source is unaffected. `earliest-offset` and `timestamp` on
the legacy source also survive; it is `specific-offset` that breaks.
>
> I fixed it by handing the legacy path a client that refuses an empty/blank
GTID set, which is where the state actually latches and needs no knowledge of
which branch Debezium took. I have a test against a real `gtid_mode=ON` server
that fails without the fix — happy to contribute it here.
>
> ## 3. A fatal binlog-thread error hangs the reader instead of failing the
job
> `MySqlErrorHandler` now passes `null` for Debezium's new fourth
`ErrorHandler` argument (`replacedErrorHandler`). I did the same thing
initially. The consequence is that a fatal error raised on the binlog-client
thread is recorded on the error handler but never reaches `BinlogSplitReader`,
which stays blocked in `ChangeEventQueue.poll`.
>
> `flink-cdc-base`'s `IncrementalSourceScanFetcher` has its own
`checkReadException`, but I do not see an equivalent on the MySQL binlog reader
path on this branch (`getProducerThrowable` has no call sites). In CI this
shows up as a job that never finishes rather than a red build, which is
unpleasant to debug — it is how a MySQL test class in my own branch appeared
"flaky" for a while.
>
> ## 4. Removed `debezium.*` passthrough keys are silently ignored (scope
judgement)
> Users can set arbitrary `debezium.*` properties, and Debezium's
`Configuration` does not reject unknown keys. After this bump, a job carrying
`debezium.database.server.name` keeps starting, but the key no longer does
anything — which changes the offset partition key and triggers a **full
re-snapshot with duplicate rows** rather than an error.
>
> I added a validator that fails fast at job submission for the keys removed
between 1.9.8 and 2.7.4, with the replacement named in the message. Two things
I got wrong first time and would flag:
>
> * `never`, `schema_only` and `schema_only_recovery` are all **still
valid** at 2.7.4 (the enum carries
> both spellings), so a 3.x-era list of removed values is wrong here. Only
Postgres's `exported`
> actually disappears between 1.9.8 and 2.7.4.
> * `database.history.instance.name` never existed; the real 1.9 key is
`database.history.name`.
>
> Whether that belongs in this PR or a follow-up is your call — it is a
behaviour change, not a bump.
>
> ## Things you caught that I did not, or did better
> * Your `PostgresOffsetUtils` fix also covers `transaction_id` and the
incremental-snapshot prefix
> keys; mine only handled the transaction-order counters. Yours is the
more complete allow-list.
> * The jsr305 demotion at 2.5 taking `javax.annotation` off the compile
classpath, including in
> `mongodb-cdc` which has no Debezium fork at all — I hit that as a
mystery build break and did not
> diagnose it as cleanly.
> * Staging by minor is simply better than my topical commits for bisecting
an upgrade like this.
>
> Happy to send the Java-target fix as its own PR, and to contribute the
GTID reproduction and test here if useful.
@jeff-jk thank you for the review, this is exactly the kind of handover that
makes a migration like this safer. I reproduced or traced each item against
this branch, notes per point below, in your order.
## 1. Java 11 guarantee vs. the `java-17-target` profile
Agreed, and confirmed on my side: the profile auto-activates on
`<jdk>[17,)</jdk>` and hard-codes `<source>17</source>/<target>17</target>`, so
the declared `target.java.version` is not authoritative on a JDK 17 toolchain.
It's on `master` unchanged and this branch doesn't touch those profiles, so
it's genuinely pre-existing, but you're right that it undercuts the specific
claim this PR leans on.
I'd rather keep this bump free of unrelated build-system changes, so
**please do send it as the separate PR you offered**, deriving both profiles'
`<source>`/`<target>` from `${source.java.version}`/`${target.java.version}` is
the right shape. Happy to review it.
## 2. `specific-offset` + GTID hang
This one I fully reproduce it, and it's the one I most want to fix here.
- The call is `client.setGtidSet("")` in the forked
`BinlogStreamingChangeEventSource`, verbatim from upstream 2.7.4, so inherited
rather than introduced, but present all the same.
- Binlog client resolves to
`com.zendesk:mysql-binlog-connector-java:0.29.2`, matching what you saw.
- Decompiling `BinaryLogClient#setGtidSet`: a `null` argument returns early,
but the empty string sets `gtidEnabled = true` and then the `arg.equals("")`
guard jumps **past** the `gtidSet` assignment — so `gtidEnabled` latches true
while `gtidSet` stays null, which is the NPE path through
`DumpBinaryLogGtidCommand#toByteArray` you described.
So: real, legacy-`DebeziumSourceFunction`-only, `specific-offset`-only on
`gtid_mode=ON`. **Yes please, contribute the reproduction and the test here**,
a client that rejects an empty/blank GTID set at the latch point is the right
place to fix it, since it's independent of which branch Debezium takes. I'll
fold it into the hop where the behaviour first regresses.
## 3. Fatal binlog-thread error → hang
I confirmed the mechanical facts ,`MySqlErrorHandler` passes `null` for the
fourth `replacedErrorHandler` argument, and `getProducerThrowable` has no call
sites on the MySQL path, but when I traced the base
`ErrorHandler.setProducerThrowable`, a **non-retriable** throwable still goes
to `queue.producerException(new ConnectException("…will be stopped"))`, which
does surface on the reader's `queue.poll()` (and `BinlogSplitReader` does have
a `checkReadException`). The thing `replacedErrorHandler = null` demonstrably
breaks is retry-count continuity across restarts (the base ctor only copies
`getRetries()` when it's non-null), which points more at a *retriable* error
looping than at a fatal error being swallowed, and that overlaps with the
10s-retry symptom in item 2.
I'd like to fix the right thing rather than just drop the `null`. Could you
share the exact exception + stacktrace from the class that appeared flaky for
you? If it's a genuinely swallowed fatal error I'm missing a path; if it's a
retriable error never exhausting its budget, the fix is about retry accounting
(or classifying that specific error). Either way I'll address it, just want to
confirm the mechanism first.
## 4. Removed `debezium.*` passthrough keys silently ignored
Agreed on the substance, `Configuration` doesn't reject unknown keys, so a
stale `debezium.database.server.name` keeps starting and silently changes the
offset partition key, i.e. a full re-snapshot rather than an error. And thank
you for the two corrections; they match what I see: `never` / `schema_only` /
`schema_only_recovery` are all still valid at 2.7.4 (only Postgres's `exported`
disappears in the 1.9.8→2.7.4 window), and the real 1.9 key is
`database.history.name`, not `database.history.instance.name`.
Since it's a behaviour change rather than part of the bump, I'd prefer to
keep it out of this PR and do it as a **follow-up** with the corrected
key/value list — a fail-fast validator at submission is the right call, just
not in the commit series that has to stay a pure version ladder. Happy to
co-author or review.
## On the rest
Appreciated the cross-check on `PostgresOffsetUtils` — the
incremental-snapshot prefix and `transaction_id` handling were exactly the
parts I was unsure I'd covered fully, so good to have that confirmed
independently. And glad the by-minor staging helped; that was the main bet of
this PR.
Following: I'll take **2** here (with your test), leave **1** and **4** to
the separate PRs you proposed, and circle back on **3** once I have the
stacktrace.
Thanks again for choosing to hand this over rather than fork the effort.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]