-
Notifications
You must be signed in to change notification settings - Fork 9k
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
Limit the number of samples remote read can return. #4532
Conversation
0d18599
to
533ee47
Compare
This might break some Thanos users. @fabxc @Bplotka |
Yes, sorry should have said; OOMs seemed to be caused by queries submitted via Thanos. We'll see if we can remove this limit through a streaming API as discussed at the dev summit, this is just a quick fix. |
d2afd39
to
eec2302
Compare
cmd/prometheus/main.go
Outdated
@@ -164,6 +164,9 @@ func main() { | |||
a.Flag("storage.remote.flush-deadline", "How long to wait flushing sample on shutdown or config reload."). | |||
Default("1m").PlaceHolder("<duration>").SetValue(&cfg.RemoteFlushDeadline) | |||
|
|||
a.Flag("storage.remote.read-sample-limit", "Maxium number of samples to return via the remote read interface, in a single query."). |
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.
Maximum
cmd/prometheus/main.go
Outdated
@@ -164,6 +164,9 @@ func main() { | |||
a.Flag("storage.remote.flush-deadline", "How long to wait flushing sample on shutdown or config reload."). | |||
Default("1m").PlaceHolder("<duration>").SetValue(&cfg.RemoteFlushDeadline) | |||
|
|||
a.Flag("storage.remote.read-sample-limit", "Maxium number of samples to return via the remote read interface, in a single query."). | |||
Default("20m").IntVar(&cfg.web.RemoteReadLimit) |
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.
Usually 0 would mean no limit.
Does m mean milli here?
cmd/prometheus/main.go
Outdated
@@ -164,6 +164,9 @@ func main() { | |||
a.Flag("storage.remote.flush-deadline", "How long to wait flushing sample on shutdown or config reload."). | |||
Default("1m").PlaceHolder("<duration>").SetValue(&cfg.RemoteFlushDeadline) | |||
|
|||
a.Flag("storage.remote.read-sample-limit", "Maxium number of samples to return via the remote read interface, in a single query."). | |||
Default("20m").IntVar(&cfg.web.RemoteReadLimit) |
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.
Default is "20m"
, but the flag is an IntVar
- running this results in Error parsing commandline arguments: strconv.ParseFloat: parsing "20m": invalid syntax
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.. how to assess what max number of samples Prometheus can handle from remote read API?
Yeah, should be 2e7.
I can also make 0 mean no limit, but it’s not very safe...
…On Thu, 23 Aug 2018 at 19:01, Miles Bryant ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In cmd/prometheus/main.go
<#4532 (comment)>
:
> @@ -164,6 +164,9 @@ func main() {
a.Flag("storage.remote.flush-deadline", "How long to wait flushing sample on shutdown or config reload.").
Default("1m").PlaceHolder("<duration>").SetValue(&cfg.RemoteFlushDeadline)
+ a.Flag("storage.remote.read-sample-limit", "Maxium number of samples to return via the remote read interface, in a single query.").
+ Default("20m").IntVar(&cfg.web.RemoteReadLimit)
Default is "20m", but the flag is an IntVar - running this results in Error
parsing commandline arguments: strconv.ParseFloat: parsing "20m": invalid
syntax
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#4532 (review)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAbGhWFcKEjnhTll1a7RFVj92GkhHMT7ks5uTu3-gaJpZM4WJ8eW>
.
|
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 long as this has reasonable default and configurable flag, Thanos should be fine.
Incidentally is there any spike for streaming API for remote Read @tomwilkie? Can I help with this?
cmd/prometheus/main.go
Outdated
@@ -164,6 +164,9 @@ func main() { | |||
a.Flag("storage.remote.flush-deadline", "How long to wait flushing sample on shutdown or config reload."). | |||
Default("1m").PlaceHolder("<duration>").SetValue(&cfg.RemoteFlushDeadline) | |||
|
|||
a.Flag("storage.remote.read-sample-limit", "Maxium number of samples to return via the remote read interface, in a single query."). | |||
Default("20m").IntVar(&cfg.web.RemoteReadLimit) |
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.. how to assess what max number of samples Prometheus can handle from remote read API?
storage/remote/codec.go
Outdated
resp := &prompb.QueryResult{} | ||
for ss.Next() { | ||
series := ss.At() | ||
iter := series.Iterator() | ||
samples := []*prompb.Sample{} | ||
|
||
for iter.Next() { | ||
numSamples += 1 | ||
if numSamples > sampleLimit { | ||
return nil, fmt.Errorf("too many samples") |
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.
Can we also wrap the error with what is the limit?
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 should probably have some metrics around this too.
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 make it return a 400, not a 500...
I've set the default to 20 million based on discussions on the other PR (#4513), but will happily consider making it higher. |
Yes we discussed at the dev summit making a streaming API to help thanos & other systems. I've just done similar in cortex: cortexproject/cortex#933 Its beyond the scope of this PR - I suggest you start a thread on the -dev mailing list. We discussed streaming the compressed chunks out, instead of samples; previously I think you + I discussed trying to make a common API that can be used in both Cortex and Thanos. |
We're in a different context here, so a higher value could be fine. I arbitrarily say that we aim to keep it under 1GB by default. |
Ignoring labels, 60M * 16 bytes = 1GB, so I'll set it to that. |
Call it 50M to allow for the labels, and requests for many series with few points. It's also more round. |
e21a12c
to
ea7e52a
Compare
- Return 413 entity too large. - Limit can be set be a flag. Allow 0 to mean no limit. - Include limit in error message. - Set default limit to 50M (* 16 bytes = 800MB). Signed-off-by: Tom Wilkie <[email protected]>
ea7e52a
to
14b0449
Compare
Rebased against master; ready for final review I hope! |
storage/remote/codec.go
Outdated
if sampleLimit > 0 && numSamples > sampleLimit { | ||
return nil, HTTPError{ | ||
msg: fmt.Sprintf("exceeded sample limit (%d)", sampleLimit), | ||
status: http.StatusRequestEntityTooLarge, |
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 isn't a 413, as it's the server response that's too big. I think this should be a 400.
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.
Agree, 413 indicates the request size too big, 400 would be less confusing
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 might want to add some metrics around 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.
Typo + some suggestions
cmd/prometheus/main.go
Outdated
@@ -177,6 +177,9 @@ func main() { | |||
a.Flag("rules.alert.resend-delay", "Minimum amount of time to wait before resending an alert to Alertmanager. Must be lower than resolve_timeout in Alertmanager"). | |||
Default("1m").SetValue(&cfg.resendDelay) | |||
|
|||
a.Flag("storage.remote.read-sample-limit", "Maximum number of samples to return via the remote read interface, in a single query. 0 means no limit."). |
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.
s/query. 0 means no limit/ query. 0 means no limit/
(double space)
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.
Also worth to mention that this is "overall" number of samples. not per series (this could be a follow up question if there is no clarification)
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.
Also, is there anyway we can make it more convenient for user and already map this flag into memory size used? It is easier to limit the memory here and we can calculate/estimate mem into number of samples... The cost is that the calculation is on our part.. but it's still better, than thousands question on IRC how the number of samples here maps into memory (: Any thoughts?
Ignore me, we can only roughly estimate the mem usage with the simple logic, so let's stick to just samples limit.
storage/remote/codec.go
Outdated
if sampleLimit > 0 && numSamples > sampleLimit { | ||
return nil, HTTPError{ | ||
msg: fmt.Sprintf("exceeded sample limit (%d)", sampleLimit), | ||
status: http.StatusRequestEntityTooLarge, |
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.
Agree, 413 indicates the request size too big, 400 would be less confusing
storage/remote/codec.go
Outdated
resp := &prompb.QueryResult{} | ||
for ss.Next() { | ||
series := ss.At() | ||
iter := series.Iterator() | ||
samples := []*prompb.Sample{} | ||
|
||
for iter.Next() { | ||
numSamples += 1 |
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.
minor nit: ++
instead?
web/api/v1/api.go
Outdated
http.Error(w, err.Error(), http.StatusInternalServerError) | ||
if httpErr, ok := err.(remote.HTTPError); ok { | ||
http.Error(w, httpErr.Error(), httpErr.Status()) | ||
} else { |
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.
can we just return in each case? no need for else
here and one indent less below
Can you actually try a large size message? As far as I know the default maximum size for a proto message is limited to 64Mb (last time I checked they warned of larger sizes as it loosens some security constraints, but not sure of the details). |
To clarify @brancz do you mean "if it's even useful to have this flag because anything more than 50m samples will make protobuf message larger than default proto message (64MB)"? Good point, not sure of the implications. Also, how you get 64MB? Just for comparison: The grpc grpc/grpc#7927 by default allows max 4MB (: But it might be because protocol limitations itself. Would love to know more about implications. BTW we are working on streaming as well: https://docs.google.com/document/d/1JqrU3NjM9HoGLSTPYOvR217f5HBKBiJTqikEB9UiJL0/edit?ts=5b8f135a#
|
We don't use gRPC, we marshal into proto and send it over HTTP. prometheus/storage/remote/codec.go Lines 68 to 81 in 14b0449
Not sure where the 64MB limit is documented though. |
I know @gouthamve, just putting grpc as an example |
Signed-off-by: Tom Wilkie <[email protected]>
I'm not aware, nor could I find, any limit in the golang proto bindings at https://github.com/golang/protobuf.
Yes it is still useful; we build the datastructure before encoding it, and its the building of the datastructure to which this limits applies. Even if there was a 64MB limit, we still see OOMs for large queries to the remote read endpoint, imply its the building of the datastructure causing the OOM.
Until an agreed upon approach exists for the streaming, no. I can only speculate that this could apply to a single "frame", but thats just a guess. Lets take this one offline, we can always deprecate the flag if needs be. |
The thing I was thinking of is this: https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.io.coded_stream#CodedInputStream.SetTotalBytesLimit.details The golang implementations shouldn't actually be affected by this, but it feels like we should follow the best practice especially for an API that is exposed to be consumed by arbitrary implementations. |
👍 |
By setting the limit to 64MB by default, you'd be limiting Thanos to fetching ~8k timeseries per query (64MB / 16bytes * 15s scrape interval / 2hr head block). I think that limit would be too low, unless my math is wrong. |
That's fair. Maybe we should improve this step by step instead and only impose smaller limits if we actually manage to get streaming for example. |
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 should just have a test for the too large case, otherwise lgtm.
@@ -861,6 +862,10 @@ func TestReadEndpoint(t *testing.T) { | |||
recorder := httptest.NewRecorder() | |||
api.remoteRead(recorder, request) | |||
|
|||
if recorder.Code/100 != 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.
Can we add a test for the too large case as well?
Team, I have major issue on grafana dashboard which is increasing step size by itself resulting in substantial loss of data. As per them this is limitation of Prometheus where prometheus is making limit of 11K records. Could you please confirm if there is any limit enforced for query to return data points and what is that value. |
@brancz Any thoughts on claim by Grafana teams? |
Tracking down Prometheus OOMs for a client, initially thought it was PromQL (see #4414), but looks like it is remote read requests.
Quick and dirty limit; probably want this plumbed through as a command line argument?
Signed-off-by: Tom Wilkie [email protected]