-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
Throttling logging appender #2384
Conversation
I haven't looked too close at the impl, but have you tested to see if Guava's RateLimiter is sufficient? |
The problem with Guava's RateLimiter is that it will not allow small bursts of messages without blocking, so I went for a simple implementation based on a ring buffer of timestamps. It does not block until the time window is "full". |
Oh man, now I'm kinda torn between the two philosophies. I have an example in my head: Let's say you set a throttle 100 messages over 10 seconds (ie: average 10 messages a second), but the actual log rate is 100 per second. With this impl, you see all 100 messages for the first second, but the logs are then silent for 9 seconds. With guava, there is no silent period, but if the log rate of 100 per second lasts only 1 second and subsequently drops to 0 then guava will drop 90% of the logs in that 10 second period. So each impl appears to have pros and cons. But in my opinion, the guava ratelimiter seems in better control of bursts -- especially if the infrastructure hosting the logs is measured / charged in ops, the ratelimiter would ensure those ops weren't exceeded. There is also no period of time with the ratelimiter where all logs are dropped until the next window. Can you comment as to why you prefer one side over the other, so we get the whole story 😄 |
Your approach using ops makes sense as well. However, I have two problems with
|
I think if you're implying that the RateLimiter prefers under-utilization -- I believe the opposite is true: You can see in this sample that even though I only allow one qps, I can immediately acquire 1000 seconds worth: final RateLimiter rateLimiter = RateLimiter.create(1);
final boolean acquired = rateLimiter.tryAcquire(1000);
assertThat(acquired).isTrue(); Subsequent requests will not be successfully acquired for the next 999 seconds. So for those 999 seconds, the rate limiter is over-utilized. The example is a bit contrived (we're not trying to acquire 1000 at a time), but is demonstrative. On the topic of under-utilization and bursts, the final RateLimiter rateLimiter = RateLimiter.create(1);
Thread.sleep(1000);
final boolean acquired = rateLimiter.tryAcquire();
final boolean acquired2 = rateLimiter.tryAcquire();
assertThat(acquired).isTrue();
assertThat(acquired2).isTrue(); Without the
I don't think this is accurate. The javadoc has Not trying to push the use of |
My bad, I did not read thoroughly the documentation: |
@@ -137,6 +144,17 @@ public void setDiscardingThreshold(int discardingThreshold) { | |||
this.discardingThreshold = discardingThreshold; | |||
} | |||
|
|||
@JsonProperty | |||
@Nullable | |||
public BigDecimal getMaxMessagesPerSecond() { |
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.
Are there benefits to using a BigDecimal
when it is converted into a double
straightaway?
@@ -111,6 +114,8 @@ | |||
|
|||
private int discardingThreshold = -1; | |||
|
|||
private double maxMessagesPerSecond = -1; |
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 can't help but think if someone set
maxMessagesPerSecond: -2
that this should fail fast instead of ignoring it (eg. the dash was a typo). So I was thinking of using a validation constraint for the range (0, ∞). Unfortunately the Min
annotation cannot represent an exclusive range, and DecimalMin
isn't technically supported for double
according to the bean validation spec -- but Hibernate Validator might support it
so you might want to try something like:
@DecimalMin(value = "0", inclusive = false)
private Double maxMessagesPerSecond;
And write a test that ensures that validation fails if someone enters 0 or below. What do you think?
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.
Sounds good. It must be @Nullable
as well, I guess.
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.
Excellent, looks in great shape!
@@ -111,6 +115,10 @@ | |||
|
|||
private int discardingThreshold = -1; | |||
|
|||
@Nullable | |||
@DecimalMin(value="0", inclusive = false) | |||
private Double maxMessagesPerSecond; |
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.
does it make sense to support a fractional number of messages per second instead of making this an Integer
or even an OptionalInt
?
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.
Yeah fractional makes sense, if you want 30 messages per minute, you'd set to this 0.5
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.
It’s too bad a Duration wouldn’t work here somehow
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 think you could be on to something, you could have it like (may not be the best name):
messageThrottle: 1ms
Here you'd be throttling logging to 1 message per millisecond.
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.
Exactly what I thought. Duration has a count and time unit which we need in this case even though it’s not the best name.
Following your comments, I amended the PR to use a throttle defined by a |
Excellent! Does anyone have suggestions for a name other than
|
|
Slight slip-up, |
@nickbabcock this looks good to me 👍 Great job @ochedru! |
We also need to update the release notes with this change. |
Updated in bfce31f |
Extract `ThrottlingAppenderWrapper` from Dropwizard and keep it as a separate (tiny) project. Refs dropwizard/dropwizard#2376 Refs dropwizard/dropwizard#2384 Refs dropwizard/dropwizard#2458
See #2376.
This PR implements a throttling mechanism for logging appenders.
The existing parameters limiting the queue size (
queueSize
anddiscardingThreshold
) do not prevent the application from flooding a remote logging service. Such services usually become expensive when their usage quota is exceeded.The proposed feature aims at providing a safety net when the application logging goes out of control.
Two new logging configuration parameters are introduced and can apply to any logging appender:
throttlingTimeWindow
is aDuration
defining a sliding window for throttling. By default, it is not set and throttling is disabled.maxMessagesPerThrottlingTimeWindow
is the maximum number of messages sent during the throttling time window. Once this number is reached, messages are silently discarded until there is room for new messages in the sliding time window.