-
Notifications
You must be signed in to change notification settings - Fork 809
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
Implement max_fetched_data_bytes_per_query limit #4854
Implement max_fetched_data_bytes_per_query limit #4854
Conversation
# ingester and storage. This limit is enforced in the querier and ruler only | ||
# when running Cortex with blocks storage. 0 to disable. | ||
# CLI flag: -querier.max-fetched-label-bytes-per-query | ||
[max_fetched_label_bytes_per_query: <int> | default = 0] |
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 wondering if it would be better if we deprecate max_fetched_chunk_bytes_per_query
and have a new configuration that is simply called max_fetched_data_bytes_per_query
, which limits the combination of labels and chunks.
Reason for this suggestion is so that we don't make users think about what is "label" v.s. what is "chunk"; I this is simpler for users.
What do you think @harry671003 @alanprot @friedrichg ?
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 talking to @alanprot who wasn't convinced we really need this limit. But from the stress tests, I saw that queriers were OOM killed when fetching 2 million series with 10KB labels size. So we still need a way to control the total bytes fetched during a query.
It makes sense to combine them into a single limit.
It might also be possible to not enable other limits like, max_fetched_chunks
, max_fetched_series
etc if we enable the max_fetched_data_bytes_per_query
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.
gently ping @alanprot @alvinlin123
2ddd736
to
de03d68
Compare
Test 1: without enabling the max_fetched_data_bytes_per_queryLimit
Result
Test 2: With max_fetched_data_bytes_per_query set to 10 GBLimits:
Result
The above test verifies that the combined data bytes fetched per query is necessary to prevent queriers from getting OOM killed. |
f1c3f89
to
05903b4
Compare
05903b4
to
8309bbc
Compare
ae3827e
to
b5b92c0
Compare
Signed-off-by: 🌲 Harry 🌊 John 🏔 <[email protected]>
b5b92c0
to
e7224cd
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.
I will merge this PR once the code comment is addressed.
pkg/distributor/distributor.go
Outdated
@@ -1029,6 +1029,9 @@ func (d *Distributor) MetricsForLabelMatchers(ctx context.Context, from, through | |||
} | |||
ms := ingester_client.FromMetricsForLabelMatchersResponse(resp) | |||
for _, m := range ms { | |||
if err := queryLimiter.AddDataBytes(resp.Size()); err != 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.
Why do we increase response size per matcher? I might miss something, but why we don't add response size once since it is a single 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.
It should be outside the loop. Thanks for catching it. :)
8e6a552
to
5a23b2d
Compare
@@ -687,14 +691,18 @@ func (q *blocksStoreQuerier) fetchSeriesFromStores( | |||
|
|||
numSeries := len(mySeries) | |||
chunkBytes := countChunkBytes(mySeries...) | |||
labelBytes := countDataBytes(mySeries...) | |||
dataBytes := labelBytes + chunkBytes |
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 here labelBytes := countDataBytes(mySeries...)
already includes both chunk bytes and label bytes so we count chunkBytes
twice.
if chunkBytesLimitErr := queryLimiter.AddChunkBytes(chunksSize); chunkBytesLimitErr != nil { | ||
return validation.LimitError(chunkBytesLimitErr.Error()) | ||
} | ||
if chunkLimitErr := queryLimiter.AddChunks(len(s.Chunks)); chunkLimitErr != nil { | ||
return validation.LimitError(chunkLimitErr.Error()) | ||
} | ||
if dataBytesLimitErr := queryLimiter.AddDataBytes(dataSize); dataBytesLimitErr != 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.
One question here. So the query limiter we are using have the same lifecycle with each query, let's say if we first query a SG and we limit some data bytes, then maybe due to network issue or SG restart, the stream erros and we need to retry another store gateway.
In this case, do you think it makes more sense to release the bytes we consumed and then start retrying another store gateway? Otherwise probably it is easy to hit the limit.
But it is also okay to hit the limit, and the query frontend will retry. 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.
This is a valid point. I also want to note that we are not doing this for other existing limits like fetched chunks and fetched series limits.
The query-frontend only retries 5XXs. In this case, we'll be returning a 422 error which will not be retried.
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 could do something like:
if isRetryableError(err) {
level.Warn(spanLog).Log("err", errors.Wrapf(err, "failed to receive series from %s due to retryable error", c.RemoteAddress()))
queryLimiter.RemoveSeries(seriesCnt)
queryLimiter.RemoveDataBytes(databytesCnt)
queryLimiter.RemoveChunkBytes(chunkbytesCnt)
return nil
}
If others agree with this approach, I can implement this as an enhancement in another PR.
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.
SGTM
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.
That's a good point. We should follow up with another PR to fix this i think!
# ingester and storage. This limit is enforced in the querier and ruler only | ||
# when running Cortex with blocks storage. 0 to disable. | ||
# CLI flag: -querier.max-fetched-data-bytes-per-query | ||
[max_fetched_data_bytes_per_query: <int> | default = 0] |
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.
Does this limit apply only to Select
call? Since I don't see we are limiting label names and label values. If that's the case probably we can mention it in the doc or give the flag a better name.
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.
That's the only question I have now. Other 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.
The naming of the limit is consistent with other similar limits. I think we can implement this limit also on the LabelNames and LabelValues as well. Right now I've updated the doc saying that it is only applied for query, query_range and series APIs
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.
NIT: when running Cortex with blocks storage
Currently we only have block storage as chunk storage was removed.
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.
All the other limits have this line:
This limit is enforced in the querier only when running Cortex with blocks storage
Should we remove it everywhere?
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...
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'll also address this in the PR to handle retryable failures.
5a23b2d
to
1635c33
Compare
@@ -1008,7 +1007,7 @@ func countChunkBytes(series ...*storepb.Series) (count int) { | |||
return count | |||
} | |||
|
|||
// countChunkBytes returns the size of the chunks making up the provided series in bytes | |||
// countDataBytes returns the combined size of the all the series |
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.
of the all the series
we can probably remove one the
. Small nit lol
4f186b4
to
7e772f1
Compare
Signed-off-by: 🌲 Harry 🌊 John 🏔 <[email protected]>
7e772f1
to
65a5bde
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.
lgtm
Signed-off-by: 🌲 Harry 🌊 John 🏔 [email protected]
What this PR does:
A query fetches both series and samples. The size of samples is already limited using the
max-fetched-chunk-bytes-per-query
limit. We don't currently limit the total size of series labels fetched. This can OOM kill queriers.This PR adds a new limit:
max-fetched-data-bytes-per-query
and deprecatesmax-fetched-chunks-bytes-per-query
.Which issue(s) this PR fixes:
Fixes #
Checklist
CHANGELOG.md
updated - the order of entries should be[CHANGE]
,[FEATURE]
,[ENHANCEMENT]
,[BUGFIX]