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

Give high priority to the innermost MetricCollectingService #4908

Merged
merged 5 commits into from Jun 9, 2023

Conversation

ikhoon
Copy link
Contributor

@ikhoon ikhoon commented May 31, 2023

Motivation:

If duplicate MetricCollectingServices are used to decorate a service, the first MetricCollectingService encountered will be selected to collect metrics.

if (Boolean.TRUE.equals(isRequestMetricsSet)) {
return;
}

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 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.
@trustin
Copy link
Member

trustin commented May 31, 2023

Can we avoid calling .as() and other potentially expensive operations only once, so we don't have to call it on every request?

@ikhoon
Copy link
Contributor Author

ikhoon commented Jun 1, 2023

RouteDecoratingServices for a request are dynamically resolved depending on RoutingContext.

router.findAll(ctx.routingContext()).forEach(routed -> {
if (routed.isPresent()) {
serviceChain.add(routed.value().decorator());
}
});

I'm not sure there is a simple way to cache the result. We may cache a set of Routes that a MetricCollingService should handle.

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 
    // ...

Copy link
Member

@minwoox minwoox left a 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) {
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Member

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?

Copy link
Member

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?

Copy link
Contributor Author

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. 😉

Copy link
Member

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!

Copy link
Contributor

@jrhee17 jrhee17 left a 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 🙇 👍 🚀

Copy link
Member

@trustin trustin left a comment

Choose a reason for hiding this comment

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

Nice work!

@trustin trustin merged commit 8ffabc6 into line:main Jun 9, 2023
11 checks passed
ikhoon added a commit to be-hase/armeria that referenced this pull request Jun 12, 2023
)

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.
@ikhoon ikhoon deleted the metric-collecting-service-order branch June 12, 2023 16:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants