-
Notifications
You must be signed in to change notification settings - Fork 3
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
add firstready balancer #349
Conversation
|
||
var subConn balancer.SubConn | ||
for sc := range info.ReadySCs { | ||
subConn = sc |
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.
Note that we might want to actually pick randomly from the set of ready subconns rather than just taking the first one in the map.
I'm frankly not sure it matters in practice since the order should be basically nondeterministic anyway, and this is downstream from the already randomized shuffle from discovery, but in case it's possible that something like the order in which vtgates are started up somehow influences this, then maybe there's extra value in doing so?
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 think we want this to be deterministic in the input - we assume that Build
will be called only when there is a change in the ready list, but maybe there are circumstances where nothing has changed but it gets re-invoked. In those situations we don't want to jump around vtgates. (This is different from round_robin
which explicitly does want to choose a different starting point each time, and has code to do 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.
Thinking further - we only want to pick a new gate if the currently picked one is no longer ready. So the Nth subconn changing state isn't something we should act on. We should pick the same subconn if it's still in the ready list, which is tricky because we don't know what we last picked.
Without this, any vtgate subconn state change causes us to pick a new gate. We'd need some stable, but host-specific, way of deterministically choosing the same gate from a list (like sorting by hash of <conn-id>-<hostname>
.
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.
If deterministic order matters, we can't rely on the ordering of this map, since map ordering is not guaranteed (in past versions of go it was intentionally shuffled). We would need to take the output of this map and sort it deterministically. If we don't care about order, then this is fine, but we'll have to be aware of the fact that the target may or may not change during every discovery.
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.
Great points! -- I pushed a rework where we actually simplify the model so that the same struct implements both the Builder and the Picker interfaces (golang is really nice sometimes!). This way we will stick to the current conn as long as it's READY.
type frPickerBuilder struct{} | ||
|
||
func (*frPickerBuilder) Build(info base.PickerBuildInfo) balancer.Picker { | ||
logger.Infof("firstreadyPicker: Build called with info: %v", info) |
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.
worth normalizing on names, we have: first_ready, firstready, firstreadyPicker all in labels in this file
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 point. done.
var subConn balancer.SubConn | ||
for sc := range info.ReadySCs { | ||
subConn = sc |
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.
am I missing something clever by not just returning info.ReadSCs[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.
It's a map, the values aren't indexible or sorted deterministically
grpcclient.RegisterGRPCDialOptions(func(opts []grpc.DialOption) ([]grpc.DialOption, error) { | ||
return append(opts, grpc.WithDefaultServiceConfig(`{"loadBalancingConfig": [{"round_robin":{}}]}`)), nil | ||
log.Infof("registering %s load balancer", *balancerType) | ||
return append(opts, grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingConfig": [{"%s":{}}]}`, *balancerType))), nil |
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 should sanitize the input here - I'm not sure what the behaviour of gRPC is if you ask it for a balancer it doesn't know about.
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 point. done.
|
||
var subConn balancer.SubConn | ||
for sc := range info.ReadySCs { | ||
subConn = sc |
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 think we want this to be deterministic in the input - we assume that Build
will be called only when there is a change in the ready list, but maybe there are circumstances where nothing has changed but it gets re-invoked. In those situations we don't want to jump around vtgates. (This is different from round_robin
which explicitly does want to choose a different starting point each time, and has code to do that).
// created. The slice is immutable. Each Get() will do a round robin | ||
// selection from it and return the selected SubConn. |
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.
round robin selection
- The C&P is showing 😆
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.
fixed
|
||
var subConn balancer.SubConn | ||
for sc := range info.ReadySCs { | ||
subConn = sc |
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.
Thinking further - we only want to pick a new gate if the currently picked one is no longer ready. So the Nth subconn changing state isn't something we should act on. We should pick the same subconn if it's still in the ready list, which is tricky because we don't know what we last picked.
Without this, any vtgate subconn state change causes us to pick a new gate. We'd need some stable, but host-specific, way of deterministically choosing the same gate from a list (like sorting by hash of <conn-id>-<hostname>
.
) | ||
|
||
// Name is the name of first_ready balancer. | ||
const Name = "first_ready" |
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 will become a public package level constant, so vtgateproxy.Name
will become "first_ready" which might be confusing. (PascalCase names are automatically public in go)
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.
Removed
// Name is the name of first_ready balancer. | ||
const Name = "first_ready" | ||
|
||
var logger = grpclog.Component("firstready") |
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 will also become a package level variable, although at least private. But any file that references logger
in the vtgateproxy package will reference this instance.
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 point - removed
ee7e879
to
60dc5df
Compare
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.
Nice, this all looks good to me. Let's give it a spin!
* add firstready balancer * fail if an invalid balancer is picked * maintain the currentConn even if another one becomes ready * remove unnecessary globals * normalize the names
* add firstready balancer * fail if an invalid balancer is picked * maintain the currentConn even if another one becomes ready * remove unnecessary globals * normalize the names
Description
Implement a "first_ready" load balancer and a config to select it.
Despite how simple the code actually is, this balancer (I think!) does what (I think!) we want it to do (basically!):
This means that in practice, in steady state a webapp host will have one "hot" connection to a single vtgate, and will route all queries to it. At the same time it will maintain N-1 idle connections to other vtgates, and if the connection to the primary vtgate goes away or is interrupted, queries will get routed to one of the other ones as a backup.
AZ Affinity: Just like the case of
round_robin
if there aren't N hosts in the same AZ, then some amount of cross-AZ queries are inevitable. When usingfirst_ready
it can be especially bad since if the "first" ready subconn is in a different AZ then webapp will be stuck crossing AZs until discovery runs again.In practice I think this isn't actually a problem since we should always have at least N vtgates in each AZ.
Testing
This seems to do what it's supposed to with some basic testing, including using iptables rules to temporarily black hole network traffic to the "selected" gate. After the keepalive timeout, the connection was marked as TRANSIENT_FAILURE and the query was routed to a different gate, so the app saw a slowdown but not a failure.
Related Issue(s)
Checklist
Deployment Notes