Skip to content
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 series limit using ingester own series #6718

Merged
merged 44 commits into from
Nov 28, 2023
Merged

Conversation

pstibrany
Copy link
Member

@pstibrany pstibrany commented Nov 23, 2023

What this PR does

This PR implements limiting of user series by only using series owned by ingesters to check the limit.

How it works:

  • After ingester opens all local TSDBs, ingester will wait until it's "ACTIVE" in the ring. At that point, ingester will read tokens from the ring to see which series it actually "owns", and ingester will update "owned series" for all open TSDBs. After that, ingester starts accepting push requests.

  • When new series is created, we assume that it was assigned to this ingester because ingester "owns" it, and simply increase "owned series" for TSDB.

  • When series is deleted, we don't update "owned series" counter for TSDB. Deletion only happens during compaction, so we will instead recompute "owned series" for TSDB after compaction has finished.

  • We recompute "owned series" for TSDB periodically for each user

    • After compaction -- series may have been deleted.
    • After ring changes -- "owned series" may be different now. Ring is checked for updates periodically.
    • After shard size has changed -- even if there was no change in the global ring, users' subring is now possibly different.
    • When new TSDB is opened while ingester already runs. We set shard size immediately, but only update owned series during next iteration. That seems good-enough, because we can assume that TSDB was opened because ingester owns all incoming series, so there's no rush to compute token ranges for such TSDB.
  • How user limits are affected:

    • Instead of "number of in-memory series", we limit on number of "owned series".
    • Changes in tenants's shard size are not reflected immediately, but only after updating token ranges and "owned series".
      Until this happens, old shard size is used for limits computation.

Credits: Original code was created by @pr00se, I continue in his work before he returns.

Which issue(s) this PR fixes or relates to

Fixes #

Checklist

  • Tests updated.
  • Documentation added.
  • CHANGELOG.md updated - the order of entries should be [CHANGE], [FEATURE], [ENHANCEMENT], [BUGFIX].
  • about-versioning.md updated with experimental features.

@pracucci pracucci self-requested a review November 24, 2023 08:55
Copy link
Collaborator

@pracucci pracucci left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice job! The overall logic is what I expected, so no surprises. I left few minor comments. Looking forward for the tests!

