Skip to content
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

Make requests emit metrics when rate limited #423

Merged
merged 3 commits into from
Nov 12, 2024

Conversation

andrew4699
Copy link
Contributor

@andrew4699 andrew4699 commented Nov 5, 2024

Description

Makes requests emit metrics when they get rate limited.

There were 2 issues preventing this:

  • RateLimiterFilter ran outside the Jersey Servlet, which is before application events get sent
  • Changing it to run inside the servlet isn't enough. Aborted requests don't emit the RESOURCE_METHOD_START event.

This PR makes the rate limiting filter a Jersey Servlet filter and makes the per-request metric emitter emit count metrics for requests that have a REQUEST_MATCHED But not RESOURCE_METHOD_START event.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

  • See the test that verifies emitted metrics

Checklist:

Please delete options that are not relevant.

  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • If adding new functionality, I have discussed my implementation with the community using the linked GitHub issue

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** Request filter that returns a 429 Too Many Requests if the rate limiter says so */
@Priority(Priorities.AUTHORIZATION + 1)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fun fact: this actually did nothing previously. It's a lucky coincidence that this ran after the ContextResolverFilter due to addFilter ordering.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing this moves it back to USER right?

Copy link
Contributor Author

@andrew4699 andrew4699 Nov 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Previously I don't think it did anything because it was outside Jersey. Also we don't seem to have any Jersey filters so the priority doesn't really matter currently.

environment
.servlets()
.addFilter("ratelimiter", new RateLimiterFilter(configuration.getRateLimiter()))
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
environment.jersey().register(new RateLimiterFilter(configuration.getRateLimiter()));
Copy link
Contributor

@eric-maynard eric-maynard Nov 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a semantic change unrelated to the metrics. If I'm reading this correctly, this changes the rate limiter to only apply to Jersey resources. Do we want that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes because TimedApplicationEventListener, which is what emits metrics on requests, depends on Jersey events.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get that, but my concern is whether this actually changes the behavior of the system by rate-limiting less things now

Copy link
Contributor Author

@andrew4699 andrew4699 Nov 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Please fact check me as I only started working with Java web services recently)

I think all of our endpoints are in Jersey, so it shouldn't change the scope, but at a minimum it does push rate limiting further into the request handling chain.

The fundamental problem seems to be that only Jersey knows how to map a request path to its handling method (and thus its metric name). If that mapping was available everywhere, most of TimedApplicationEventListener could be re-written as a top level filter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semantically, there is a difference, as ServletFilters are applied before Jersey Filters. Practically, however, it shouldn't have an impact. I think the only real dependency the RateLimiter might have is that the RealmContext is set in case there are different rate limits per realm. Since the ContextResolverFilter still applies first, we shouldn't see a practical difference.

.servlets()
.addFilter("ratelimiter", new RateLimiterFilter(configuration.getRateLimiter()))
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
environment.jersey().register(new RateLimiterFilter(configuration.getRateLimiter()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is unrelated to metrics, right ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -66,16 +66,16 @@ private class TimedRequestEventListener implements RequestEventListener {
@Override
public void onEvent(RequestEvent event) {
String realmId = CallContext.getCurrentContext().getRealmContext().getRealmIdentifier();
if (event.getType() == RequestEvent.Type.RESOURCE_METHOD_START) {
if (event.getType() == RequestEvent.Type.REQUEST_MATCHED) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, this listener to @TimedApi (populated in mustache template) is not required with Quarkus.

> 0);
}

private double getCounter(String metricName) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

environment
.servlets()
.addFilter("ratelimiter", new RateLimiterFilter(configuration.getRateLimiter()))
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
environment.jersey().register(new RateLimiterFilter(configuration.getRateLimiter()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semantically, there is a difference, as ServletFilters are applied before Jersey Filters. Practically, however, it shouldn't have an impact. I think the only real dependency the RateLimiter might have is that the RealmContext is set in case there are different rate limits per realm. Since the ContextResolverFilter still applies first, we shouldn't see a practical difference.

@eric-maynard eric-maynard enabled auto-merge (squash) November 12, 2024 18:01
@eric-maynard eric-maynard merged commit 455423d into apache:main Nov 12, 2024
5 checks passed
@andrew4699 andrew4699 deleted the aguterman-rate-limiter-metrics branch November 12, 2024 21:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants