enhance(normalizr): Lazy-clone entity tables to fix getNewEntities deopt#3884
enhance(normalizr): Lazy-clone entity tables to fix getNewEntities deopt#3884
Conversation
🦋 Changeset detectedLatest commit: 6216c5c The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
Size Change: 0 B Total Size: 80.7 kB ℹ️ View Unchanged
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #3884 +/- ##
=======================================
Coverage 98.11% 98.11%
=======================================
Files 153 153
Lines 2913 2916 +3
Branches 565 566 +1
=======================================
+ Hits 2858 2861 +3
Misses 11 11
Partials 44 44 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Benchmark React
Details
| Benchmark suite | Current: a55d329 | Previous: cc330d6 | Ratio |
|---|---|---|---|
data-client: getlist-100 |
147.06 ops/s (± 5.7%) |
142.86 ops/s (± 5.9%) |
0.97 |
data-client: getlist-500 |
42.28 ops/s (± 4.1%) |
38.24 ops/s (± 7.1%) |
0.90 |
data-client: update-entity |
339.08 ops/s (± 8.9%) |
344.83 ops/s (± 9.0%) |
1.02 |
data-client: update-user |
344.83 ops/s (± 6.9%) |
384.62 ops/s (± 7.9%) |
1.12 |
data-client: getlist-500-sorted |
43.39 ops/s (± 6.1%) |
42.74 ops/s (± 7.0%) |
0.99 |
data-client: update-entity-sorted |
285.71 ops/s (± 5.9%) |
322.58 ops/s (± 8.3%) |
1.13 |
data-client: update-entity-multi-view |
312.5 ops/s (± 6.2%) |
357.14 ops/s (± 7.8%) |
1.14 |
data-client: list-detail-switch-10 |
7.19 ops/s (± 4.8%) |
7.88 ops/s (± 7.4%) |
1.10 |
data-client: update-user-10000 |
76.34 ops/s (± 13.3%) |
81.3 ops/s (± 15.4%) |
1.06 |
data-client: invalidate-and-resolve |
35.71 ops/s (± 5.2%) |
35.59 ops/s (± 4.4%) |
1.00 |
data-client: unshift-item |
217.39 ops/s (± 4.6%) |
227.27 ops/s (± 5.1%) |
1.05 |
data-client: delete-item |
285.71 ops/s (± 6.5%) |
285.71 ops/s (± 6.6%) |
1 |
data-client: move-item |
186.93 ops/s (± 6.4%) |
196.08 ops/s (± 5.9%) |
1.05 |
This comment was automatically generated by workflow using github-action-benchmark.
There was a problem hiding this comment.
Benchmark
Details
| Benchmark suite | Current: a55d329 | Previous: cc330d6 | Ratio |
|---|---|---|---|
normalizeLong |
459 ops/sec (±1.80%) |
438 ops/sec (±2.01%) |
0.95 |
normalizeLong Values |
416 ops/sec (±0.44%) |
409 ops/sec (±1.30%) |
0.98 |
denormalizeLong |
247 ops/sec (±4.23%) |
249 ops/sec (±4.26%) |
1.01 |
denormalizeLong Values |
229 ops/sec (±3.44%) |
226 ops/sec (±3.37%) |
0.99 |
denormalizeLong donotcache |
1005 ops/sec (±0.15%) |
1035 ops/sec (±0.71%) |
1.03 |
denormalizeLong Values donotcache |
754 ops/sec (±0.20%) |
751 ops/sec (±0.14%) |
1.00 |
denormalizeShort donotcache 500x |
1583 ops/sec (±0.16%) |
1572 ops/sec (±0.21%) |
0.99 |
denormalizeShort 500x |
723 ops/sec (±3.63%) |
745 ops/sec (±3.28%) |
1.03 |
denormalizeShort 500x withCache |
6322 ops/sec (±0.13%) |
7527 ops/sec (±0.25%) |
1.19 |
queryShort 500x withCache |
2972 ops/sec (±0.10%) |
2974 ops/sec (±0.10%) |
1.00 |
buildQueryKey All |
53314 ops/sec (±0.39%) |
56343 ops/sec (±0.45%) |
1.06 |
query All withCache |
6757 ops/sec (±0.32%) |
6654 ops/sec (±0.35%) |
0.98 |
denormalizeLong with mixin Entity |
268 ops/sec (±6.15%) |
231 ops/sec (±4.16%) |
0.86 |
denormalizeLong withCache |
7104 ops/sec (±0.24%) |
7143 ops/sec (±0.22%) |
1.01 |
denormalizeLong Values withCache |
5021 ops/sec (±0.23%) |
4991 ops/sec (±0.67%) |
0.99 |
denormalizeLong All withCache |
6546 ops/sec (±0.21%) |
6369 ops/sec (±0.31%) |
0.97 |
denormalizeLong Query-sorted withCache |
6868 ops/sec (±0.23%) |
6738 ops/sec (±0.23%) |
0.98 |
denormalizeLongAndShort withEntityCacheOnly |
1727 ops/sec (±0.24%) |
1819 ops/sec (±0.20%) |
1.05 |
denormalize bidirectional 50 |
6991 ops/sec (±0.29%) |
5227 ops/sec (±4.07%) |
0.75 |
denormalize bidirectional 50 donotcache |
40548 ops/sec (±1.54%) |
41228 ops/sec (±1.25%) |
1.02 |
getResponse |
4620 ops/sec (±0.64%) |
4534 ops/sec (±0.66%) |
0.98 |
getResponse (null) |
10010189 ops/sec (±0.61%) |
10894251 ops/sec (±0.78%) |
1.09 |
getResponse (clear cache) |
334 ops/sec (±2.76%) |
234 ops/sec (±3.72%) |
0.70 |
getSmallResponse |
3531 ops/sec (±0.17%) |
3476 ops/sec (±0.19%) |
0.98 |
getSmallInferredResponse |
2709 ops/sec (±0.14%) |
2748 ops/sec (±0.14%) |
1.01 |
getResponse Collection |
4529 ops/sec (±0.80%) |
4471 ops/sec (±0.35%) |
0.99 |
get Collection |
4545 ops/sec (±0.32%) |
4550 ops/sec (±0.77%) |
1.00 |
get Query-sorted |
4551 ops/sec (±0.58%) |
5228 ops/sec (±0.33%) |
1.15 |
setLong |
467 ops/sec (±0.24%) |
464 ops/sec (±0.20%) |
0.99 |
setLongWithMerge |
252 ops/sec (±0.35%) |
252 ops/sec (±0.50%) |
1 |
setLongWithSimpleMerge |
277 ops/sec (±0.18%) |
270 ops/sec (±0.24%) |
0.97 |
setSmallResponse 500x |
929 ops/sec (±0.97%) |
929 ops/sec (±0.08%) |
1 |
This comment was automatically generated by workflow using github-action-benchmark.
getNewEntities eagerly cloned entity and meta table POJOs on first
access per key, causing a Maglev bailout ("Insufficient type feedback
for generic named access") because this.entities lacked stable type
feedback at optimization time.
Move the clone to setEntity (lazy, on first write per entity type) so
getNewEntities stays a pure Map operation that Maglev can optimize and
keep optimized. Also extract MetaEntry type alias to reduce repetition.
Made-with: Cursor
6216c5c to
a55d329
Compare
Motivation
V8 trace profiling (
BENCH_V8_TRACE=true) showedgetNewEntitiessuffering a Maglev bailout — "Insufficient type feedback for generic named access" onthis.entities. The function was compiled early by Maglev but bailed out because the POJO clone (this.entities[key] = { ...this.entities[key] }) accessedthis.entitiesbefore V8 had gathered enough type feedback. After bailout, the function stayed in interpreter mode for the rest of the benchmark.Solution
Move the entity/meta table POJO clone from
getNewEntitiesintosetEntity, triggered lazily on the first write per entity type (updateMeta && newEntities.size === 0). This keepsgetNewEntitiesas a pure Map operation that Maglev can optimize and keep optimized.Also extracts a
MetaEntrytype alias to reduce the repeated{ fetchedAt: number; date: number; expiresAt: number }inline type.V8 deopt trace results
Before (baseline):
getNewEntitiescompiled → bailed out ("generic named access" onthis.entities) → stuck in interpreter for remainder of benchmark.After (fix):
getNewEntitiescompiled → bails out once ("generic global access" onnew Map) → V8 recovers and re-optimizes → stays in Maglev-compiled code for the rest of the run.Remaining "generic named access" deopts (5) are all React internals, not data-client code.
Benchmark results
Focused benchmarks (5 runs each, CI-tightened convergence) show the fix is performance-neutral — no measurable throughput improvement or regression:
All deltas are within the per-run variance bands (±7-14%). Low-variance scenarios (
getlist-100,invalidate-and-resolve) were also unchanged.The value of this change is V8 optimization quality:
getNewEntitiesstays in compiled Maglev code instead of falling back to the interpreter. This is a code-health improvement that protects against future performance cliffs as the normalize path evolves.Note
Low Risk
Low risk performance refactor confined to POJO normalization; main risk is subtle mutation/clone timing differences per entity type on first write.
Overview
Keeps
getNewEntities()as a pureMaplookup by moving POJO cloning ofentities[key]/entitiesMeta[key]out of that method and intosetEntity()on the first write per entity type.Adds a small type cleanup via a shared
MetaEntryalias and includes a patch changeset documenting the Maglev deopt motivation and the lazy-clone behavior.Reviewed by Cursor Bugbot for commit a55d329. Bugbot is set up for automated code reviews on this repo. Configure here.