pkg/ingester/secondary_hash.go Outdated Show resolved Hide resolved
@@ -629,7 +629,7 @@ func (t *Mimir) initIngesterService() (serv services.Service, err error) {
t.Cfg.Ingester.InstanceLimitsFn = ingesterInstanceLimits(t.RuntimeConfig)
t.tsdbIngesterConfig()

t.Ingester, err = ingester.New(t.Cfg.Ingester, t.Overrides, t.ActiveGroupsCleanup, t.Registerer, util_log.Logger)
t.Ingester, err = ingester.New(t.Cfg.Ingester, t.Overrides, t.Ring, t.ActiveGroupsCleanup, t.Registerer, util_log.Logger)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[non blocking] Unrelated. In a separate PR would be nice to rename t.Ring to t.IngesterRing. This naming was done in a era when we only had ingesters ring, but now we have many rings.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pkg/ingester/ingester.go Outdated Show resolved Hide resolved
pkg/ingester/ingester.go Outdated Show resolved Hide resolved
pkg/ingester/ingester.go Outdated Show resolved Hide resolved
ownedPrev, shardSizePrev := u.OwnedSeriesAndShards()

var ownedNew int
for {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could potentially lead to an infinite loop, if new series are added continuously. I think it's a bad idea. I would limit the max number of attempts.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it can lead to infinite loop, because eventually user will hit the limit :) But I agree with your proposal to allow small difference (say up to 100 series). We can also limit the attempts, set whatever value we have, but also report error to retry later. WDYT?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a best practice we should never have a potentially infinite loop. I don't want to even reason whether could be infinite or not because of "other conditions outside of the loop". However, I've seen you adding a max retries, so LGTM.

pkg/ingester/user_tsdb.go Outdated Show resolved Hide resolved
pkg/ingester/user_tsdb.go Outdated Show resolved Hide resolved
pkg/ingester/user_tsdb.go Outdated Show resolved Hide resolved
Copy link
Collaborator

@pracucci pracucci left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Remember a CHANGELOG
  • Remember to list new experimental config params to docs/sources/mimir/configure/about-versioning.md

pkg/ingester/ingester.go Outdated Show resolved Hide resolved
pkg/ingester/owned_series.go Outdated Show resolved Hide resolved
pkg/ingester/owned_series.go Outdated Show resolved Hide resolved
pkg/ingester/owned_series.go Outdated Show resolved Hide resolved
pkg/ingester/owned_series.go Outdated Show resolved Hide resolved
pkg/ingester/owned_series.go Outdated Show resolved Hide resolved
}

// Updates token ranges and recomputes owned series for user, if necessary. If recomputation happened, true is returned.
func (oss *ownedSeriesService) updateTenant(userID string, db *userTSDB, ringChanged bool) bool {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the logic around reason a bit cumbersome here. Let's talk offline about it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it is cumbersome because it covers many different scenarios. I've extended the comment to explain which scenarios we cover, to help understand why it is like this.

pkg/ingester/user_tsdb.go Outdated Show resolved Hide resolved
pkg/ingester/user_tsdb.go Outdated Show resolved Hide resolved
pr00se and others added 25 commits November 27, 2023 14:16
… series.

Handle situation when number of series changed while recomputing owned series.

Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Update token ranges for new users soon, even if there was no ring change.
Make sure to update shard size if it changed.

Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
…ged, but trigger was set.

Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
…nts.

Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
…ries difference.

Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
@pstibrany pstibrany marked this pull request as ready for review November 27, 2023 13:23
@pstibrany pstibrany requested review from a team as code owners November 27, 2023 13:23
Copy link
Collaborator

@pracucci pracucci left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice job! I haven't found any issue. I left few last minor comments. Thanks!

pkg/ingester/owned_series.go Outdated Show resolved Hide resolved
ownedSeriesCheckDuration: promauto.With(reg).NewHistogram(prometheus.HistogramOpts{
Name: "cortex_ingester_owned_series_check_duration",
Help: "How long does it take to check for owned series for all users.",
Buckets: prometheus.DefBuckets,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think default buckets are appropriate here. We expect way lower timings (e.g. 10s as highest bucket looks infinitely high). I would customise buckets.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm worried that on some of our large cells, this can indeed take several seconds. Let's measure and adjust buckets once we see it working.

pkg/ingester/owned_series.go Outdated Show resolved Hide resolved
pkg/ingester/owned_series_test.go Outdated Show resolved Hide resolved
pkg/ingester/user_tsdb.go Outdated Show resolved Hide resolved
recomputeOwnedSeriesMaxSeriesDiff = 1000
)

func (u *userTSDB) recomputeOwnedSeriesWithComputeFn(shardSize int, reason string, logger log.Logger, compute func() int) (retry bool, _ int) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] It looks a bit weird to me calling the return param retry (even in the callers) when it's effectively a failed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like calling return value "failed" (I prefer "success"), hence I opted for "retry". I will rename it to success instead.

u.ownedSeriesMtx.Unlock()
}

level.Info(logger).Log("msg", "owned series: recomputed owned series for user",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think level should change to warning if we failed to update it after all attempts.

u.ownedSeriesMtx.Unlock()
}

level.Info(logger).Log("msg", "owned series: recomputed owned series for user",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[non blocking] Do we really have to log it for every tenant? I guess it can help you debugging in case of issues for now, but we may consider getting rid of it once feature will be stable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree to remove it in the future.

// Check how many new series were added while we were computing owned series.
seriesDiff := u.ownedSeriesCount - prevOwnedSeriesCount
seriesDiffOk := seriesDiff >= 0 && seriesDiff <= recomputeOwnedSeriesMaxSeriesDiff // seriesDiff should always be >= 0, but in case it isn't, we can try again.
if seriesDiffOk || attempts == recomputeOwnedSeriesMaxAttemps {
Copy link
Collaborator

@pracucci pracucci Nov 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[non blocking] Generally speaking I'm not a big fan of checking for for loop stop conditions inside the loop itself. Have you considered simply changing reportError into success? success gets initialised to false, and switched to true once value gets updated. The for loop could simply change to for !success && attempts < recomputeOwnedSeriesMaxAttemps.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you suggesting to move update of u.ownedSeriesCount and u.ownedSeriesShardSize outside of for loop? Because that would surely be nicer, but then the locking situation is more complicated. Let me try and see.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After our chat, I've reworked the method. PTAL

Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
Signed-off-by: Peter Štibraný <pstibrany@gmail.com>
@pstibrany pstibrany enabled auto-merge (squash) November 28, 2023 11:16
@pstibrany pstibrany merged commit 6f421c7 into main Nov 28, 2023
28 checks passed
@pstibrany pstibrany deleted the owned-series-poc branch November 28, 2023 11:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants