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
feat(spanner): support of client-level custom retry settings #2599
feat(spanner): support of client-level custom retry settings #2599
Conversation
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 really like how clean and easy this seems to make passing in custom retry settings, but I'm a little bit confused about how the merging of the custom settings with the default settings works. Would you mind explaining that a little?
t.Fatalf("merged CallOptions is incorrect: got %v, want %v", got, want) | ||
} | ||
|
||
merged.CreateSession[1].Resolve(cs) |
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'm not very familiar with the exact working of these options in Go. When exactly would the merged.CreateSession[1]
setting be used instead of the merged.CreateSession[0]
setting?
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.
google-cloud-go/spanner/apiv1/spanner_client.go
Lines 308 to 311 in 06b6069
func (c *Client) CreateSession(ctx context.Context, req *spannerpb.CreateSessionRequest, opts ...gax.CallOption) (*spannerpb.Session, error) { | |
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "database", url.QueryEscape(req.GetDatabase()))) | |
ctx = insertMetadata(ctx, c.xGoogMetadata, md) | |
opts = append(c.CallOptions.CreateSession[0:len(c.CallOptions.CreateSession):len(c.CallOptions.CreateSession)], opts...) |
In the generated method, you can see that opts = [c.CallOptions.CreateSession, opts...]
. So the passed-in opts have higher order of precedence.
Therefore, merged.CreateSession[1]
will override merged.CreateSession[0]
.
My guess is that it will iterate all gax.CallOption
in merged.CreateSession
and call Resolve
with gax.CallSettings
. Only the effect of the last one remains. Then, it uses gax.CallSettings
for calling gRPC.
} | ||
|
||
// This is the custom retry setting. | ||
c.CallOptions.CreateSession[1].Resolve(cs) |
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 setting that will actually be used in this case? Is it in that case necessary to keep the other settings still in c.CallOptions.CreateSession[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.
As I described in the previous comment, c.CallOptions.CreateSession[1]
has higher order of precedence. Yes, it is not necessary to keep c.CallOptions.CreateSession[0]
. But I have two reasons to do it in this way:
- Follow the same pattern in
spanner_client.go
:opts = append(c.CallOptions.CreateSession[0:len(c.CallOptions.CreateSession):len(c.CallOptions.CreateSession)], opts...)
- Simplify the merging logic: we don't need to say
if b is not empty, use b
,else if a is not empty, use a
, andif both empty, leave it empty
. We can just mergea
andb
in the right order.
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.
Sounds reasonable to me. I do think we should consider adding some kind of end-to-end test for this, as we are relying on what is effectively an undocumented feature of the generated client that might silently change in the future. That end-to-end test could probably be created using the mock server, and does not need to be a full integration test.
The default CallOptions are set in: google-cloud-go/spanner/apiv1/spanner_client.go Lines 256 to 261 in 06b6069
We override the default CallOptions in: google-cloud-go/spanner/sessionclient.go Lines 279 to 281 in c455fe2
Assume client.CallOptions = {
CreateSession: [opt1],
BatchCreateSessions: [op2],
GetSession: [],
} and sc.callOptions = {
CreateSession: [opt3],
BatchCreateSessions: [],
GetSession: [op5],
} Then, the merged one will become: {
CreateSession: [opt1, opt3],
BatchCreateSessions: [opt2],
GetSession: [op5],
} The final effect would be |
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.
Thank you for the elaborate explanation, that makes it a lot clearer for me.
This seems good to me, but as noted in my comment above, I do think we should consider adding an end-to-end test as well, as it is relying on what seems to be an undocumented feature of the generated client (the undocumented feature being that the generated client will use the last element of the array as its actual call options).
I dig a bit more into the code and found: It actually does what I think. It iterates all CallOptions and calling
Therefore, only the last one will be remained in the But I am happy to add an end-to-end test for this. |
372a8e4
to
987a8ab
Compare
@olavloite , I have added an end-to-end test. Please take a look. I learned an interesting fact that the gax retrying does not actually work for Basically, what I learned is:
Originally, I tried to set a custom retry setting for ExecuteStreamingSql(), but it doesn't work. So I changed it to use ExecuteSql(). |
@hengfengli Thanks for adding the end to end test. This looks good to me. The retry logic for |
Changes included are:
CallOptions
inClientConfig
.callOptions
insessionClient
.mergeCallOptions
to merge two CallOptions.nextClient
in sessionclient.go so that if a custom CallOptions is provided, merge it with the default one.Fixes #2594