Skip to content

PHOENIX-7978 Harden replay/forward poll scheduling against wall-clock… - #2598

Open
Himanshu-g81 wants to merge 1 commit into
apache:PHOENIX-7562-feature-newfrom
Himanshu-g81:PHOENIX-7978
Open

PHOENIX-7978 Harden replay/forward poll scheduling against wall-clock…#2598
Himanshu-g81 wants to merge 1 commit into
apache:PHOENIX-7562-feature-newfrom
Himanshu-g81:PHOENIX-7978

Conversation

@Himanshu-g81

Copy link
Copy Markdown
Contributor

Problem

The replay/forward round-eligibility gate is evaluated on the wall clock, but PHOENIX-7813
aligned the scheduler wake — fired on the monotonic clock (System.nanoTime) — to that boundary
with zero margin. Small nanoTime-vs-wall-clock drift can tip a wake just below the boundary,
so the round isn't yet eligible and the region server loses a full (~60s) cycle. Most damaging
during planned failover.

Fix

Both changes live in the shared base class ReplicationLogDiscovery (inherited by
ReplicationLogDiscoveryReplay and ReplicationLogDiscoveryForwarder):

  1. Epsilon margin so the aligned wake lands just after the eligibility boundary rather than
    exactly on it. New config phoenix.replication.discovery.aligned.delay.epsilon.millis (default 500).
  2. Per-cycle re-anchor: replace scheduleAtFixedRate with a self-rescheduling one-shot that
    recomputes the aligned delay every cycle, so drift can't accumulate. Each cycle is bound to its
    scheduler generation to avoid double-scheduling after a stop()start() restart.

… and scheduler drift

The round-eligibility gate for replication replay/forward becomes eligible when
currentTime - lastRoundEndTimestamp >= roundTimeMills + bufferMillis, evaluated on the
wall clock. PHOENIX-7813 aligned the scheduler wake to that grid, but the wake is fired on
the monotonic clock (System.nanoTime) and was computed with zero margin, so small
nanoTime-vs-wall-clock drift could tip a wake just below the boundary and the region server
would lose a full (~60s) cycle. Most damaging during planned failover.

Two fixes, both in the shared base class ReplicationLogDiscovery (inherited by
ReplicationLogDiscoveryReplay and ReplicationLogDiscoveryForwarder):

- Epsilon margin on the aligned wake instant: anchor the delay at bufferMillis + epsilon
  (via Math.floorMod) so the wake lands just after the eligibility boundary rather than
  exactly on it. New config phoenix.replication.discovery.aligned.delay.epsilon.millis
  (default 500).

- Per-cycle re-anchor: replace scheduleAtFixedRate with a self-rescheduling one-shot chain
  that recomputes the aligned delay every cycle, re-pinning each wake to the wall-clock grid
  instead of letting a one-time misalignment persist. Uses a ScheduledThreadPoolExecutor with
  setExecuteExistingDelayedTasksAfterShutdownPolicy(false) so stop() is deterministic.

Each replay cycle is bound to the scheduler generation it was launched on and reschedules
only if isRunning && owner == scheduler, preventing a stale in-flight cycle from grafting a
second chain onto a new scheduler after a stop()->start() restart (which would otherwise
double the effective poll rate).

Testing: ReplicationLogDiscoveryTest 48/48 (incl. stale-generation, start()-rollback, and
replay/reschedule error-swallow regressions); ReplicationLogDiscoveryReplayTestIT 48/48;
StoreAndForwardFailoverIT 1/1; spotless:check green on phoenix-core and phoenix-core-server.
@Himanshu-g81
Himanshu-g81 marked this pull request as ready for review August 13, 2026 12:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants