Linear ramping and probabilistic ramping#218
Conversation
Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Prerequisite to envoyproxy#217 Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
…te-limiter Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Modulo an improved test for the batching rate limiter, this pulls off some refactoring for upcoming new rate limiters. Split out off from draft PR envoyproxy#218 Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
…te-limiter Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Modulo an improved test for the batching rate limiter, this pulls off some refactoring for upcoming new rate limiters. This has no functional changes, and is an intermediate step for draft PR #218, which contains new rate limiters (linear & probabilistic ramping, first stab at a zipf distribution support). Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
…te-limiter Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
…-linear-rate-limiter Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
mum4k
left a comment
There was a problem hiding this comment.
Partial review to start the discussion.
source/common/rate_limiter_impl.h
Outdated
| class ZipfRateLimiterImpl : public FilteringRateLimiterImpl { | ||
| public: | ||
| /** | ||
| * From the absl header associated to the zipf distribution: |
There was a problem hiding this comment.
Can we also try to say what q and v are? Either something like "The parameters v and q determine the skew of the distribution." (also copied from the header).
Or copy the relevant portion from the zipf_distribution class'es comment:
zipf_distribution produces random integer-values in the range [0, k], distributed according to the discrete probability function:
P(x) = (v + x) ^ -q
source/common/rate_limiter_impl.h
Outdated
| * If NDEBUG is defined and either or both of these parameters take invalid | ||
| * values, the behavior of the class is undefined. | ||
| */ | ||
| ZipfRateLimiterImpl(RateLimiterPtr&& rate_limiter, bool deterministic, double q = 2.0, |
There was a problem hiding this comment.
(optional) Not sure what we prefer in this codebase. I am generally wary of boolean "flag" arguments since they produce client code that is hard to read.
E.g:
ZipfRateLimiterImpl(rate_limiter, true, v, q) // what does true mean?
Of course the client can name it by creating a local variable. We could consider shaping the API in a way that unreadable usage won't be possible, say by defining a well named enum instead.
E.g.:
enum class ZipfBehavior { ZIPF_DETERMINISTIC, ZIPF_NON_DETERMINISTIC };
// Or maybe a bit more transparent:
enum class ZipfBehavior { ZIPF_PSEUDO_RANDOM, ZIPF_RANDOM };WDYT?
There was a problem hiding this comment.
Maybe we should have a place to put code-level guidance like this?
source/common/rate_limiter_impl.h
Outdated
| /** | ||
| * Thin wrapper around absl::zipf_distribution that will pull zeroes and ones from the distribution | ||
| * with the intent to probabilistically suppress the wrapped rate limiter. | ||
| * This may need further consideration, because it will shoot holes in the pacing, lowering the |
There was a problem hiding this comment.
Did you have any pre-existing discussions on this with @htuch, is this going to be an issue considering the use cases of this rate limiter? What are the use cases of this rate limiter?
…te-limiter Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
- Back out 1 ns adjustment - Update test to use microsecond step resolution now that we can - Add comment with explanation Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
source/common/rate_limiter_impl.h
Outdated
| }; | ||
|
|
||
| /** | ||
| * A rate limiter which linearly ramps up to the desired frequency over the specified period. |
There was a problem hiding this comment.
(nit) Would it make sense to sync the terminology? The comment refers to "the specified period" while the argument is called ramp_time.
source/common/rate_limiter_impl.cc
Outdated
| 100.0 - (static_cast<double>(ramp_time_.count() - elapsed().count()) / | ||
| (ramp_time_.count() * 1.0)) * | ||
| 100.0; | ||
| return std::round(provider_->getValue() / 10000.0) <= chance_percentage; |
There was a problem hiding this comment.
I am trying to understand the fixed division by 10k. It seems to imply some expectations about the discrete numeric distribution provider that don't seem to be specified on the API.
This may be related to my comment that we should add more documentation next to the constructor for the GraduallyOpeningRateLimiterFilter that will explain the arguments and their roles.
| } | ||
|
|
||
| GraduallyOpeningRateLimiterFilter::GraduallyOpeningRateLimiterFilter( | ||
| const std::chrono::nanoseconds ramp_time, DiscreteNumericDistributionSamplerPtr&& provider, |
There was a problem hiding this comment.
Should we validate some of the arguments?
test/rate_limiter_test.cc
Outdated
| // and the rate limiter computes at nanosecond precision internally. As we | ||
| // want to have microsecond level precision, this should be more then sufficient. | ||
| EXPECT_EQ(getAcquisitionTimings(5_Hz, 5s), | ||
| std::vector<int64_t>({1000050, 1732100, 2236100, 2645800, 3000000, 3316650, 3605600, |
There was a problem hiding this comment.
Tests apart from testing are often times used by the readers to understand what the code does. (i.e. as additional documentation). Feels like the values that we chose for these tests don't really help the reader. Additionally the unit of these values isn't obvious since the last change to micro-seconds.
I am wondering if we could choose different parameters than 5Hz over 5sec or maybe add test cases with other parameters that will be easier / more obvious for the reader to comprehend. We don't want to decrease test coverage, but we should aim towards self-documenting and readable test cases. Can we try to find a balance?
There was a problem hiding this comment.
@mum4k I couldn't really obtain easy-to-grok numbers by picking different parameter values here; but how about this: 9d248f2 ?
Instead of matching expectations against hard-coded numbers, that uses the second law of motion to figure out control acquisition timings for comparison, and adds a little slack to when verifying expectations with a comment explaining the need for that. I'm hoping that will help future readers of this code. wdyt, will this be better?
(As a side-effect, this approach also allowed adding tests with higher frequencies / duration arguments)
| return rate_limiter_->tryAcquireOne() ? filter_() : false; | ||
| } | ||
|
|
||
| GraduallyOpeningRateLimiterFilter::GraduallyOpeningRateLimiterFilter( |
There was a problem hiding this comment.
(fyi) Now that I read the code and understand the implementation here, I have to say that this is truly beautiful, including your use of class inheritance for the ForwardingRateLimiter.
I have learned something today and I appreciate it, thanks.
There was a problem hiding this comment.
That is nice to hear, thanks!
- Check expectations on the distribution argument to GraduallyOpeningRateLimiterFilterTest - Clean up the computation in GraduallyOpeningRateLimiterFilter - Add a bunch of explanatory comments What's left is getting easy to grok numbers in test expectations. Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Instead of matching hard-coded expectations for release timings, use the second law of motion as a control method for verifying the acquisition timings the rate limiter. Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
|
@mum4k I think I addressed all comments, so marking this as ready-for-review. Thanks for the review! |
| public: | ||
| virtual ~DiscreteNumericDistributionSampler() = default; | ||
| virtual uint64_t getValue() PURE; | ||
| virtual uint64_t min() const PURE; |
There was a problem hiding this comment.
Can we document the new methods?
(optional / unrelated) Since we are here, can we document the entire interface?
| 1.0 - static_cast<double>(ramp_time_.count() - elapsed_time.count()) / | ||
| (ramp_time_.count() * 1.0); | ||
| // Get a random number r, where 0 < r ≤ 1. | ||
| const double random_between_0_and_1 = 1.0 * provider_->getValue() / provider_->max(); |
There was a problem hiding this comment.
Thank you, this is easier to follow with the new comments and the min() and max() methods.
test/rate_limiter_test.cc
Outdated
| public: | ||
| std::vector<int64_t> getAcquisitionTimings(const Frequency frequency, | ||
| const std::chrono::seconds duration) { | ||
| void checkAcquisitionTimings(const Frequency frequency, const std::chrono::seconds duration) { |
There was a problem hiding this comment.
Can we document this helper? Specifically its arguments.
| 3873000, 4123150, 4358900, 4582600, 4795850, 5000000})); | ||
| EXPECT_EQ(getAcquisitionTimings(4_Hz, 2s), | ||
| std::vector<int64_t>({707150, 1224750, 1581150, 1870850})); | ||
| checkAcquisitionTimings(1_Hz, 3s); |
There was a problem hiding this comment.
This is indeed much better for readability, but i am debating with myself whether it doesn't decrease our coverage. Could there later be a bug that would go undetected? Should we add at least one test case that verifies the exact values as before? WDYT?
…te-limiter Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Signed-off-by: Otto van der Schaaf <oschaaf@we-amp.com>
Contains new RateLimiter features:
Precise and relatively easy to reason about
Useful for ramping up when wrapping an underlying rate limiter which is non-deterministic,
and possibly in distributed scenarios as well to add some entropy / jitter.
This needs a bit of polish, but ideally we discuss this early so we can cherry pick what we like
from this and possibly discard what we don't.
TODO:
RampingLinearRateLimitertake a ramp duration argument & final frequency to unify the interface with the probabilistic filtering oneIn a follow up we can wire up options for these new rate limiters.
(May also be done in a follow-up PR instead).
In conjunction with the upcoming Phases this would allow implementing a better warmup phase, which ramps up to the targeted frequency.
Groundwork for #31