You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Large session replay mutations still block the main thread on node-dense pages. The mutation emitter allocates a serialization options object and four callbacks for every serialized node, adding avoidable allocation work during subtree moves and repeated rebuilds.
This PR is stacked on #4801. It contains only the follow-up optimization and benchmark extensions. It is another partial improvement for #4217, not a complete fix.
Changes
Initialize serialization options lazily once per mutation emission instead of once per node. Keep the options local to that emission. No DOM values are cached, and each node still computes its own masking context.
Add regression tests for allocation reuse, fresh options in later batches, adjacent masked/unmasked nodes, passwords and changing content.
Extend the opt-in benchmark with nested insertion, sustained churn, matching recording-off controls, shadow DOM, trusted input probes and intermediate replay checkpoints.
Add source-map-based profile attribution and diagnostic heap sampling. Document measurement limits and remaining coverage gaps.
Upload replay validation data in bounded chunks to avoid Chromium's 100 MB DevTools message limit. Runs are marked passed only after all checkpoints succeed.
Performance results
Local medians from three runs per build on an Apple M4 Pro, Chromium 136.0.7103.25, approximately 50k nodes, compression enabled and no CPU throttling. Both builds used the same benchmark source, with alternating baseline/candidate runs. These are descriptive samples, not customer-level guarantees or significance estimates.
Metric
Before
After
Move subtree: longest task
260 ms
187 ms
Move subtree: input delay
266.9 ms
194.3 ms
Five churn bursts: total blocking
979 ms
837 ms
Nested insertion: longest task
260 ms
252 ms
Rebuild: longest task
242 ms
247 ms
Single rebuild performance was essentially flat/slightly worse. Startup and full-snapshot paths are not optimized. All six comparison runs passed replay, input, privacy and drop/recovery checks. A separate 100k-node candidate run still showed 451–618 ms tasks, so substantial blocking remains.
The deterministic test failed before the change with 500 options objects for 500 fixture nodes and passed afterward with one. The recorder artifact increased by 17 raw bytes and 15 gzip bytes.
Validation
SDK/dependency and rrweb builds/typechecks passed.
Browser masking: 9 tests passed across Chromium, Firefox and WebKit.
Table, CSSOM/adopted-style and shadow-root smoke tests passed with both compression settings.
A 10k-node, 4x page-throttled run passed with both compression settings. Larger shadow and 100k-node table checks passed with compression enabled.
Dropping the first churn generation in a temporary negative probe correctly failed an intermediate replay checkpoint.
Targeted lint/format, syntax and ES5/ES6 checks passed.
Isolated autoreview of c9b2c2216743cd177b2958aee6af7549538ae055 against the parent branch reported no actionable findings.
No changes to lazy-load signatures, masking policy, mirror IDs, queue ordering, sampling, session rotation or unload behavior. No recording-volume change is expected. This PR does not add workers or asynchronous DOM capture.
Release info Sub-libraries affected
Libraries affected
All of them
posthog-js (web)
posthog-js-lite (web lite)
posthog-node
posthog-react-native
@posthog/react-native-plugin
@posthog/react
@posthog/ai
@posthog/convex
@posthog/next
@posthog/nextjs-config
@posthog/nuxt
@posthog/openfeature-node-provider
@posthog/openfeature-web-provider
@posthog/rollup-plugin
@posthog/webpack-plugin
@posthog/types
@posthog/browser-common
Checklist
Tests for new code
Accounted for the impact of any changes across different platforms
Accounted for backwards compatibility of any changes (no breaking changes!)
Took care not to unnecessarily increase the bundle size
If releasing new changes
Added a posthog-js patch changeset
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Implemented with Pi using repository inspection, Chromium profiling, Vitest, Playwright and isolated autoreview. Profiling found that preprocessing was only part of the stall. The change reduces repeated configuration allocation without caching live DOM or masking decisions.
The code formatter hook was skipped to preserve the existing rrweb formatting rather than reformat entire files. Targeted formatting/lint checks and the other commit hooks passed. No hook configuration was changed. Human review is required.
This diff touches code involved in past incidents. This is a heads-up, not a verdict: read the matched sections of INCIDENTS.md and answer their review questions before merging.
For a judgment on whether this diff has the same failure mode, run the replay-incident-risk skill locally: open a Claude session in the repo root and ask it to review your diff with that skill. Or run node .agents/skills/replay-incident-risk/check.mjs and answer the review questions for each matched class yourself.
Matched 1 past incident pattern(s) (diff vs origin/perf/replay-dom-accessor-cache):
## rrweb serialization and snapshot correctness [path match]
Serializer bugs corrupt replays silently: no exception, no volume change. In May 2026 a cssText spec quirk silently dropped layout styles for sites using CSS custom properties. Test against framework-generated CSS (Chakra/Panda, Emotion speedy, Tailwind) in a real browser.
Touched paths:
- packages/rrweb/rrweb/src/record/mutation.ts
- packages/rrweb/rrweb/test/record/mutation-serialization-options.test.ts
Read: .agents/skills/replay-incident-risk/INCIDENTS.md#class-4-rrweb-serialization-and-snapshot-correctness
This check is advisory. It flags resemblance to past incidents, not correctness.
For a judgment pass on whether this diff has the same failure mode, run the
`replay-incident-risk` skill in a Claude session from the repo root, or answer the
review questions in the matched sections of .agents/skills/replay-incident-risk/INCIDENTS.md yourself.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Large session replay mutations still block the main thread on node-dense pages. The mutation emitter allocates a serialization options object and four callbacks for every serialized node, adding avoidable allocation work during subtree moves and repeated rebuilds.
This PR is stacked on #4801. It contains only the follow-up optimization and benchmark extensions. It is another partial improvement for #4217, not a complete fix.
Changes
Performance results
Local medians from three runs per build on an Apple M4 Pro, Chromium 136.0.7103.25, approximately 50k nodes, compression enabled and no CPU throttling. Both builds used the same benchmark source, with alternating baseline/candidate runs. These are descriptive samples, not customer-level guarantees or significance estimates.
Single rebuild performance was essentially flat/slightly worse. Startup and full-snapshot paths are not optimized. All six comparison runs passed replay, input, privacy and drop/recovery checks. A separate 100k-node candidate run still showed 451–618 ms tasks, so substantial blocking remains.
The deterministic test failed before the change with 500 options objects for 500 fixture nodes and passed afterward with one. The recorder artifact increased by 17 raw bytes and 15 gzip bytes.
Validation
c9b2c2216743cd177b2958aee6af7549538ae055against the parent branch reported no actionable findings.No changes to lazy-load signatures, masking policy, mirror IDs, queue ordering, sampling, session rotation or unload behavior. No recording-volume change is expected. This PR does not add workers or asynchronous DOM capture.
Release info Sub-libraries affected
Libraries affected
Checklist
If releasing new changes
posthog-jspatch changeset🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Implemented with Pi using repository inspection, Chromium profiling, Vitest, Playwright and isolated autoreview. Profiling found that preprocessing was only part of the stall. The change reduces repeated configuration allocation without caching live DOM or masking decisions.
The code formatter hook was skipped to preserve the existing rrweb formatting rather than reformat entire files. Targeted formatting/lint checks and the other commit hooks passed. No hook configuration was changed. Human review is required.