Skip to content

High resource usage when using replay #16797

Closed

Description

@Keller18306

Problem Statement

I use react-draggable for my project. This package allows you to drag and drop elements on the page. However, when I installed Sentry, I encountered very high resource consumption. After debugging Sentry, I realized that MutaionObserver catches all style attribute changes and then sends them to Service Worker. There are so many events that the processor core is clogged up to 100%. In addition, the movement of elements becomes ragged and intermittent.

Solution Brainstorm

Image

While browsing in the debugger I noticed an interesting function options.onMutation(mutations). In the file node_modules/@sentry/react/node_modules/node_modules/@sentry-internal/rrweb/dist/rrweb.js (I couldn't find the source code, so I attached it as is)

function initMutationObserver(options, rootEl) {
  const mutationBuffer = new MutationBuffer();
  mutationBuffers.push(mutationBuffer);
  mutationBuffer.init(options);
  let mutationObserverCtor = window.MutationObserver || /**
  * Some websites may disable MutationObserver by removing it from the window object.
  * If someone is using rrweb to build a browser extention or things like it, they
  * could not change the website's code but can have an opportunity to inject some
  * code before the website executing its JS logic.
  * Then they can do this to store the native MutationObserver:
  * window.__rrMutationObserver = MutationObserver
  */
  window.__rrMutationObserver;
  const angularZoneSymbol = window?.Zone?.__symbol__?.("MutationObserver");
  if (angularZoneSymbol && window[angularZoneSymbol]) {
    mutationObserverCtor = window[angularZoneSymbol];
  }
  const observer = new mutationObserverCtor(
    callbackWrapper((mutations) => {
      // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
      if (options.onMutation && options.onMutation(mutations) === false) {
        return;
      }
      // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
      mutationBuffer.processMutations.bind(mutationBuffer)(mutations);
    })
  );
  observer.observe(rootEl, {
    attributes: true,
    attributeOldValue: true,
    characterData: true,
    characterDataOldValue: true,
    childList: true,
    subtree: true
  });
  return observer;
}

It calls the code provided below.

private _onMutationHandler(mutations: unknown[]): boolean {
const count = mutations.length;
const mutationLimit = this._options.mutationLimit;
const mutationBreadcrumbLimit = this._options.mutationBreadcrumbLimit;
const overMutationLimit = mutationLimit && count > mutationLimit;
// Create a breadcrumb if a lot of mutations happen at the same time
// We can show this in the UI as an information with potential performance improvements
if (count > mutationBreadcrumbLimit || overMutationLimit) {
const breadcrumb = createBreadcrumb({
category: 'replay.mutations',
data: {
count,
limit: overMutationLimit,
},
});
this._createCustomBreadcrumb(breadcrumb);
}
// Stop replay if over the mutation limit
if (overMutationLimit) {
// This should never reject
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.stop({ reason: 'mutationLimit', forceFlush: this.recordingMode === 'session' });
return false;
}
// `true` means we use the regular mutation handling by rrweb
return true;
}

Why not make a call, for example, another call to a callback function, which will be specified in options to Sentry.replayIntegration. Where it will be possible to filter out those events that the user considers unnecessary?

Sentry.replayIntegration({
    beforeMutationEvent(events: MutationRecord[]): boolean {
         // my condition, to exclude some style change events for specified element
         return true;
    }
}),

Activity

  1. moved this to Waiting for: Product Owner in GitHub Issues with 👀 3on Jul 3, 2025
  2. iamextasy commented on Jul 3, 2025

    @iamextasy

    I also encountered the same issue when I was making a dating site for pets. Please fix this.

  3. andreiborza commented on Jul 3, 2025

    @andreiborza
    Member

    Hi @Keller18306, thanks for writing in. This sounds reasonable to me, will add it to our backlog.

  4. moved this from Waiting for: Product Owner to No status in GitHub Issues with 👀 3on Jul 3, 2025
  5. mydea commented on Jul 3, 2025

    @mydea
    Member

    Hey, not sure if such a callback would actually fix stuff, as that would also run often etc...

    Did you try ignoring the selectors that are involved in drag and drop? you could try ignore: ['.selector'] to avoid capturing events being captured, that may or may not work depending on how that is setup. or try block: ['.selector'], that could also have an impact.

  6. Keller18306 commented on Jul 3, 2025

    @Keller18306
    Author

    @mydea

    Hey, not sure if such a callback would actually fix stuff, as that would also run often etc...

    As I understand it, all resources are spent on creating "objects" and then transferring them to the worker, which itself is also loaded at 100%. Therefore, it may be fixed if we limit the sending of such events. Unfortunately, I have no way to check this now. I can't imagine how to do this yet, but maybe I'll do it a little later.

    Did you try ignoring the selectors that are involved in drag and drop? you could try ignore: ['.selector'] to avoid capturing events being captured, that may or may not work depending on how that is setup. or try block: ['.selector'], that could also have an impact.

    Yes, I saw these parameters. But this is not exactly what I need. They "remove"/"hide" the element itself. I need the content and the element itself to remain on the replay, because this is the main functionality of the project. I would like these elements to remain.

  7. moved this to Waiting for: Product Owner in GitHub Issues with 👀 3on Jul 3, 2025
  8. mydea commented on Jul 3, 2025

    @mydea
    Member

    one clarifying question: Is it lagging always, or just while dragging? then you could try ignoring/blocking it only while it is being dragged, e.g. block: ['.dragged']. Not 100% sure if that would work but worth a try 🤔 You may also be able to just block the shadow element, if such a thing exists (some drag and drop libraries create a shadow element that they move, this could be blocked for example).

    One important thing to keep in mind with regard to blocking mutations: This will not necessarily work as intended 😬 because rrweb works with relative changes. so if you only block certain mutations, then you may end up with a broken UI. Think:

    1. Start position of element is 10
    2. Mutation: position +5 --> new position is 15
    3. Mutation: position +3 --> new position is 18

    if you'd drop 2, then after 3 the element would have the position 13, which would be wrong.

  9. moved this from Waiting for: Product Owner to No status in GitHub Issues with 👀 3on Jul 3, 2025
  10. 4 remaining items

  11. mydea commented on Jul 4, 2025

    @mydea
    Member

    OK, I see, thanks for helping investigate this so much!
    I think this may make sense, but I would probably opt to not provide a callback here, as that may break and behave weirdly. But we could add an option like ignoreMutations: ['.dragging'] that basically does the same thing, to allow opting out of this. cc @chargome, thoughts on this?

  12. moved this from Waiting for: Product Owner to No status in GitHub Issues with 👀 3on Jul 4, 2025
  13. chargome commented on Jul 4, 2025

    @chargome
    Member

    I think the block, ignore, ignoreMutations API is becoming confusing for users, would be nice to auto-detect these cases with something similar to #15438 – but yeah for the time being this might be a fix?

  14. changed the title [-]High resource usage[/-] [+]High resource usage when using replay[/+] on Jul 4, 2025
  15. Keller18306 commented on Jul 5, 2025

    @Keller18306
    Author

    but yeah for the time being this might be a fix?

    Is there any way I can test this API?

  16. moved this to Waiting for: Product Owner in GitHub Issues with 👀 3on Jul 5, 2025
  17. chargome commented on Jul 7, 2025

    @chargome
    Member

    @Keller18306 we have not merged this yet – you'll get a notification in this ticket once this has been released!

  18. moved this from Waiting for: Product Owner to No status in GitHub Issues with 👀 3on Jul 7, 2025
  19. billyvg commented on Jul 7, 2025

    @billyvg
    Member

    Ah I wrote about this here #16831, but just realized this ticket was created in the wrong repo. As @chargome said the API is getting confusing - we should make this work with an existing API like block

  20. github-actions commented on Jul 9, 2025

    @github-actions
    Contributor

    A PR closing this issue has just been released 🚀

    This issue was referenced by PR #16816, which was included in the 9.36.0 release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions