Skip to content

fix(tracing-internal): Delay pageload transaction finish until document is interactive #10215

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

Merged
merged 5 commits into from
Jan 18, 2024

Conversation

lforst
Copy link
Contributor

@lforst lforst commented Jan 17, 2024

Explanation

Before this PR we automatically finished pageload transactions when there were no spans on the transaction for max 1 second, OR when there were no added/removed child spans consecutively in 3x5 seconds.

This caused inaccuracies in some cases, however, with the Next.js app router this causes problems consistently. Reason being that Next.js streams in the document (HTML) meaning the document "interactive" readyState is delayed by as long as the streaming response hasn't finished yet.

The symptom of this is that, in Next.js applications (and also all other apps that have long document loading times), pageload transactions will be finished prematurely, because while the document is streaming in, it is possible that there are no additional spans on the transaction, meaning that the IdleTransaction's idletimeout (1s) kicks in and finishes the transaction.

Ideally we fix this by delaying finishing pageload transactions until the document readyState is "interactive". Here is a great illustration on when exactly within the document loading pipeline this is: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigationTiming

For illustrative purposes of this change, here is what a pageload transaction looked like for a slow Next.js page before this PR:

Screenshot 2024-01-17 at 13 47 30

Here is what it looks like after:

Screenshot 2024-01-17 at 13 44 03

The difference is huge but it is arguably what the user would want. Notice that the request and response spans are completely missing in the first image because the transaction was finished before the data for these spans could be collected.

A piece of consideration: In traditional applications, this should not change the behaviour and more importantly the duration of pageload transactions by much. Only if you have an application that streams in HTML you will notice a difference, however, the new approach is much more accurate.

@lforst lforst force-pushed the lforst-idle-transaction-wait-until-interactive branch from 2aa8cdb to 3b5bb8d Compare January 17, 2024 12:51
@lforst lforst changed the base branch from lforst-absolutely-unhinged-nextjs-experiment to develop January 17, 2024 12:56
Copy link
Contributor

github-actions bot commented Jan 17, 2024

size-limit report 📦

Path Size
@sentry/browser (incl. Tracing, Replay, Feedback) - Webpack (gzipped) 77.52 KB (+0.2% 🔺)
@sentry/browser (incl. Tracing, Replay) - Webpack (gzipped) 68.8 KB (+0.19% 🔺)
@sentry/browser (incl. Tracing, Replay with Canvas) - Webpack (gzipped) 72.68 KB (+0.28% 🔺)
@sentry/browser (incl. Tracing, Replay) - Webpack with treeshaking flags (gzipped) 62.43 KB (+0.2% 🔺)
@sentry/browser (incl. Tracing) - Webpack (gzipped) 32.8 KB (+0.4% 🔺)
@sentry/browser (incl. Feedback) - Webpack (gzipped) 31.13 KB (0%)
@sentry/browser - Webpack (gzipped) 22.48 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback) - ES6 CDN Bundle (gzipped) 75.11 KB (+0.15% 🔺)
@sentry/browser (incl. Tracing, Replay) - ES6 CDN Bundle (gzipped) 66.72 KB (+0.14% 🔺)
@sentry/browser (incl. Tracing) - ES6 CDN Bundle (gzipped) 32.58 KB (+0.31% 🔺)
@sentry/browser - ES6 CDN Bundle (gzipped) 24.19 KB (0%)
@sentry/browser (incl. Tracing, Replay) - ES6 CDN Bundle (minified & uncompressed) 210.25 KB (+0.16% 🔺)
@sentry/browser (incl. Tracing) - ES6 CDN Bundle (minified & uncompressed) 98.22 KB (+0.35% 🔺)
@sentry/browser - ES6 CDN Bundle (minified & uncompressed) 72.34 KB (0%)
@sentry/browser (incl. Tracing) - ES5 CDN Bundle (gzipped) 35.62 KB (+0.36% 🔺)
@sentry/react (incl. Tracing, Replay) - Webpack (gzipped) 69.2 KB (+0.21% 🔺)
@sentry/react - Webpack (gzipped) 22.52 KB (0%)
@sentry/nextjs Client (incl. Tracing, Replay) - Webpack (gzipped) 85.79 KB (+0.14% 🔺)
@sentry/nextjs Client - Webpack (gzipped) 49.93 KB (+0.24% 🔺)
@sentry-internal/feedback - Webpack (gzipped) 17.12 KB (+0.02% 🔺)

@lforst lforst marked this pull request as ready for review January 17, 2024 15:22
@lforst lforst requested review from mydea, Lms24 and AbhiPrasad January 17, 2024 15:22
@lforst
Copy link
Contributor Author

lforst commented Jan 17, 2024

@AbhiPrasad and I had a chat on slack and we both deem this change useful. In a future PR we may want to create a span for DOM processing.

image

Copy link
Member

@AbhiPrasad AbhiPrasad left a comment

Choose a reason for hiding this comment

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

Can we add tests to see that this does not interfere with heartbeat?

Copy link
Member

@Lms24 Lms24 left a comment

Choose a reason for hiding this comment

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

I like the idea! Just a bit worried about adding another public method but since we'll need something like an IdleSpan anyway, I think we should be good.

A bit out of scope but as an idea: what if we add a 0ms span/mark to the transaction when we reach the interactive point? This should make the txn duration is more transparent to users.

also +1 on tests (maybe for these, such a span would even be helpful 🤔)

@lforst
Copy link
Contributor Author

lforst commented Jan 17, 2024

A bit out of scope but as an idea: what if we add a 0ms span/mark to the transaction when we reach the interactive point? This should make the txn duration is more transparent to users.

@Lms24 The "interactive" document status doesn't mean a lot I believe. I am not sure what it really indicates. The MDN docs say

representing the time immediately before the user agent sets the document's readyState to "interactive".

and

Note: This property is not Time to interactive (TTI). This property refers to the time when DOM construction is finished and interaction to it from JavaScript is possible. See also the interactive state of Document.readyState which corresponds to this property.

But I think DOM interaction through JS is already possible before (?) 🤷. We already have a mark for the domContentLoadedEvent. If anything I would probably add a span for "DomProcessing" in general, starting when "interactive" is hit. I would probably do this in a follow-up PR though.

Copy link
Member

@AbhiPrasad AbhiPrasad left a comment

Choose a reason for hiding this comment

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

cc @krystofwoldrich - not sure if this breaks anything with RN

/**
* Restarts idle timeout, if there is no running idle timeout it will start one.
*/
private _restartIdleTimeout(endTimestamp?: Parameters<IdleTransaction['end']>[0]): void {
this.cancelIdleTimeout();
this._idleTimeoutID = setTimeout(() => {
if (!this._finished && Object.keys(this.activities).length === 0) {
if (!this._finished && Object.keys(this.activities).length === 0 && this._autoFinishAllowed) {
Copy link
Member

Choose a reason for hiding this comment

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

do we need this check here? 🤔 as we are guarding even calling this function above?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

great point

Copy link
Member

@mydea mydea left a comment

Choose a reason for hiding this comment

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

overall I think this makes sense! The whole idle transaction code is a bit confusing IMHO but I wouldn't spent too much time on this now as we'll rewrite this soonish with something like #9972 anyhow. I guess this should then be the default behavior in v8?

Copy link
Member

@Lms24 Lms24 left a comment

Choose a reason for hiding this comment

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

thx for explaining. Yeah if adding the span doesn't make sense I'm fine with it, too!

@lforst lforst merged commit 7992d25 into develop Jan 18, 2024
@lforst lforst deleted the lforst-idle-transaction-wait-until-interactive branch January 18, 2024 11:20
@krystofwoldrich
Copy link
Contributor

@AbhiPrasad Thanks for the ping. Looks good for RN.

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.

5 participants