-
Notifications
You must be signed in to change notification settings - Fork 92
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
ref(metrics): Rework metrics aggregator to keep internal partitions #4378
Conversation
53da55e
to
cc3619e
Compare
34b71b3
to
d5498e3
Compare
d5498e3
to
576014c
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.
Looks very nice!
let slot = time_slot * u64::from(self.num_partitions) + assigned_partition; | ||
|
||
let slots_len = self.slots.len() as u64; | ||
let index = (slot + slots_len).wrapping_sub(self.head % slots_len) % slots_len; |
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.
Naively I would expect this to be (slot - self.head) % slots_len
.
Is the + slots_len
here to make the wrapping sub work in case slot < self.head
? This operation is complex enough to warrant some documentation, and / or a helper function.
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.
What we need here is rem_euclid
in case slot - self.head
is negative, but for that we need to go from u64
to i64
which truncates the size of the u64
down to u64 / 2
. This is a way to shift the operation entirely into the positive space.
Will add some docs, and/or a function, this is double confusing since the wrapping_sub
isn't necessary but still here (it can't wrap).
let slot = time_slot * u64::from(self.num_partitions) + assigned_partition; | ||
|
||
let slots_len = self.slots.len() as u64; | ||
let index = (slot + slots_len).wrapping_sub(self.head % slots_len) % slots_len; |
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.
What happens with different time stamps that map to the same index? Is that case prevented by how slots.len()
is chosen in the beginning?
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 fine and will happen for backdated und future buckets, there are tests that cover this case. Since the timestamp is part of the key, they won't be aggregated together and are independent.
slot.buckets.hasher().clone(), | ||
), | ||
..slot | ||
}); |
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 wonder if this would be simpler with an actual statically sized ring buffer. Then you could reset the slot at head
and move the head, without pop / push
.
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 also get the impression that this implementation has features of a queue and a static ring buffer simultaneously.
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.
You can rotate the VecDeque
, initially this is what I had, but it turns out it's a bit nicer this way. We need ownership of parts of the slot (e.g. all buckets), with a rotation we then have to std::mem::replace
the parts we need ownership of. We also don't get around a fallible access/unwrap like here, since we still need a bounds check.
Removing the item (which just shifts indices internally and returns the value) and adding a new one back turned out to be nicer.
d730f51
to
ab2dcc1
Compare
f16e425
to
d8ea0fb
Compare
slot.buckets.hasher().clone(), | ||
), | ||
..slot | ||
}); |
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 also get the impression that this implementation has features of a queue and a static ring buffer simultaneously.
15c6dfc
to
49ac131
Compare
0ae810c
to
930eb06
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.
The PR is pretty big so I probably missed some details, but overall the design makes sense & I'm looking forward to see how this performs in production.
The rollout is probably gonna be merge -> test -> revert?
// threaded runtime. | ||
self.do_try_flush() | ||
} else { | ||
tokio::task::block_in_place(|| self.do_try_flush()) |
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.
What does this do? I'm reading the docs of block_in_place
but I don't understand its purpose.
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.
Might not need this anymore, but the previous aggregators' max on the flush was up to a 200ms. Since we run in a tokio task here, we're blocking all queued other tasks on this tokio worker for the duration of the call. With block_in_place
we can tell the runtime to clear it's local queue before running the closure. This in theory should bring down p99+ latencies.
Merge, test, it works, rollout prod ideally. Let's talk tomorrow sync about it? |
Replaces the current metrics aggregator, which works based off fixed priorities in a priority queue with regular intervals, with a ring buffer based aggregator.
Overview:
ahash
with a fixed seed instead of fnv (it's faster)For implementation details see the exhaustive code documentation, especially in the inner aggregator.
Fixes: https://github.com/getsentry/team-ingest/issues/606