nzw921rx commented on PR #12943:
URL: https://github.com/apache/seatunnel/pull/12943#issuecomment-6333111413

   ## Architecture review — not ready to merge
   
   Scope: static review of `a5230b0` against `upstream/dev@ee83c538` (64 files, 
+5736/−187). I did not run the full build or test suite.
   
   The documentation and test coverage are unusually thorough, and the failure 
modes are clearly described. The concern is implementation boundaries, not 
feature intent. The current shape introduces a second Application runtime and a 
second resource-manager model, and threads an HA boolean through generic 
execution code. That is the main architecture debt.
   
   ---
   
   ### P0 — blocking
   
   #### 1. The PR contains #12942's connector changes
   
   **Where**
   - 
`seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/split/MultipleTableFileSourceSplitEnumerator.java`
   - 
`seatunnel-connectors-v2/.../MultipleTableFileSourceSplitEnumeratorTest.java`
   - `seatunnel-e2e/.../connector-file-local-e2e/.../LocalFileIT.java`
   - `seatunnel-e2e/.../local_file_bounded_restore.conf`
   - `docs/{en,zh}/connectors/source/LocalFile.md`
   
   The PR body says the branch includes #12942 and will be rebased after it 
lands; #12942 is still open. These files are 381 lines unrelated to Kubernetes 
HA.
   
   **Why it matters**
   The HA change cannot be reviewed, merged, reverted, or bisected 
independently of the connector fix. This violates the repository's 
one-PR-one-problem boundary.
   
   **Recommendation**
   Land #12942 first, rebase this branch, and remove those files from this PR.
   
   ---
   
   ### P1 — high priority
   
   #### 2. HA is a second Application runtime, not a strategy
   
   **Where**
   - Single-master: `SeatunnelKubernetesApplicationCli.java:51-72, 104-126, 
185-206` uses `KubernetesResourceManagerDriver` + `ApplicationJobRunner`.
   - HA: `SeatunnelKubernetesHaApplication.java:66-117, 122-311` uses 
`FixedPoolApplicationResourceManager` + `SeaTunnelClient` + a temporary job 
file.
   - Entry selection: `KubernetesApplicationClusterDescriptor.java:146-151`.
   
   **What is duplicated**
   - Job submission: `ApplicationJobRunner:66-126` vs 
`SeatunnelKubernetesHaApplication:206-245`.
   - Terminal mapping: `ApplicationJobRunner:258-265` vs 
`SeatunnelKubernetesHaApplication:422-430`.
   - Status persistence: `ApplicationResourceManager.finish` vs owner Job 
annotations.
   - Resource management: `ApplicationResourceManager` (`:48-58, 177-260`) 
already owns worker registration, failure propagation, and `finish`; 
`FixedPoolApplicationResourceManager:32` instead extends 
`StandaloneResourceManager` and only overrides the worker-count gate. HA 
cleanup is reimplemented in `SeatunnelKubernetesHaApplication:432-444`.
   - Mode selection: `ResourceManagerFactory.java:28, 61-75` adds a mutable 
`externallyManagedWorkers` field outside `DeployType`.
   
   **Why it matters**
   The same application configuration now has two different submission, 
restore, status, and cleanup contracts. A fix in one path does not 
automatically apply to the other. `FixedPoolApplicationResourceManager` also 
loses the application identity, failure future, and finish semantics provided 
by `ApplicationResourceManager`.
   
   **Recommendation**
   Extract a provider-neutral `ApplicationRuntime`/`ApplicationLifecycle` 
(submit, observe, terminal, cleanup) with `SingleMasterRuntime` and 
`HaObserverRuntime`. On the resource side, keep the 
`ApplicationResourceManager` contract and add a controller-owned driver instead 
of extending `StandaloneResourceManager`. Keep one launcher and select the 
runtime by strategy.
   
   #### 3. `applicationHaEnabled` enters the generic execution state machine
   
   **Where**
   - `EngineConfig.java:94-95` adds the boolean.
   - `SeatunnelApplicationConfig.java:337-350` sets it and rewrites Hazelcast 
map backups / schedule strategy.
   - `SubPlan.java:413-421, 514-516, 749-758, 778-813`.
   - `JobMaster.java:340-361, 531-629`.
   - `CheckpointManager.java:163-195`.
   
   **What changes**
   `SubPlan` now uses `isApplicationHaEnabled()` to change:
   - whether `forceStopPipeline` takes `synchronized (this)`;
   - whether restore sleeps before retrying;
   - whether FAILED/CANCELED uses the normal `preApplyResources` path or an 
asynchronous HA restore loop;
   - whether a shared-executor loop sleeps and polls in 
