Experimental triggerNextLedger timer Change#4865
Conversation
6af4b3f to
99cf3aa
Compare
99cf3aa to
24db760
Compare
24db760 to
52b7601
Compare
538021b to
72a3614
Compare
7a0d9bc to
226b1e7
Compare
226b1e7 to
fbbbdc4
Compare
785022c to
b17483d
Compare
Key findingsOverall, the change works as expected. When clocks are mostly synced and most nodes enable the new timer, We do see that nomination timeouts and nomination timing overall increase fairly significantly when clocks However, as clocks become unsynced, we do see a significant increase in timeouts and nomination timing. Importantly, while performance does decrease, the network does not get wedged, and degrades back to the We actually see the peak decrease in performance at around 6 seconds of absolute clock drift. This seems to I also tested with a mixed network, where some nodes did not have the flag enabled while others did. Block time gradually decreases This seems to indicate that this feature is fine as a non-protocol upgrade. Given that the network can proceed with Setup
The general idea is to try to mimic pubnet as close as possible, without spinning up hundreds of Changes to MetricsOur traditional ledger.age.current-seconds benchmark is not accurate in determining the actual network We've also added meta info to node names. pX and mY indicate the node has a drifting Test ResultsFirst, we want to analyze the change itself, where all nodes have the new timer change. We will then All the following results have the experimental timer disabled on the left, and then enabled on all nodes No Drifthttps://grafana.stellar-ops.com/goto/bGk5BZTDg?orgId=1 Block TimeWe see a similar block time to pubnet in our control of 5.7s. With the experimental timer, this drops to 5.01 seconds. Nomination TimeoutsWe see an increase in timeouts with the new timer. Nomination p75We see an increase in nomination timing with the timer change:
Compared to the control group:
Nomination p99We see an increase again with the timer change:
Compared to the control group:
2 Second Absolute DriftAll nodes given a random drift uniformly selected from [-1000,+1000] ms. https://grafana.stellar-ops.com/goto/ttwsLWTvg?orgId=1 Block TimeWe see little change compared to synchronized clocks. Nomination TimeoutsElevated nomination timeouts observed in both experimental and non-experimental runs. Nomination p75We see a significant increase in nomination time in the experimental timer, compared to
Compared to no experimental flags
Nomination p99
Compared to no experimental flags
6 Second Absolute DriftAll nodes given a random drift uniformly selected from [-3000,+3000] ms. https://grafana.stellar-ops.com/goto/3w0a6Kovg?orgId=1 Block TimeAt this point, we see higher variance in the experimental timer and slower blocks overall, but still faster than Nomination TimeoutsWe see a significant increase in timeouts compared to more synced clocks. Nomination p75Much higher nomination times as well, with the new timer:
Compared to control:
Nomination p99Experimental Timer:
Compared to control:
20 Second Absolute DriftAll nodes given a random drift uniformly selected from [-10,+10] seconds. At this point, https://grafana.stellar-ops.com/goto/I05-W5ovg?orgId=1 Block TimeBasically the same as the control block time with relatively low variance. Nomination TimeoutsStill significantly greater than the control, but less than the 6 second absolute drift case. Nomination p75Still larger than the control, but much improved from the 6 second case. With trigger timer:
vs baseline:
Nomination p99
vs baseline:
Extreme bimodal distributionOriginally what I expected to be a worst case stress test. 25% of nodes with minor drift (within 1 second), My laptop died between runs, so they are on separate graphs. Baseline, Block TimeFunctionally equivalent. Experimental timer: Baseline: Nomination TimeoutsExperimental timer: Baseline: Nomination p75Experimental:
Baseline:
Nomination p99Experimental:
Baseline:
Network with mix of experimental flag and non-experimental flagFor this test, we used a moderate clock drift of +- 1 second across all nodes. We then ran several This grafana board shows several Block TimeWe see blocktime decrease gradually as more timers are enabled, but with higher variance in mixed networks. At around NominationWhenever a node enables the experimental flag, it's nomination timeouts and timing increase based on how much of the During this in-between phase, only nodes with the new timer seem affected. Even when most of the nodes have upgraded their Timeouts Nomination Timing Different topologiesMost testing was with topology 1, as this is the "closest" 100 node approximation to pubnet, where tier 1 is moderately While other tests were run at 250 TPS, both the control and experimental flag failed at this load, so we reduced to 150 TPS. Worst case topology, no drifthttps://grafana.stellar-ops.com/goto/U5eoCpTvg?orgId=1 We tested the worst case topology with "realistic" ntp drift (+- 100ms). Nomination timeouts and timing were increased, more so than in topology Worst case topology, worst case driftWith topology 3 and the worst drift of 6 seconds absolute, we see high https://grafana.stellar-ops.com/goto/ja6WR2oDg?orgId=1 Max TPSWhile not the primary motivation of this change, the max TPS test gives us some It looks like the experimental timer has a small increase in overall max tps, Here is the max TPS test with the experimental timer |
There was a problem hiding this comment.
Pull request overview
Adds an experimental mode to anchor HerderImpl’s triggerNextLedger scheduling off the last externalized SCP close time (with drift/availability fallbacks), plus test-only knobs to simulate clock drift and slow nomination message emission.
Changes:
- Add
EXPERIMENTAL_TRIGGER_TIMERand implement consensus-close-time-based trigger anchoring with fallback/metrics. - Add test-only support for simulated system clock drift and delayed nomination emit to exercise timeout/drift behavior.
- Add a new SCP trigger fallback meter and a (hidden) herder simulation test covering drift and long nomination scenarios.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/util/Timer.h | Adds test-only system clock offset state + new actual_and_fake_system_now() API. |
| src/util/Timer.cpp | Implements drifted system_now() via injected offset and exposes paired sampling helper. |
| src/test/test.cpp | Enables the experimental trigger timer in the default test config. |
| src/scp/Slot.h | Adds a test-only SCP timer ID for delayed nomination emission. |
| src/scp/SCPDriver.h | Adds a test-only virtual hook for configuring nomination emit delay. |
| src/scp/NominationProtocol.cpp | Defers nomination broadcast in tests via a new driver timer when configured. |
| src/main/Config.h | Adds EXPERIMENTAL_TRIGGER_TIMER and two new test-only timing knobs. |
| src/main/Config.cpp | Initializes/parses new config options; extends testing-only option list. |
| src/main/ApplicationImpl.cpp | Applies configured test-only system clock offset at startup via VirtualClock. |
| src/herder/test/HerderTests.cpp | Adds a (hidden) simulation test for experimental trigger behavior under drift/slow nomination. |
| src/herder/HerderSCPDriver.h | Declares test-only nomination emit delay accessor; exposes nomination timeout count getter. |
| src/herder/HerderSCPDriver.cpp | Implements test-only nomination emit delay accessor and special-cases emit timer callback behavior. |
| src/herder/HerderImpl.h | Declares new trigger anchor helper methods and adds a fallback meter to SCP metrics. |
| src/herder/HerderImpl.cpp | Implements consensus-close-time anchoring logic, fallback conditions, and new metrics wiring. |
| docs/metrics.md | Documents the new scp.trigger.prepare-start-fallback meter. |
There seems to be a consistent increase in nomination time and timeouts across different measurements with the timer change. How can we reason about those? That doesn't seem expected, does it? |
Based on my tests, I see two different classes of increase: a small increase when clocks are synced, and a much larger increase under drifting clocks. In the first case (see no drift above), I think the increase is more of an artifact of decreased block time. We've shaved 700 ms off of block time, which means we have less buffer for slow nodes. With the old timer, because we started blocks late, we have more time for slow nodes to catch up before starting nomination. We tested at a fairly high TPS for this topology/pod config, so nodes were stressed. In addition to faster blocks, high latency nodes are much more likely to have more timeouts/higher nomination times because they trigger faster. Suppose we have a node N whose blocking set has latency L relative to each other, but N has latency 2L with its blocking set. Previously, node N starts nomination about L time behind the rest of it's blocking set. This is because the current timer is based on local ballot state. N will take 2L time to get the messages required to enter this phase, while the rest of the blocking set only takes L time. The nomination time of N appears smaller, since it started the timer later. With this change, all nodes start the block timer at the same time, regardless of latency. This results in N starting nomination faster, but actually taking more time to complete nomination since it still has 2L latency with it's peers. The second case, where clocks are not synced, shows much larger nomination changes. I think this is because some nodes start nomination so early/so late that it's impossible to make progress in that round. For example, suppose a single node is 2 seconds ahead of it's blocking set. That node will have up to 2 whole seconds of attempting nomination where none of it's quorum has begun nomination. Wrt the second case, my main priority is to ensure that the network remains relatively live in this scenario. Synchronized clocks are an assumption of basically every performance orientated L1 these days, and most node operator instructions include instructions for running NTP sync (which we need to give to tier 1). We should also warn more aggressively when clocks are out of sync to encourage good clock behavior from tier 1s. I don't think we should expect the network to maintain performance is this case (we can't), but we need to make sure that we don't overload or snowball the network via timeout load and are resistant to byzantine or incompetent validators. |
|
based on your findings above, would it make sense to first land the work around fully connected tier-1 that will place all validators within at most 2 hops? |
It would definitely decrease nomination load with this change. One test I'm curious about though is if the nomination changes are more attributable to the timer change or the blocktime change. For desynced clocks, it's definitely caused by the timer. For in-sync or lightly desynced clocks, I wonder if it's more the timer or blocktime. I can run a test where I lower the target blocktime on the control timer to around 4.2s so we can try to get an average around 5 seconds to compare. All that to say, the network will definitely perform better with the trigger timer + topology change, but I don't know if we're blocked on the topology change or not. I think it's likely we see increased nomination timeouts with this change regardless, given that we are increasing the TPS by ~15%. We definitely need to get clocks synced before rollout, but let me run a few more tests wrt topology. The relevant questions seem to be:
|
b17483d to
bc66a97
Compare
bc66a97 to
ec7bb7c
Compare
|
I've run a few more tests, comparing both timers with the same ledger close time. To do this I artificially lowed the target close time to ~4.25 seconds on the old timer such that when it overshot it averages around 5 seconds. I ran the test with "realistically" synced clocks (+- 100ms drift per node) on topology 3, our most realistic topology, and topolgy 2, our worst case topology. TL;DR the results are basically the same, the new timer still demonstrates higher nomination timings. Here's a brief summary of the results: Realistic 100 node topologyRun on the left is with the timer change, run on the right is the control with artificially low close time: https://grafana.stellar-ops.com/goto/dfnaoqoh9vmdca?orgId=1 We see virtually no timeouts in each case. Both nodes held a fairly steady close time around 5 seconds. p75 nomination times:
p99 nomination times:
Worst case 100 node topologyI accidentally reversed these, so the left is the control without the chance, and the right has the change: https://grafana.stellar-ops.com/goto/dfnapb24cs7b4a?orgId=1 We see slightly higher levels of nomination timeouts across both nodes, but still virtually none. The old timer had more variance with close time, but still had an average around 5.06 seconds. p75 nomination times:
p99 nomination times:
TakeawayIt looks like the increase in nomination time is intrinsic to the timer change, not a side effect of reducing block times. It appears that the new timer trades variance in nomination timing for stability in block time, vs. our old timer, which has more variance in block time for stable nomination periods. This is why we can't just keep the old timer but artificially reduce the target close time, as there is more instability of block times with load. I think this nomination increase has to do with latency, given that each node keeps a local nomination clock with the old timer, vs a global timer with the new change. If we consider a lagging node, that is generally 300 ms behind the network, it enters ballot phase 300 ms behind it's peers. This means it starts it's nomination clock 300 ms after its peers. While from an absolute perspective this node will finish nomination later due to the latency, the timer itself started later, so it's not perceivable via this metrics. Now, we have a global starting point, such that both the fast nodes and lagging node start their nomination timers at the same time. This makes the nomination metric larger, since the start of the timer is no longer offset by latency (aka time to start ballot phase compared to its peers). This goes both ways, as the fast nodes also experience longer nomination times whenever the slow node is leader (though the overall impact is less). |
ec7bb7c to
dd8b84b
Compare
|
A few more simulation runs. This time, we used the most recent pubnet survey data trimmed to 277 nodes. We ran mixed loadgen mode with SAC soroban load. First two runs with current network limits, the last 2 with proposed network limits for 200 SAC TPS. The old timer is on the left, the new trigger timer on the right. This simulation showed similar results to previous ones, namely that block times decrease but nomination times increase. We've concluded that this is not a "real" increase in terms of wall clock time of nomination, but rather an artifact of changing when we start the nomination timer. This is still important, as this can trigger more timeouts. However, we've also realized that nomination is not uniformly increased, but increased just for high latency watcher nodes, whose topology is many hops away from tier 1. I've added a new panel in the link above that shows nomination times for named nodes (so tier 1 plus folks like sl8), and we actually see that nomination timings are almost identical across both timers. This is also reinforced by timeout values, which remain the same across both timers. The reason for this is that the current nomination timer is not a true measure of how long nomination really takes. With the old timer, each node triggers at their local prepare_start + 5 seconds. This means that each node starts their tirgger, and therefore nomination timer, at different times. Consider a tightly connected tier 1, where most nodes are directly connected, and a single distant node, who is 2 hops away from the rest of tier 1. Each hop has latency L. At a minimum, the distant node will start prepare at least L time after the rest of it's quorum, so it's nomination timer will be started at least L after it's peers. The key observation is that the rest of its peers start nomination and have messages in-flight (and potentially even queued in the slow node) before the slow node actually enters nomination time. This means that with the current timer, slow nodes start nomination later and "cheat", since nomination messages are in flight before they actually start their timer. Note that for the tightly connected nodes, this is less of an issue. They have the same latency relative to each other, so they enter prepare and start their nomination timer at approximately the same time. These fast nodes don't cheat and don't have any messages in flight since they start nomination at the same time as most of their quorum. The new timer change shifts this such that everyone starts the trigger timer and starts nomination at the same time, regardless of distance from tier 1. This means that the long tail of distant watcher nodes will have increased nomination times and potential for increased timeouts. However, so long as tier 1 is relatively densely connected, the fast nodes will not see a nomination time increase. To answer @MonsieurNicolas question on fully connected tier 1, this would give us the safest rollout possible, and is something we should definitely do longer term with this timer change. However, it does not seem necessary with the current topology for the initial roll out. The most recent network survey data from earlier this month shows no nomination issues with tier 1, so it looks like the network is currently naturally dense enough to avoid issues. Given that we need the timer change for the new SLP, and we haven't really started working on fully connected tier 1, the risk seems low if we enable it after the timer change. |
|
I think the critical realization here is that we're not seeing "slower nomination" per se, but rather old timer masking some of the nomination latency as "idle time waiting to trigger". Because we currently trigger pessimistically, it actually offsets some of the total nomination time (specifically, extra latency for the first nomination message to reach a node). With trigger timer change, we're getting a more accurate measurement of nomination latency. The down side of this is that latency is actually higher than what we report on pubnet right now, and this directly affects timeouts, so we're not exactly sure if this is going to be a problem on mainnet. I think what's blocking us right now is a proper rollout plan. We see that connectivity among Tier1 in the most recent network survey might be "good enough" to avoid extra timeouts. The problem is that the network is constantly churning, and even simple things like node restarts impact Tier1 connectivity. @SirTyson given your proposal to make the change without changing the topology, could we put together the actual rollout plan, metrics/nodes to monitor, as well as plan in place in case things go sideways (and what does sideways actually mean here? is it just a perf degradation?) |
|
Btw, I think there are options to explore wrt rollout plan. For example, we could do something like better utilize preferred peers to guarantee hop count that is "good enough" for trigger time (assuming future Tier1 expansion to 10 orgs, for example) |
dd8b84b to
411eff9
Compare
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
I've put together a rollout plan here. I've also added in the last commit an NTP probe. By default, we configure a NTP server that core will periodically check to make sure it's local clock is synced. It's on by default for validators, but can be disabled. Hopefully this will help make sure the network has good clock hygiene before we make the switch. |
411eff9 to
fdf1e98
Compare
|
I left a few cleanup comments. Given that with various simulation bugfixes described in this doc we don't observe nomination degradation with 100% new timer nodes, I think it's fine to merge this PR. The new functionality is behind an experimental flag, and we can finalize the rollout strategy independently to avoid bitrot. |
fdf1e98 to
23b6095
Compare
23b6095 to
ad88f37
Compare































Description
This adds an experimental flag that when set, uses the closeTime from the last externalized SCP message as the basis for setting the triggerNextLedger timer.
I include a couple of basic unit tests, making sure that the behavior of the trigger is correct when nodes are drifting and when we have long nomination timeouts. Most of the simulation testing is reported below using this super cluster change: stellar/supercluster#384
Checklist
clang-formatv8.0.0 (viamake formator the Visual Studio extension)