-
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
pickfirst: New pick first policy for dualstack #7498
pickfirst: New pick first policy for dualstack #7498
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #7498 +/- ##
==========================================
+ Coverage 81.84% 82.15% +0.30%
==========================================
Files 361 362 +1
Lines 27827 28102 +275
==========================================
+ Hits 22775 23086 +311
+ Misses 3850 3830 -20
+ Partials 1202 1186 -16
|
f6a52fc
to
84194db
Compare
d77dd20
to
586b091
Compare
e44b7a2
to
31e8a10
Compare
al.reset() | ||
} | ||
|
||
// seekTo returns false if the needle was not found and the current index was left unchanged. |
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.
Nit: please wrap all comments to 80 chars.
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 of these nits applies to all comments. I also see different capital abbreviations in the comments (i.e. SubConn and subConn and others). Can you please standardize?
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.
Wrapped the comments.
All the comments were using "subconn" while variable names were using "SubConn/subConn". Change the comments to also use "SubConn".
b.subConns = resolver.NewAddressMap() | ||
} | ||
|
||
// deDupAddresses ensures that each address belongs to only one endpoint. |
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.
According to Mark, this is an implicit requirement out of the resolver (I asked about WRR and Outlier Detection). What should the behavior be in this undefined case? For OD, Mark mentioned he just uses the first endpoint that an address maps too, but the scenario where an endpoint wraps the same address as another isn't really a valid scenario.
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 that is in the context of EDS. Here, pick_first could be the top-level LB policy on the channel and there is no guarantee that the name resolver being used does not send duplicate addresses. Also, there is no complexity around localities here. The pick_first LB policy gets a flat list of endpoints or addresses.
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, there is no guarantee, but Mark mentioned in the xDS Chat duplicate addresses is an implicit bug in the resolver emissions. So behavior in OD/WRR is undefined in that scenario (mainly deployed in xDS Tree where EDS has a requirement of no deduping).
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.
pick_first
doesn't really care about endpoints, it will flatten the endpoint list and try to connect to all the addresses. The effect of for de-duplicating addresses is that we don't try to connect to the same address multiple times during a pass of pickfirst.
I've changed the implementation of UpdateClientConnState
to flatten the endpoint list very early so that other parts of the LB policy handle a slice of addresses instead of a slice of endpoints. I initially started off converting the address list to endpoint list in UpdateClientConnState
instead of the opposite after looking at Java's implementation, but its not really required.
Can you please summarize what part of the gRFC this current PR implements in the PR description? (i.e. from a quick pass, I see the pick first iterations through the addresses one by one, but I don't see the happy eyeballs implementation) |
…ly in UpdateClientConnState
This PR implements this section of A61: https://github.com/grpc/proposal/blob/master/A61-IPv4-IPv6-dualstack-backends.md#move-pick_first-logic-out-of-subchannel-javago I've rephrased the first line of the PR description to emphasise what the hyperlink was pointing to. |
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 wrote a couple comments against an older commit. Sending & starting over since they're getting lost behind later commits.
// Can be changed in init() if this balancer is to be registered as the default | ||
// pickfirst. |
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.
Nit: you should say something like "Is changed to pick_first
in init() if this ..." so it's clear what it changes to, not just that it changes.
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 the comment.
6ff5043
to
f0e479e
Compare
@@ -19,6 +19,9 @@ jobs: | |||
- name: Run coverage | |||
run: go test -coverprofile=coverage.out -coverpkg=./... ./... | |||
|
|||
- name: Run coverage with new pickfirst | |||
run: GRPC_EXPERIMENTAL_ENABLE_NEW_PICK_FIRST=true go test -coverprofile=coverage_new_pickfirst.out -coverpkg=./... ./... |
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.
Does anything use this coverage_new_pickfirst.out
file? How? By wildcard?
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 verified that it gets used by checking that the coverage went up after adding this step.
Codecov docs just say that multiple reports are merged "automatically", but don't specify the logic:
- https://docs.codecov.com/docs/github-4a-merging-reports
- https://docs.codecov.com/docs/merging-reports
It is possible to explicitly specify the coverage files, we don't do that for the existing coverage.out
filename.
1ff881d
to
0ee7d48
Compare
0ee7d48
to
3103efe
Compare
|
||
func (b *pickfirstBalancer) newSCData(addr resolver.Address) (*scData, error) { | ||
sd := &scData{ | ||
state: connectivity.Idle, |
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 is a bit intertwined with the behavior of the calling function -- it needs this to be Idle
to cause it to connect. A comment here might be a good idea because of that.
Alternatively, we could make this call Connect
(or make the caller always call Connect
and do an early return
) and set the state to Connecting
.
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 state stored in scData
is updated only when the state notification is received by the stateListener
. Since SubConns
start in IDLE, I initialized the state with IDLE here.
Lines 827 to 835 in b8ee37d
ac := &addrConn{ | |
state: connectivity.Idle, | |
cc: cc, | |
addrs: copyAddresses(addrs), | |
scopts: opts, | |
dopts: cc.dopts, | |
channelz: channelz.RegisterSubChannel(cc.channelz, ""), | |
resetBackoff: make(chan struct{}), | |
} |
Is there a reason to initialize the SubConn state with CONNECTING?
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 would only initialize to CONNECTING if you also call connect.
Yeah I think this is all fine actually.
// addresses within each endpoint. - A61 | ||
if cfg.ShuffleAddressList { | ||
endpoints = append([]resolver.Endpoint{}, endpoints...) | ||
internal.ShuffleAddressListForTesting.(func(int, func(int, int)))(len(endpoints), func(i, j int) { endpoints[i], endpoints[j] = endpoints[j], endpoints[i] }) |
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 this will need to be updated after your other PR.
Can we move this LB policy into the pickfirst package?
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.
Moved the pickfirstleaf
package inside the pickfirst
package.
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 there any strong reason to keep it in a separate package at all? Why not put both PF policies inside the PF package? Longer term we will only have the one pickfirst
again.
Also, in general, be very careful about adding exported packages and symbols if the plan isn't to keep them.
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.
Marking the new policy as experimental is easier when the new policy is in its own subpackage. What functions/structs should be marked as experimental when I merge the packages?
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.
Marking the new policy as experimental is easier when the new policy is in its own subpackage.
Is it? What is even exported from an LB policy package? Usually only the Name
.
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.
Discussed this offline. Sharing the same module would cause collisions in the unexported symbol names used by both packages such as logger
, Name
which. This would require renaming symbols and can cause cause confusion. Decided not to combine the packages.
* | ||
*/ | ||
|
||
package test |
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 can move these tests into pickfirstleaf
and be package pickfirstleaf_test
instead. That's nicer since they're colocated with the code they're testing, but they're still a separate package. Having a separate package for only tests is generally not ideal -- especially where the name of the package is just test
, so it's hard to tell from the package name itself what is happening.
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 agree, moved the files as suggested.
This PR moves the pickfirst logic into the LB policy as mentioned in A61.
Background
In Java and Go, pick first logic is implemented within the subchannel itself instead of inside the load balancer unlike C-core.
We will take this opportunity to bring gRPC to a more uniform architecture across implementations and write a new pick first policy. This is important so that different implementations do not continue to diverge as more features are implemented.
This change will include creating a PickFirstLeafLoadBalancer which will contain the pick first logic, as well as redesigning some components such as backoffs and address updates. This will set us up nicely to implement Happy Eyeballs and use pick first as a universal leaf policy.
The new pick first policy is not used by default and can be set using an environment variable. A GitHub actions workflow is created to run the tests with the env var set. Code coverage is also calculated by running with and without the env var set.
This implementation is based on the Java implementation.
RELEASE NOTES: