-
Notifications
You must be signed in to change notification settings - Fork 816
Add global limit to the max series per user and metric #1760
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
Merged
gouthamve
merged 13 commits into
cortexproject:master
from
pracucci:add-global-max-series-per-user-and-metric
Oct 31, 2019
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
a4da6af
Add global limit to the max series per user and metric:
pracucci 6cc0106
Commented ReadRingMock struct and functions
pracucci a6667db
Vendored new testify dependencies used for mocking
pracucci 70a0da6
Fixed data race in SeriesLimiter test
pracucci 0515640
Add HealthyIngestersCount() to the ring lifecycler, to avoid having t…
pracucci ca6b1ae
Converted string error const into error instance var in limits.go
pracucci ddabfab
Added PR number reference to changelog
pracucci aebb6dc
Do not update ring lifecycler counters unless the CAS operation succe…
pracucci 96c265e
Fixed nil pointer dereference in the ring lifecycler when updating co…
pracucci 866d7f5
Reduce to the minimum the block of code wrapped by mutex in the ring'…
pracucci ff0e05b
Added a comment to explain what userStates struct is
pracucci b1849c7
Renamed ring's HealthyIngestersCount() to HealthyInstancesCount() to …
pracucci c0ef755
Fixes after rebasing with master
pracucci File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
package ingester | ||
|
||
import ( | ||
"fmt" | ||
"math" | ||
|
||
"github.com/cortexproject/cortex/pkg/util/validation" | ||
) | ||
|
||
const ( | ||
errMaxSeriesPerMetricLimitExceeded = "per-metric series limit (local: %d global: %d actual local: %d) exceeded" | ||
pracucci marked this conversation as resolved.
Show resolved
Hide resolved
|
||
errMaxSeriesPerUserLimitExceeded = "per-user series limit (local: %d global: %d actual local: %d) exceeded" | ||
) | ||
|
||
// RingCount is the interface exposed by a ring implementation which allows | ||
// to count members | ||
type RingCount interface { | ||
HealthyInstancesCount() int | ||
} | ||
|
||
// SeriesLimiter implements primitives to get the maximum number of series | ||
// an ingester can handle for a specific tenant | ||
type SeriesLimiter struct { | ||
limits *validation.Overrides | ||
ring RingCount | ||
replicationFactor int | ||
shardByAllLabels bool | ||
} | ||
|
||
// NewSeriesLimiter makes a new in-memory series limiter | ||
func NewSeriesLimiter(limits *validation.Overrides, ring RingCount, replicationFactor int, shardByAllLabels bool) *SeriesLimiter { | ||
return &SeriesLimiter{ | ||
limits: limits, | ||
ring: ring, | ||
replicationFactor: replicationFactor, | ||
shardByAllLabels: shardByAllLabels, | ||
} | ||
} | ||
|
||
// AssertMaxSeriesPerMetric limit has not been reached compared to the current | ||
// number of series in input and returns an error if so. | ||
func (l *SeriesLimiter) AssertMaxSeriesPerMetric(userID string, series int) error { | ||
actualLimit := l.maxSeriesPerMetric(userID) | ||
if series < actualLimit { | ||
return nil | ||
} | ||
|
||
localLimit := l.limits.MaxLocalSeriesPerMetric(userID) | ||
globalLimit := l.limits.MaxGlobalSeriesPerMetric(userID) | ||
|
||
return fmt.Errorf(errMaxSeriesPerMetricLimitExceeded, localLimit, globalLimit, actualLimit) | ||
} | ||
|
||
// AssertMaxSeriesPerUser limit has not been reached compared to the current | ||
// number of series in input and returns an error if so. | ||
func (l *SeriesLimiter) AssertMaxSeriesPerUser(userID string, series int) error { | ||
actualLimit := l.maxSeriesPerUser(userID) | ||
if series < actualLimit { | ||
return nil | ||
} | ||
|
||
localLimit := l.limits.MaxLocalSeriesPerUser(userID) | ||
globalLimit := l.limits.MaxGlobalSeriesPerUser(userID) | ||
|
||
return fmt.Errorf(errMaxSeriesPerUserLimitExceeded, localLimit, globalLimit, actualLimit) | ||
} | ||
|
||
// MaxSeriesPerQuery returns the maximum number of series a query is allowed to hit. | ||
func (l *SeriesLimiter) MaxSeriesPerQuery(userID string) int { | ||
return l.limits.MaxSeriesPerQuery(userID) | ||
} | ||
|
||
func (l *SeriesLimiter) maxSeriesPerMetric(userID string) int { | ||
localLimit := l.limits.MaxLocalSeriesPerMetric(userID) | ||
globalLimit := l.limits.MaxGlobalSeriesPerMetric(userID) | ||
|
||
if globalLimit > 0 { | ||
if l.shardByAllLabels { | ||
// We can assume that series are evenly distributed across ingesters | ||
// so we do convert the global limit into a local limit | ||
localLimit = l.minNonZero(localLimit, l.convertGlobalToLocalLimit(globalLimit)) | ||
} else { | ||
// Given a metric is always pushed to the same set of ingesters (based on | ||
// the replication factor), we can configure the per-ingester local limit | ||
// equal to the global limit. | ||
localLimit = l.minNonZero(localLimit, globalLimit) | ||
} | ||
} | ||
|
||
// If both the local and global limits are disabled, we just | ||
// use the largest int value | ||
if localLimit == 0 { | ||
localLimit = math.MaxInt32 | ||
} | ||
|
||
return localLimit | ||
} | ||
|
||
func (l *SeriesLimiter) maxSeriesPerUser(userID string) int { | ||
localLimit := l.limits.MaxLocalSeriesPerUser(userID) | ||
|
||
// The global limit is supported only when shard-by-all-labels is enabled, | ||
// otherwise we wouldn't get an even split of series across ingesters and | ||
// can't take a "local decision" without any centralized coordination. | ||
if l.shardByAllLabels { | ||
// We can assume that series are evenly distributed across ingesters | ||
// so we do convert the global limit into a local limit | ||
globalLimit := l.limits.MaxGlobalSeriesPerUser(userID) | ||
localLimit = l.minNonZero(localLimit, l.convertGlobalToLocalLimit(globalLimit)) | ||
} | ||
|
||
// If both the local and global limits are disabled, we just | ||
// use the largest int value | ||
if localLimit == 0 { | ||
localLimit = math.MaxInt32 | ||
} | ||
|
||
return localLimit | ||
} | ||
|
||
func (l *SeriesLimiter) convertGlobalToLocalLimit(globalLimit int) int { | ||
if globalLimit == 0 { | ||
return 0 | ||
} | ||
|
||
// Given we don't need a super accurate count (ie. when the ingesters | ||
// topology changes) and we prefer to always be in favor of the tenant, | ||
// we can use a per-ingester limit equal to: | ||
// (global limit / number of ingesters) * replication factor | ||
numIngesters := l.ring.HealthyInstancesCount() | ||
|
||
// May happen because the number of ingesters is asynchronously updated. | ||
// If happens, we just temporarily ignore the global limit. | ||
if numIngesters > 0 { | ||
return int((float64(globalLimit) / float64(numIngesters)) * float64(l.replicationFactor)) | ||
} | ||
|
||
return 0 | ||
} | ||
|
||
func (l *SeriesLimiter) minNonZero(first, second int) int { | ||
if first == 0 || (second != 0 && first > second) { | ||
return second | ||
} | ||
|
||
return first | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.