Coming from a proposal https://expensify.slack.com/archives/C08CZDJFJ77/p1783094934084619?thread_ts=1782801937.464879&cid=C08CZDJFJ77
Proposal: Coalesce OnyxDerived recomputes into one macrotask flush
Background
OnyxDerived values (e.g. reportAttributes) are precomputed values stored in Onyx so that expensive computations shared across the app run once instead of in every consumer. Each derived value declares a list of Onyx dependencies and opens one connection per dependency; whenever a dependency broadcasts a change, the connection callback runs the derived value's compute function. reportAttributes alone declares 13 dependencies (reports, transactions, violations, report actions, NVPs, policies, personal details, etc.) and is among the heaviest computes in the app. Critically, Onyx delivers a single logical write (such as a server response that touches reports, transactions, and violations together) as several separate broadcasts spread across microtasks, and each derived dependency is connected independently.
Problem
When a single logical Onyx update touches several dependencies of a derived value, each dependency's connection callback fires its own synchronous recompute, so one change recomputes the derived value many times back-to-back, blocking the JS thread and dropping frames during startup and report opens.
Solution
Coalesce the per-dependency callbacks for each derived value into a single compute, scheduled on the next macrotask, and reconstruct the changed-member delta at flush time:
- Coalescing. recomputeDerivedValue no longer computes inline. It records the triggering dependency in a pending set and, if no flush is already scheduled, schedules one via setTimeout(flushRecompute, 0). Every dependency callback from the same logical update lands in that one pending flush, so N recomputes collapse to 1.
- Flush-time delta reconstruction. Rather than depending on Onyx's per-broadcast sourceValue (which doesn't survive coalescing — you'd be merging N partial payloads — and is a mechanism being phased out), the flush reconstructs the delta itself. For each collection dependency that fired, it diffs the current snapshot against a baseline snapshot captured at the previous flush, using a new shared getCollectionDelta helper. Onyx's structural sharing keeps unchanged members reference-equal, so this is a cheap reference scan that yields exactly the changed members. Non-collection dependencies pass their whole value. The first flush has no baselines, so it computes from scratch (no sourceValues → full compute) and captures the initial baselines for subsequent diffs.
- Incremental configs are hardened to honor every coalesced trigger. Configs that update incrementally previously assumed a compute was triggered by a single dependency. Coalescing can now deliver several changed dependencies in one compute, so any config that branched on "which one changed" was updated to process all of them, namely reportAttributes, reportTransactionsAndViolations and sortedReportActions.
With recomputes coalesced, one logical update produces exactly one compute of each derived value instead of one-per-dependency, removing the redundant passes that block the thread. Derived values become eventually consistent within one macrotask of the originating write — consumers re-render normally through useOnyx when the single updated value lands.
Measured results (Applause heavy account)
- ManualAppStartup (inbox tab) = 2717 ms -> 2708 ms (−0.3%) (stable, no perf regressions)
- ManualSendMessage = 168 ms -> 84 ms (−50.0%)
- Derived computation after startup and ReconnectApp call
- Total time = 575 ms -> 215 ms (−62.6%)
- Dependencies changes = 62 -> 62 (expected, we want to see improvements in the recomputes)
- Recomputes = 62 -> 19 (−69.4%)
- On main, reportAttributes fired ~14 times/run, including a burst of large ones (123 + 105 + 101 + 75 ≈ 404 ms of compute).
- On the solution branch, reportAttributes fires ~4 times/run, with a single large compute (~110 ms) instead of four.
Upwork Automation - Do Not Edit
Issue Owner
Current Issue Owner: @shubham1206agra
Issue Owner
Current Issue Owner: @shubham1206agra
Coming from a proposal https://expensify.slack.com/archives/C08CZDJFJ77/p1783094934084619?thread_ts=1782801937.464879&cid=C08CZDJFJ77
Proposal: Coalesce OnyxDerived recomputes into one macrotask flush
Background
OnyxDerived values (e.g. reportAttributes) are precomputed values stored in Onyx so that expensive computations shared across the app run once instead of in every consumer. Each derived value declares a list of Onyx dependencies and opens one connection per dependency; whenever a dependency broadcasts a change, the connection callback runs the derived value's compute function. reportAttributes alone declares 13 dependencies (reports, transactions, violations, report actions, NVPs, policies, personal details, etc.) and is among the heaviest computes in the app. Critically, Onyx delivers a single logical write (such as a server response that touches reports, transactions, and violations together) as several separate broadcasts spread across microtasks, and each derived dependency is connected independently.
Problem
When a single logical Onyx update touches several dependencies of a derived value, each dependency's connection callback fires its own synchronous recompute, so one change recomputes the derived value many times back-to-back, blocking the JS thread and dropping frames during startup and report opens.
Solution
Coalesce the per-dependency callbacks for each derived value into a single compute, scheduled on the next macrotask, and reconstruct the changed-member delta at flush time:
With recomputes coalesced, one logical update produces exactly one compute of each derived value instead of one-per-dependency, removing the redundant passes that block the thread. Derived values become eventually consistent within one macrotask of the originating write — consumers re-render normally through useOnyx when the single updated value lands.
Measured results (Applause heavy account)
Upwork Automation - Do Not Edit
Issue Owner
Current Issue Owner: @shubham1206agraIssue Owner
Current Issue Owner: @shubham1206agra