-
Notifications
You must be signed in to change notification settings - Fork 10.4k
mvcc: refactor watch store by consolidating watcherGroup.choose/chooseAll #21108
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,8 @@ | |
| package mvcc | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "math" | ||
| "sync" | ||
| "time" | ||
|
|
||
|
|
@@ -360,7 +362,7 @@ func (s *watchableStore) syncWatchers(evs []mvccpb.Event) (int, []mvccpb.Event) | |
| curRev := s.store.currentRev | ||
| compactionRev := s.store.compactMainRev | ||
|
|
||
| wg, minRev := s.unsynced.choose(maxWatchersPerSync, curRev, compactionRev) | ||
| wg, minRev := s.unsafeSelectForSync(maxWatchersPerSync, curRev, compactionRev) | ||
| evs = rangeEventsWithReuse(s.store.lg, s.store.b, evs, minRev, curRev+1) | ||
|
|
||
| victims := make(watcherBatch) | ||
|
|
@@ -413,6 +415,46 @@ func (s *watchableStore) syncWatchers(evs []mvccpb.Event) (int, []mvccpb.Event) | |
| return s.unsynced.size(), evs | ||
| } | ||
|
|
||
| // unsafeSelectForSync selects up to maxWatchers from the unsynced group for syncing. | ||
| // It handles compacted watchers by sending compaction responses and removing them. | ||
| // Returns the selected watchers and the minimum revision needed for DB fetch. | ||
| // Unsafe because it assumes s.mu is held by the caller. | ||
| func (s *watchableStore) unsafeSelectForSync(maxWatchers int, curRev, compactRev int64) (*watcherGroup, int64) { | ||
| minRev := int64(math.MaxInt64) | ||
| ret := newWatcherGroup() | ||
| if s.unsynced.size() == 0 { | ||
| return &ret, minRev | ||
| } | ||
| for w := range s.unsynced.watchers { | ||
| if w.minRev > curRev { | ||
| // Watchers moved from synced to unsynced during Restore() may have future revisions. | ||
| // They will be included but won't match any events, then moved back to synced. | ||
| if !w.restore { | ||
| panic(fmt.Errorf("watcher minimum revision %d should not exceed current revision %d", w.minRev, curRev)) | ||
| } | ||
| w.restore = false | ||
| } | ||
| if w.minRev < compactRev { | ||
| select { | ||
| case w.ch <- WatchResponse{WatchID: w.id, CompactRevision: compactRev}: | ||
| w.compacted = true | ||
| s.unsynced.delete(w) | ||
| default: | ||
| // retry next time | ||
| } | ||
| continue | ||
| } | ||
| if minRev > w.minRev { | ||
| minRev = w.minRev | ||
| } | ||
| ret.add(w) | ||
| if ret.size() >= maxWatchers { | ||
| break | ||
| } | ||
|
Comment on lines
+450
to
+453
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't consistent with the current behaviour. You may return an incorrect
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think returning returning minRev for the watchers is consistent with previous behavior.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minRev part is correct. However, it may scan more than before. for w := range s.unsynced.watchers {
if ret.size() >= maxWatchers {
break
}
ret.add(w)
...
}
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, not sure. I have another suggestion, there is no sense trying to compile code in our heads, maybe we should start from ensuring the functions are properly tested before changing anything :P
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. re-read the function, and confirmed that the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
agreed on this. It'd better to add an unit test separately to cover different scenarios, and ensure all cases pass on main. Afterwards, rebase this PR, ensure this PR doesn't break any case. |
||
| } | ||
| return &ret, minRev | ||
| } | ||
|
|
||
| // rangeEventsWithReuse returns events in range [minRev, maxRev), while reusing already provided events. | ||
| func rangeEventsWithReuse(lg *zap.Logger, b backend.Backend, evs []mvccpb.Event, minRev, maxRev int64) []mvccpb.Event { | ||
| if len(evs) == 0 { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
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.
Behavior changes slightly when
maxWatchersPerSync<s.unsynced.size(): because s.unsynced is a map, iteration order is random, and the new loop can traverse many entries while still filling the batch. This means more compacted watchers may be handled (and compact responses sent) earlier than before, but in a nondeterministic order and ifw.chis not too busy.Uh oh!
There was an error while loading. Please reload this page.
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.
You mean that new implementation will return maxWatchers, no matter number of compaction attempts it did, while the previous implementation would count compactions into maxWatchers. Have I understood it correctly?
This means that now resync work mount is not bounded by maxWatchers, but by maxWatchers + numCompacted. So we could be slowing down resync.
Good catch, this is a good suggestion as the presented PR was meant as refactor.
This could be imply changed by changing
ret.size() >= maxWatcherstowatchersConsidered >= maxWatchers.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 catch.
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.
Yes. map iteration order is nodeterministic. So it may go through all the watchers for that.