[No QA] PoC: measure personalDetailsList as a single key vs an Onyx collection - #98857
Draft
TMisiukiewicz wants to merge 3 commits into
Draft
[No QA] PoC: measure personalDetailsList as a single key vs an Onyx collection#98857TMisiukiewicz wants to merge 3 commits into
TMisiukiewicz wants to merge 3 commits into
Conversation
Contributor
|
🚧 mountiny has triggered a test Expensify/App build. You can view the workflow run here. |
Contributor
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Explanation of Change
Measurement-only PoC — not for merge. This quantifies what
personalDetailsListcosts as a single Onyx key versus as a collection, so the migration can be scoped from numbers instead of intuition. Two instruments:src/libs/telemetry/instrumentPersonalDetailsMerge.tspatchesOnyx.merge/Onyx.updateat startup, mirrors every personal-details write into a write-onlypersonalDetailsShadow_collection, and times both shapes on identical data;tests/perf-test/PersonalDetailsListShape.perf-test.tsis a controlled Reassure A/B at 1k/5k/20k members with an equal subscriber fleet on both shapes.Result — web, real IndexedDB, 11,173 members, 3 cold OpenApp runs: the initial full-list write costs ~1,471 ms as a single key vs 488 ms as a collection (3.1×); a 79-member follow-up costs ~393 ms vs ~17 ms (~24×). Personal details alone account for ~1.86 s of blocking Onyx work per OpenApp today, against ~505 ms for the collection shape. The Jest A/B shows why: the single key is O(N) in members (179 µs → 706 µs → 3,805 µs at 1k → 5k → 20k) while a collection member write is flat at ~25 µs, because every append re-allocates the whole object, deep-equals it in
OnyxCache.hasValueChanged, rewrites it to storage, and broadcasts it to ~300 subscribers.Two findings that matter for scoping. The collection's ~490 ms full-list cost is almost entirely fixed
mergeCollectionoverhead for 11k keys, not per-value work — it was identical across runs where 11,093 members changed and runs where none did — so collection cost tracks key count, not change count. And the win only materialises if read sites move to per-member subscriptions: ~60 sites currently douseOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST)with no selector, and a back-compat derived value re-materialising the O(N) object would be a regression in the interim.Fixed Issues
$
PROPOSAL:
Tests
This PR adds no user-facing behaviour. The mirror collection is write-only — nothing in the app subscribes to it — so app behaviour should be unchanged.
TEST_RUNNER_PATH=./node_modules/jest/bin/jest.js npm run perf-test -- --testMatch '**/PersonalDetailsListShape.perf-test.ts'and verify 6 passing cases; the.reassure/current.perfmeans should show the single key scaling with member count while the collection stays flat.[PersonalDetailsListPerf] writeconsole lines. Pair asingle-keyline with thecollectionline that has the sameincomingKeys,updatesInBatchandchangedMembers, and discard any line withcomparable: false.Cold start is mandatory between runs. The shadow collection persists in IndexedDB; on a warm run every mirror write finds the member byte-identical, Onyx short-circuits it, and the collection posts near-zero durations against real single-key writes.
changedMembersexposes this rather than letting it pass silently.Offline tests
No offline behaviour changes. The instrumentation only wraps write timing and mirrors into an unsubscribed key, so queued and replayed writes are timed the same as any other.
QA Steps
None — this is instrumentation behind no user-facing surface and is not intended to ship. [No QA]
Known limitations
Onyx.merge/Onyx.updateglobally at startup is a measurement hack, not a pattern to copy.mirrorChainshould prevent. It does not affect the numbers above (allconcurrentWrites: 0), but the serialisation is not behaving as designed.changedMembersand will settle it outright.PR Author Checklist
### Fixed Issuessection above[PersonalDetailsListPerf]logging)