Summary
Load testing on a 3-validator shard (FTT=0.1, heartbeat 2s) shows sustained deploy throughput tops out at ~5 deploys/sec before finalization degrades. The bottleneck is the block replay pipeline — each peer block takes 500-1500ms to validate, even for trivial deploys.
Current Performance (after PR #473 optimizations)
| Deploy Rate |
Inclusion p50 |
Finalization p50 |
LFB Rate |
| 1/sec |
1.7s |
4.7s |
40.3 blk/min |
| 5/sec |
25.7s |
49.7s |
9.4 blk/min |
| 10/sec |
58.9s |
95.3s |
3.8 blk/min |
Detailed Merger Breakdown (high load, avg per merge)
| Step |
Time |
Notes |
| branches (depends O(D²)) |
349ms |
Pre-computed derived sets (was 731ms) |
| conflicts_map (O(B²)) |
860ms |
Cached branch data (was 2809ms) |
| rejection_options |
0ms |
|
| channel_reads (storage) |
1ms |
|
| combine_changes |
1ms |
|
| compute_trie_actions |
~2ms |
|
| apply_trie_actions |
~18ms |
|
The conflicts_map (860ms) and branches (349ms) remain the dominant costs. Both are O(N²) pairwise comparisons — the caching in PR #473 reduced per-call cost but the quadratic iteration count remains.
Replay Breakdown (high load, avg per block)
| Step |
Time |
| user_deploys replay |
3962ms |
| system_deploys |
114ms |
| checkpoint |
44ms |
| reset |
3ms |
The user_deploys replay (3962ms) for trivial @N!(N) deploys indicates the cost is in runtime spawn and state management, not Rholang execution.
Root Cause
Feedback loop: more load → more blocks → larger unfinalized DAG scope → slower conflict detection (O(N²)) → slower replay → finalization falls behind → more blocks accumulate.
Already Optimized (PR #473)
Remaining Optimization Opportunities
1. Reduce O(N²) to O(N) for conflict detection
The compute_relation_map function compares every pair. For non-conflicting deploys on disjoint channels, all comparisons return false. A channel-indexed approach could skip comparisons for items with no shared channels. Attempted but reverted due to correctness issues with branch grouping — needs careful design that preserves exact branch structure.
2. Reduce bonds validation cost
compute_bonds() spawns a Rholang runtime and executes a PoS contract query per unique post_state_hash. The DashMap cache helps for repeated hashes but misses at high load (unique state per block). The proper fix: read bonds directly from the trie state without spawning a runtime.
3. Reduce replay runtime spawn overhead
spawn_replay_runtime() clones RSpace state per block (10-50ms). Potential: runtime pooling or reset-in-place between sequential replays. Significant refactor with consensus safety implications.
4. Suppress empty heartbeat blocks
~54% of blocks are empty. Each still requires full replay by peers. Skipping proposals when a validator has no deploys and no meaningful parents would eliminate wasted replay work.
Reproduction
DEFAULT_IMAGE=f1r3flyindustries/f1r3fly-rust-node:local \
F1R3FLY_RUST_IMAGE=f1r3flyindustries/f1r3fly-rust-node:local \
poetry run pytest integration-tests/test/test_load.py -v --log-cli-level=INFO --timeout=900
Related
Co-Authored-By: Claude noreply@anthropic.com
Summary
Load testing on a 3-validator shard (FTT=0.1, heartbeat 2s) shows sustained deploy throughput tops out at ~5 deploys/sec before finalization degrades. The bottleneck is the block replay pipeline — each peer block takes 500-1500ms to validate, even for trivial deploys.
Current Performance (after PR #473 optimizations)
Detailed Merger Breakdown (high load, avg per merge)
The conflicts_map (860ms) and branches (349ms) remain the dominant costs. Both are O(N²) pairwise comparisons — the caching in PR #473 reduced per-call cost but the quadratic iteration count remains.
Replay Breakdown (high load, avg per block)
The
user_deploysreplay (3962ms) for trivial@N!(N)deploys indicates the cost is in runtime spawn and state management, not Rholang execution.Root Cause
Feedback loop: more load → more blocks → larger unfinalized DAG scope → slower conflict detection (O(N²)) → slower replay → finalization falls behind → more blocks accumulate.
Already Optimized (PR #473)
Remaining Optimization Opportunities
1. Reduce O(N²) to O(N) for conflict detection
The
compute_relation_mapfunction compares every pair. For non-conflicting deploys on disjoint channels, all comparisons return false. A channel-indexed approach could skip comparisons for items with no shared channels. Attempted but reverted due to correctness issues with branch grouping — needs careful design that preserves exact branch structure.2. Reduce bonds validation cost
compute_bonds()spawns a Rholang runtime and executes a PoS contract query per uniquepost_state_hash. The DashMap cache helps for repeated hashes but misses at high load (unique state per block). The proper fix: read bonds directly from the trie state without spawning a runtime.3. Reduce replay runtime spawn overhead
spawn_replay_runtime()clones RSpace state per block (10-50ms). Potential: runtime pooling or reset-in-place between sequential replays. Significant refactor with consensus safety implications.4. Suppress empty heartbeat blocks
~54% of blocks are empty. Each still requires full replay by peers. Skipping proposals when a validator has no deploys and no meaningful parents would eliminate wasted replay work.
Reproduction
Related
Co-Authored-By: Claude noreply@anthropic.com