Repository navigation
Description
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
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.
sentry-javascript/packages/replay-internal/src/replay.ts
Lines 1307 to 1337 in 7f3f6f7
| 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;
}
}),Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsNo status

Activity
iamextasy commentedon Jul 3, 2025
I also encountered the same issue when I was making a dating site for pets. Please fix this.
andreiborza commentedon Jul 3, 2025
Hi @Keller18306, thanks for writing in. This sounds reasonable to me, will add it to our backlog.
mydea commentedon Jul 3, 2025
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 tryblock: ['.selector'], that could also have an impact.Keller18306 commentedon Jul 3, 2025
@mydea
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.
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.
mydea commentedon Jul 3, 2025
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:
if you'd drop 2, then after 3 the element would have the position 13, which would be wrong.
4 remaining items
mydea commentedon Jul 4, 2025
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?chargome commentedon Jul 4, 2025
I think the
block,ignore,ignoreMutationsAPI 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?[-]High resource usage[/-][+]High resource usage when using replay[/+]Keller18306 commentedon Jul 5, 2025
Is there any way I can test this API?
chargome commentedon Jul 7, 2025
@Keller18306 we have not merged this yet – you'll get a notification in this ticket once this has been released!
feat(replay): Add `_experiments.ignoreMutations` option (#16816)
billyvg commentedon Jul 7, 2025
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
blockgithub-actions commentedon Jul 9, 2025
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.