-
Notifications
You must be signed in to change notification settings - Fork 91
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
feat(stats): Emit outcomes for applied rate limits #951
Conversation
0d6e45f
to
cdf9933
Compare
TODO: - Fix derive(Debug) on CheckEnvelope - Add envelope summary updates per existing "TODO" comments - Integration test updates
cdf9933
to
3c556b6
Compare
relay-server/src/actors/project.rs
Outdated
fn emit_rate_limit_outcomes(&self, applied_limits: &RateLimits) { | ||
for applied_limit in applied_limits.iter() { | ||
if applied_limit.categories.is_empty() { | ||
// Empty categories value indicates that the rate limit applies to all data. |
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.
Per https://github.com/getsentry/relay/blob/master/relay-quotas/src/rate_limit.rs#L140-L141. So far I have only blind faith in that comment to support that this is correct/necessary behavior, but maybe the integration tests will clarify things as I continue digging.
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 believe this approach is problematic since you cannot reconstruct the accurate drop reason from the RateLimits
structure, unfortunately. This is an inherent flaw of RateLimits
and we can consider changing that, too.
To illustrate, consider the following example:
- There is a single quota
category:error limit:0
. It drops all error events. EnvelopeLimiter
will also drop all attachments in the same envelope as a result of that.- When you check
RateLimits
here, you won't find theattachment
category, even though you just dropped them.
From the top of my head, I have two ideas to solve that:
- Move emitting outcomes into
EnvelopeLimiter
, because that's where you can keep track of the dropped quantities. - From
EnvelopeLimiter::enforce
, return an instance ofEnvelopeSummary
that contains the dropped quantities. Then, use that summary (+ scoping) to emit outcomes. This approach is preferable as it decouples concerns.
Revised to-do list:
The current integration test failures are because there are redundant outcomes in the event category. |
relay-server/src/actors/events.rs
Outdated
Some(envelope) => Ok(envelope), | ||
Some(envelope) => { | ||
// TODO: Fix scope problem and uncomment | ||
// envelope_summary.replace(EnvelopeSummary::compute(&envelope)); |
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.
@jan-auer Can you advise on this? I'm not sure whether I need to propagate envelope_summary
through all the and_then
closures up to this point, enclose them all in one big parent closure (which is what makes this simpler in common.rs
, AFAICT), or something else.
Update: Never mind, resolved in be73b2b. That turned out to be embarrassingly simple. 🤦 (In my defense, I had to take another pass at wrapping my head around what clone!
does in order to understand why that works. Though I'm a little surprised I hadn't tried the same thing by accident already.)
relay-server/src/actors/events.rs
Outdated
None => { | ||
envelope_summary.replace(EnvelopeSummary::empty()); | ||
Err(ProcessingError::RateLimited(rate_limits)) | ||
} |
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 believe at least one integration test is failing because a rate-limited item is still in the envelope here, making the outcome emitted below redundant to the new one. That means that updating the envelope summary is essentially a no-op. From debug logs, it looks like the item is being correctly removed by RateLimit::enforce
, so I'm not clear why a non-empty item list would in the CheckedEnvelope
. The order of the debug logs also implies that RateLimit::enforce
is happening after this code section, so I seem to be misunderstanding something here.
[Update: This may have changed something but I think the tests are still failing for the same reason.)
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.
There is indeed one case in which we retain rate limited items. Minidump attachments are both errors and attachments. It works like this:
- Assume an organization has run out of attachment quota, we will need to reject all attachments now.
- A minidump comes in. Since the organization still has errors quota, we need to process the minidump.
- We check the rate limiter and it tells us to drop the attachment. Now we take two actions:
- We emit an outcome for the dropped attachment.
- Leave the minidump item in but mark it as "rate_limited" in its header.
- In all subsequent rate limiting checks, the rate limited item is ignored since we already emitted an outcome.
- After processing, we store the processed event but drop the attachment without another outcome.
EnvelopeSummary
already has a check to compensate for that and ignores items with the rate_limited
header set. However, we might have missed a case there.
relay-server/src/actors/project.rs
Outdated
event_id: Option<EventId>, | ||
remote_addr: Option<IpAddr>, |
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 could move these two into EnvelopeSummary
. It looks like this type is a utility that would fit well into utils::rate_limits
relay-server/src/actors/project.rs
Outdated
fn emit_rate_limit_outcomes(&self, applied_limits: &RateLimits) { | ||
for applied_limit in applied_limits.iter() { | ||
if applied_limit.categories.is_empty() { | ||
// Empty categories value indicates that the rate limit applies to all data. |
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 believe this approach is problematic since you cannot reconstruct the accurate drop reason from the RateLimits
structure, unfortunately. This is an inherent flaw of RateLimits
and we can consider changing that, too.
To illustrate, consider the following example:
- There is a single quota
category:error limit:0
. It drops all error events. EnvelopeLimiter
will also drop all attachments in the same envelope as a result of that.- When you check
RateLimits
here, you won't find theattachment
category, even though you just dropped them.
From the top of my head, I have two ideas to solve that:
- Move emitting outcomes into
EnvelopeLimiter
, because that's where you can keep track of the dropped quantities. - From
EnvelopeLimiter::enforce
, return an instance ofEnvelopeSummary
that contains the dropped quantities. Then, use that summary (+ scoping) to emit outcomes. This approach is preferable as it decouples concerns.
TODO: Integration test changes still needed?
relay-server/src/actors/project.rs
Outdated
let rate_limits = envelope_limiter.enforce(&mut envelope, scoping)?; | ||
let rate_limits = envelope_limiter.enforce(&mut envelope, scoping, |outcome| { | ||
self.outcome_producer.do_send(outcome) | ||
})?; |
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'd like to be able to just pass self.outcome_producer
to enforce
rather than this awkward closure. I did it this way to accommodate the EnvelopeLimiter
unit tests, so that we can use a simple closure as a mock in place of the outcome producer. Let me know if you can suggest a better way to mock out the outcome producer.
The goal of injecting the outcome producer into enforce
is to obviate the RateLimitEnforcement
struct, as in d4af165. I'm not certain it's worth it. If it isn't, we can just revert that commit.
[EDIT: Never mind, it broke some stuff I hadn't noticed. I've reverted it. The aforementioned commit is still in the history if you want to explore my idea but I don't think it's a high priority at all.]
7279497
to
d1d2ff3
Compare
* master: feat(stats): Emit outcomes for applied rate limits (#951) release: 21.3.1
Emit outcomes to represent events and attachments being removed from an
envelope by project rate limits. The main difference is in
EnvelopeLimiter
, which returns a tuple ofEnforcement
andRateLimits
used for emitting outcomes:limited with the individual reason codes that caused rate limiting. If
multiple rate limits applied to a category, then the longest limit is
reported.
have been applied to items in the envelope.
Example
Interaction between Events and Attachments
An envelope with an
Error
event and anAttachment
. Two quotasspecify to drop all attachments (reason
"a"
) and all errors(reason
"e"
). The result of enforcement will be:reason
"e"
, since dropping an event automatically drops allattachments with the same reason.
"e"
, since attachmentlimits do not need to be checked in this case.
Required Attachments
An envelope with a single Minidump
Attachment
, and a single quotaspecifying to drop all attachments with reason
"a"
:it remains in the envelope and is marked as
rate_limited
."a"
.attachments even when rate limited.
Previously Rate Limited Attachments
An envelope with a single item marked as
rate_limited
, and a quotaspecifying to drop everything with reason
"d"
:stage in the pipeline.