-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
receive: Only wait for write quorum #2621
Conversation
f60b63f
to
f6fb934
Compare
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'd be nice to mention about the added flag in the changelog.
And also I believe this fixes #2567
pkg/receive/handler.go
Outdated
level.Error(h.logger).Log("msg", "storing locally", "err", err, "endpoint", endpoint) | ||
} | ||
ec <- err | ||
ec <- errors.Wrapf(err, "storagin locally, endpoint %v", 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.
ec <- errors.Wrapf(err, "storagin locally, endpoint %v", endpoint) | |
ec <- errors.Wrapf(err, "storing locally, endpoint %v", endpoint) |
f6fb934
to
c3cbbd2
Compare
@kakkoyun good point! Added! :) |
ce24cc8
to
2258e46
Compare
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.
🥇
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.
Love those integration test additions!
For a full-on review I would need to check this out locally start diving into the gritty details. From a reviewing point of this, all LGTM! 😊 👍
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.
OK I am generally happy with direction, but I am not fan of current parallelizeRequests
syntax, we might work on API a bit.
Essentially it confused me, then also confused you (there is a small bug) ;p Let's maybe find something better..
Also you use a lot this errors.Wrap(nil, ....
and tsdbError.MultiError.Add(nil)
and it just looks too scary to me. It might be just me, but I feel like it should be antipatterns, mentioned in code style ): It's just scares reader a lot (: For me it's just opportunity for easier errors.
pkg/receive/handler.go
Outdated
@@ -324,29 +326,39 @@ func (h *Handler) forward(ctx context.Context, tenant string, r replica, wreq *p | |||
} | |||
h.mtx.RUnlock() | |||
|
|||
return h.parallelizeRequests(ctx, tenant, replicas, wreqs) | |||
n, ec := h.parallelizeRequests(ctx, tenant, replicas, wreqs) |
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.
why not just for err := range <-ec
? without n?
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.
we need to know the potential number of results so we know when we have reached quorum, it could also return the quorum amount instead, but that would really result in the same code
pkg/receive/handler.go
Outdated
defer func() { | ||
go func() { | ||
for { | ||
err, more := <-ec |
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.
defer cancel()
for err := range <-ec {
Would do the work I think.
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.
Ok because you relied on err != nil on caller side.... and you forgot about it here, I would really recommend my suggestion in comment above =D
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.
And yes, I understand this is how we know if it was success or not.. but maybe we can come up with cleaner API
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 problem is that we want to drain the channel, and stop when it's actually empty and closed, so we need to have the more
returned.
Any suggestions for a better API? The problem is that we need to know in advance how many potential results we would be getting from the channel.
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.
But for ... <-ec
has exactly the same semantics, no?
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 will stop iterating when it's closed. If empty err, more := <-ec
will block as well
pkg/receive/handler.go
Outdated
level.Error(h.logger).Log("msg", "storing locally", "err", err, "endpoint", endpoint) | ||
} | ||
ec <- err | ||
ec <- errors.Wrapf(err, "storing locally, endpoint %v", 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.
I think we should just return if no error?
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 you rely on err != nil
on caller side, maybe I would do it here for readability, but not a blocker.
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.
errors.Wrap
returns nil if the input err is nil
pkg/receive/handler.go
Outdated
go func(endpoint string) { | ||
ec <- h.replicate(ctx, tenant, wreqs[endpoint]) | ||
defer wg.Done() | ||
ec <- errors.Wrap(h.replicate(ctx, tenant, wreqs[endpoint]), "could not replicate write request") |
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.
Should pass error only on error?
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.
errors.Wrap returns nil if the input err is nil
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 am totally aware of that but IMO it's extremely confusing and prone to error. Plus adds major overhead on critical path.
pkg/receive/handler.go
Outdated
continue | ||
} | ||
|
||
if uint64(countCause(errs, isNotReady)) >= (h.options.ReplicationFactor+1)/2 { |
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 worth to keep this important number in some function.. (h.options.ReplicationFactor+1)/2
pkg/receive/handler.go
Outdated
return nil | ||
} | ||
} | ||
errs.Add(err) |
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 am so confused, why we are passing err nil to multiError?
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 can flow through above)
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.
nil errors are not actually added
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.
Again: I am totally aware of that but IMO it's extremely confusing and prone to error.
I don't see anything in the style guide that's violated here. The only other API that I could think of that wouldn't end up in just a rearrangement of the current code is using two channels, one for errors one for successes, but that would complicate draining them a lot. |
3de93f8
to
9c72f1e
Compare
Yea, I am proposing to add that. Plus only this PR is doing so, 100% of Thanos codebase is not putting nil to multierror and does not wrap nils with message. |
Let me think about API, I totally see the aim of it, maybe we can find something cleaner |
437fe34
to
28f7407
Compare
return ctx.Err() | ||
case err, more := <-ec: | ||
if !more { | ||
return errs |
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 was thinking about this case, but I thought that this will never happen (: We either have success or errors quorum kind of in my previous version (:
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 attempt here is to return the best possible error at the possible cost of higher latency, for example in a 3x replication, and 2x return tsdb-not-ready/unavailable, and 1 conflict, would end up with a generalized error when in reality a retry is likely to resolve the error.
It's a trade off, either better error reporting or lower latency. Since the request is failing in this case anyways, I prefer better errors over latency.
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.
LGTM, just some question (:
} | ||
return errors.Wrap(err, "could not replicate write request") | ||
if countCause(err, isConflict) >= quorum { |
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 still think it should len - quorum
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.
Let's take the example of replication factor 3, which has a quorum factor of 2, and we get 1 conflict error. 3-2=1
, so we would be returning conflict, even though write quorum was met.
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.
Ok, then it has to be > len(reqs) - quorum
, right? (not >=
)
I think we are both right if we assume that quorum is always +1 than half. This is however very depending on quorum value.... This algorithm should never assume things like that. Let's say quorum is 1 for some reason, with replication 3, then this logic will not hold true, vs > len(reqs) - quorum
is always correct. (:
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 however very depending on quorum value....
what do you mean by this?
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.
We talked offline. Not a blocker so merging.
This patch modifies receive replication slightly, in that it doesn't always wait for all requests to complete anymore. If quorum amount of replication requests were successful it now does not wait for the remaining request to finish as it's not necessary to reach quorum anymore. In error cases where quorum is not reached, it still continues to wait for all requests to finish in an attempt to return a quorum error. Additionally this patch moves log lines printed in the parallelize requests function to debug logging. Calling functions already print the resulting error(s), so this was previously just noise, even in cases where requests actually succeeded. Signed-off-by: Frederic Branczyk <[email protected]>
28f7407
to
85b03e2
Compare
Thanks! |
Changes
This patch modifies receive replication slightly, in that it doesn't
always wait for all requests to complete anymore. If quorum amount of
replication requests were successful it now does not wait for the
remaining request to finish as it's not necessary to reach quorum
anymore. In error cases where quorum is not reached, it still continues
to wait for all requests to finish in an attempt to return a quorum
error.
Additionally this patch moves log lines printed in the parallelize
requests function to debug logging. Calling functions already print the
resulting error(s), so this was previously just noise, even in cases
where requests actually succeeded.
Let me know if you think there should be a changelog entry for this, in reality there is not really a user noticable change, other than less noisy logs and lower latency for requests.
Verification
@bwplotka @krasi-georgiev @metalmatze @squat @kakkoyun