-
Notifications
You must be signed in to change notification settings - Fork 400
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
perf(consensus): Remove reactor rlocks on Consensus #3211
Conversation
@czarcas7ic profiled the number of mutex contentions before/after this change on Osmosis. This is the number of contentions on the recvRoutine over a 500s profile. (200 blocks) We had 137000 contentions over 500s! This became 0 contentions in the mconnection.RecvRoutine after this change :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @ValarDragon ❤️
I need to go through in more detail, but why not send this shallow copy with the synchronous message sent from state to the reactor? This PR works (without locks) because the calls are synchronous, so nothing happens in the protocol while it is called. |
I thought it was easier to handle copying/pointers to a struct in an independent Or do you mean something different? Maybe I don't understand |
I meant that adding the shallow copy of the consensus state to the |
Moreover, we still have the |
So, for context here. The At the beginning of the times, each time we needed This of course was not efficient, so at some point we replaced this by creating a However, for some reason (also not 100% clear to me), in some points you have touched in this PR, in the From what I understood, you are proposing to still use a (potentially) outdated My general question here is: if we are pretty sure we don't need a fresh copy of |
In summary, I suggest we open an issue to understand better this synchronization/shared memory design and link the issues and PRs (like this one) that improves performance by saving useless lock acquisition and data copy. |
Talked about this with Bucky IRL as well. Left it in within this PR, since its a nice fallback that may not be costing us. It would let us get this PR in, without risking any events updating the round state that we missed. Agreed we should be able to remove it. However, I feel like we can remove that in a second PR, after getting this one tested on production more extensively. It seems fine to leave the updateStatsRoutine as a backup on a perhaps slower timer than 100 microsecond. This PR should only help, it took us to 0 lock conflicts! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left some general comments here.
I think this change is in the right direction. The description of the PR and, specially, the changelog entry are imprecise and misleading.
Once they are fixed or reworded, we are good to go.
@@ -0,0 +1,4 @@ | |||
- `[consensus]` Make the consensus reactor no longer have packets on receive take the consensus lock. | |||
Consensus will now update the reactor's view after every relevant change through the existing event |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not really an event bus.
I would say that we use the synchronous events produced by the state to the reactor to transmit a fresh/updated copy of the consensus state to the reactor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok! I thought this event system was called the event bus, thats my bad. Thank you!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No problem, it is described like that:
cometbft/internal/consensus/state.go
Lines 131 to 133 in 2dd5209
// synchronous pubsub between consensus state and reactor. | |
// state only emits EventNewRoundStep and EventVote | |
evsw cmtevents.EventSwitch |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And the comment is outdated, as we have a third event, eheheheheh
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have an asynchronous bus in state.go
too, just to make it simple to any reader. : )
rs *cstypes.RoundState | ||
rsMtx cmtsync.RWMutex | ||
rs *cstypes.RoundState | ||
initialHeight int64 // under rsMtx |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this value for every received message? Not questioning the change, but the rationale.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need it to be updated on every prune, since that can change our state.initial Height. I didn't see a synchronous point to update for prunes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is the first height of the blockchain, it never changes. In any case, the first block (height) stored is something we can get from the block store, not from the state.
That makes sense to me! |
Anything needed for this PR to progress? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good.
The discussion, to have in another issue/PR, is whether we still, after this changes, have the updateRoundStateRoutine()
, which does the same as this PR proposes.
* Backport cometbft#3211 * Fix Race * bp cometbft#3157 * Speedup tests that were hitting timeouts * bp cometbft#3161 * Fix data race * Mempool async processing * Forgot to commit important part * Add changelog
* Backport cometbft#3211 * Fix Race * bp cometbft#3157 * Speedup tests that were hitting timeouts * bp cometbft#3161 * Fix data race * Mempool async processing * Forgot to commit important part * Add changelog
…3335) This PR reverts #3211 since it is making the e2e nightly to fail in reproducible ways. This PR was identified as the reason for the recent e2e nightly failure on `main` and doing a bi-sect test of all recent commits it was determined that this PR introduced a behavior that makes the tests to fail and indicate a bug or unknown behavior has been introduced. Even though we are reverting this logic for now, we'd be happy to consider it in the future again once more tests are performed and we can ensure it passes all tests. --- #### PR checklist - [ ] Tests written/updated - [ ] Changelog entry added in `.changelog` (we use [unclog](https://github.com/informalsystems/unclog) to manage our changelog) - [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments - [ ] Title follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) spec
…#3341) Pretty simple bug fix for the e2e failure on #3211. There was a race condition at iniitialization for initial height, because we didn't initialize it early on enough. The error in the E2E logs was: ``` validator03 | E[2024-06-21|21:13:20.744] Stopping peer for error module=p2p peer="Peer{MConn{10.186.73.2:34810} 4fe295e4cfad69f1247ad85975c6fd87757195db in}" err="invalid field LastCommitRound can only be negative for initial height 0" validator03 | I[2024-06-21|21:13:20.744] service stop module=p2p peer=4fe295e4cfad69f1247ad85975c6fd87757195db@10.186.73.2:34810 msg="Stopping Peer service" impl="Peer{MConn{10.186.73.2:34810} 4fe295e4cfad69f1247ad85975c6fd87757195db in}" ``` hinting at initial height not being set rpoperly. --- #### PR checklist - [ ] Tests written/updated - [ ] Changelog entry added in `.changelog` (we use [unclog](https://github.com/informalsystems/unclog) to manage our changelog) - [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments - [ ] Title follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) spec --------- Co-authored-by: Andy Nogueira <me@andynogueira.dev>
PR with @ebuchman !!!
Remove (most) of the reactor RLock's on consensus! (Remaining one is in the gossip routine, should be easy to fix, but as a separate PR)
The consensus reactor on ingesting messages takes RLock's on Consensus mutex, preventing us from adding things to the queue. (And therefore blocking behavior)
This can cause the peer message queue to be blocked. Now it won't be blocked because we update the round state directly from the cs state update routine (via event bus) when:
This shouldn't change reactor message validity, because Reactor.Receive could always view a cs.RoundState that will be old when the packet reaches the cs.HandleMsg queue. Every update to consensus' roundstate pushes the update into the reactor's view, so we avoid locks.
I don't think any new tests are required!
PR checklist
.changelog
(we use unclog to manage our changelog)docs/
orspec/
) and code comments