-
Notifications
You must be signed in to change notification settings - Fork 173
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: add ReplicaOnly support for cluster client #346
Conversation
Codecov ReportPatch coverage:
❗ Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please install the Github App Integration for your organization. Read more. Additional details and impacted files@@ Coverage Diff @@
## main #346 +/- ##
==========================================
+ Coverage 97.60% 97.62% +0.01%
==========================================
Files 75 75
Lines 30408 30429 +21
==========================================
+ Hits 29681 29705 +24
+ Misses 612 610 -2
+ Partials 115 114 -1
☔ View full report in Codecov by Sentry. |
cluster.go
Outdated
if c.opt.ReplicaOnly { | ||
if replicaAddr := g.pickReplica(); replicaAddr != "" { | ||
cc = conns[replicaAddr] | ||
result := cc.Do(context.Background(), cmds.ReadOnlyCmd) |
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.
Hi @NeoHuang,
Thank you for implementing the ReplicaOnly
for the cluster client.
Your implementation generally looks good to me. However, setting the READONLY
state right here is not a good idea. It will not only make this refresh function slow but also make it hard to finish its job.
I think it will be better to pipeline the READONLY
right after the SELECT
command.
Here for RESP3:
Lines 173 to 175 in 6556f2f
if option.SelectDB != 0 { | |
init = append(init, []string{"SELECT", strconv.Itoa(option.SelectDB)}) | |
} |
And here for RESP2:
Lines 247 to 249 in 6556f2f
if option.SelectDB != 0 { | |
init = append(init, []string{"SELECT", strconv.Itoa(option.SelectDB)}) | |
} |
We can probably rely on the option.ReplicaOnly
flag to pipeline the READONLY
command after these two blocks if the pipe is managed under a cluster client.
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.
Hi @rueian , thanks for the feedback and I think it's a good idea. I'll make that change
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.
actually if we call it here in newPipe, it would panic if the redis doesn't have the cluster mode enabled. but we don't seem to have any context that whether cluster mode is supported or not here as we always try cluster mode first?
any idea how we can get around? or we can simply return error if ReplicaOnly is set for non clustered redis?
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.
Hi @NeoHuang,
We do try cluster mode first in the NewClient
. If the server is not a cluster, then we treat it as a single redis:
Lines 303 to 306 in 19e115b
if client, err = newClusterClient(&option, makeConn); err != nil { | |
if len(option.InitAddress) == 1 && (err.Error() == redisErrMsgCommandNotAllow || strings.Contains(strings.ToUpper(err.Error()), "CLUSTER")) { | |
option.PipelineMultiplex = singleClientMultiplex(pmbk) | |
client, err = newSingleClient(&option, client.(*clusterClient).single(), makeConn) |
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.
Was the panic you mentioned caused by the return nil, err
here?
Lines 217 to 218 in 19e115b
p.Close() | |
return nil, err |
We probably need to intercept that case and return p, err
instead.
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 panic here
Line 306 in 19e115b
client, err = newSingleClient(&option, client.(*clusterClient).single(), makeConn) |
the client is nil and not checked in this line. and yeah, the nil client was returned by your above code, but I am not sure if it's a good idea to intercept that specific case. I think I would just return error after this line
Line 303 in 19e115b
if client, err = newClusterClient(&option, makeConn); err != nil { |
like this
if client, err = newClusterClient(&option, makeConn); err != nil {
if client == nil || client == (*clusterClient)(nil) {
return nil, err
}
reason is that setting ReplicaOnly on single client is not expected anyway, I would rather throw this error instead of hiding it. also it would add more graceful handling of nil client cases for other scenarios. WDYT? :)
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.
Hi @NeoHuang,
I now think, instead of returning p, err
, we can just simply ignore the error caused by READONLY
.
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.
Because if the target server belongs to a redis cluster, the READONLY
command will succeed, and if the target server doesn't belong to a cluster, the READONLY
command will have no effect.
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.
So, we can safely pipeline the READONLY
command if option.ReplicaOnly
is set without checking its response.
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.
For throwing error in the case of ReplicaOnly+singleClient, we can do that before these line:
Lines 305 to 306 in 19e115b
option.PipelineMultiplex = singleClientMultiplex(pmbk) | |
client, err = newSingleClient(&option, client.(*clusterClient).single(), makeConn) |
dd8642a
to
2aa436f
Compare
@rueian thanks. updated the code according to the discussions and added tests |
pipe.go
Outdated
@@ -204,6 +209,10 @@ func _newPipe(connFn func() (net.Conn, error), option *ClientOption, r2ps bool) | |||
err = r.Error() | |||
} | |||
if err != nil { | |||
if readOnlyCmdIndex >= 0 && i == readOnlyCmdIndex { |
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.
Maybe this way is better and we don't have to introduce the readOnlyCmdIndex
:
if readOnlyCmdIndex >= 0 && i == readOnlyCmdIndex { | |
if init[i][0] == "READONLY" { |
2aa436f
to
4f31803
Compare
cluster.go
Outdated
func (g group) pickReplica() string { | ||
// no replica exists | ||
if len(g.nodes) <= 1 { | ||
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.
Hi @NeoHuang,
Almost there. I find that this line is not covered by tests:
However, I am also wondering if the double-checking logic len(g.nodes) <= 1
and replicaAddr != ""
could be merged?
For example just:
addr := master
if c.opt.ReplicaOnly && len(g.nodes) > 1 {
addr = g.nodes[1+rand.Intn(len(g.nodes)-1)]
}
4f31803
to
345c26d
Compare
btw, CLUSTER SLOTS seems to be deprecated. do we have plan to implement CLUSTER SHARDS |
Yes, we do need to implement CLUSTER SHARDS. We already have an issue opened for this #350. Given that CLUSTER SLOTS can still work for a long time, we have plenty of time on this. |
Merged. Many thanks! @NeoHuang |
added ReplicaOnly support for cluster client.
tested with a cluster with two shards, 3 nodes each.
@rueian could you let me know if this implementation makes sense? I'll add tests later