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

   > Thanks both for keeping this moving.
   > 
   > @Vivek1106-04, the full sweep is useful and it actually lines up with 
Issue 1 from my earlier review: `triggerBodyMicros` (0 vs 500) barely moves the 
numbers at any pipeline count, which is exactly what you'd expect if the 
measured task almost never has to wait behind the periodic body. The knee at 
500 pipelines looks like thread/CPU contention from ~1000 live threads in one 
JVM rather than scheduling-queue pressure, so I'd read this sweep as "per-pool 
wake-up latency under near-zero load," not yet as a load-bearing comparison for 
#12165 - which is really the same thing @nzw921rx is getting at below.
   > 
   > @nzw921rx, I think you're right that the harness measures thread wake-up 
delay rather than queueing under the engine's real trigger pattern, and that 
this limits how far the numbers can be trusted as a stand-in for the actual 
scheduling model comparison - that's the same gap I flagged as Issue 1 
(non-blocking) in my review.
   > 
   > On routing through the real path: there's already a testing hook at that 
boundary - 
`CheckpointCoordinator#scheduleTriggerPendingCheckpoint(CheckpointType, long)` 
(`CheckpointCoordinator.java:547-553`) is `@VisibleForTesting protected`. The 
catch is that it schedules `tryTriggerPendingCheckpoint(checkpointType)`, which 
on the happy path calls `createPendingCheckpoint(...)` and 
`startTriggerPendingCheckpoint(...)` (`CheckpointCoordinator.java:816-824`) - 
i.e. it builds a real `PendingCheckpoint`, writes to checkpoint storage, and 
sends real RPC operations out to task locations. Driving the benchmark through 
that method means constructing a real (or fully mocked, as 
`CheckpointCoordinatorTest` does with Mockito) `CheckpointManager`, execution 
plan and task locations, so the sample would then include storage and RPC noise 
on top of scheduling delay, which is a different and heavier thing to measure 
than what this PR set out to isolate.
   > 
   > A cheaper middle ground that gets at the same question without that 
machinery: my Issue 1 write-up already proposed arming both a trigger-like task 
and a watchdog-like task per pool with a duty cycle long enough that both of a 
pool's 2 workers can be occupied, so the measured task genuinely has to queue 
behind them - closer to how `tryTriggerPendingCheckpoint`'s own re-scheduling 
(`scheduleTriggerPendingCheckpoint(checkpointType, 500L)` when 
`pendingCounter.get() > 0`, line ~816) can pile a second timer onto the same 
pool. That would show whether pool width matters under real contention, still 
as a standalone harness.
   > 
   > Could you say which of these you're looking for: exercising 
`tryTriggerPendingCheckpoint`'s full side effects (storage + RPC, higher 
fidelity but a much bigger, more intrusive change), or a queuing-faithful 
standalone harness along those lines? Either is a reasonable ask, but they're 
different asks with different cost, and it'd help @Vivek1106-04 to know which 
one unblocks this PR versus which is really scoped to the #12165 discussion.
   
   @DanielLeens Thank you for your reply
   Real RPC/IO can cause distortion in test scheduling, but existing custom 
thread pools cannot test the engine's true methods. I provide the following 
ideas:
   Add new construction methods to eliminate existing RPC behaviors and mockups 
such as IMAP
   
   Design:
   # Checkpoint Scheduling Benchmark Design
   
   ## Goal
   
   Measure the scheduling delay of the real `CheckpointCoordinator` path 
without starting SeaTunnel or Hazelcast nodes.
   
   The benchmark must not override `tryTriggerPendingCheckpoint`.
   
   ## Measurement Boundary
   
   ```text
   Start:
   scheduleTriggerPendingCheckpoint()
   
   Measured:
   scheduler queueing
   → scheduler wake-up
   → shared dispatch, when applicable
   → tryTriggerPendingCheckpoint()
   → state checks
   → coordinator lock
   → submission of createPendingCheckpoint to executorService
   
   End:
   executorService.execute() receives the checkpoint creation task
   ```
   
   The remaining checkpoint path continues outside the measured region:
   
   ```text
   CheckpointID allocation
   → PendingCheckpoint creation
   → barrier RPC
   → task ACK
   → checkpoint storage
   → IMap state update
   → checkpoint completion
   ```
   
   ## CheckpointCoordinator Constructor
   
   The production constructor delegates to a testing constructor that accepts 
the real scheduler.
   
   ```java
   public CheckpointCoordinator(
           CheckpointManager manager,
           CheckpointStorage checkpointStorage,
           CheckpointConfig checkpointConfig,
           long jobId,
           CheckpointPlan plan,
           CheckpointIDCounter checkpointIdCounter,
           PipelineState pipelineState,
           ExecutorService executorService,
           IMap<Object, Object> runningJobStateIMap,
           boolean isStartWithSavePoint,
           CheckpointMonitorService checkpointMonitorService) {
   
       this(
               manager,
               checkpointStorage,
               checkpointConfig,
               jobId,
               plan,
               checkpointIdCounter,
               pipelineState,
               executorService,
               runningJobStateIMap,
               isStartWithSavePoint,
               checkpointMonitorService,
               manager.leaseCheckpointScheduler(plan.getPipelineId()));
   }
   ```
   
   ```java
   @VisibleForTesting
   protected CheckpointCoordinator(
           CheckpointManager manager,
           CheckpointStorage checkpointStorage,
           CheckpointConfig checkpointConfig,
           long jobId,
           CheckpointPlan plan,
           CheckpointIDCounter checkpointIdCounter,
           PipelineState pipelineState,
           ExecutorService executorService,
           IMap<Object, Object> runningJobStateIMap,
           boolean isStartWithSavePoint,
           CheckpointMonitorService checkpointMonitorService,
           PipelineCheckpointScheduler scheduler) {
   
       // Keep the existing initialization unchanged.
       this.executorService = executorService;
       this.checkpointManager = manager;
       this.checkpointStorage = checkpointStorage;
       this.checkpointIdCounter = checkpointIdCounter;
       this.runningJobStateIMap = runningJobStateIMap;
       this.checkpointMonitorService = checkpointMonitorService;
       this.scheduler = scheduler;
   
       // Initialize the remaining existing fields normally.
   }
   ```
   
   On `dev`, the same testing constructor accepts the real per-pipeline 
`ScheduledExecutorService`. The two revisions are compiled separately by the 
benchmark workflow.
   
   ## Scheduling Probe
   
   Use the constructor-injected `ExecutorService` as the measurement probe.
   
   ```java
   final class ProbingExecutorService extends AbstractExecutorService {
   
       private final ExecutorService delegate;
       private final AtomicReference<CountDownLatch> nextProbe =
               new AtomicReference<>();
   
       ProbingExecutorService(ExecutorService delegate) {
           this.delegate = delegate;
       }
   
       void arm(CountDownLatch probe) {
           nextProbe.set(probe);
       }
   
       @Override
       public void execute(Runnable command) {
           CountDownLatch probe = nextProbe.getAndSet(null);
   
           if (probe != null) {
               probe.countDown();
           }
   
           delegate.execute(command);
       }
   
       // Delegate shutdown and lifecycle methods to delegate.
   }
   ```
   
   The probe is armed only after coordinator initialization and task readiness 
have completed.
   
   ## Benchmark Coordinator
   
   The benchmark subclass only exposes the protected scheduling method.
   
   ```java
   final class BenchmarkCheckpointCoordinator
           extends CheckpointCoordinator {
   
       void scheduleForBenchmark() {
           scheduleTriggerPendingCheckpoint(
                   CheckpointType.CHECKPOINT_TYPE,
                   0L);
       }
   }
   ```
   
   It must not override:
   
   ```java
   tryTriggerPendingCheckpoint(...)
   ```
   
   Therefore all existing checks, locking and pending-checkpoint behavior 
remain active.
   
   ## Fixture
   
   The Fixture uses real scheduling implementations and local in-memory 
replacements for external services.
   
   ```text
   Scheduler                 real
   CheckpointCoordinator     real
   tryTriggerPendingCheckpoint real
   ExecutorService           real delegate with a submission probe
   CheckpointIDCounter       in-memory AtomicLong
   IMap                      ConcurrentHashMap-backed adapter
   CheckpointStorage         in-memory implementation
   RPC                       successful local fake
   Task ACK                  successful local callback
   SeaTunnel nodes           not started
   Hazelcast cluster         not started
   ```
   
   The memory-backed `IMap` must support the operations used by 
`CheckpointCoordinator`:
   
   ```java
   get()
   set()
   compute()
   remove()
   ```
   
   Example:
   
   ```java
   ConcurrentMap<Object, Object> state =
           new ConcurrentHashMap<>();
   
   IMap<Object, Object> imap = mock(IMap.class);
   
   when(imap.get(any()))
           .thenAnswer(invocation ->
                   state.get(invocation.getArgument(0)));
   
   doAnswer(invocation -> {
               state.put(
                       invocation.getArgument(0),
                       invocation.getArgument(1));
               return null;
           })
           .when(imap)
           .set(any(), any());
   
   when(imap.compute(any(), any()))
           .thenAnswer(invocation -> {
               Object key = invocation.getArgument(0);
               BiFunction<Object, Object, Object> function =
                       invocation.getArgument(1);
               return state.compute(key, function);
           });
   
   when(imap.remove(any()))
           .thenAnswer(invocation ->
                   state.remove(invocation.getArgument(0)));
   ```
   
   The RPC fake returns a completed `InvocationFuture`. After a barrier is 
sent, it feeds successful `TaskAcknowledgeOperation` instances back to the 
coordinator so that `pendingCounter` returns to zero.
   
   The Fixture must report all tasks as `READY_START` and wait for 
initialization to finish before arming the scheduling probe.
   
   ## Benchmark Invocation
   
   ```java
   CountDownLatch triggerSubmitted = new CountDownLatch(1);
   
   probingExecutor.arm(triggerSubmitted);
   coordinator.scheduleForBenchmark();
   triggerSubmitted.await();
   ```
   
   After the measured invocation, wait for the mocked ACK and checkpoint 
completion outside the measured region:
   
   ```java
   fixture.awaitCheckpointCompleted();
   ```
   
   This prevents one sample from leaking pending checkpoint state into the next 
sample.
   
   ## Comparison
   
   The existing workflow compiles and runs each revision separately:
   
   ```text
   A1 = dev
   B1 = candidate PR
   B2 = candidate PR
   A2 = dev
   ```
   
   `dev` uses its real per-pipeline scheduler. The candidate PR uses its real 
shared scheduler. Both revisions keep the same Fixture behavior, workload, 
parameters and measurement boundary.
   
   
   @Vivek1106-04 We look forward to your reply


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