`restoreHaPipelineWhenResourcesAvailable`.
   
   There is also a concrete API hazard: `JobMaster.preApplyResources(SubPlan)` 
(`:527-529`) always passes `recoveryStillCurrent = null`. With HA enabled it 
takes the non-HA branch at `:619-621` and does **not** call 
`setOwnedSlotProfiles`. Correct HA behavior depends on every caller remembering 
the two-argument overload at `:532`.
   
   `configureHa` also sets the flag on lite workers and on the owner client 
(`SeatunnelKubernetesApplicationWorker.java:50-56`, 
`SeatunnelKubernetesHaApplication.java:97-103`), even though it is a 
coordinator recovery behavior.
   
   **Recommendation**
   Replace the boolean with an explicit 
`CoordinatorRecoveryPolicy`/`ApplicationExecutionPolicy` injected into 
`JobMaster` and `SubPlan`. Keep `SubPlan` state transitions mode-agnostic. At 
minimum, make `preApplyResources(SubPlan)` safe under HA and restrict the flag 
to coordinators.
   
   #### 4. `CheckpointStorage` SPI is widened with a default-throwing method
   
   **Where**
   - `CheckpointStorage.java:86-98`: new `getCheckpointsForRecovery`, default 
implementation throws `CheckpointStorageException`.
   - `CheckpointManager.java:219-242`: HA restart calls it.
   - `HdfsStorage.java:236-289` and `LocalFileStorage.java:115-171`: each 
duplicates the retry/snapshot scan.
   
   **Trigger and result**
   A custom checkpoint storage plugin, or a test double, can work for normal 
jobs but throws `"Checkpoint storage does not support fail-closed recovery"` on 
the first HA master failover. There is no capability check at submission time, 
so the failure appears during recovery rather than deployment. 
`architecture.md:197-199` already documents the custom-plugin requirement, 
while the PR checklist marks `incompatible-changes.md` as N/A.
   
   The new method also overlaps with the existing 
`getCheckpointsByJobIdAndPipelineId` (`:86`), so callers must know which 
semantics to use.
   
   **Recommendation**
   Introduce a `RecoverableCheckpointStorage` capability interface, or validate 
support when HA is submitted. Move the retry scan into 
`AbstractCheckpointStorage`. Also, `CheckpointManager:230,238` deserializes the 
same state twice; cache the decoded checkpoint.
   
   #### 5. HA deployment depends on the submitter's local `seatunnel.yaml`
   
   **Where**
   - `KubernetesApplicationConfig.java:140-148`
   - `KubernetesOptions.java:42-47`
   
   When `kubernetes.ha.backup-count` is unset, the 
`KubernetesApplicationConfig` constructor calls 
`ConfigProvider.locateAndGetSeaTunnelConfig()` and reads the submitter 
machine's `seatunnel.engine.backup-count`.
   
   **Why it matters**
   The same `application.config` submitted from two machines with different 
local `seatunnel.yaml` files produces different `haBackupCount` values and 
different Hazelcast map replication. The value is silently embedded in the Job 
spec; it is not part of the application configuration and is not reviewable. 
The application spec is no longer a self-contained, reproducible source of 
truth.
   
   **Recommendation**
   Require an explicit `kubernetes.ha.backup-count`, or resolve it from the 
runtime `seatunnel.yaml` actually mounted into the masters and write the 
resolved value into the spec. Do not read local engine configuration inside a 
value-object constructor.
   
   #### 6. Single-master `cancel` behavior regressed when the Job is absent
   
   **Where**
   - `KubernetesApplicationClient.java:119-137`
   - `KubernetesApplicationClusterDescriptor.java:484-488`
   - The new behavior is codified by `KubernetesApplicationTest.java:593-598`.
   
   The new path calls `getJob` first; on 404 it sets `canceled = true` and 
returns. The base path (`KubernetesApplicationClusterDescriptor:450-453` plus 
`DefaultKubernetesClient.deleteApplication:250-279`) continued to delete 
worker/master Pods by label.
   
   **Trigger**
   The Job was deleted by TTL or foreground deletion, but labeled Pods remain 
(or are still being deleted). `cancel` now reports `CANCELED` without cleaning 
them up.
   
   **Why it matters**
   This contradicts "existing single-master defaults and standalone behavior 
are unchanged" and can leak compute resources. It affects the shared 
single-master path, not only HA.
   
   **Recommendation**
   For non-HA, preserve the label-based fallback when the Job is 404. Only skip 
cleanup when HA is enabled and the owner UID is genuinely unknown, or provide 
explicit orphan cleanup.
   
   ---
   
   ### P2 — design and boundary cleanup
   
   #### 7. Role Pods are assembled by clone-and-mutate; master-only 
configuration leaks
   
   **Where**
   - `HaApplicationFactory.java:92-150` builds master and worker from 
`ApplicationJobFactory.createMasterPod`, then overwrites them.
   - `HaApplicationFactory.java:152-179` builds the owner from the same master 
factory.
   - `ApplicationJobFactory.java:117-131` adds the checkpoint PVC to every 
master Pod.
   - `HaApplicationFactory.java:111-136` copies the master container's volume 
mounts/volumes into the worker.
   - `HaApplicationFactory.java:216` uses the master discovery Service as the 
worker StatefulSet's `serviceName`; `:229-247` selects only masters.
   
   **Why it matters**
   The owner only needs the Kubernetes API token, but inherits the checkpoint 
PVC and other master-only fields. Workers also mount the checkpoint PVC even 
though only the coordinator uses checkpoint storage. This adds unnecessary 
scheduling constraints and couples every role to future master changes. Worker 
Pods get `subdomain=<app>-discovery`, but that Service has no worker endpoints, 
so the stable worker DNS name does not resolve. HA also ignores the `mainClass` 
argument (`KubernetesApplicationClusterDescriptor.java:146-151`).
   
   **Recommendation**
   Create dedicated `ObserverPodFactory` and HA `WorkerPodFactory` 
implementations and compose only role-required volumes/env. Use a role-specific 
headless Service, or make the discovery Service selector role-aware.
   
   #### 8. Public APIs are widened for one provider
   
   **Where**
   - `KubernetesClient.java:131-150`: `getService`, `updateHaState`, 
`deleteHaResources`, and `deleteApplication(id, uid)` are default-throwing 
methods on the general client interface.
   - `SeaTunnelClient.java:210-215`: new `getMasterCount()`.
   
   **Why it matters**
   An alternative `KubernetesClient` implementation or test double compiles but 
throws `UnsupportedOperationException` at runtime on HA. `getMasterCount()` 
counts non-lite members; in normal standalone/cluster mode workers are not 
lite, so it returns all members, not masters. The name and contract only hold 
for Application HA.
   
   **Recommendation**
   Move HA operations to a separate `HaApplicationClient`/capability interface. 
Rename `getMasterCount` to `getDataMemberCount` or move it back to the HA 
launcher.
   
   #### 9. HA lifecycle state is scattered across four classes
   
   **Where**
   - `HaApplicationState.java:29-63`: raw string constants and a hardcoded 
`terminal()`.
   - `HaApplicationFactory.java:190-198`: seeds the annotations.
   - `SeatunnelKubernetesHaApplication.java:313-419`: transitions and CAS.
   - `KubernetesApplicationClient.java:60-89`: reads them.
   
   **Why it matters**
   Adding a phase such as `SUBMITTING` or `CANCELING` requires edits in four 
places. Unrecognized values silently become `UNKNOWN`, and there is no single 
transition guard.
   
   **Recommendation**
   Add a typed `HaApplicationPhase` enum and a single `HaApplicationLifecycle` 
class that owns reads, CAS transitions, and terminal-state detection; have the 
client, factory, and CLI delegate to it.
   
   #### 10. Worker mode is selected by argument count
   
   **Where**
   - `SeatunnelKubernetesApplicationWorker.java:44-56`
   
   `args.length == 4` selects HA, while the error message still documents only 
three arguments. A future fourth positional argument would silently change the 
execution mode.
   
   **Recommendation**
   Use an explicit mode/role argument instead of arity-based mode selection.
   
   #### 11. Documentation and diff hygiene
   
   - `docs/en/getting-started/application/kubernetes/commands.md:56` and 
`docs/zh/.../commands.md:50` say single-master and HA `cancel` both use a UID 
precondition. The code only does so for HA 
(`KubernetesApplicationClient.java:131-135`); single-master still calls 
`deleteApplication(id)`. Fix the docs or implement UID-safe deletion for 
single-master.
   - `KubernetesApplicationConfig.java:461-467` adds a pointless local 
`resolved` variable, and `:743` adds an unrelated blank line. Remove this noise 
so the diff remains traceable.
   
   ---
   
   ### Suggested convergence order
   
   1. Land #12942, rebase, and remove the connector diff.
   2. Extract a shared `ApplicationRuntime`/`ApplicationLifecycle` and keep the 
`ApplicationResourceManager` contract for HA.
   3. Replace `applicationHaEnabled` with a coordinator-only recovery policy 
and remove the HA branches from `SubPlan`, `JobMaster`, and `CheckpointManager`.
   4. Make checkpoint recovery a capability with submission-time validation, 
and consolidate the storage scan.
   5. Replace role clone-and-mutate with dedicated factories and role-specific 
Services.
   6. Clean up the public APIs, docs, and diff noise.
   


-- 
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]

Reply via email to