debezium/dbz#1662 Start & Stop API for the Quarkus Runtime Extension#33
Conversation
8401b3f to
e2e1241
Compare
| catch (Exception e) { | ||
| LOGGER.error("Failed to shutdown engine for manifest: {}", manifest.id(), e); | ||
| } |
There was a problem hiding this comment.
I'm skeptical of not throwing the shutdown exception up to the caller here. Are there not use cases where it could be good for the caller here to know the runner failed to stop and it shouldn't attempt to restart it?
| } | ||
|
|
||
| @Override | ||
| public void start(EngineManifest manifest) { |
There was a problem hiding this comment.
In addition, start/stop appear to be mostly copy-n-paste between the producers. I'd rather introduce some sort of abstract class here and pull this up for now to minimize the dupe.
There was a problem hiding this comment.
Yes, as @Naros mentioned, you currently have some duplication that suggests an opportunity for refactoring. My recommendation would be to extract a RunnableDebeziumConnectorRegistry that receives the engine map and the Connector in its constructor. @yuanglili WDYT? Let me know if this approach sounds reasonable.
There was a problem hiding this comment.
@yuanglili Thanks. I’ve added some comments for us to discuss.
| } | ||
|
|
||
| @Override | ||
| public void start(EngineManifest manifest) { |
There was a problem hiding this comment.
Yes, as @Naros mentioned, you currently have some duplication that suggests an opportunity for refactoring. My recommendation would be to extract a RunnableDebeziumConnectorRegistry that receives the engine map and the Connector in its constructor. @yuanglili WDYT? Let me know if this approach sounds reasonable.
| DebeziumRunner existing = runners.putIfAbsent(manifest.id(), runner); | ||
| if (existing != null) { | ||
| LOGGER.warn("Engine already running for manifest: {}", manifest.id()); | ||
| return; |
There was a problem hiding this comment.
I’m not convinced that using a return is the right approach here. I would rather throw an exception such as IllegalStateException or IllegalDebeziumStateException. The same strategy should be applied consistently across methods like start and stop. @Naros @yuanglili WDYT?
There was a problem hiding this comment.
I Agreed, My original thinking was that a duplicate start() call might not be critical, but a clear exception gives the caller immediate feedback would be better.
I don't see an existing IllegalDebeziumStateException in codebase , I can use IllegalStateException at here first I remember debezium/dbz#1662 (comment) @jpechane mentioned some broader lifecycle control features, I can create a IllegalDebeziumStateException in a follow-up PR for future use.
There was a problem hiding this comment.
That's fine as long as we don't loose sight of it @yuanglili
There was a problem hiding this comment.
You can create the exception in this PR. As for the lifecycle control feature, it’s fine to handle it in a separate PR. It's necessary some work around it
There was a problem hiding this comment.
I’ll make the updates to this PR later this week, and handle the lifecycle control feature in a separate PR.
| catch (Exception e) { | ||
| LOGGER.error("Failed to shutdown engine for manifest: {}", manifest.id(), e); | ||
| } |
|
@Naros @kmos Thanks for the feedback! Addressed all comments in the latest commit:
While adding restart support, I realized the current design stores pre-built Debezium instances, but I switched to So I think Would that direction make sense, or is there a simpler way you’d recommend? |
|
@yuanglili can you fix the CI errors? |
@kmos Sure, I’ll take care of it. Last week and this week are finals weeks for me, so I’m a bit busy, but I’ll have the CI issues fixed before the end of this week.I’ll also get Create example connector submitted |
6f24158 to
cb598c5
Compare
|
@kmos @Naros Summary of changes after the last review: Happy to iterate if you think there’s a better approach. |
kmos
left a comment
There was a problem hiding this comment.
@kmos @Naros The CI error is because
CompatibleModeConnectorRecorder.javaneeds to support the new methods, includingstart()andstop().I also added manifests() to more clearly separate what’s registered from what’s actually running, so engines() now returns only live instances.Summary of changes after the last review: • Extracted
RunnableDebeziumConnectorRegistryto reduce duplication across all 7 producers • Updatedstart() / stop()to throwIllegalDebeziumStateException• AddedIllegalDebeziumStateException• Addedmanifests()to theDebeziumConnectorRegistryinterface • Implemented the full lifecycle (start / stop / manifests) in CompatibleModeConnectorRecorderHappy to iterate if you think there’s a better approach.
Thanks @yuanglili. Let's give a run to the CI and a review from @Naros . I have only a doubt but overall it's a great work! 🚀
| */ | ||
| package io.quarkus.debezium.engine; | ||
|
|
||
| public class IllegalDebeziumStateException extends RuntimeException { |
There was a problem hiding this comment.
@Naros WDYT? I have a bias for RuntimeException but I am not sure if in this case should be an unchecked exception
There was a problem hiding this comment.
@yuanglili @kmos I don't have an issue with throwing an unchecked exception in this context, as long as the methods that can throw it document the possible unchecked exceptions. This keeps in line with other Java APIs that throw IllegalStateException.
My concern here is whether we need a specialized exception class? Why can we not merely rely on the existing DebeziumException?
There was a problem hiding this comment.
I don't have any concern to rely to DebeziumException
There was a problem hiding this comment.
I'll switch to using DebeziumException.
c50011d to
c4e0b36
Compare
|
Hi @yuanglili, can you rebase on the latest main? |
66d35f9 to
e4dacc2
Compare
Done, rebased on the latest main. |
|
I noticed the Oracle CI failure, looking into it now. I usually skip Oracle tests locally on my Mac since |
|
@kmos @Naros While debugging, I also found two lifecycle issues: |
|
The failed test passed on my local laptop. The problem might be: Fix: in |
c1374c6 to
4655e0a
Compare
|
@kmos Hi, I found the issue! Failures down to one issue in QuarkusHeartbeatEmitter L55, This wasn't hit before because a recent PR changed config phase to I can open another PR add an empty-list guard in |
@yuanglili, Can I give you a suggestion? in the this.registries
.stream()
.flatMap(registry -> registry.engines().stream())
.filter(engine -> engine.manifest().equals(context().manifest()))
.map(engine -> Map.entry(engine, heartbeat.select(Engine.Literal.of(engine.manifest().id()))))
.forEach(entry -> entry.getValue().fire(
new DebeziumHeartbeat(
entry.getKey().connector(),
entry.getKey().status(),
partition,
offset.getOffset())));and in the Let me know WDYT. About this PR or another one, it's perfectly fine for me |
Sounds good, this approach is cleaner and safer than using getFirst(). I’ll open a separate PR for this so it’s easier to review and track independently, then rebase this PR on top of it. |
to be super-precise @yuanglili , it's also necesssary to address the this.registries
.stream()
.flatMap(registry -> registry.engines().stream())
.filter(engine -> engine.manifest().equals(context().manifest()))
.map(engine -> Map.entry(engine, DefaultEngine.Literal.selectDefault(heartbeat.select(Engine.Literal.of(engine.manifest().id())), engine.manifest())))
.forEach(entry -> entry.getValue().fire(
new DebeziumHeartbeat(
entry.getKey().connector(),
entry.getKey().status(),
partition,
offset.getOffset()))); |
|
@yuanglili I have opened a new GH issue about the heartbeat debezium/dbz#2006 . Please let me know if you have cycles to open a PR |
@kmos PR is up : #56. Sorry for the delay. Once that PR is approved and merged, I’ll rebase this PR on top of it and update it right away. |
… manual engine lifecycle control Signed-off-by: nonononoonononon <yuangli971@gmail.com>
Signed-off-by: nonononoonononon <yuangli971@gmail.com>
Signed-off-by: nonononoonononon <yuangli971@gmail.com>
…ndling Signed-off-by: nonononoonononon <yuangli971@gmail.com>
…d fix CI errors Signed-off-by: nonononoonononon <yuangli971@gmail.com>
…Exception Signed-off-by: nonononoonononon <yuangli971@gmail.com>
Signed-off-by: nonononoonononon <yuangli971@gmail.com>
Signed-off-by: nonononoonononon <yuangli971@gmail.com>
Signed-off-by: nonononoonononon <yuangli971@gmail.com>
Signed-off-by: nonononoonononon <yuangli971@gmail.com>
922b80c to
119d9fb
Compare
|
@yuanglili I think there is still something failing for sqlserver |
Signed-off-by: nonononoonononon <yuangli971@gmail.com>
I noticed the failing, it passes locall, probably CI is slower, so the SQL Server engine dies on its own before the test's I Added a catch ( |
Summary
Adds
start(EngineManifest)andstop(EngineManifest)methods toDebeziumConnectorRegistry, enabling users to manually control engine lifecycle.Changes
API
Add
start(EngineManifest)andstop(EngineManifest)toDebeziumConnectorRegistryinterfaceEngine Producers
(all 7 connectors: Postgres, MySQL, MariaDb, SqlServer, Oracle, MongoDB, Db2)
ConcurrentHashMap<String, DebeziumRunner>to manage runner lifecyclestart(): uses putIfAbsent for idempotency, cleans up map on failurestop(): uses remove atomically, logs error on shutdown failure without rethrowingConfiguration
Add
quarkus.debezium.engine.autostart(default true) read inEngineProcessorTest App
Add
start/stopendpoints toEngineResource(single and per-id)Add
/engine/statusesendpoint for multi-engine status queriesIntegration Tests
ManualStartSingleEngineIT: autostart=false, manual start, lifecycle events, restart, graceful shutdown when never startedManualStartMultiEngineIT: single/all engine start/stop, state verification across enginesClose issue dbz#1662