Skip to content
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

Merged
merged 11 commits into from
Jun 20, 2024

Conversation

ValarDragon
Copy link
Collaborator

@ValarDragon ValarDragon commented Jun 8, 2024

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:

  • A vote is added
  • A block part is added
  • We enter a new consensus step. (Receiving a full block triggers this for us, we will enter prevote or precommit round)

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

  • Tests written/updated
  • Changelog entry added in .changelog (we use unclog to manage our changelog)
  • Updated relevant documentation (docs/ or spec/) and code comments
  • Title follows the Conventional Commits spec

@ValarDragon ValarDragon requested review from a team as code owners June 8, 2024 20:27
internal/consensus/reactor.go Outdated Show resolved Hide resolved
@ValarDragon
Copy link
Collaborator Author

ValarDragon commented Jun 8, 2024

@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)
image

We had 137000 contentions over 500s!

This became 0 contentions in the mconnection.RecvRoutine after this change :)

Copy link
Contributor

@melekes melekes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ValarDragon ❤️

internal/consensus/state.go Show resolved Hide resolved
@cason
Copy link
Contributor

cason commented Jun 10, 2024

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.

@ValarDragon
Copy link
Collaborator Author

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?

I thought it was easier to handle copying/pointers to a struct in an independent getRoundState method (e.g. reasoning about shallow copy)

Or do you mean something different? Maybe I don't understand

@cason
Copy link
Contributor

cason commented Jun 10, 2024

Or do you mean something different? Maybe I don't understand

I meant that adding the shallow copy of the consensus state to the data in the multiple func(data cmtevents.EventData) will have the same effect.

@cason
Copy link
Contributor

cason commented Jun 10, 2024

Moreover, we still have the func (conR *Reactor) updateRoundStateRoutine() that does this shallow copy from the consensus state.

@cason
Copy link
Contributor

cason commented Jun 10, 2024

So, for context here.

The Reactor needs the consensus state, the infamous rs that is the way that the consensus logic "sends" information to the reactor (shared memory communication).

At the beginning of the times, each time we needed rs in the reactor, either in the Receive() method or in the multiple sending routines, we copied it from the consensus state (conR.rs = conR.conS.GetRoundState()).

This of course was not efficient, so at some point we replaced this by creating a updateRoundStateRoutine infinite for loop that every 100 micros (I don't know why this granularity) does the same.

However, for some reason (also not 100% clear to me), in some points you have touched in this PR, in the Receive() method, we didn't trust in the rs copy (from 100 micros ago), but instead retrieved the current value.

From what I understood, you are proposing to still use a (potentially) outdated rs copy in the Receive() method but, instead of only updating it every 100 micros, also "forcedly" update it each time that the consensus logic produces a synchronous event to the reactor, the three that you mentioned and touched.

My general question here is: if we are pretty sure we don't need a fresh copy of rs on each Receive() call, because relevant changes on rs lead us to update our copy, why do we still need the updateRoundStateRoutine() loop?

@cason
Copy link
Contributor

cason commented Jun 10, 2024

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.

@ValarDragon
Copy link
Collaborator Author

ValarDragon commented Jun 10, 2024

My general question here is: if we are pretty sure we don't need a fresh copy of rs on each Receive() call, because relevant changes on rs lead us to update our copy, why do we still need the updateRoundStateRoutine() loop?

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!

Copy link
Contributor

@cason cason left a 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.

internal/consensus/state.go Show resolved Hide resolved
@@ -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
Copy link
Contributor

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.

Copy link
Collaborator Author

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!

Copy link
Contributor

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:

// synchronous pubsub between consensus state and reactor.
// state only emits EventNewRoundStep and EventVote
evsw cmtevents.EventSwitch

Copy link
Contributor

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

Copy link
Contributor

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
Copy link
Contributor

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.

Copy link
Collaborator Author

@ValarDragon ValarDragon Jun 10, 2024

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

Copy link
Contributor

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.

@ValarDragon
Copy link
Collaborator Author

I meant that adding the shallow copy of the consensus state to the data in the multiple func(data cmtevents.EventData) will have the same effect.

That makes sense to me!

@ValarDragon
Copy link
Collaborator Author

Anything needed for this PR to progress?

ValarDragon added a commit to osmosis-labs/cometbft that referenced this pull request Jun 15, 2024
Copy link
Contributor

@cason cason left a 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.

internal/consensus/state.go Outdated Show resolved Hide resolved
ValarDragon added a commit to osmosis-labs/cometbft that referenced this pull request Jun 17, 2024
ValarDragon added a commit to osmosis-labs/cometbft that referenced this pull request Jun 17, 2024
* 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
czarcas7ic pushed a commit to osmosis-labs/cometbft that referenced this pull request Jun 19, 2024
* 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
@melekes melekes added this pull request to the merge queue Jun 20, 2024
Merged via the queue into main with commit 4b278e9 Jun 20, 2024
38 checks passed
@melekes melekes deleted the dev/remove_reactor_rlocks branch June 20, 2024 03:50
@andynog andynog restored the dev/remove_reactor_rlocks branch June 21, 2024 21:05
andynog added a commit that referenced this pull request Jun 21, 2024
github-merge-queue bot pushed a commit that referenced this pull request Jun 24, 2024
…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
@sergio-mena sergio-mena deleted the dev/remove_reactor_rlocks branch June 24, 2024 16:31
github-merge-queue bot pushed a commit that referenced this pull request Jul 3, 2024
…#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>
itsdevbear pushed a commit to berachain/cometbft that referenced this pull request Jul 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants