Closed shanson7 closed 2 years ago
We could also (in addition to your solution, or as an alternative to your solution) allocate the slice of points at ROB creation time. and reuse that same slice every time we need it to return the slice out of Add().
I suppose the issue with that is we're holding on to a bunch of memory we only use occasionally. That could in turn be solved by each ROB sharing a pool of slices, but at that point, I prefer your approach.
Yeah, that was my first step but the extra memory was too much (basically doubling the ROB size). It's also a slightly awkward interface from a multi-threaded context, though that isn't a big deal for this code.
Underflow Bug
This was not my main goal, but was breaking the benchmarks. This bug is unlikely in production because it requires very old timestamps.
The line
rob.buf[rob.newest].Ts-(uint32(cap(rob.buf))*rob.interval
can cause an underflow whenrob.buf[rob.newest].Ts
is smaller thancap(rob.buf)*rob.interval
and will wrap around to a very large number. This incorrectly returns and error (metric too old). The benchmarks all start at Ts=1 so they were never actually getting beyond this check. I added a test case for this as well.Reduce Allocations
One of the top allocators in our Metrictank deploy is the ROB. It makes an allocation if any point is flushed during
Add
. This is due to how it returns the flushed values fromAdd
in a slice. However, in the most common case data comes in order and will flush exactly 1 point. This change is to return aschema.Point
along with the slice. If a single point needs to be flushed, no slice needs to be allocated. If more than one is flushed, then we still need to allocate the slice. However, I also pre-allocate the slice to the size needed, which speeds up even the out-of-order cases.This change does make the code a little uglier, but it is quite a bit faster and reduces allocations.