-
Notifications
You must be signed in to change notification settings - Fork 9.8k
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
clientv3: kv ordering wrapper for serialized requests #7623
Comments
Please correct me if I am wrong, but isn't this just a band-aid over a cache-coherence issue on the proxy/server? Shouldn't we make sure that the server or caches response on the proxy doesn't return older revisions on the proxy/server rather than on the client? I'm not sure exactly how we would deal with this on the server side, but I just wanted to get your thoughts on the possibility of a server-side solution and to better understand the issue. |
@mangoslicer there's already linearized reads for server-side ordering. The serialized case is different since it never goes through quorum. Suppose there are three members A,B,C with C partitioned from A and B. Let a client issue a serialized read to A/B, then partition the client so it can only access C. The client will reconnect to C and if C is somewhat behind, the next serialized read will likely be stale with respect to the rest of the cluster. The client can see the response revision is older than the last seen revision and know that it's out of date (and handle it accordingly), but there's no way C would know it's behind. Even if there's no partitioning, raft only requires a quorum, so switching between members can still lead to serialized requests going backwards. |
@heyitsanthony Alright thanks for explaining. I was looking through the clientv3 src folder in order to find a reasonable place to cache the last rev. Do you think that the kv struct defined in clientv3/kv.go would be an appropriate place to add an int64 field named something like "PrevRev"? I thought that the kv struct might be an appropriate place to cache because both the txn and get responses have access to the kv struct. What do you think? |
@mangoslicer this shouldn't modify the base client. It'd be a wrapper for KV sort of like |
I made the initial wrapper. I have some questions about the retry and endpoint switching functionality. Would the retry functionality be as simple as setting a time out and then sending the request again, or is there a different way that you want this to be implemented? For the endpoint switching, I think the the implementation would involve using clientv3's |
@mangoslicer the wrapper shouldn't return an old revision error, it would make the Get call, see an old revision, then reissue the read on a different endpoint; the user wouldn't see any of this. |
Alright. Here's a link to my implementation so far. I'm not sure how to switch endpoints in the actual Get call, it doesn't seem like the KV interface has any way of switching endpoints. Do you have any suggestions on how to switch endpoints? |
@mangoslicer possibly separate the policy (how to switch/what's retry rate) from the mechanism of detecting ordering violation? That way the action the client takes based on order violation doesn't have to be baked into the wrapper. Something like: type OrderViolationFunc func(op clientv3.Op, resp clientv3.OpResponse, prevRev int64)
func NewKV(kv clientv3.KV, f OrderViolationFunc) *kvOrderingCache {...}
func (kv *kvOrderingCache) SomeRPC(...) {
... Do() ...
if isPastRev {
kv.orderViolation(op, resp, prevRev)
}
...
} The switching code can be passed in as a closure: orderingKV := NewKV(client.KV, func(...) { client.DoStuff() }) Also, can you put this up as a PR? Thanks! |
Thanks for the suggestions. I think that should work. Right now I am trying to figure out how to implement the endpoint switching, it seems like the |
@mangoslicer the easiest way at the moment would probably be something like: var mu sync.Mutex
violationCount := 0
handleViolation := func(...) {
mu.Lock()
defer mu.Unlock()
eps := client.Endpoints()
client.SetEndpoints([]string{eps[violationCount%len(eps)]})
client.SetEndpoints(eps)
violationCount++
} |
Ok thanks for the code. I don't understand why |
@mangoslicer otherwise the next call to client.Endpoints() will only have eps[0] even if the client was configured with multiple endpoints |
Ok, so the
|
@mangoslicer no, it's meant to cause the balancer to try to connect to another endpoint. I don't think it should try submitting the RPC while there's only one endpoint configured. |
Ok that makes sense. |
I pushed to the branch in this PR. Can you take a look? I have to notes regarding to testing. In kv_test.go, I had to make the I also noticed that the clientv3.Client type does not implement an interface, so mocking in unit tests is not possible. I would use the client.Client interface but it doesn't seem like the clientv3.Client properly implements that interface. In the client.Client interface the |
Fixed by #8092 |
Based on discussion from #7597
Check whether serialized Get/Txn responses are ordered by caching last known revision on the client side. Each Get/Txn client call will check the response's header revision is equal to or greater than the last known revision at the time of issuing the RPC.
This avoids the case where an etcd client connects to multiple endpoints, issues multiple rev=0 Gets, and receives revisions older than those already received. If this happens on a single proxy/server, it's a bug.
Two behavior notes:
The text was updated successfully, but these errors were encountered: