8000 ingest-storage: Use pooled timeseries slices when deserializing by alexweav · Pull Request #11393 · grafana/mimir · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

ingest-storage: Use pooled timeseries slices when deserializing #11393

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

alexweav
Copy link
Contributor
@alexweav alexweav commented May 5, 2025

What this PR does

The pusherConsumer deserializes directly into a &mimirpb.WriteRequest{} by calling the generated Unmarshal. This path will fetch invidiual PreallocTimeseries from the pool, but not the timeseries slice itself, which is subject to the usual extra allocations when appending.

This PR uses a slice from the pool in addition to the contained objects.

There are 2 possible paths after we take from the pool, depending on configuration.

  • The sequentialStoragePusher, which forwards the request straight to the ingester. The ingester frees the slice and its contents. Our contents came from the pool but prior to this PR the slice did not, so we were putting the externally created slice back into the sync.Pool on each request. sync.Pool has an internal safeguard against it growing infinitely by periodically releasing things to GC, so it doesn't leak, but now we can use it for its intended purpose and reduce churn.
  • The parallelStoragePusher, which rearranges the timeseries into batches. The batch slice is already fetched from the pool. That means we can free our serialization slice (but not the contents!) when we're done rearranging it. The ingester will free the batch slice and all the timeseries as usual.

The main purpose of this PR is more of a refactor than an optimization. The block builder already uses a timeseries slice from the pool here. By aligning what we fetch from pools, I can move toward using the same deserialization logic in both services, which I'd like to do for record format V2. The reduced allocations are just a bonus.

TL;DR fewer allocations, supports a future change

go test ./pkg/storage/ingest/... -run $^ -bench BenchmarkPusherConsumer -benchtime=10s -benchmem

Before:

BenchmarkPusherConsumer/sequential_pusher-16         	   20348	    592474 ns/op	  826381 B/op	     933 allocs/op
BenchmarkPusherConsumer/parallel_pusher-16           	    7610	   1519643 ns/op	 1020897 B/op	    1952 allocs/op

After:

BenchmarkPusherConsumer/sequential_pusher-16         	   31896	    385991 ns/op	   28848 B/op	     463 allocs/op
BenchmarkPusherConsumer/parallel_pusher-16           	    9435	   1246078 ns/op	  208315 B/op	    1502 allocs/op

Which issue(s) this PR fixes or relates to

Rel https://github.com/grafana/mimir-squad/issues/2253

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.

@alexweav alexweav marked this pull request as ready for review May 5, 2025 23:03
@alexweav alexweav requested a review from a team as a code owner May 5, 2025 23:03
8000
@@ -395,6 +397,10 @@ func labelAdaptersHash(b []byte, ls []mimirpb.LabelAdapter) ([]byte, uint64) {
// PushToStorage ignores SkipLabelNameValidation because that field is only used in the distributor and not in the ingester.
// PushToStorage aborts the request if it encounters an error.
func (p *parallelStorageShards) PushToStorage(ctx context.Context, request *mimirpb.WriteRequest) error {
// We're moving Timeseries into batches, each batch has a fresh timeseries slice.
// We're done with the slice in the request here, the contents will live on in the batch and be freed when the batch is freed.
defer mimirpb.ReuseTimeseriesSliceDangerous(request.Timeseries)
Copy link
Contributor

Choose a reason for hiding this comment

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

Consume is responsible for allocating from the pool, then PushToStorage frees back to the pool with the assumption that Consume is the caller. Not super familiar with the details, but can Consume do the freeing for symmetry? Like right after it calls c.pushToStorage(...).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It would definitely be cleaner to just free where it was allocated. Let's see if I can get it into that form or if I run into issues.

The trickiest part I see so far is we still have some implementation specific behavior making it hard to factor out. The parallelStoragePusher might collapse N requests into 1 batch, so you have N slices to free in that path, but only 1 slice to free in the sequential path.

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.

2 participants
0