DanielLeens opened a new issue, #11558:
URL: https://github.com/apache/seatunnel/issues/11558
### Search before asking
- [x] I searched open and closed SeaTunnel issues and STIPs for SourceReader
mailbox, checkpoint lock, split assignment, Source coordinator event loop, and
engine-managed connector concurrency.
- [x] Existing issues such as #5694 and #2936 describe individual
checkpoint-lock or split/checkpoint atomicity failures, but no existing STIP
defines the complete engine-owned Source concurrency and recovery contract
proposed here.
## Summary
This STIP proposes an opt-in, engine-managed Source runtime for Zeta that
makes the engine, rather than each connector, responsible for callback
serialization, checkpoint cuts, command admission, split-assignment recovery,
and lifecycle ordering.
The proposal introduces two explicit execution lanes:
- **Lane A: managed runtime.** Eligible connectors run under a bounded
Reader event loop and a Coordinator event loop. The engine owns all
checkpoint-visible state transitions.
- **Lane B: legacy runtime.** Existing connectors retain the current
`SourceReader` SPI, checkpoint monitor identity, private locks, and behavior.
No connector is migrated silently.
The delivery scope is Phase 0 through Phase 3 plus Phase 4-lite. There is no
mandatory repository-wide Phase 5 migration.
## Motivation
Source concurrency boundaries are currently distributed across:
- `SourceFlowLifeCycle.collect()` and `SourceReader.pollNext(...)`;
- `Collector.getCheckpointLock()` and `SourceReader.snapshotState(...)`;
- split assignment and source-event Hazelcast operations;
- `SourceSplitEnumeratorTask` callbacks;
- connector-owned locks, background fetchers, and scheduler threads;
- schema-change checkpoint callbacks and close handling.
As a result, a connector author must reason about multiple engine threads,
remote operations, checkpoint ordering, restore, and custom locks. This has
caused long checkpoint-lock stalls, ambiguous split ownership, operation-thread
blocking, and implementation-specific concurrency patterns.
Replacing every lock with a queue is not sufficient. The production contract
must answer:
1. Which execution context owns Reader and Enumerator state?
2. When is a command admitted, applied, and included in a checkpoint?
3. How are duplicate delivery, lost acknowledgements, stale attempts,
failover, and rescale handled?
4. How are queues bounded without blocking Hazelcast operation threads?
5. How do existing connectors remain compatible?
## Goals
1. Make the engine the single owner of checkpoint-visible Source state for
Lane A.
2. Remove connector callbacks from Hazelcast operation threads.
3. Define a strict and recoverable ordering between controls, polling, and
checkpoints.
4. Provide at-least-once command delivery with idempotent apply and attempt
fencing.
5. Track split assignment until checkpoint inclusion is proven.
6. Move slow Enumerator discovery to engine-managed workers while applying
results only on the Coordinator event loop.
7. Preserve all existing connector behavior through Lane B.
8. Keep resource usage bounded by command count, payload bytes, and
worker-wide budgets.
9. Version wire commands, connector payloads, capabilities, and checkpoint
metadata independently.
10. Provide a production kill switch, metrics, conformance tests, and
fault-injection coverage.
## Non-goals
1. This STIP does not redesign Sink concurrency.
2. It does not replace Hazelcast.
3. It does not remove `SourceReader.pollNext(...)` or
`Collector.getCheckpointLock()`.
4. It does not migrate all existing connectors.
5. It does not persist and replay arbitrary connector `SourceEvent`
instances.
6. It does not add one mailbox thread per task.
7. It does not provide synchronous `Mailbox.call(...)` or synchronous
cross-thread Coordinator calls.
## Required invariants
1. **Single owner:** one event loop is the only owner of a Lane A Reader or
Enumerator's checkpoint-visible state.
2. **No operation blocking:** Hazelcast operation threads validate and offer
commands only; they never invoke connector callbacks or wait for application.
3. **No event-loop RPC wait:** Reader and Coordinator event loops never call
`.get()` or `.join()` on remote futures.
4. **Strict checkpoint cut:** every ordered command admitted before a
barrier is applied before the snapshot; later commands belong to a later
checkpoint.
5. **Atomic state and emit:** emitted records and their split-state
advancement are complete within one poll turn before a snapshot can run.
6. **At-least-once transport:** commands may be delivered more than once,
but one command has one business effect within an attempt.
7. **Attempt fencing:** stale Reader attempts, Coordinator epochs, and
async-worker results cannot mutate the current attempt.
8. **Bounded resources:** command count, payload bytes, and worker-wide
mailbox bytes are all bounded.
9. **Bounded control latency:** an ordered control waits for at most one
compliant poll turn.
10. **Terminal preemption:** cancel and fatal failure use an independent
terminal signal and invoke `wakeup()`.
11. **Versioned recovery:** wire and checkpoint state use explicit versioned
serializers, never long-term Java native serialization.
12. **No silent lane switch:** restore uses the lane and capability digest
recorded in runtime metadata.
13. **Legacy identity:** Lane B retains the existing checkpoint-monitor
identity.
14. **No per-task thread:** the Reader event loop is driven by the existing
task execution resource.
## Architecture
```text
Hazelcast operation
-> SourceCommandTransportAdapter
-> bounded ReaderMailbox
-> ReaderEventLoop
-> ManagedSourceReader
-> connector state + runtime checkpoint metadata
CoordinatorEventLoop
-> SourceAssignmentTracker
-> versioned command transport
-> ReaderMailbox
CoordinatorEventLoop
-> immutable discovery input
-> shared SourceAsyncWorkerPool
-> epoch-fenced result
-> CoordinatorEventLoop
```
The Reader event loop reuses the existing `SeaTunnelTask` execution
resource. Polling is a coalesced turn, not a command per record. The loop
always checks terminal state and ordered controls before starting another poll
turn.
## Managed Reader capability
The additive API is experimental until it has survived at least one release
cycle:
```java
@Experimental
public interface ManagedSourceReader<T, SplitT extends SourceSplit>
extends SourceReader<T, SplitT> {
ManagedSourceCapability managedSourceCapability();
CompletableFuture<Void> isAvailable();
PollStatus pollNextManaged(Collector<T> output, PollContext context)
throws Exception;
void wakeup();
}
```
`PollStatus` contains `MORE_AVAILABLE`, `NOTHING_AVAILABLE`, and
`END_OF_INPUT`.
`PollContext` exposes remaining record and byte budgets, a deadline, and
`shouldYield()`.
Lane A requires:
- bounded, non-blocking polling;
- no lost availability wakeup;
- idempotent and thread-safe `wakeup()`;
- stable split IDs;
- no background thread mutation of checkpoint-visible state;
- versioned state and durable event serializers;
- passing the managed Source conformance suite.
A versioned capability descriptor is used instead of a boolean. It includes
runtime protocol version, bounded-poll support, wakeup support, attempt-fencing
support, source-event support, async-enumerator support, and a stable digest.
## Reader scheduling
Scheduling categories are:
| Category | Examples | Ordering |
| --- | --- | --- |
| `TERMINAL` | cancel, fatal failure, force close | independent atomic
signal; preempts normal work |
| `ORDERED_CONTROL` | split assignment, source event, checkpoint, graceful
close | strict FIFO |
| `ASYNC_COMPLETION` | async I/O and scheduler completion | returned to the
owner loop, bounded and coalescible |
| `POLL` | record production | low priority, at most one pending turn |
| `OBSERVABILITY` | metric and diagnostic sampling | lowest priority,
droppable or coalescible |
A poll turn yields when any configured records, bytes, or time budget is
reached. A soft-budget violation records diagnostics. A hard-budget violation
invokes `wakeup()`. Failure to return within the cancellation timeout fails the
task; the runtime never changes lane online.
## Versioned command protocol
Every cross-thread or cross-node ordered command uses an immutable envelope:
```text
SourceCommandEnvelope
protocolVersion
jobId
sourceRuntimeId
coordinatorEpoch
senderAttemptId
targetAttemptId
senderSequence
commandId
commandKind
durability
payloadVersion
payloadChecksum
payloadBytes
```
Admission returns:
```text
AdmissionAck
status
targetAttemptId
mailboxSequence
retryAfterMillis
reasonCode
```
Statuses are `ACCEPTED`, `DUPLICATE`, `RETRY_LATER`, `STALE_TARGET`,
`TERMINAL_REJECTED`, `UNSUPPORTED_PROTOCOL`, and `INVALID_PAYLOAD`.
`ACCEPTED` means that validation passed, capacity was reserved, a mailbox
sequence was allocated, and the command is visible to the current target
attempt. It does not mean the command was applied or checkpointed.
Commands are classified as:
- `CHECKPOINT_COUPLED`: split assignments and explicitly versioned state
events;
- `RECONSTRUCTABLE`: registration, split request, and no-more-splits control;
- `EPHEMERAL`: availability and diagnostics;
- `TERMINAL`: cancellation and fatal failure.
Phase 1 only permits built-in split assignment and no-more-splits as durable
engine commands. Arbitrary durable custom `SourceEvent` requires a separate
reviewed serializer and recovery contract.
## Admission and overload
Admission is linearized only after:
1. protocol, attempt, header, checksum, and payload-size validation;
2. command-count and byte-capacity reservation;
3. mailbox-sequence allocation;
4. publication to the Reader event loop.
The operation thread does not deserialize connector state, invoke connector
code, sleep, spin, or wait.
Capacity is bounded at three levels:
- per-mailbox command count;
- per-mailbox payload bytes;
- per-worker Source mailbox bytes.
Checkpoint, graceful-close, checkpoint-callback, and assignment-ack controls
have reserved capacity. Terminal state has independent capacity. A normal full
queue returns `RETRY_LATER`; exhausted reserved capacity is an invariant
violation and fails the task.
## Checkpoint ordering
Local barriers enter the same target sequencer as ordered controls.
1. Commands with a lower sequence are applied first.
2. Poll turns do not consume a sequence and run only between ordered
controls.
3. At the barrier, there is no accepted-before-barrier command left
unapplied.
4. Reader state and engine runtime metadata are snapshotted.
5. State registration, task acknowledgement, and downstream barrier
forwarding happen only after a successful snapshot.
6. A terminal preemption prevents the checkpoint from succeeding.
The mailbox queue itself is never checkpointed.
## Split assignment ownership
The engine tracks each assignment:
| State | Meaning |
| --- | --- |
| `UNASSIGNED` | still owned by the Enumerator |
| `DISPATCHED` | command created, admission not yet proven |
| `ADMITTED` | current Reader attempt accepted the command |
| `APPLIED` | Reader invoked `addSplits` once |
| `CHECKPOINT_INCLUDED` | Reader runtime metadata proves inclusion |
| `GC_ELIGIBLE` | the corresponding global checkpoint completed |
An apply acknowledgement is useful for flow control but is not durability
proof. The Coordinator retains assignment ownership until checkpoint inclusion
and global checkpoint completion are reconciled.
Reader checkpoint metadata contains:
```text
SourceRuntimeCheckpointMetadata
metadataVersion
commandProtocolVersion
managedLaneVersion
readerAttemptLineage
appliedSenderWatermarks
boundedSequenceGaps
noMoreSplitsGeneration
capabilityDigest
```
Reader failover creates a new attempt and restores connector state plus
runtime metadata. Coordinator reconciliation returns assignments after the
restored watermark to the Enumerator. Commands addressed to the old attempt are
rejected.
Coordinator failover increments the epoch, restores Enumerator and tracker
state in one checkpoint cut, cancels old async tasks, and reconciles registered
Readers before new assignment.
Rescale keys ownership by stable split identity, not old subtask index.
## Coordinator event loop and scheduler
The Coordinator event loop serializes:
- `open` and `close`;
- Reader registration;
- split requests and returned splits;
- source events;
- Enumerator snapshot;
- checkpoint complete and abort callbacks;
- async discovery results and scheduler ticks.
The scheduler exposes asynchronous APIs only:
```java
public interface CoordinatorScheduler {
<T> Cancellable callAsync(
AsyncTaskKey key,
Callable<T> callable,
BiConsumer<T, Throwable> resultHandler,
AsyncTaskOptions options);
Cancellable scheduleInCoordinatorThread(
AsyncTaskKey key,
Duration delay,
Runnable task);
}
```
Slow work receives immutable input and runs in a shared bounded engine
worker pool. The result handler always returns to the Coordinator event loop
and is fenced by epoch. Fixed-delay scheduling, no implicit re-entry,
`COALESCE_ONE` by default, timeout, cancellation, failure policy, worker class,
and context classloader are mandatory lifecycle properties.
## Lifecycle and schema change
The main lifecycle is:
```text
CREATED -> RESTORING -> RUNNING -> DRAINING -> CLOSED
\-> CANCELLING -> CLOSED
\-> FAILED -> CLOSED
```
Schema change is an orthogonal sub-state:
```text
IDLE -> QUIESCING -> TRIGGER_REQUESTED -> WAITING_END -> IDLE
```
Only the bound schema checkpoint ID and request epoch may advance the
sub-state. During schema change, no new poll turn runs. A matching completion
returns to `RUNNING`, or to `DRAINING` if graceful close/end-of-input was
latched. Abort, timeout, overlapping schema changes, and trigger failure fail
the task. Stale and duplicate callbacks are ignored and counted. Cancel/fatal
failure fences the request and cancels its future.
## Compatibility and upgrades
Lane B remains fully independent and is the default for existing connectors.
Lane selection is recorded in:
- job plan;
- task deployment descriptor;
- checkpoint runtime metadata.
Restore never chooses a lane from the currently installed connector alone. A
capability-digest mismatch fails before execution unless an explicit, tested
migration exists.
The command protocol, connector payload version, and runtime-metadata
version evolve independently. Unknown major versions fail fast. The first
release may require a homogeneous cluster; mixed-version support is enabled
only after a permanent compatibility matrix exists.
## Initial production defaults
| Setting | Initial value |
| --- | ---: |
| Reader mailbox maximum commands | `1024` |
| Reader mailbox maximum payload bytes | `4 MiB` |
| Reserved control commands | `64` |
| Reserved control bytes | `256 KiB` |
| Worker Source mailbox maximum bytes | `256 MiB` |
| Maximum command payload | `512 KiB` |
| Poll maximum records per turn | `64` |
| Poll soft duration | `5 ms` |
| Poll hard duration | `1 s` |
| Poll cancellation timeout | `30 s` |
| Admission p99 budget | `5 ms` |
| Retry initial/max backoff | `10 ms` / `1 s` |
| Coordinator async maximum concurrency | `4` |
| Assignment tracker maximum entries | `100000` |
| Assignment tracker maximum bytes | `64 MiB` |
All settings use engine configuration options, have tests and documentation,
and are not connector-overridable. Correctness-critical settings are not
hot-reloadable.
## Observability
Reader metrics cover admission status, mailbox count/bytes, reserved usage,
oldest age, queue wait, command service time, poll records/bytes/time, wakeup,
checkpoint stages, applied watermarks, and output-blocked time.
Coordinator metrics cover mailbox usage, assignment states, oldest
uncheckpointed assignment, async queue/execution time, overlap/coalescing,
stale results, registration reconciliation, tracker size/compaction, and
assignment backpressure.
Metric labels are bounded. Command IDs, split IDs, table paths, payloads,
credentials, and exception text are never labels or logs.
## Validation
Deterministic event-loop tests cover all adjacent command/barrier
permutations, availability wakeups, poll yielding, terminal preemption,
duplicates, stale attempts/epochs, checkpoint callback ordering, and every
schema transition.
Fault injection covers:
1. command creation;
2. operation send;
3. capacity reservation;
4. admission acknowledgement;
5. connector apply before return;
6. apply completion;
7. apply acknowledgement;
8. Reader snapshot before state registration;
9. state registration before task acknowledgement;
10. task acknowledgement before downstream forwarding;
11. global checkpoint completion before tracker garbage collection.
Model-based tests randomly generate assignment, duplicate delivery, ACK
loss, Reader and Coordinator failover, checkpoint complete/abort, rescale,
no-more-splits, disabled checkpoints, and sustained checkpoint failure.
The conformance suite validates non-blocking poll, budgets, no-lost-wakeup,
cancellation, state/emit atomicity, duplicate split assignment, callback
idempotence, background-thread confinement, source-event durability
declaration, and context-classloader restoration.
The performance matrix covers Fake Source, Kafka/Pulsar, JDBC Lane B
control, Iceberg discovery, split storms, and blocking controls at parallelism
`1/16/128/512`. Exit criteria are no more than 3% steady-state throughput
regression, 5% CPU and heap increase, checkpoint p99 increase no greater than
max(5%, 100 ms), admission p99 at most 5 ms, bounded mailbox memory, and no
monotonic tracker growth in a 24-hour soak.
## Implementation plan in one Draft PR
The implementation remains reviewable through phase-gated commits, but all
work is delivered in one Draft PR:
### Phase 0: protocol and measurement
- runtime contention metrics;
- temporary Lane A allowlist, defaulting every connector to Lane B;
- versioned runtime-metadata serializer;
- deterministic fault-injection fixtures and managed Fake Source.
### Phase 1: Reader managed runtime
- command envelope, admission acknowledgement, deduplication, attempt
fencing;
- bounded Reader mailbox and cooperative event loop;
- minimal assignment tracker and Reader checkpoint metadata;
- Fake Source and an asynchronous streaming Reader pilot;
- production kill switch.
### Phase 2: Coordinator event loop
- serialized Enumerator callbacks;
- attempt-aware registration;
- bounded async worker and scheduler lifecycle;
- assignment tracker checkpoint and reconciliation;
- Iceberg streaming Enumerator pilot.
### Phase 3: lifecycle and schema change
- executable main and schema lifecycle state machines;
- event-loop checkpoint callbacks;
- close latch, cancellation, timeout, stale callback, and failover handling.
### Phase 4-lite: stable capability and connector adoption
- versioned capability descriptor;
- connector-common managed Reader base;
- conformance suite;
- at least two different connector types using Lane A;
- maintainer policy for new connectors, with an explicit Lane B exception
path.
There is no mandatory all-connector migration phase.
## Rollout and rollback
The feature is disabled by default and enabled by allowlist/capability.
Initial production rollout is canary-only.
A global kill switch affects only new deployments and restores. Running
tasks never switch lane. A rollback must preserve checkpoint readers for every
published runtime-metadata version. If an older binary cannot read the current
managed metadata, downgrade is rejected and the job must restore with a
compatible binary or from a compatible savepoint.
## Acceptance criteria
- [ ] Existing connectors remain in Lane B unless explicitly eligible.
- [ ] Operation threads never execute connector callbacks for Lane A.
- [ ] Reader and Enumerator checkpoint-visible state have one owner each.
- [ ] Commands are bounded, versioned, fenced, deduplicated, and checkpoint
ordered.
- [ ] Split ownership survives lost ACK, Reader failover, Coordinator
failover, and rescale.
- [ ] Mailbox queues and arbitrary SourceEvents are not persisted as generic
state.
- [ ] Slow discovery uses engine-managed workers and epoch-fenced event-loop
application.
- [ ] Schema change, close, cancellation, timeout, duplicate callback, and
failover transitions are deterministic.
- [ ] Runtime metadata, lane, and capability are restore compatible and
never switch silently.
- [ ] At least two different connector types pass the Lane A conformance
suite.
- [ ] Required fault-injection, compatibility, performance, and soak gates
pass before the PR leaves Draft.
- [ ] English and Chinese documentation describe configuration,
compatibility, migration, metrics, and rollback.
### Usage Scenario
Connector authors should be able to implement a Source without designing
their own checkpoint lock or coordinating framework callbacks from multiple
engine threads. Operators should gain bounded control latency, explicit
overload behavior, deterministic recovery, and enough diagnostics to identify a
non-compliant connector without changing existing jobs.
### Related issues and implementation
- Historical checkpoint-lock stall: #5694
- Historical split/checkpoint atomicity issue: #2936
- Schema-change ordering discussion: #11402
- Implementation Draft PR: #11557
### Are you willing to submit a PR?
- [x] Yes, I am willing to submit a PR.
### Code of Conduct
- [x] I agree to follow the Apache Software Foundation Code of Conduct.
--
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]