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

Use specific types when reading offset table #3393

Merged
merged 7 commits into from
Nov 7, 2022

Conversation

colega
Copy link
Contributor

@colega colega commented Nov 4, 2022

What this PR does

Modified the index.ReadOffsetTable function to use a specific type instead of a slice, that escapes to heap (for every label value in the postings offset table).

I will upstream this change to Prometheus, and then use the upstreamed version again when it's available, but wanted to bring it here first, especially because we already have benchmarks for this.

name          old time/op    new time/op    delta
BinaryReader    42.7µs ± 3%    33.9µs ± 8%  -20.63%  (p=0.008 n=5+5)

name          old alloc/op   new alloc/op   delta
BinaryReader    41.7kB ± 0%    35.3kB ± 0%  -15.31%  (p=0.000 n=5+4)

name          old allocs/op  new allocs/op  delta
BinaryReader       623 ± 0%       423 ± 0%  -32.10%  (p=0.008 n=5+5)

I also added a benchmark with a larger block size:

name                                old time/op    new time/op    delta
BinaryReader_LargerBlock/benchmark    2.72ms ± 4%    1.89ms ± 3%  -30.51%  (p=0.008 n=5+5)

name                                old alloc/op   new alloc/op   delta
BinaryReader_LargerBlock/benchmark    1.70MB ± 0%    1.06MB ± 0%     ~     (p=0.079 n=4+5)

name                                old allocs/op  new allocs/op  delta
BinaryReader_LargerBlock/benchmark     40.2k ± 0%     20.1k ± 0%  -49.93%  (p=0.008 n=5+5)

Which issue(s) this PR fixes or relates to

None, just was surfing the code.

Checklist

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

Signed-off-by: Oleg Zaytsev <mail@olegzaytsev.com>
Instead of reading a generic-ish []string, we can read a generic type
which would be specifically labels.Label.

This avoid allocating a slice that escapes to the heap, making it both
faster and more efficient in terms of memory management.

name          old time/op    new time/op    delta
BinaryReader    42.7µs ± 3%    33.9µs ± 8%  -20.63%  (p=0.008 n=5+5)

name          old alloc/op   new alloc/op   delta
BinaryReader    41.7kB ± 0%    35.3kB ± 0%  -15.31%  (p=0.000 n=5+4)

name          old allocs/op  new allocs/op  delta
BinaryReader       623 ± 0%       423 ± 0%  -32.10%  (p=0.008 n=5+5)

Signed-off-by: Oleg Zaytsev <mail@olegzaytsev.com>
@colega colega requested a review from a team as a code owner November 4, 2022 13:47
Signed-off-by: Oleg Zaytsev <mail@olegzaytsev.com>
@colega colega marked this pull request as draft November 4, 2022 14:41
I initially assumed that we can just check for label emptiness, but we
can't as there's a special entry in the postings offsets table that is
an empty name/value label that corresponds to the "all postings" entry.

Signed-off-by: Oleg Zaytsev <mail@olegzaytsev.com>
Copy link
Contributor

@56quarters 56quarters left a comment

Choose a reason for hiding this comment

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

Changes seem reasonable to me but I'm not a TSDB expert

// ReadOffsetTable reads an offset table and at the given position calls f for each
// found entry. If f returns an error it stops decoding and returns the received error.
// TODO: use the upstreamed version from Prometheus tsdb/index package when available.
func readOffsetTable[T any](bs index.ByteSlice, off uint64, read func(d *encoding.Decbuf) (T, error), f func(T, uint64, int) error) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Does this need to be generic? It's always called with labels.Label, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since I'm upstreaming the change to Prometheus I want to keep code exactly the same to just make a drop-in replacement later.

But meh, maybe you're right, it will be very easy to replace it anyway. Removing in 8633e9b

We'll bring them back once we start using the Prometheus version.

Signed-off-by: Oleg Zaytsev <mail@olegzaytsev.com>
@colega colega marked this pull request as ready for review November 4, 2022 15:16
Copy link
Member

@pstibrany pstibrany left a comment

Choose a reason for hiding this comment

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

Looks good, thanks!

@@ -533,84 +534,79 @@ func newFileBinaryReader(path string, postingOffsetsInMemSampling int, cfg Binar
return nil, errors.Wrap(err, "read symbols")
}

var lastKey []string
var lastLbl labels.Label
var lastSet bool
Copy link
Member

Choose a reason for hiding this comment

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

super-nit: lastSet := false (it's just more explicit)

Copy link
Contributor Author

@colega colega Nov 7, 2022

Choose a reason for hiding this comment

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

This was an internal fight for me, being more explicit vs being consistent with the var declaration above.

Changed in 1d2cfb6

prevRng = index.Range{Start: int64(off + postingLengthFieldSize)}
return nil
}); err != nil {
return nil, errors.Wrap(err, "read postings table")
}
if lastKey != nil {
if lastLbl.Name != "" {
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be: if lastSet ? (Although it doesn't change the logic, unless someone writes postings for empty label name)

Copy link
Contributor Author

@colega colega Nov 7, 2022

Choose a reason for hiding this comment

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

You're right, I'll change it.

Empty label name is a valid case for "all postings" entry, but if it's last, it would be just a noop here anyway.

Applied in 30b5533

r.postings[lastKey[0]].lastValOffset = int64(off - crc32.Size)
lastKey = nil
r.postings[lastLbl.Name].lastValOffset = int64(off - crc32.Size)
lastLbl = labels.Label{}
Copy link
Member

Choose a reason for hiding this comment

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

Why not lastSet = false? (Or: why is this even needed? We overwrite lastSet and lastLbl shortly anyway).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Even if not strictly needed, doing lastSet = false here may better explain the intention.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Instead of adding yet another useless line, I just removed this lastLbl = labels.Label{} line.

Once you have a last, you can't unhave it.

30b5533

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(Please can you ACK that you're okay with this change so I can merge?)

Copy link
Member

Choose a reason for hiding this comment

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

works for me.

Copy link
Collaborator

Choose a reason for hiding this comment

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

ACK

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.

LGTM (modulo Peter's comments)

r.postings[lastKey[0]].lastValOffset = int64(off - crc32.Size)
lastKey = nil
r.postings[lastLbl.Name].lastValOffset = int64(off - crc32.Size)
lastLbl = labels.Label{}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Even if not strictly needed, doing lastSet = false here may better explain the intention.

Signed-off-by: Oleg Zaytsev <mail@olegzaytsev.com>
Signed-off-by: Oleg Zaytsev <mail@olegzaytsev.com>
@colega colega merged commit 5d144a5 into main Nov 7, 2022
@colega colega deleted the use-specific-types-when-reading-offset-table branch November 7, 2022 14:07
masonmei pushed a commit to udmire/mimir that referenced this pull request Dec 16, 2022
* Add BenchmarkBinaryReader_LargerBlock

Signed-off-by: Oleg Zaytsev <mail@olegzaytsev.com>

* Use specific types when reading offset table

Instead of reading a generic-ish []string, we can read a generic type
which would be specifically labels.Label.

This avoid allocating a slice that escapes to the heap, making it both
faster and more efficient in terms of memory management.

name          old time/op    new time/op    delta
BinaryReader    42.7µs ± 3%    33.9µs ± 8%  -20.63%  (p=0.008 n=5+5)

name          old alloc/op   new alloc/op   delta
BinaryReader    41.7kB ± 0%    35.3kB ± 0%  -15.31%  (p=0.000 n=5+4)

name          old allocs/op  new allocs/op  delta
BinaryReader       623 ± 0%       423 ± 0%  -32.10%  (p=0.008 n=5+5)

Signed-off-by: Oleg Zaytsev <mail@olegzaytsev.com>

* Update CHANGELOG.md

Signed-off-by: Oleg Zaytsev <mail@olegzaytsev.com>

* Use a specific flag to signal that lastLbl was set

I initially assumed that we can just check for label emptiness, but we
can't as there's a special entry in the postings offsets table that is
an empty name/value label that corresponds to the "all postings" entry.

Signed-off-by: Oleg Zaytsev <mail@olegzaytsev.com>

* Remove generics as we don't need them now

We'll bring them back once we start using the Prometheus version.

Signed-off-by: Oleg Zaytsev <mail@olegzaytsev.com>

* Apply rewiew comments

Signed-off-by: Oleg Zaytsev <mail@olegzaytsev.com>

* Check for lastSet

Signed-off-by: Oleg Zaytsev <mail@olegzaytsev.com>

Signed-off-by: Oleg Zaytsev <mail@olegzaytsev.com>
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.

4 participants