Visitar URL original
feat(#9399): expose the follower replication-path and unreachable-leader state in GET /api/v1/cluster by robfrank · Pull Request #9451 · ArcadeData/arcadedb · GitHub
Skip to content

feat(#9399): expose the follower replication-path and unreachable-leader state in GET /api/v1/cluster - #9451

Merged
robfrank merged 4 commits into
mainfrom
feat/9399-ha-follower-replication-path-state
Oct 7, 2026
Merged

robfrank merged 4 commits into
mainfrom
feat/9399-ha-follower-replication-path-state

Conversation

@robfrank

@robfrank robfrank commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #9399
Closes #9013
Closes #8953

Summary

A follower whose Raft layer was restarted in place reports raftState: RUNNING, because the new division is running, even when the leader's appends never reach it (#8898). Before this PR the status document had no machine-readable field for that state. This PR adds two node-level fields to GET /api/v1/cluster and one new alert:

  • localReplicationPathUnproven (HA: expose the held stale-term reformat (dead replication path) as machine-readable state in GET /api/v1/cluster #9013) is true when the node was restarted in place and has taken no replicated entry since, and no newer term with a known leader either. This is the state that makes the health monitor hold the stale-term reformat back (HA: stale-term divergence reformat fires on a follower that has received no append since its restart (dead path, not divergence) #8901). The follower-stuck-at-stale-term alert now carries details.replicationPathUnproven, and its recommendation depends on it: "restart this node by hand" when it is true, the self-heal text when it is false. Dashboards can now tell "held" from "will self-heal" without parsing prose.
  • localLeaderUnreachableSinceRestart (HA: a follower the leader cannot reach after an in-place Ratis restart still reports raftState=RUNNING (follow-up of #8900) #8953) is the incident form of the same state. It is set when the path is still unproven while either no leader has made itself known, or the leader reports a commit index past every entry this node holds, and that lasts longer than 2x the effective election timeout max. The health monitor tracks it on every tick with the new HealthTarget.trackLeaderReachSinceRestart() hook, placed next to trackFollowerStall() so it runs before the early returns. The node logs a WARNING when the condition is first reported and an INFO when it clears. The condition raises the new alert follower-leader-unreachable-since-restart. It is critical when a known leader reports entries this node does not hold, which is direct evidence that its appends are not arriving. It is only a warning when no leader is known, because every node of a cluster that has lost its quorum shows that too. In that case the recommendation first tells you to check the quorum. details.leaderKnown and details.unreachableForMs carry the same information as fields.
  • Alert precedence: each condition raises one alert, and the most specific cause wins. stuck-at-stale-term suppresses leader-unreachable-since-restart, and that one suppresses stalled-behind-leader. Both new fields are declared as required in the OpenAPI ClusterStatus schema.

Why not leaderContactElapsedMs / lastRpcElapsedTimeMs (the signal #8953 suggested first)

I read Ratis 3.3 FollowerState.runImpl() and LeaderElection.askForVotes(). A follower that no leader reaches becomes a CANDIDATE after one election timeout. Its pre-vote is rejected, and server.rejected() turns it back into a follower with a new FollowerState whose lastRpcTime starts at creation. So the figure never grows past one election timeout, and while the node is a candidate it reads -1. RaftHAServer.isReadyForTraffic() also notes that this signal was tried and removed in PR #7605 because a granted PRE_VOTE refreshes it. That is why the new signal uses the in-place-restart baseline (#8901) plus the leader-reported commit index (#7619), which arrives over a follower-to-leader call that does not depend on the broken channel.

Why the field is not just localReplicationPathUnproven

On an idle cluster a healthy restarted follower receives heartbeats but no entries, so the path stays "unproven" for as long as nobody writes. That is fine as a qualifier on the stale-term alert. Raising an alert on it alone would be a false positive. Issue8901ReplicationPathAfterInPlaceRestartIT now checks this against real Ratis: after the restart the path is unproven, the reach hooks run for longer than the grace, and localLeaderUnreachableSinceRestart stays false.

Finding ledger

# Finding (from the issue) Status
1 #9013: plumb isReplicationPathUnprovenSinceRestart() into GetClusterHandler and add a replicationPathUnproven detail to the stale-term alert Fixed: details.replicationPathUnproven plus a recommendation keyed on it
2 #9013: a matching node-level field Fixed: localReplicationPathUnproven
3 #8953: surface "leader cannot reach this follower after an in-place restart" as a separate field on /api/v1/cluster Fixed: localLeaderUnreachableSinceRestart, plus the follower-leader-unreachable-since-restart alert
4 #8953: and as a health-check signal Fixed in the HealthMonitor sense the issue uses ("the HealthMonitor lifecycle branch cannot see the case"): a debounced, tick-tracked signal with a WARNING log. /api/v1/ready and /api/v1/health are deliberately unchanged, see Known gaps

Coverage

Entry point / path Disposition Test
GetClusterHandler writes localReplicationPathUnproven fixed Issue8901ReplicationPathAfterInPlaceRestartIT (true after the real in-place restart, false after the first entry), GetClusterHandlerIT (false, present on every node)
GetClusterHandler writes localLeaderUnreachableSinceRestart fixed GetClusterHandlerIT, Issue8901ReplicationPathAfterInPlaceRestartIT (idle, no false positive)
ClusterAlerts.addStuckAtStaleTermAlert details and recommendation fixed Issue9399ReplicationPathStateTest.theStaleTermAlertSaysWhetherTheReformatIsHeld, ClusterAlertsTest
New follower-leader-unreachable-since-restart alert and precedence fixed anUnreachableLeaderRaisesACriticalAlertWithItsDuration, oneAlertPerConditionMostSpecificCauseFirst, noUnreachableLeaderNoAlert
RaftHAServer.trackLeaderReachSinceRestart (both arms, grace, clear-on-entry, resync, CLOSED division, never restarted) fixed 7 fixture tests in Issue9399ReplicationPathStateTest
HealthMonitor.tick() calls the hook, including on early-return ticks fixed theHealthTickTracksTheLeaderReachEvenOnATickThatReturnsEarly
OpenAPI ClusterStatus schema fixed PluginApiSpecTest, Issue7577ClusterStatusSchemaMatchesTheHandlerTest.theReplicationPathSignalsAreDeclaredAndRequired
Older ClusterAlerts.scan(...) overloads delegate with false, -1 (no new alert) covered by the existing Issue8342FollowerLocalStallSignalTest

Known gaps:

Test plan

  • mvn -pl ha-raft test -Dtest=Issue9399ReplicationPathStateTest,ClusterAlertsTest,Issue7577ClusterStatusSchemaMatchesTheHandlerTest,Issue8342FollowerLocalStallSignalTest,Issue8901DeadReplicationPathNoReformatTest,HealthMonitorTest,OldRatisServerTerminationTest: 123 tests, 0 failures
  • mvn -pl ha-raft test -DexcludedGroups=benchmark,slow,vector (whole ha-raft unit suite): 2780 tests, 0 failures
  • mvn -pl server test -Dtest=PluginApiSpecTest,HardcodedTestServerPortsTest: 29 tests, 0 failures
  • mvn -pl ha-raft verify -DskipITs=false -Dit.test=Issue8901ReplicationPathAfterInPlaceRestartIT,GetClusterHandlerIT: 7 tests, 0 failures

Adversarial pass

The Phase 1.5 subagent could not be spawned because no subagent tool was available in this run. I did the pass myself instead. That is weaker than an independent reviewer, so I'm noting it here:

  • Stale leaderReportedCommitIndex from before the restart: every value it holds is a commit index some leader really reported. A live path would deliver those entries and prove the path, so a stale value is still valid evidence. Not real.
  • Long snapshot install right after the restart (no new log entry, leader far ahead): the resync is excluded through isResyncInProgress() (snapshotDownloadInProgress is part of LocalResyncState). Not real.
  • A reformat leaves an empty log while the leader's commit is ahead: heldIndex is max(lastLogIndex, appliedIndex), and during the install the resync exclusion applies. Not real.
  • The stale-term alert's replicationPathUnproven is read at request time, while the monitor's hold decision is made at tick time: they can disagree for at most one health tick. The request-time value is the more current of the two. Accepted.

Review cycles

Cycle Head Changes Bot outcome
1 4c6a2e6 initial implementation claude: nothing blocking. Point 1 (a critical alert on every quorum loss) was addressed. CodeRabbit was rate-limited and did not review
2 4410d42 leaderless arm downgraded to warning and carries details.leaderKnown; hard-coded defaults removed from the recommendation; comment on the shared clock claude: nothing blocking. Asked for a test of the reset path
3 c270fb1 test: a leader appearing with nothing new to send clears the leaderless report claude: nothing blocking. Asked to fix this description (done in the Summary above) and to use URI in the IT
4 f6e95a9 IT builds its URL through URI.create(...).toURL() claude: nothing blocking. Asked again to fix this description (done). Max cycles reached, so nothing more was pushed

Deferred items

Skipped or deferred review suggestions, each with its reason:

Final state

max-cycles-reached (4 of 4). No review flagged anything blocking. The open items are the deferred suggestions above.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Cluster status now reports whether replication has resumed after an in-place restart and whether the leader remains unreachable.
    • Added alerts for prolonged leader unreachability, with severity based on whether the leader is known. Stale-term alerts now give guidance based on replication-path status.
  • Bug Fixes
    • Avoids duplicate stalled-follower alerts when a stale-term or leader-unreachable alert already explains the issue.

…der state in GET /api/v1/cluster

- localReplicationPathUnproven (#9013): the in-place-restart state that holds the stale-term reformat back;
  the follower-stuck-at-stale-term alert carries it in details.replicationPathUnproven and picks its
  recommendation from it (held, restart by hand vs. self-heals).
- localLeaderUnreachableSinceRestart (#8953): tracked by the health monitor - path unproven since the
  in-place restart while no leader is known or the leader reports entries this node does not hold, for
  more than 2x the election timeout. New follower-leader-unreachable-since-restart critical alert; the
  stall alert stands down for it.
- OpenAPI ClusterStatus schema declares both fields as required.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@robfrank robfrank added this to the 26.11.1 milestone Oct 7, 2026
@robfrank robfrank added enhancement New feature or request ha High availability / replication (Raft) labels Oct 7, 2026
@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: a939bdd2-b699-4218-b783-7123b72ef2a9
📥 Commits

Reviewing files that changed from the base of the PR and between 3119a4c and f6e95a9.

📒 Files selected for processing (12)
  • ha-raft/src/main/java/com/arcadedb/server/ha/raft/ClusterAlerts.java
  • ha-raft/src/main/java/com/arcadedb/server/ha/raft/GetClusterHandler.java
  • ha-raft/src/main/java/com/arcadedb/server/ha/raft/HealthMonitor.java
  • ha-raft/src/main/java/com/arcadedb/server/ha/raft/LeaderReachSinceRestartTracker.java
  • ha-raft/src/main/java/com/arcadedb/server/ha/raft/RaftHAServer.java
  • ha-raft/src/test/java/com/arcadedb/server/ha/raft/ClusterAlertsTest.java
  • ha-raft/src/test/java/com/arcadedb/server/ha/raft/GetClusterHandlerIT.java
  • ha-raft/src/test/java/com/arcadedb/server/ha/raft/Issue7577ClusterStatusSchemaMatchesTheHandlerTest.java
  • ha-raft/src/test/java/com/arcadedb/server/ha/raft/Issue8901ReplicationPathAfterInPlaceRestartIT.java
  • ha-raft/src/test/java/com/arcadedb/server/ha/raft/Issue9399ReplicationPathStateTest.java
  • server/src/main/java/com/arcadedb/server/http/handler/openapi/PluginApiSpec.java
  • server/src/test/java/com/arcadedb/server/http/handler/openapi/PluginApiSpecTest.java

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 1 remain after this review.


📝 Walkthrough

Walkthrough

The change tracks whether a follower’s replication path is proven after an in-place Raft restart. Cluster status reports replication-path and leader-unreachable state. Cluster alerts use these signals to describe stale-term and unreachable-leader conditions.

Changes

Follower replication-path diagnostics

Layer / File(s) Summary
Track leader reach after restart
ha-raft/src/main/java/com/arcadedb/server/ha/raft/LeaderReachSinceRestartTracker.java, ha-raft/src/main/java/com/arcadedb/server/ha/raft/RaftHAServer.java, ha-raft/src/main/java/com/arcadedb/server/ha/raft/HealthMonitor.java, ha-raft/src/test/java/com/arcadedb/server/ha/raft/Issue9399ReplicationPathStateTest.java
The health monitor invokes leader-reach tracking. The tracker reports an unreachable state after its grace period and resets when its conditions stop applying. Tests cover tracking, timing, and reset cases.
Expose replication state
ha-raft/src/main/java/com/arcadedb/server/ha/raft/GetClusterHandler.java, server/src/main/java/com/arcadedb/server/http/handler/openapi/PluginApiSpec.java, ha-raft/src/test/java/com/arcadedb/server/ha/raft/*, server/src/test/java/com/arcadedb/server/http/handler/openapi/PluginApiSpecTest.java
Cluster status includes localReplicationPathUnproven and localLeaderUnreachableSinceRestart. The OpenAPI schema declares both fields as required. Tests check schema and endpoint values.
Select alerts
ha-raft/src/main/java/com/arcadedb/server/ha/raft/ClusterAlerts.java, ha-raft/src/main/java/com/arcadedb/server/ha/raft/GetClusterHandler.java, ha-raft/src/test/java/com/arcadedb/server/ha/raft/ClusterAlertsTest.java, ha-raft/src/test/java/com/arcadedb/server/ha/raft/Issue9399ReplicationPathStateTest.java
Stale-term alerts include replication-path state and provide different recommendations based on that state. A leader-unreachable alert reports severity and duration. The stalled alert is suppressed when another alert reports the cause.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature · Severity of issue fixed: Low

Sequence Diagram(s)

sequenceDiagram
  participant HealthMonitor
  participant RaftHAServer
  participant GetClusterHandler
  participant ClusterAlerts
  HealthMonitor->>RaftHAServer: Track leader reach after restart
  RaftHAServer-->>GetClusterHandler: Provide replication-path state
  GetClusterHandler->>ClusterAlerts: Scan using replication-path state
  ClusterAlerts-->>GetClusterHandler: Return selected alerts
Loading

Suggested reviewers: lvca

Merge Risk: ⚪ Minimal · up to f6e95

The new cluster-status fields and alerts have no identified merge-blocking issue; proceed with normal checks.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 56 functions across 12 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Active issue #9399 requires machine-readable follower replication-path and unreachable-leader state in GET /api/v1/cluster. GetClusterHandler reports localReplicationPathUnproven and `localLeade…
Out of Scope Changes check ✅ Passed The changed production files implement the two cluster-status fields and their state tracking, alert details, alert precedence, and OpenAPI declarations. The changed tests verify these behaviors. The …
Title check ✅ Passed The title clearly summarizes the primary change: exposing follower replication-path and unreachable-leader state through the cluster API.
Description check ✅ Passed The description provides a detailed summary, motivation, related issues, implementation notes, known gaps, test coverage, and validation results. It uses different headings from the template and does …
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mergify

mergify Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

This pull request does not currently match the merge queue conditions, so it cannot be queued from here. The box comes back if it matches again.

@codacy-production

codacy-production Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 21 complexity

Metric Results
Complexity 21

View in Codacy

🟢 Coverage 87.91% diff coverage · -5.73% coverage variation

Metric Results
Coverage variation ✅ -5.73% coverage variation
Diff coverage ✅ 87.91% diff coverage

View coverage diff in Codacy

Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (3119a4c) 215829 184073 85.29%
Head commit (f6e95a9) 248518 (+32689) 197722 (+13649) 79.56% (-5.73%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#9451) 91 80 87.91%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@claude

claude Bot commented Oct 7, 2026

Copy link
Copy Markdown

Review of PR 9451

I read the main-code diff: LeaderReachSinceRestartTracker, the RaftHAServer hook, the HealthMonitor placement, GetClusterHandler and ClusterAlerts. I skimmed the tests and the OpenAPI changes. I did not compile or run anything.

Overall: the design is clean. The predicate is pure and separate from the stateful tracker, and the hook is defensive (it never propagates, and it resets on any read failure). It sits before the early returns in tick() and has a sensible grace of 2x the election timeout max. The alert precedence is documented, and the new fields are marked required in the schema.

Points to check

  1. Full-cluster restart or no-quorum case. When !leaderKnown and the path is unproven, the predicate returns true. A node that sits through a cluster-wide outage or a long election after an in-place restart will raise a critical alert after about 2x election max. The alert text admits it is "a symptom" of a missing quorum. A critical alert that fires on every lost-quorum event could be noisy for dashboards. Consider whether leaderKnown == false should need a longer grace, or a lower severity. Please also confirm that isReplicationPathUnprovenSinceRestart() is false for nodes that were never restarted in place, so ordinary startup cannot trigger it.
  2. leaderReportedCommitIndex. The hook reads this field from the health thread. Please confirm it is volatile and that a stale or negative value cannot cause a false positive. The predicate guards >= 0, which is good. A short test on the boundary leaderCommitIndex == heldIndex would pin it down.
  3. Clock sharing. The tracker uses followerStallClock, which is named for the stall feature. That is fine as a test seam, but a neutral name, or a comment at the field, would avoid surprise if one is changed independently.
  4. Alert text. The recommendation literal in addLeaderUnreachableSinceRestartAlert has one very long line, which breaks the wrapping used elsewhere. It also hard-codes the defaults (60s, escalation). Those can drift from GlobalConfiguration, so consider building the text from the config values or dropping the numbers.
  5. Flapping. observe resets on any single false tick, so the spell restarts after one good tick. Please make sure a test covers a single false tick resetting the spell.

Tests

Coverage looks reasonable: a unit test for the tracker and path state, ClusterAlertsTest, a schema-matches-handler test, and the #8901 IT. Please confirm there is a test for each of the following:

  • alert precedence (stale-term suppresses unreachable, which suppresses stalled)
  • the leader-masked -1 path in GetClusterHandler
  • isResyncInProgress resetting the tracker

Per CLAUDE.md, check that none of the new tests assert on raw wall-clock time. They should use the injected clock or StallAwareStopwatch.

Minor

  • sinceMs is non-volatile, which is fine given the single-writer comment.
  • No new dependencies, no security concerns, and no hot-path cost: the work is one cheap call per health tick.

Nothing blocking from my side; point 1 is the one I would settle before merging.

…arning, drop hard-coded defaults, note the shared clock

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Oct 7, 2026

Copy link
Copy Markdown

Review of #9451

Overall this is a solid, well-documented change. It adds two node-level fields (localReplicationPathUnproven, localLeaderUnreachableSinceRestart) and one new alert, with clear alert precedence. I read the main-code diff: ClusterAlerts, GetClusterHandler, HealthMonitor, LeaderReachSinceRestartTracker and the RaftHAServer hook. I did not read the test diffs in detail and did not compile or run anything.

Good

  • LeaderReachSinceRestartTracker keeps the predicate pure and the state small. The single volatile snapshot is a safe publication for the HTTP readers, and only the health thread writes.
  • The hook is placed beside trackFollowerStall(), ahead of the early returns, as the description says. Every exception path resets the tracker, so a Ratis transient can't leave a stale "unreachable" report.
  • Alert precedence (stale-term, then leader-unreachable, then stalled) avoids duplicate critical alerts for one condition. Dropping severity to warning when no leader is known avoids noise during a quorum loss.
  • details.leaderKnown and replicationPathUnproven let consumers branch on fields instead of prose.

Suggestions (minor)

  1. ClusterAlerts.scan now has three overloads that each delegate to the next, with an ever-longer positional parameter list (12 args, several of them booleans and nullable trackers). The next addition will make this worse. Consider a small parameter record, or an options object, for the node-local state (stuckAtStaleTerm, replicationPathUnproven, stalledBehindLeader, leaderUnreachable).
  2. leaderReachTracker shares followerStallClock on purpose, and the comment says so. A dedicated field or a rename (e.g. followerTrackingClock) would be less surprising to the next reader, and wouldn't change tests that already set the clock.
  3. In trackLeaderReachSinceRestart the catch is Exception, which also swallows unexpected bugs at FINE. That matches isReadyForTraffic(), but a narrower catch (IllegalStateException) would stop genuine defects from being hidden.
  4. observe() and reset() assume a single writer, and sinceMs is non-volatile. That is fine today and documented. A @VisibleForTesting note, or an assertion in tests, would protect against a future second caller.
  5. The no-leader-known arm is true for any unproven node on a quorum-less cluster. That is acknowledged as a warning. It would help to confirm in a test that the alert clears right away when a leader appears and the path is proven (the reset path), if that isn't already covered.
  6. The ClusterStatus OpenAPI schema now requires both fields. Check that no other consumer (Studio, the other HA status clients, the docs) assumes the old shape.

Tests
Unit and IT coverage is present for the alerts, the handler and the schema parity. Please confirm that there is a tracker unit test for the grace boundary (elapsed == graceMs) and for flapping (hold, break, hold restarts the clock). Also confirm that the clock-based tests don't assert on raw wall time, per CLAUDE.md.

Nothing blocking from me.

… clears the leaderless report

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Oct 7, 2026

Copy link
Copy Markdown

Review of PR #9451 (diff read only, tests not run). Well-scoped and well-documented; nothing blocking. Items worth a look: (1) the PR description says the new alert is critical, but the code raises critical only when a leader is known and warning when none is, so update the description. (2) On the leaderless arm, localLeaderUnreachableSinceRestart is true on every in-place-restarted node during quorum loss while the alert is only a warning; consider keeping the boolean false there or logging at INFO. (3) leaderReachTracker shares followerStallClock; a neutral name would be less surprising. (4) Note in the tracker Javadoc that observe/reset are single-threaded (sinceMs is a plain long). (5) The two stale-term recommendation strings duplicate much prose; consider shared constants. (6) The IT uses deprecated new URL(String); prefer URI.create(...).toURL() or an existing fixture HTTP helper. (7) Confirm HardcodedTestServerPortsTest stays green with SERVER_LIST in Issue9399ReplicationPathStateTest. (8) The IT adds about grace+2s of waiting; consider @tag("slow") if the grace is large. Positives: thorough tests (predicate, tracker, both arms, grace, resync, closed division, precedence, OpenAPI, real-Ratis idle IT), negligible hot-path cost, no new deps, no new security surface. Items 1 and 2 are the ones I would address before merge.

🤖 Generated with Claude Code

…deprecated URL constructor

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Oct 7, 2026

Copy link
Copy Markdown

Review

I read the diff only. I did not build the PR or run its tests. The design is clean: the tracker is a small pure class with an injectable clock, alert precedence is explicit, and the OpenAPI schema is updated. Findings:

1. PR description and code disagree on severity. The description calls the new alert "critical", but addLeaderUnreachableSinceRestartAlert raises critical only when leaderKnown is true, and warning otherwise. Please update the description, since anyone alerting on "critical" will miss the no-leader arm.

2. The no-leader arm can fire on healthy or idle nodes. leaderNotReaching returns true whenever the path is unproven and no leader is known, with no evidence about this node's own path. The warning severity and the quorum-loss hint soften this, but localLeaderUnreachableSinceRestart is a bare boolean that is true on both arms. Consumers reading only that field cannot tell direct evidence from a symptom of quorum loss. Consider exposing the leader-known flag at node level, or documenting that the alert details must be read.

3. Staleness of leaderReportedCommitIndex. Please confirm it is reset on a leader change or in-place restart. Otherwise a value from a previous leader or term could cause a false positive for up to the grace period (2x election timeout max). A unit test with a stale index would pin this down.

4. Thread-safety is fine but fragile. sinceMs is a plain field written only by the health-monitor thread, which is correct today. If reset() is later called from elsewhere, it becomes a race. Making it volatile costs nothing, or add a comment.

5. Test coverage. The tracker predicate and grace, alert precedence, schema required fields, and the idle/clearing IT are covered. Missing is an end-to-end test where the alert actually raises with localLeaderUnreachableSinceRestart true. The IT only proves the negative. The Awaitility.during(grace + 2s) adds fixed wall-clock time, so consider @Tag("slow") if the grace is large.

6. Minor. The WARNING log text is long. Consider a one-line log and moving the guidance into docs.

None of these look blocking. Item 1 is a doc fix, and items 3 and 5 are worth a quick check before merge.

@codecov

codecov Bot commented Oct 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.51648% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.04%. Comparing base (3119a4c) to head (f6e95a9).
⚠️ Report is 14 commits behind head on main.

Files with missing lines Patch % Lines
...java/com/arcadedb/server/ha/raft/RaftHAServer.java 71.42% 6 Missing and 4 partials ⚠️
...com/arcadedb/server/ha/raft/GetClusterHandler.java 0.00% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff            @@
##               main    #9451   +/-   ##
=========================================
  Coverage     77.04%   77.04%           
- Complexity     4004     4046   +42     
=========================================
  Files          2131     2132    +1     
  Lines        215829   215914   +85     
  Branches      46026    46045   +19     
=========================================
+ Hits         166278   166349   +71     
- Misses        31795    31804    +9     
- Partials      17756    17761    +5     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request ha High availability / replication (Raft)

Projects

None yet

1 participant