Skip to content

fix: stop consensus_is_ready from carrying a stale node_mode label#2544

Open
Thiago316316 wants to merge 2 commits into
cloudwalk:mainfrom
Thiago316316:fix/consensus-is-ready-stale-label
Open

fix: stop consensus_is_ready from carrying a stale node_mode label#2544
Thiago316316 wants to merge 2 commits into
cloudwalk:mainfrom
Thiago316316:fix/consensus-is-ready-stale-label

Conversation

@Thiago316316

Copy link
Copy Markdown

Hi @carneiro-cw I first tried the same idea as #2123 (129ff91) — reset stale state right at the mode-flip point — but applied to the metric value instead of a cache. That zeroed the value but didn't remove the duplicate series itself, since Prometheus doesn't garbage-collect a label combination. The final one involved more layers of the system but was a proper correction, i think.

the fix works in two steps:
1° i added a new gauge/leg on metrics_impl_fn_inc to remove the automatic node mode labeling, with the new leg that didin't pass this, the metric consensus_is_ready (a health reading), just emit 0 or 1 depending on the readiness/health.
2° the responsability to show wich role the node has now is of node_mode{mode} (declared right after it in the source) and the process happens on the update_health, that iterates over all 3 modes and the one that correspond to the currently state get the 1, the rest are zeroed.
Plus: i put ctx.server.update_health().await; on every mode/role swap, so the info about the leader is more recent as possible.

manual test:

:~/projects/stratus$ curl -s http://localhost:9001/metrics | grep '^stratus_consensus_is_ready'
curl -s http://localhost:9001/metrics | grep '^stratus_node_mode'
stratus_consensus_is_ready{service="stratus-stratus",version="fix/consensus-is-ready-stale-label::4df4e3c4",group="consensus"} 1
stratus_node_mode{service="stratus-stratus",version="fix/consensus-is-ready-stale-label::4df4e3c4",group="consensus",mode="leader"} 0
stratus_node_mode{service="stratus-stratus",version="fix/consensus-is-ready-stale-label::4df4e3c4",group="consensus",mode="fake-leader"} 0
stratus_node_mode{service="stratus-stratus",version="fix/consensus-is-ready-stale-label::4df4e3c4",group="consensus",mode="follower"} 1


:~/projects/stratus$ curl -s http://localhost:9000/metrics | grep '^stratus_consensus_is_ready'
curl -s http://localhost:9000/metrics | grep '^stratus_node_mode'
stratus_consensus_is_ready{service="stratus-stratus",version="fix/consensus-is-ready-stale-label::4df4e3c4",group="consensus"} 1
stratus_node_mode{service="stratus-stratus",version="fix/consensus-is-ready-stale-label::4df4e3c4",group="consensus",mode="fake-leader"} 0
stratus_node_mode{service="stratus-stratus",version="fix/consensus-is-ready-stale-label::4df4e3c4",group="consensus",mode="follower"} 0
stratus_node_mode{service="stratus-stratus",version="fix/consensus-is-ready-stale-label::4df4e3c4",group="consensus",mode="leader"} 1
:~/projects/stratus$
:~/projects/stratus$
:~/projects/stratus$

:~/projects/stratus$ cast rpc --rpc-url http://localhost:3001 stratus_disableTransactions
cast rpc --rpc-url http://localhost:3001 stratus_changeToLeader
false
true

:~/projects/stratus$ cast rpc --rpc-url http://localhost:3000 stratus_disableTransactions
cast rpc --rpc-url http://localhost:3000 stratus_changeToFollower \
  '"http://localhost:3001/"' '"ws://localhost:3001/"' '"2s"' '"100ms"' '"10485760"'
false
true
:~/projects/stratus$
:~/projects/stratus$

:~/projects/stratus$ curl -s http://localhost:9001/metrics | grep '^stratus_consensus_is_ready'
curl -s http://localhost:9001/metrics | grep '^stratus_node_mode'
stratus_consensus_is_ready{service="stratus-stratus",version="fix/consensus-is-ready-stale-label::4df4e3c4",group="consensus"} 1
stratus_node_mode{service="stratus-stratus",version="fix/consensus-is-ready-stale-label::4df4e3c4",group="consensus",mode="leader"} 1
stratus_node_mode{service="stratus-stratus",version="fix/consensus-is-ready-stale-label::4df4e3c4",group="consensus",mode="fake-leader"} 0
stratus_node_mode{service="stratus-stratus",version="fix/consensus-is-ready-stale-label::4df4e3c4",group="consensus",mode="follower"} 0
:~/projects/stratus$
:~/projects/stratus$

:~/projects/stratus$ curl -s http://localhost:9000/metrics | grep '^stratus_consensus_is_ready'
curl -s http://localhost:9000/metrics | grep '^stratus_node_mode'
stratus_consensus_is_ready{service="stratus-stratus",version="fix/consensus-is-ready-stale-label::4df4e3c4",group="consensus"} 0
stratus_node_mode{service="stratus-stratus",version="fix/consensus-is-ready-stale-label::4df4e3c4",group="consensus",mode="fake-leader"} 0
stratus_node_mode{service="stratus-stratus",version="fix/consensus-is-ready-stale-label::4df4e3c4",group="consensus",mode="leader"} 0
stratus_node_mode{service="stratus-stratus",version="fix/consensus-is-ready-stale-label::4df4e3c4",group="consensus",mode="follower"} 1
:~/projects/stratus$
>
...

Fixes #2529

node_mode was injected as an implicit label on every metric (including
consensus_is_ready) by the generic gauge/counter/histogram codegen, read
fresh from global state at call time. Since a node is only ever in one
mode, labeling a singleton reading by a mutable dimension meant every
leader/follower swap created a brand-new Prometheus series while leaving
the old one registered forever (metrics-exporter-prometheus never expires
a series without an explicit idle_timeout, which isn't configured here) -
so consensus_is_ready ended up reporting both leader and follower labels
simultaneously after any swap.

Add a gauge_no_auto_label metric kind that skips the automatic node_mode
label, and use it for consensus_is_ready so it's structurally a single
series. Node mode itself is now exposed as its own dedicated
stratus_node_mode gauge, refreshed for all known modes together on every
health check tick so no per-mode series can go stale independently.

Fixes cloudwalk#2529
@Thiago316316
Thiago316316 requested a review from a team as a code owner July 15, 2026 03:09

@cloudwalk-review-agent cloudwalk-review-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice fix — this addresses the stale-series root cause in a concrete way.

What I validated

  • consensus_is_ready no longer carries the auto node_mode label (gauge_no_auto_label path), preventing mode-flip label fanout for this health metric.
  • A dedicated node_mode{mode} gauge is introduced and explicitly maintained as one-hot in update_health().
  • Metric type registration was updated so gauge_no_auto_label is described correctly (metrics_types.rs).
  • Health/role metric refresh is triggered immediately on mode transitions (leader/follower paths), reducing observability lag.

Risk check

No issues found in the diff related to auth, permissions, money movement, migration/deploy safety, or API contract regressions. Changes are scoped to metrics emission behavior and appear internally consistent.

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.

stratus_consensus_is_ready metric is sending both leader and follower labels for leader and primary-follower

1 participant