Skip to content

Commit 03136fd

Browse files
committed
Exclude scheduled-to-stop members from resolver availability
AvailableMembers/AvailableMemberCount already excluded draining hosts; extend that to hosts with a future stopAt label. Such a host has announced its departure and flips itself to NOT_SERVING, so it should not be offered for new request selection or counted as available, the same way a draining frontend isn't. The consistent-hash lookup ring is intentionally left untouched: a leaving host stays in the ring until its scheduled eviction so that shard/task-queue ownership transfers once, simultaneously across nodes, rather than diverging during the drain window.
1 parent a31f476 commit 03136fd

3 files changed

Lines changed: 59 additions & 4 deletions

File tree

common/membership/interfaces.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,13 @@ type (
8080
RemoveListener(name string) error
8181
// MemberCount returns the number of known hosts running this service.
8282
MemberCount() int
83-
// AvailableMemberCount returns the number of hosts running this service that are accepting requests (not draining).
83+
// AvailableMemberCount returns the number of hosts running this service that are accepting
84+
// requests (not draining and not scheduled to stop).
8485
AvailableMemberCount() int
8586
// Members returns all known hosts available for this service.
8687
Members() []HostInfo
87-
// AvailableMembers returns all hosts available for this service that are accepting requests (not draining).
88+
// AvailableMembers returns all hosts available for this service that are accepting requests
89+
// (not draining and not scheduled to stop).
8890
AvailableMembers() []HostInfo
8991
// RequestRefresh requests that the membership information be refreshed.
9092
RequestRefresh()

common/membership/ringpop/monitor_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,42 @@ func (s *RpoSuite) TestCompareMembersWithDraining() {
222222
s.Len(resolver.AvailableMembers(), 2)
223223
}
224224

225+
func (s *RpoSuite) TestAvailableMembersExcludeLeaving() {
226+
running := newHostInfo("a", nil)
227+
draining := newHostInfo("b", map[string]string{drainingKey: "true"})
228+
stopping := newHostInfo("c", map[string]string{stopAtKey: "9999999999"})
229+
hosts := []*hostInfo{running, draining, stopping}
230+
231+
ring := newHashRing(100)
232+
for _, h := range hosts {
233+
ring.AddMembers(h)
234+
}
235+
resolver := &serviceResolver{}
236+
resolver.ringAndHosts.Store(ringAndHosts{
237+
ring: ring,
238+
hosts: map[string]*hostInfo{
239+
running.GetAddress(): running,
240+
draining.GetAddress(): draining,
241+
stopping.GetAddress(): stopping,
242+
},
243+
})
244+
245+
// Leaving hosts stay visible in full membership.
246+
s.Len(resolver.Members(), 3)
247+
s.Equal(3, resolver.MemberCount())
248+
249+
// ...but are not offered as available for new request selection.
250+
s.Len(resolver.AvailableMembers(), 1)
251+
s.Equal(1, resolver.AvailableMemberCount())
252+
s.Equal(running.GetAddress(), resolver.AvailableMembers()[0].GetAddress())
253+
254+
// Ownership guard: leaving hosts remain in the lookup ring so that
255+
// shard/task-queue ownership transfers only once, at their scheduled
256+
// eviction, rather than diverging across nodes during the drain window.
257+
addrs := ring.LookupN("some-key", 3)
258+
s.ElementsMatch([]string{"a", "b", "c"}, addrs)
259+
}
260+
225261
func eventToString(event *membership.ChangedEvent) []string {
226262
var diff []string
227263
for _, a := range event.HostsAdded {

common/membership/ringpop/service_resolver.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ func (r *serviceResolver) AvailableMemberCount() int {
216216
_, hosts := r.ring()
217217
n := 0
218218
for _, host := range hosts {
219-
if !isDraining(host) {
219+
if !isLeaving(host) {
220220
n++
221221
}
222222
}
@@ -236,7 +236,7 @@ func (r *serviceResolver) AvailableMembers() []membership.HostInfo {
236236
_, hosts := r.ring()
237237
var servers []membership.HostInfo
238238
for _, host := range hosts {
239-
if !isDraining(host) {
239+
if !isLeaving(host) {
240240
servers = append(servers, host)
241241
}
242242
}
@@ -531,3 +531,20 @@ func isDraining(host *hostInfo) bool {
531531
}
532532
return false
533533
}
534+
535+
// hasStopAt reports whether the host has announced a scheduled eviction time.
536+
// getReachableMembers drops hosts whose stopAt is already in the past, so a
537+
// label present here always denotes a future eviction.
538+
func hasStopAt(host *hostInfo) bool {
539+
_, ok := host.Label(stopAtKey)
540+
return ok
541+
}
542+
543+
// isLeaving reports that a host has announced it is going away, either by
544+
// draining or by scheduling a future eviction. Such a host stays in the hash
545+
// ring until it actually leaves (so shard/task-queue ownership transfers only
546+
// once, at the scheduled time), but it should no longer be offered for new
547+
// request selection or counted as available.
548+
func isLeaving(host *hostInfo) bool {
549+
return isDraining(host) || hasStopAt(host)
550+
}

0 commit comments

Comments
 (0)