Skip to content

perf: block replay pipeline bottleneck limits sustained deploy throughput #474

Description

@spreston8

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)

  • Removed adaptive deploy cap
  • Enabled deploy heartbeat wake
  • Bonds computation cache (DashMap, 64 entries)
  • Block index cache 32 → 128
  • Self-propose-cooldown 15s → 5s
  • Conflict detection caching (2.9x faster merge)
  • Prometheus metrics for full merger pipeline breakdown

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions