Skip to content

feat(replay): Save replay in sessionStorage and attempt to re-request if necessary #6698

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

Closed
wants to merge 3 commits into from

Conversation

billyvg
Copy link
Member

@billyvg billyvg commented Jan 10, 2023

Save replay request parameters to sessionStorage before we await any async functions when flushing. This is cleared after each successful request. If integration is reloaded and there is data in sessionStorage, we will attempt a new request to send saved data.

This happens because when a user refreshes, we have some async tasks (e.g. compression worker) which will not resolve before the page is unloaded. However, we can retrieve the raw events from the worker and save to sessionStorage as those are all synchronous tasks.

Should help address #6533

Requires #6696, #6695, #6699

@billyvg billyvg changed the base branch from master to feat-replay-change-addevent-to-be-async January 10, 2023 00:50
@billyvg billyvg changed the base branch from feat-replay-change-addevent-to-be-async to feat-replay-eventbuffer-save-pending-events January 10, 2023 00:53
@billyvg billyvg changed the title feat replay retry lost events session storage feat(replay): Save replay in sessionStorage and attempt to re-request if necessary Jan 10, 2023
@github-actions
Copy link
Contributor

github-actions bot commented Jan 10, 2023

size-limit report 📦

Path Size
@sentry/browser - ES5 CDN Bundle (gzipped + minified) 19.82 KB (+0.01% 🔺)
@sentry/browser - ES5 CDN Bundle (minified) 61.47 KB (0%)
@sentry/browser - ES6 CDN Bundle (gzipped + minified) 18.5 KB (+0.01% 🔺)
@sentry/browser - ES6 CDN Bundle (minified) 54.77 KB (0%)
@sentry/browser - Webpack (gzipped + minified) 20.22 KB (0%)
@sentry/browser - Webpack (minified) 66.17 KB (0%)
@sentry/react - Webpack (gzipped + minified) 20.24 KB (0%)
@sentry/nextjs Client - Webpack (gzipped + minified) 47.51 KB (0%)
@sentry/browser + @sentry/tracing - ES5 CDN Bundle (gzipped + minified) 26.73 KB (-0.02% 🔽)
@sentry/browser + @sentry/tracing - ES6 CDN Bundle (gzipped + minified) 25.02 KB (+0.01% 🔺)
@sentry/replay ES6 CDN Bundle (gzipped + minified) 43.4 KB (-0.17% 🔽)
@sentry/replay - Webpack (gzipped + minified) 38.65 KB (-0.48% 🔽)
@sentry/browser + @sentry/tracing + @sentry/replay - ES6 CDN Bundle (gzipped + minified) 60.55 KB (-0.27% 🔽)

@billyvg billyvg force-pushed the feat-replay-retry-lost-events-session-storage branch from d95e731 to aeb40b9 Compare January 10, 2023 01:04
Comment on lines -16 to -18
// Replays have separate set of breadcrumbs, do not include breadcrumbs
// from core SDK
delete event.breadcrumbs;
Copy link
Member Author

Choose a reason for hiding this comment

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

Moved this to the SDK where we send the replay request (right after we prepare event), otherwise the restoration request happens before this handler is attached.

Comment on lines 186 to 187
const pendingEvent = await getPendingReplay({ useCompression });
if (pendingEvent) {
await this.sendReplayRequest(pendingEvent);
Copy link
Member Author

Choose a reason for hiding this comment

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

This happens before we attach any listeners at all and will block until we make this request. Note this calls the function that does not retry, but maybe it should?

} else if (state === FlushState.COMPLETE) {
WINDOW.sessionStorage.removeItem(PENDING_REPLAY_STATUS_KEY);
} else {
WINDOW.sessionStorage.setItem(PENDING_REPLAY_STATUS_KEY, state);
Copy link
Member Author

@billyvg billyvg Jan 10, 2023

Choose a reason for hiding this comment

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

After reading this... this probably isn't necessary. It would let us know at which stage the user dropped off.

@billyvg billyvg force-pushed the feat-replay-retry-lost-events-session-storage branch 2 times, most recently from a0059a4 to 9624e4e Compare January 10, 2023 01:29
@billyvg billyvg requested review from mydea and Lms24 January 10, 2023 04:08
@billyvg billyvg force-pushed the feat-replay-eventbuffer-save-pending-events branch from c58b7c0 to efa539c Compare January 11, 2023 01:52
@billyvg billyvg force-pushed the feat-replay-eventbuffer-save-pending-events branch 2 times, most recently from be829e1 to 2756060 Compare January 11, 2023 18:18
Base automatically changed from feat-replay-eventbuffer-save-pending-events to master January 11, 2023 21:17
@billyvg billyvg force-pushed the feat-replay-retry-lost-events-session-storage branch from 9624e4e to d6a5f05 Compare January 11, 2023 22:48
@billyvg billyvg marked this pull request as ready for review January 11, 2023 22:50
@billyvg billyvg force-pushed the feat-replay-retry-lost-events-session-storage branch from d6a5f05 to 7f3fa0b Compare January 18, 2023 03:04
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.

Just talked with @mydea about this. I'm a little bit concerned that we might not always clear the pending replays from session storage. Which is proabaly generally a problem with everything we try to keep in session storage. We talked a little bit about using a SharedWorker instead which would live across tabs but stay alive on unloads. So we'd probably need to have a map of session ids and eventbuffers but it might be worth a try. WDYT?

Regardless, I went over the PR and code-wise I think it looks good. I'd just want to avoid the enum if possible (see comment).

@@ -18,6 +18,17 @@ export interface SendReplayData {
options: ReplayPluginOptions;
}

export enum FlushState {
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 generally avoid enums as they have a pretty hefty bundle size impact (more details)

We could use string constants instead, something along these lines:

export type FlushState = 'start' | 'sent-request' | 'complete' | 'error'

WDYT?

@mydea
Copy link
Member

mydea commented Jan 18, 2023

After thinking about this a bit, and trying a few things, I actually think a nicer solution for this problem would be to avoid compression when we need to send events ASAP (e.g. on unload). I'll try to write up a proof of concept to illustrate what I mean.

@billyvg
Copy link
Member Author

billyvg commented Jan 18, 2023

@mydea yeah I had that idea in mind as well - we would avoid keeping state in session storage and segments should be small enough that it's fine to have them uncompressed.

@mydea
Copy link
Member

mydea commented Jan 18, 2023

This PR illustrates how that could work - actually it's quite straightforward, "only" thing missing is to make sure to use the finishImmediate stuff when appropriate in replay itself. WDYT?

@billyvg
Copy link
Member Author

billyvg commented Jan 30, 2023

I'm going to close this out for now as we have improved segment quality a significant amount already. This PR will add a bit of complexity and overhead, so let's hold off unless we really deem it necessary.

@billyvg billyvg closed this Jan 30, 2023
@billyvg billyvg deleted the feat-replay-retry-lost-events-session-storage branch January 30, 2023 14:58
@billyvg billyvg mentioned this pull request Feb 2, 2023
2 tasks
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.

3 participants