feat(l1): add full-sync throughput regression watch - #7114
Conversation
|
🤖 Codex Code ReviewFindings:
No EVM/consensus-state correctness issues surfaced here since the PR is tooling-only. Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
Greptile SummaryAdds a continuous full-sync benchmark that maintains rolling per-network bases, extracts execution metrics, persists leg results, and posts observe-only Slack summaries.
Confidence Score: 4/5The base-promotion ordering needs to be fixed before merging because a routine snapshot failure can terminate the continuous watch and require manual base recovery. The implementation moves the only active base to a retained generation before fallible snapshot and metadata operations have completed, leaving no automatic recovery path when either operation fails. Files Needing Attention: tooling/sync/fullsync_bench.py
|
| Filename | Overview |
|---|---|
| tooling/sync/fullsync_bench.py | Implements the benchmark lifecycle and reporting, but rotates away the active base before the replacement is durably created. |
| tooling/sync/fullsync_metrics.py | Extracts batch throughput, block rate, normalized phase timings, and regeneration duration from node logs. |
| tooling/sync/test_fullsync_metrics.py | Exercises metric extraction against representative captured log lines and invalid-run behavior. |
| tooling/sync/docker-compose.fullsync-bench.yaml | Overrides multisync services for full-sync execution, bounded memory, and exposed beacon APIs. |
| tooling/sync/Makefile | Adds watch, one-cycle, and metric-test entry points. |
| tooling/sync/README.md | Documents operation, bootstrap requirements, metrics, and safety invariants. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
B[Restore base.0] --> M[Measure fixed block window]
M --> D[Discard measured state]
D --> R[Restore base.0]
R --> A[Advance to consensus head minus gap]
A --> H{Restart health check passes?}
H -- No --> K[Keep previous base]
H -- Yes --> G[Rotate base generations]
G --> S[Snapshot live state as new base.0]
S --> W[Record base head]
W --> B
Prompt To Fix All With AI
### Issue 1
tooling/sync/fullsync_bench.py:279-281
**Rotation removes the active base**
When the snapshot or metadata write fails after generation rotation, `base.0` remains absent because the previous base has already moved to `base.1`. The next cycle raises `SystemExit`, terminating the continuous benchmark watch until an operator manually restores or re-bootstraps the base.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "feat(l1): add full-sync throughput regre..." | Re-trigger Greptile
| rotate_generations(state_root, net) | ||
| snapshot(net, base_dir(state_root, net, 0), link_dest=base_dir(state_root, net, 1)) | ||
| write_base_head(base_dir(state_root, net, 0), advanced["reached_block"]) |
There was a problem hiding this comment.
Rotation removes the active base
When the snapshot or metadata write fails after generation rotation, base.0 remains absent because the previous base has already moved to base.1. The next cycle raises SystemExit, terminating the continuous benchmark watch until an operator manually restores or re-bootstraps the base.
Prompt To Fix With AI
This is a comment left during a code review.
Path: tooling/sync/fullsync_bench.py
Line: 279-281
Comment:
**Rotation removes the active base**
When the snapshot or metadata write fails after generation rotation, `base.0` remains absent because the previous base has already moved to `base.1`. The next cycle raises `SystemExit`, terminating the continuous benchmark watch until an operator manually restores or re-bootstraps the base.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
🤖 Claude Code ReviewCode Review — PR #7114 (full-sync throughput regression watch)Scope: this is pure Python tooling ( Findings1. Lock is held for the entire Related minor gap: there's no stale-lock recovery — if the process is SIGKILLed, 2. 3. 4. 5. Minor statistical nits (fullsync_metrics.py:79, fullsync_bench.py:401-408)
What's solid
Overall: solid, well-tested tooling addition with one substantive design issue (Point 1) worth resolving before this and #7112 are expected to coexist on the same box, and a few minor robustness nits. Automated review by Claude (Anthropic) · sonnet · custom prompt |
Motivation
We have no continuous signal for execution throughput on real mainnet blocks. The per-PR CI benchmark runs a synthetic dense-ERC20 import, and multisync validates snap sync completion — so a change that lands on
mainand slows mainnet full sync stays invisible until someone runs a manual benchmark. We did that manually for #7008/#7023 and it cost days of setup each time.Implements the design in #7111. Sibling manual A/B tool is #7112; both are meant to share the leg primitive added here.
Description
tooling/sync/fullsync_bench.pykeeps a node a fixed distance behind head and, per cycle per network:The base creeps forward at chain rate, so the node never ages and there is no anchor or reference commit to maintain.
Mis fixed and independent of the base's movement, so consecutive measurements overlap heavily — that overlap is what keeps day-to-day workload variation small enough for the series to be meaningful.The advance targets
head − GAPrather than a fixed block count, so it self-calibrates to each network's real block production and absorbs missed slots (relevant on testnets).run_legis deliberately pure — it never decides what happens to the resulting state, never assumes a particular ref, and never rotates anything. That is what lets #7112 reuse it unchanged.Reuses multisync's per-network service pairs via a compose override (
--syncmode full,mem_limit, published beacon APIs) rather than duplicating them, and its Slack webhook conventions.Scope of this PR
Observe-only. It measures, records one JSON per leg, and posts a Slack summary with the delta against the trailing median. Alerting and step detection are deliberately not here: the real day-to-day σ is unknown, and thresholds should be derived from 2–3 weeks of collected data rather than guessed. Multi-network config is in place; the default is mainnet only.
Testing
The metric extraction is split into
fullsync_metrics.pyprecisely so it can be tested without a node, a box or Docker — it is also the part most likely to be silently wrong.make fullsync-bench-testruns 7 tests against real log lines captured from the #7008/#7023 runs, covering: batch throughput mean/samples, the(unified pipeline)wording variant, blocks/s, gas-normalised phase timings, phase lines with no preceding block header (a truncated log must not skew the ratio), state-regeneration timing, and — importantly — that a run with no batches yieldsNonerather than0 Ggas/s, so a dead run is reported invalid instead of as a catastrophic regression.The orchestration itself needs a box with a synced node and cannot be exercised in CI;
make fullsync-bench-onceis the intended first real run.Operational invariants encoded in the code
These come from the manual benchmarking, not from preference:
docker stop -t 300only — repeated abrupt stops previously left the canonical head ahead of any durably-flushed state, after which the node could not regenerate and needed a full re-synceth_blockNumber, nevereth_syncing.currentBlock, which goes stale during catch-up and once let a leg overshoot its target badlyrsync --link-dest) base generations, so rollback costs deltas rather than full copiesFollow-ups (not in this PR)
Ggas/sis likely wrong for light testnet blocks, whereblocks/sor the phase timings may carry the signalChecklist
STORE_SCHEMA_VERSION(crates/storage/lib.rs) if the PR includes breaking changes to theStorerequiring a re-sync. — N/A: tooling only, no client code touched.