-
Notifications
You must be signed in to change notification settings - Fork 687
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
Standalone FAILOVER: Fix disconnect of blocked clients in standalone failover and support REDIRECT response #848
base: unstable
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3023,7 +3023,7 @@ void replicationStartPendingFork(void); | |
void replicationHandlePrimaryDisconnection(void); | ||
void replicationCachePrimary(client *c); | ||
void resizeReplicationBacklog(void); | ||
void replicationSetPrimary(char *ip, int port); | ||
void replicationSetPrimary(char *ip, int port, int disconnect_blocked); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIUC, the only case we do not want to disco blocked clients is only in the case of 'server.failover_state = FAILOVER_IN_PROGRESS' right? can we then just check for this condition inside replicationSetPrimary instead of passing on an external condition? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that should be possible, but I am reluctant to make There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO passing this flag to the replicationSetPrimary and having it handle clients disconnects is already extending the function scope. I am fine with letting it handle the state logic and encapsulate the decision into a single place. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with @gmbnomis that we shouldn't introduce this dependency ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. O.K. I agree this is also a good reasoning. |
||
void replicationUnsetPrimary(void); | ||
void refreshGoodReplicasCount(void); | ||
int checkGoodReplicasStatus(void); | ||
|
@@ -3049,7 +3049,7 @@ void showLatestBacklog(void); | |
void rdbPipeReadHandler(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask); | ||
void rdbPipeWriteHandlerConnRemoved(struct connection *conn); | ||
int rdbRegisterAuxField(char *auxfield, rdbAuxFieldEncoder encoder, rdbAuxFieldDecoder decoder); | ||
void clearFailoverState(void); | ||
void clearFailoverState(int success); | ||
void updateFailoverStatus(void); | ||
void abortFailover(const char *err); | ||
const char *getFailoverStateString(void); | ||
|
@@ -3637,10 +3637,11 @@ void blockClient(client *c, int btype); | |
void unblockClient(client *c, int queue_for_reprocessing); | ||
void unblockClientOnTimeout(client *c); | ||
void unblockClientOnError(client *c, const char *err_str); | ||
void unblockClientOnErrorSds(client *c, sds err); | ||
void queueClientForReprocessing(client *c); | ||
void replyToBlockedClientTimedOut(client *c); | ||
int getTimeoutFromObjectOrReply(client *c, robj *object, mstime_t *timeout, int unit); | ||
void disconnectAllBlockedClients(void); | ||
void disconnectOrRedirectBlockedClients(void); | ||
void handleClientsBlockedOnKeys(void); | ||
void signalKeyAsReady(serverDb *db, robj *key, int type); | ||
void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeout, int unblock_on_nokey); | ||
|
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 understand the general logic about key ownership and write commands. however module logic can vary greatly and I would hate placing assumptions on modules work. Maybe we can let modules opt it by registering API to query if the module wishes to be kept alive after role 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.
I realized that I should have added that I did not "invent" this code, it is following the redirect logic in Valkey cluster:
valkey/src/cluster.c
Line 1215 in 7795152
This particular condition was introduced by redis/redis#9483. The discussion there centered on slot migration in cluster, but the MOVED will also be sent/not sent following the usual readwrite/readonly logic on a replica.
So, it looks applicable to the redirect case here, but, TBH, I don't know the module API very well. @soloestoy, as you were part of the discussion on redis/redis#9483, do you think we should do this here as well? (alternatively we could just continue to disconnect clients which are blocked on a module or we may need to come up with something more complex, like @ranshid suggested.)
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.
@gmbnomis actually, I agree. I forgot about the redirect logic taking the same decision, so I guess the modules are already capable of handling it (even though I think modules should have the ability to impact the decision). I would like to ask, though, why not just reuse the function (clusterRedirectBlockedClientIfNeeded) ?
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.
Looking at both functions, the commonalities are quite limited because the redirect logic in cluster is much more complex and not applicable to the standalone case. We probably could extract the check whether we need to look at a client in more detail, i.e. something like:
That's not much. Do you think this is worthwhile?
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 it is worthwhile to create a small function or two, less about code reusing but more for abstraction. For instance, I see the following concepts worth capturing
This is also used at
valkey/src/server.c
Line 3913 in 829243e
Then L294-L296 could be rewritten as (in pseudo code)
Lastly, I am not sure about the module and read-only client cases. The changes look like leaving them in a perpetually blocked state; while for all other cases, we either redirect (new behavior) or drop the connection (old behavior).
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, that makes sense. I will give it a try once we sort out what the logic should be.
I realized only today that
CLUSTER FAILOVER
does not result inMOVED
errors for blocked clients satisfying these client conditions (like the documentation of the command and the comments forclusterRedirectBlockedClientIfNeeded
may suggest).Instead, we end up in exactly the function we are discussing about. That is to say, we return UNBLOCK and disconnect.
So, Valkey cluster seems to handle unblocking due to slot migration and unblocking due to primary/replica change differently. (I don't know whether this is by intention or not).
Thus, what I deemed to be "well-known territory" apparently isn't. Furthermore,
replicationSetPrimary
is not only called during failover but also by an arbitraryREPLICAOF
command.So, I think you are right and we should either redirect or disconnect. Shall I convert the current "do nothing" cases (i.e.
continue
) into disconnects? (because we are not sure whether redirecting the client is actually beneficial in these cases)(Alternatively, we could come to the conclusion that we don't need to be "better than CLUSTER FAILOVER" and decide against doing this redirect optimization at all.)