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

[APM] Optimize traces overview #70200

Merged
merged 21 commits into from
Jul 28, 2020

Conversation

dgieselaar
Copy link
Member

@dgieselaar dgieselaar commented Jun 29, 2020

Similar to #69648, achieves a 30%-50% performance improvement of the traces overview. Could possibly improve that even more by not loading a sample, as that seems to be the slowest request of the bunch, and is only used for deeplinking, so could be lazily-loaded.

Of note:

  • removed transform.ts and apply the transformation logic in/after fetching.
  • removed client-side logic for calculating relative impact. FWICT this was already happening on the server.

@dgieselaar dgieselaar added release_note:skip Skip the PR/issue when compiling release notes v7.9.0 labels Jun 29, 2020
@dgieselaar dgieselaar requested a review from a team as a code owner June 29, 2020 14:10
@botelastic botelastic bot added the Team:APM All issues that need APM UI Team support label Jun 29, 2020
@elasticmachine
Copy link
Contributor

Pinging @elastic/apm-ui (Team:apm)

@dgieselaar
Copy link
Member Author

I've made some changes in aggregations.ts to support union types in aggregations. E.g., we can now support { transaction_groups: { composite: any } | { terms: any } }. This also allows us to clean up code in a couple of other places.

Comment on lines 70 to 72
return arrayUnionToCallable(
response.aggregations?.transaction_groups.buckets
).map((bucket) => {
Copy link
Member

Choose a reason for hiding this comment

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

Is this the equivalent of?

Suggested change
return arrayUnionToCallable(
response.aggregations?.transaction_groups.buckets
).map((bucket) => {
return (response.aggregations?.transaction_groups.buckets ?? []).map((bucket) => {

Copy link
Member Author

Choose a reason for hiding this comment

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

no, it's this issue: https://ela.st/union-callable

return {
data: memoizedData,
data,
status,
error,
};
Copy link
Member

Choose a reason for hiding this comment

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

Thanks for cleaning up 👍

@@ -157,7 +157,7 @@ export function registerTransactionDurationAlertType({

const { agg } = response.aggregations;

const value = 'values' in agg ? agg.values[0] : agg?.value;
const value = 'values' in agg ? Object.values(agg.values)[0] : agg?.value;
Copy link
Member

Choose a reason for hiding this comment

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

is this an unrelated change?

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, this is a bug fix now that I think of it. This code (written by me of course) assumes that the response of a percentiles aggregation is number[], but it's actually Record<string, number> where the key is the percentile. I'll verify whether that is the case and file a separate bug for it.

Copy link
Member Author

Choose a reason for hiding this comment

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

aggs: {
transaction_groups: {
composite: {
size: bucketSize + 1, // 1 extra bucket is added to check whether the total number of buckets exceed the specified bucket size.
Copy link
Member

Choose a reason for hiding this comment

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

We should retain the comment to explain the + 1

Copy link
Member Author

Choose a reason for hiding this comment

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

there's a comment lower in the file as well, do you think we need both? (no strong opinions from my side)

Copy link
Member Author

Choose a reason for hiding this comment

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

re-added the comment

field: TRANSACTION_NAME,
size,
},
}),
Copy link
Member

Choose a reason for hiding this comment

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

Any reason not to keep it as a composite agg?

Copy link
Member Author

@dgieselaar dgieselaar Jun 29, 2020

Choose a reason for hiding this comment

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

Yeah: performance, and a terms aggregation is sorted by doc_count. Sorting on composite aggregations is unpredictable, so there's a higher risk of transaction groups with lots of transactions dropping off the list.

Comment on lines 118 to 139
allMetrics.forEach((metric) => {
// we use indexOf so we can replace the existing item with a new one
// this will give us type safety (Object.assign is unsafe)
let indexOf = findIndex(items, (i) => isEqual(i.key, metric.key));
let item = items[indexOf];

if (indexOf === -1) {
const newItem = {
key: metric.key,
};
items.push(newItem);
item = newItem;
indexOf = items.length - 1;
}

const newItem = {
...item,
...metric,
};

items[indexOf] = newItem;
});
Copy link
Member

Choose a reason for hiding this comment

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

This looks pretty intimidating. Can we extract this logic and add some comments?
Is the new logic or what does it replace?

Copy link
Member Author

Choose a reason for hiding this comment

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

I hate it too! I had Object.assign({}) first which was cleaner, but I ran into a bug because there was no type checking of the values I was assigning.

Comments might clear it up but I'm open to suggestions as well. This code is trying to flatten buckets from multiple requests by either a grouping record (service.name and transaction.name) or a grouping key (transaction.name).

Comment on lines 145 to 165
const max = Math.max(...values);
const min = Math.min(...values);

const duration = moment.duration(setup.end - setup.start);
const minutes = duration.asMinutes();

const itemsWithRelativeImpact: TransactionGroup[] = items
.map((item) => {
return {
key: item.key,
averageResponseTime: item.avg,
transactionsPerMinute: (item.count ?? 0) / minutes,
impact:
item.sum !== null && item.sum !== undefined
? ((item.sum - min) / (max - min)) * 100 || 0
: 0,
p95: item.p95,
sample: item.sample!,
};
})
.filter((item) => item.sample);
Copy link
Member

Choose a reason for hiding this comment

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

Any reason not having this in a separate function?


const params = mergeProjection(projection, {
const request = mergeProjection(projection, {
Copy link
Member

Choose a reason for hiding this comment

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

I keep running into the same issue: when I try to review these changes I end up in the rabbit hole of projections.
I started flattening it back into a single file (mostly) and I think it really improves the readability and makes it easier to spot problems.

I think it might be a good idea to go back to the drawing board and find a way to simplify ui filters (and thus projections).

Copy link
Member Author

Choose a reason for hiding this comment

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

My concern is that we're forgetting why we have projections, and end up duplicating large parts of our queries in separate files. Tbh, I don't think projections are the cause of - or the solution to - some of our issues. But curious to see what you come up with.

Copy link
Member

@sorenlouv sorenlouv Jul 1, 2020

Choose a reason for hiding this comment

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

I don't think projections are the cause of - or the solution to - some of our issues

Projections most definitely make it harder for me to make sense of the queries. But I'm not sure my suggestions are any better (I obviously think they are but I also know that most things sound better before they are converted to code).

@dgieselaar
Copy link
Member Author

@elasticmachine merge upstream

@dgieselaar
Copy link
Member Author

@elasticmachine merge upstream

@dgieselaar
Copy link
Member Author

@elasticmachine merge upstream

@dgieselaar dgieselaar requested a review from a team July 27, 2020 17:59
Copy link
Contributor

@smith smith left a comment

Choose a reason for hiding this comment

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

I didn't notice a significant performance improvement, but the code looks good.

aggs: {
timeseriesData: {
date_histogram: AggregationOptionsByType['date_histogram'];
aggs: Record<string, MetricsAggregationMap>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this just be:

Suggested change
aggs: Record<string, MetricsAggregationMap>;
aggs: Aggs;

Copy link
Member Author

Choose a reason for hiding this comment

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

yep! Changed it to MetricAggs in all places.

date_histogram: AggregationOptionsByType['date_histogram'];
aggs: Record<string, MetricsAggregationMap>;
};
} & Record<string, MetricsAggregationMap>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this just be:

Suggested change
} & Record<string, MetricsAggregationMap>;
} & Aggs;

sample: item.sample!,
};
})
.filter((item) => item.sample);
Copy link
Contributor

Choose a reason for hiding this comment

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

should the filter for itemsWithRelativeImpact be .filter((item) => item.impact > 0) or is that logically equivalent to whether or not there is a sample?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think it's pretty much the same, but the filter is mostly a safeguard for ensuring there's actually a sample as we can't deeplink without it. Previously a sample was guaranteed because it's all one request, but splitting it up into smaller requests means that is no longer the case - for instance, when there are more than 10k buckets.

<EuiToolTip id="trace-transaction-link-tooltip" content={name}>
render: (_: string, { sample }: TransactionGroup) => (
<EuiToolTip
id="trace-transaction-link-tooltip"
Copy link
Contributor

@ogupte ogupte Jul 27, 2020

Choose a reason for hiding this comment

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

I know this line was just name changes, but still: I don't think the id trace-transaction-link-tooltip is necessary anymore. I don't see it referenced anywhere, and i don't think it's used in any unit/functional testing.

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed

@kibanamachine
Copy link
Contributor

💚 Build Succeeded

Build metrics

async chunks size

id value diff baseline
apm 3.7MB -3.2KB 3.7MB

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@dgieselaar dgieselaar merged commit 19532fc into elastic:master Jul 28, 2020
kibanamachine pushed a commit that referenced this pull request Jul 28, 2020
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
@kibanamachine
Copy link
Contributor

💚 Backport successful

The PR was backported to the following branches:

@dgieselaar dgieselaar added v7.10.0 and removed v7.9.0 labels Jul 28, 2020
dgieselaar added a commit that referenced this pull request Jul 29, 2020
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
release_note:skip Skip the PR/issue when compiling release notes Team:APM All issues that need APM UI Team support v7.10.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants