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
Give high priority to the innermost MetricCollectingService
#4908
Conversation
Motivation: If duplicate `MetricCollectingService`s are used to decorate a service, the first encounted `MetricCollectingService` is picked to collect metrics. https://github.com/line/armeria/blob/0df139a41cac430b3805f2774ac6d0c8ca4b4a68/core/src/main/java/com/linecorp/armeria/internal/common/metric/RequestMetricSupport.java#L57-L59 This strategy has one downside that users can't override a globally used `MetricCollectingService` with a local decorator that is directly applied to a service. If we give high priority to the `MetricCollectingService` closest to a service, users will be able to easily override the global one. Modifications: - Fixed `MetricCollectingService` to look for an inner `MetricCollectingService` before setting up request metrics. - If an inner `MetricCollectingService` is found, delegate the decorator to collect metrics. - Moved `RouteDecoratingService` to `internal` package. - Added `RouteDecoratingService.as(ctx, serviceClass)` to find a matching decorator from the internal service chain. Result: - The innermost `MetricCollectingService` is now choosen to measure metrics for a service. - Previously, the outermost one is used to collect metrics.
Can we avoid calling |
armeria/core/src/main/java/com/linecorp/armeria/internal/server/RouteDecoratingService.java Lines 146 to 150 in 379e29f
I'm not sure there is a simple way to cache the result. We may cache a set of private Set<Route> routeCache;
private Set<Route> negativeCache;
private boolean shouldRecordMetrics(ServiceRequestContext ctx) {
Route route = ctx.routingContext().result().route();
if (routeCache.contains(route)) {
return true;
}
if (negativeCache.contains(route) {
return false;
}
// Look for an inner MetricCollectingService and update the caches
// ... |
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.
Looks nice!
RequestMetricSupport.setup(ctx, REQUEST_METRICS_SET, meterIdPrefixFunction, true, | ||
successFunction != null ? successFunction::test | ||
: ctx.config().successFunction()); | ||
} | ||
return unwrap().serve(ctx, req); | ||
} | ||
|
||
private boolean shouldRecordMetrics(ServiceRequestContext ctx) { |
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'm a bit worried that this might affect the performance in the worst case but this shouldn't be a problem in most cases.
We might add a cache later if it becomes a problem.
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.
Most applications, even complex business logic, may use less than 20~30 decorators.
I'm not sure how efficient the cache strategy is for them.
Do you want some numbers for the performance, I will try to run some benchmarks.
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 added a cache layer to check before looking for decorators with .as()
. The size of Route
is fixed when a server is built and it never changes until it is reconfigured.
All requests will hit the cache. Cache operations are not free though.
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, I thought it was not a problem in most cases so I didn't ask you the numbers because I didn't want to bother you. 😅
Anyway, because you've already done some testing, could you share the numbers?
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 never changes until it is reconfigured.
How about clearing the map in the serviceAdded
method?
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 was added already at L138. 😉
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.
because you've already done some testing, could you share the numbers?
Oops, I misread your comments. 😓 Please ignore this. Thanks!
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.
Looks good to me in terms of implementation correctness 👍 Thanks for the quick fix @ikhoon 🙇 👍 🚀
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.
Nice work!
) Motivation: If duplicate `MetricCollectingService`s are used to decorate a service, the first `MetricCollectingService` encountered will be selected to collect metrics. https://github.com/line/armeria/blob/0df139a41cac430b3805f2774ac6d0c8ca4b4a68/core/src/main/java/com/linecorp/armeria/internal/common/metric/RequestMetricSupport.java#L57-L59 The downside to this strategy is that users can't override a globally used `MetricCollectingService` with a local decorator that is even directly applied to a service. If we give high priority to the `MetricCollectingService` closest to a service, users will be able to easily override the global one. Modifications: - Fixed `MetricCollectingService` to look for an inner `MetricCollectingService` before setting up request metrics. - If an inner `MetricCollectingService` is found, delegate the decorator to collect metrics. - Moved `RouteDecoratingService` to `internal` package. - Added `RouteDecoratingService.as(ctx, serviceClass)` to find a matching decorator from the internal service chain. Result: - The innermost `MetricCollectingService` is now chosen to measure metrics for a service. - Previously, the outermost one is used to collect metrics.
Motivation:
If duplicate
MetricCollectingService
s are used to decorate a service, the firstMetricCollectingService
encountered will be selected to collect metrics.armeria/core/src/main/java/com/linecorp/armeria/internal/common/metric/RequestMetricSupport.java
Lines 57 to 59 in 0df139a
The downside to this strategy is that users can't override a globally used
MetricCollectingService
with a local decorator that is even directly applied to a service.If we give high priority to the
MetricCollectingService
closest to a service, users will be able to easily override the global one.Modifications:
MetricCollectingService
to look for an innerMetricCollectingService
before setting up request metrics.MetricCollectingService
is found, delegate the decorator to collect metrics.RouteDecoratingService
tointernal
package.RouteDecoratingService.as(ctx, serviceClass)
to find a matching decorator from the internal service chain.Result:
MetricCollectingService
is now chosen to measure metrics for a service.