-
Notifications
You must be signed in to change notification settings - Fork 4.4k
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
balancer/weightedroundrobin: add load balancing policy (A58) #6241
Conversation
I lgtmed the pr this is based off. Could you please rebase and fix vet? |
balancer/weightedroundrobin/config.go:38:6: exported type LBConfigForTesting should have comment or be unexported |
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.
Did a first pass on the implementation focused on style/readability with a light correctness focus (but still tried to break correctness). Future passes I will go heavier on breaking correctness. I haven't looked at the tests yet.
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.
Updated based on additional comments, thanks!
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.
Took a first pass at tests. Sending them out now so you can see them, beginning implementation second pass now.
// srv1 starts loaded and srv2 starts without load; ensure RPCs are routed | ||
// disproportionately to srv2 (10:1). Errors are set (but ignored | ||
// initially) such that RPCs will be routed 50/50. | ||
srv1.oobMetrics.SetQPS(10.0) | ||
srv1.oobMetrics.SetCPUUtilization(1.0) | ||
srv1.oobMetrics.SetEPS(0) | ||
// srv1 weight before: 10.0 / 1.0 = 10.0 | ||
// srv1 weight after: 10.0 / 1.0 = 10.0 | ||
|
||
srv2.oobMetrics.SetQPS(10.0) | ||
srv2.oobMetrics.SetCPUUtilization(.1) | ||
srv2.oobMetrics.SetEPS(10.0) | ||
// srv2 weight before: 10.0 / 0.1 = 100.0 | ||
// srv2 weight after: 10.0 / 1.0 = 10.0 |
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 don't get this. Can you please specify where the errors are set, and why the RPCs once errors are set are routed 50/50 rather than 10/1.
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.
Errors are set via SetEPS
, and I explain here with "weight before/after" what the effective weights will be (10:1 before and 1:1 after). The comment above goes with these to explain this.
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.
Can you specify what 0 and 10 mean wrt EPS and errors.
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.
Done I think
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) | ||
defer cancel() | ||
|
||
var mu sync.Mutex | ||
start := time.Now() | ||
now := start | ||
setNow := func(t time.Time) { | ||
mu.Lock() | ||
defer mu.Unlock() | ||
now = t | ||
} | ||
iwrr.TimeNow = func() time.Time { | ||
mu.Lock() | ||
defer mu.Unlock() | ||
return now | ||
} | ||
t.Cleanup(func() { iwrr.TimeNow = time.Now }) | ||
|
||
srv1 := startServer(t, reportBoth) | ||
srv2 := startServer(t, reportBoth) | ||
|
||
// srv1 starts loaded and srv2 starts without load; ensure RPCs are routed | ||
// disproportionately to srv2 (10:1). Because the OOB reporting interval | ||
// is 1 minute but the weights expire in 1 second, routing will go to 50/50 | ||
// after the weights expire. | ||
srv1.oobMetrics.SetQPS(10.0) | ||
srv1.oobMetrics.SetCPUUtilization(1.0) | ||
|
||
srv2.oobMetrics.SetQPS(10.0) | ||
srv2.oobMetrics.SetCPUUtilization(.1) |
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 whole codeblock is shared with test above (except reportBoth, which is fine since it's a no-op anyway if it just gets ignored). Refactor into helper?
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'd rather not have helpers that do too much.. The goal should be to have ~1 line for each type of operation. With too many helpers the code can end up harder to debug when there is a test error.
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.
Line 514 - 547 are shared. Sorry, the comment didn't include the lines. I think due to the thirty lines of shared code repeated 3 times, you can pull into helper.
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.
startServer
and the setting of metrics are already factored out enough.
Setting the context can't be shared.
The bit that I'd be willing to rework is 517-530, but it seems fine to me this way.
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.
Implementation looks solid. Some minor comments (alongside a test I want).
} | ||
|
||
// A simple RR scheduler to use for fallback when all weights are zero or the | ||
// same or when only one subconn exists. |
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.
also mention if < 2 subconns have weights. (i.e. all weights are zero or only one subconn has weight, can perhaps replace the language of "all weights are zero" to both of these cases)
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.
Done
// By default we set load reports to off, because they are not running | ||
// upon initial weightedSubConn creation. |
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.
Is this the real root cause for this decision?
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.
The reason this code is here is: 1. we need a non-nil oldCfg
, and 2. we want the wsc to know there isn't a running OOB listener.
oldCfg = &lbConfig{EnableOOBLoadReport: false} | ||
} | ||
w.cfg = cfg | ||
newPeriod := cfg.OOBReportingPeriod | ||
if cfg.EnableOOBLoadReport == oldCfg.EnableOOBLoadReport && | ||
newPeriod == oldCfg.OOBReportingPeriod { | ||
// Load reporting wasn't enabled before or after, or load reporting was | ||
// enabled before and after, and had the same period. (Note that with | ||
// load reporting disabled, OOBReportingPeriod is always 0.) | ||
return | ||
} | ||
// (Optionally stop and) start the listener to use the new config's | ||
// settings for OOB reporting. | ||
if w.stopORCAListener != nil { | ||
w.stopORCAListener() | ||
} | ||
if !cfg.EnableOOBLoadReport { | ||
w.stopORCAListener = nil | ||
return | ||
} |
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.
Is the first config update ok to be OOB? Then oldCfg would hit the nil check on 439, and this function work proceed as normal. It feels weird though that the default is off, but the first config has the bool set, so why not just use the bool in the first config since a config received is what triggers this call in the first place?
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.
By default also means zero value I guess, and your code makes it work correctly.
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.
oldCfg
= the "current state of the wsc". But on the first call, there is no current state. Not really, anyway.
I have moved the initialization to happen at initialization time, which is better.
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.
All comments addressed!
} | ||
|
||
// A simple RR scheduler to use for fallback when all weights are zero or the | ||
// same or when only one subconn exists. |
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.
Done
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.
All comments minor. LGTM otherwise though.
// and if the scheduler is replaced during this usage, we want to use the | ||
// scheduler that was live when the pick started. |
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.
is replaced after reading and usage, continue to use the scheduler that was read. or something like that.
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 can't see what's wrong with what I've written.
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 want to" is not strong enough language. I don't really care about this though minor nit.
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) | ||
defer cancel() | ||
|
||
var mu sync.Mutex | ||
start := time.Now() | ||
now := start | ||
setNow := func(t time.Time) { | ||
mu.Lock() | ||
defer mu.Unlock() | ||
now = t | ||
} | ||
iwrr.TimeNow = func() time.Time { | ||
mu.Lock() | ||
defer mu.Unlock() | ||
return now | ||
} | ||
t.Cleanup(func() { iwrr.TimeNow = time.Now }) | ||
|
||
srv1 := startServer(t, reportBoth) | ||
srv2 := startServer(t, reportBoth) | ||
|
||
// srv1 starts loaded and srv2 starts without load; ensure RPCs are routed | ||
// disproportionately to srv2 (10:1). Because the OOB reporting interval | ||
// is 1 minute but the weights expire in 1 second, routing will go to 50/50 | ||
// after the weights expire. | ||
srv1.oobMetrics.SetQPS(10.0) | ||
srv1.oobMetrics.SetCPUUtilization(1.0) | ||
|
||
srv2.oobMetrics.SetQPS(10.0) | ||
srv2.oobMetrics.SetCPUUtilization(.1) |
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.
Line 514 - 547 are shared. Sorry, the comment didn't include the lines. I think due to the thirty lines of shared code repeated 3 times, you can pull into helper.
// srv1 starts loaded and srv2 starts without load; ensure RPCs are routed | ||
// disproportionately to srv2 (10:1). Errors are set (but ignored | ||
// initially) such that RPCs will be routed 50/50. | ||
srv1.oobMetrics.SetQPS(10.0) | ||
srv1.oobMetrics.SetCPUUtilization(1.0) | ||
srv1.oobMetrics.SetEPS(0) | ||
// srv1 weight before: 10.0 / 1.0 = 10.0 | ||
// srv1 weight after: 10.0 / 1.0 = 10.0 | ||
|
||
srv2.oobMetrics.SetQPS(10.0) | ||
srv2.oobMetrics.SetCPUUtilization(.1) | ||
srv2.oobMetrics.SetEPS(10.0) | ||
// srv2 weight before: 10.0 / 0.1 = 100.0 | ||
// srv2 weight after: 10.0 / 1.0 = 10.0 |
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.
Can you specify what 0 and 10 mean wrt EPS and errors.
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.
All comments minor. LGTM otherwise though.
// or when registering a new listener, as those calls require the ORCA | ||
// producer mu which is held when calling the listener, and the listener |
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 does holding the producer mu have anything to do with this mu? Are you saying that that's what guarantees mutual exclusion on accesses? I get the last bit about the listener callback also grabs mu so can't use this mu.
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.
The ORCA producer holds a mutex when calling the listeners. If the listeners do something that requires that mutex (basically, register/unregister listeners), we get a deadlock. Transitively, if the listeners share a mutex with another part of the code that holds that mutex while registering/unregistering listeners, we get a deadlock.
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 get that wrt deadlock scenarios. However, I'm asking why you mention the second producerMu. Is it to get around this deadlock that would happen if you grab the same mutex? (and can you clarify this in comment)
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.
LGTM.
Flake in the first run is #6258. I'll run a few more times on GA and see if I can reproduce the failure with the scheduler pointer. I was never able to reproduce it locally, but found and fixed a handful of other things, so maybe it's gone now? |
Forced push to pull in 6258 as it was hitting that race a lot. |
Ohhhh... I think the problem with the bug I was seeing might be that an unsafe.Pointer is 64 bits, and the scheduler was not the first entry in the struct. I was only seeing problems on 386 and ARM, so I'm 99% sure that was it.. Debugging code removed & I think this is done! (hopefully??) |
That wasn't it, either.. But the other instrumentation I put in found it... The rrScheduler was being used and the cast to Why does go use the signed |
Implements https://github.com/grpc/proposal/blob/master/A58-client-side-weighted-round-robin-lb-policy.md
RELEASE NOTES: