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

[FIXED] Don't InstallSnapshot during shutdown, would race with monitorStream/monitorConsumer #6153

Merged
merged 1 commit into from
Nov 20, 2024

Conversation

MauriceVanVeen
Copy link
Member

When stopping a stream or consumer, we would attempt to install a snapshot. However, this would race with what's happening in monitorStream/monitorConsumer at that time.

For example:

  1. In applyStreamEntries we call into mset.processJetStreamMsg to persist one or multiple messages.
  2. We call mset.stop(..) either before or during the above.
  3. In mset.stop(..) we'd wait for mset.processJetStreamMsg to release the lock so we can enter mset.stateSnapshotLocked(). We create a snapshot with new state here!
  4. Now we call into InstallSnapshot to persist above snapshot, but n.applied does not contain the right value, the value will be lower.
  5. Then applyStreamEntries finishes and we end with calling n.Applied(..).

This would be a race condition depending on if 4 happened before or after 5.

It's essential that the snapshot we make is aligned with the n.applied value. If we don't that means we'll replay and need to increase mset.clfs which will snowball into stream desync due to this shift.

The only place where we can guarantee that the snapshot and applied are aligned is in doSnapshot of monitorStream and monitorConsumer (and monitorCluster), so we must not attempt installing snapshots outside of those.

Signed-off-by: Maurice van Veen github@mauricevanveen.com

@MauriceVanVeen MauriceVanVeen requested a review from a team as a code owner November 20, 2024 12:51
Copy link
Member

@derekcollison derekcollison left a comment

Choose a reason for hiding this comment

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

LGTM

…rStream/monitorConsumer

Signed-off-by: Maurice van Veen <github@mauricevanveen.com>
@MauriceVanVeen MauriceVanVeen force-pushed the maurice/install-snapshot-race branch from 1097171 to 6938f97 Compare November 20, 2024 15:09
@derekcollison derekcollison merged commit 71ba974 into main Nov 20, 2024
4 of 5 checks passed
@derekcollison derekcollison deleted the maurice/install-snapshot-race branch November 20, 2024 15:30
MauriceVanVeen pushed a commit that referenced this pull request Nov 21, 2024
…itorStream`/`monitorConsumer` (#6153)

When stopping a stream or consumer, we would attempt to install a
snapshot. However, this would race with what's happening in
`monitorStream`/`monitorConsumer` at that time.

For example:
1. In `applyStreamEntries` we call into `mset.processJetStreamMsg` to
persist one or multiple messages.
2. We call `mset.stop(..)` either before or during the above.
3. In `mset.stop(..)` we'd wait for `mset.processJetStreamMsg` to
release the lock so we can enter `mset.stateSnapshotLocked()`. **We
create a snapshot with new state here!**
4. Now we call into `InstallSnapshot` to persist above snapshot, but
`n.applied` does not contain the right value, the value will be lower.
5. Then `applyStreamEntries` finishes and we end with calling
`n.Applied(..)`.

This would be a race condition depending on if 4 happened before or
after 5.

It's essential that the snapshot we make is aligned with the `n.applied`
value. If we don't that means we'll replay and need to increase
`mset.clfs` which will snowball into stream desync due to this shift.

The only place where we can guarantee that the snapshot and applied are
aligned is in `doSnapshot` of `monitorStream` and `monitorConsumer` (and
`monitorCluster`), so we must not attempt installing snapshots outside
of those.


Signed-off-by: Maurice van Veen <github@mauricevanveen.com>
neilalexander added a commit that referenced this pull request Nov 22, 2024
Includes:

- #6147
- #6150
- #6151
- #6153
- #6154
- #6146
- #6139
- #6152
- #6157
- #6161

Signed-off-by: Neil Twigg <neil@nats.io>
derekcollison added a commit that referenced this pull request Dec 18, 2024
Since the race condition of installing snapshots during shutdown was
fixed in #6153, we have not
re-introduced this snapshotting but done in the right place. (Meta
already has this)

Signed-off-by: Maurice van Veen <github@mauricevanveen.com>
derekcollison added a commit that referenced this pull request Jan 10, 2025
Calling healthz could result in streams and/or consumers to be
restarted.
There's a race condition that can happen where a user recreates a
stream/consumer and the health check kicks in at that moment. This would
result in reviving a just-deleted stream/consumer, resulting in either
dead streams/consumers remaining or potentially leaderless states if
different RAFT groups would remain.

A stream/consumer must not be restarted in the health check as it has no
awareness of what's happening in another part of the system. Is the
stream just deleted, is it restarting due to an error, is it actually
stopped due to a bug? It can't know, and it shouldn't assume it's safe
to restart. Due to the way the JS lock is used combined with creating
copies of the stream/consumer assignment means that various race
conditions can happen where restarting would be the wrong choice.

More importantly (and put simply), stream/consumer assignments MUST only
be changed via meta entries or meta snapshots. Doing it in any other
place can result in race conditions/ordering issues. (Just like
snapshotting in any other place than in the monitor routine resulted in
race conditions before:
#6153)

Detecting and correcting RAFT node skew is kept, although likely the
health check shouldn't be doing that either. However, there was a bug
where RAFT node skew would be detected for a consumer, it would be
deleted, but not recreated if it was initially created within <10s.
That's now fixed as well.

Signed-off-by: Maurice van Veen <github@mauricevanveen.com>
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.

2 participants