fix(core): Speed up Redux DevTools serialization of timestamps - #4163
Conversation
…timestamp Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJNAqsGkg5XHQYV9Bp5DXx
🦋 Changeset detectedLatest commit: 3ea4fdb The changes in this PR will be included in the next version bump. This PR includes changesets to release 7 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.
|
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJNAqsGkg5XHQYV9Bp5DXx
ntucker
left a comment
There was a problem hiding this comment.
Staff engineer (Cursor agent): LGTM. Hoisting the Intl.DateTimeFormat into the dev-only module block is the simplest correct fix: same options, same output, and the typeof Intl guard now doubles as the replacer switch, so there's one less flag. Unknown options like fractionalSecondDigits are ignored rather than thrown by older engines, so moving construction to module load doesn't add a new failure point. No changes requested for this PR.
|
Size Change: 0 B Total Size: 103 kB ℹ️ View Unchanged
|
There was a problem hiding this comment.
Benchmark React
Details
| Benchmark suite | Current: 3ea4fdb | Previous: b239883 | Ratio |
|---|---|---|---|
data-client: getlist-100 |
136.06 ops/s (± 4.2%) |
175.44 ops/s (± 3.7%) |
1.29 |
data-client: getlist-500 |
42.46 ops/s (± 3.5%) |
55.71 ops/s (± 4.0%) |
1.31 |
data-client: update-entity |
339.08 ops/s (± 7.9%) |
454.55 ops/s (± 8.4%) |
1.34 |
data-client: update-user |
307.77 ops/s (± 5.3%) |
454.55 ops/s (± 9.3%) |
1.48 |
data-client: getlist-500-sorted |
42.64 ops/s (± 9.4%) |
56.03 ops/s (± 9.8%) |
1.31 |
data-client: update-entity-sorted |
266.71 ops/s (± 7.2%) |
408.33 ops/s (± 7.1%) |
1.53 |
data-client: update-entity-multi-view |
303.03 ops/s (± 7.6%) |
416.67 ops/s (± 8.6%) |
1.38 |
data-client: list-detail-switch-10 |
8.34 ops/s (± 9.6%) |
14.71 ops/s (± 7.7%) |
1.76 |
data-client: update-user-10000 |
71.43 ops/s (± 15.7%) |
85.84 ops/s (± 16.4%) |
1.20 |
data-client: invalidate-and-resolve |
36.17 ops/s (± 5.3%) |
47.96 ops/s (± 5.5%) |
1.33 |
data-client: unshift-item |
200 ops/s (± 6.4%) |
263.16 ops/s (± 6.1%) |
1.32 |
data-client: delete-item |
270.27 ops/s (± 4.8%) |
357.14 ops/s (± 4.7%) |
1.32 |
data-client: move-item |
168.08 ops/s (± 10.0%) |
212.77 ops/s (± 8.3%) |
1.27 |
This comment was automatically generated by workflow using github-action-benchmark.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #4163 +/- ##
=======================================
Coverage 98.06% 98.06%
=======================================
Files 163 163
Lines 3095 3095
Branches 616 616
=======================================
Hits 3035 3035
Misses 18 18
Partials 42 42 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Benchmark
Details
| Benchmark suite | Current: 3ea4fdb | Previous: b239883 | Ratio |
|---|---|---|---|
normalizeLong |
778 ops/sec (±5.85%) |
427 ops/sec (±3.13%) |
0.55 |
normalizeLong Values |
694 ops/sec (±0.24%) |
387 ops/sec (±0.86%) |
0.56 |
normalizeLong Scalar |
602 ops/sec (±2.84%) |
359 ops/sec (±3.70%) |
0.60 |
normalizeLong Scalar update |
1787 ops/sec (±0.29%) |
921 ops/sec (±0.19%) |
0.52 |
denormalizeLong |
396 ops/sec (±6.25%) |
243 ops/sec (±6.27%) |
0.61 |
denormalizeLong Values |
363 ops/sec (±3.68%) |
224 ops/sec (±5.37%) |
0.62 |
denormalizeLong donotcache |
1976 ops/sec (±0.78%) |
1000 ops/sec (±0.80%) |
0.51 |
denormalizeLong Values donotcache |
1378 ops/sec (±0.38%) |
749 ops/sec (±0.20%) |
0.54 |
denormalizeLong Scalar donotcache |
2152 ops/sec (±0.25%) |
1063 ops/sec (±0.17%) |
0.49 |
denormalizeShort donotcache 500x |
2491 ops/sec (±2.61%) |
1402 ops/sec (±0.12%) |
0.56 |
denormalizeShort 500x |
1001 ops/sec (±6.61%) |
637 ops/sec (±6.57%) |
0.64 |
denormalizeShort 500x withCache |
10293 ops/sec (±0.24%) |
7080 ops/sec (±0.20%) |
0.69 |
queryShort 500x withCache |
5899 ops/sec (±0.23%) |
3193 ops/sec (±0.12%) |
0.54 |
buildQueryKey All |
97421 ops/sec (±0.51%) |
58045 ops/sec (±0.68%) |
0.60 |
query All withCache |
10778 ops/sec (±5.80%) |
6282 ops/sec (±2.69%) |
0.58 |
denormalizeLong with mixin Entity |
350 ops/sec (±6.68%) |
221 ops/sec (±7.08%) |
0.63 |
denormalizeLong withCache |
14234 ops/sec (±0.58%) |
7616 ops/sec (±0.17%) |
0.54 |
denormalizeLong withCache (Scalar churn) |
14275 ops/sec (±0.96%) |
7618 ops/sec (±0.20%) |
0.53 |
denormalizeLong Values withCache |
10654 ops/sec (±1.31%) |
5205 ops/sec (±1.58%) |
0.49 |
denormalizeLong Scalar withCache |
14840 ops/sec (±0.40%) |
7839 ops/sec (±0.39%) |
0.53 |
denormalizeLong Scalar update withCache |
7993 ops/sec (±0.39%) |
4075 ops/sec (±0.28%) |
0.51 |
denormalizeLong All withCache |
13832 ops/sec (±0.44%) |
6710 ops/sec (±0.61%) |
0.49 |
denormalizeLong Query-sorted withCache |
10659 ops/sec (±6.82%) |
6598 ops/sec (±1.48%) |
0.62 |
denormalizeLongAndShort withEntityCacheOnly |
2992 ops/sec (±4.39%) |
1813 ops/sec (±0.26%) |
0.61 |
denormalize bidirectional 50 |
7506 ops/sec (±9.90%) |
4537 ops/sec (±10.97%) |
0.60 |
denormalize bidirectional 50 donotcache |
73682 ops/sec (±3.86%) |
42714 ops/sec (±0.48%) |
0.58 |
getResponse |
7723 ops/sec (±5.30%) |
4440 ops/sec (±3.68%) |
0.57 |
getResponse (null) |
20779493 ops/sec (±0.53%) |
9674787 ops/sec (±0.75%) |
0.47 |
getResponse (clear cache) |
323 ops/sec (±7.83%) |
208 ops/sec (±9.15%) |
0.64 |
getSmallResponse |
6352 ops/sec (±1.22%) |
3447 ops/sec (±2.98%) |
0.54 |
getSmallInferredResponse |
4960 ops/sec (±1.13%) |
2886 ops/sec (±1.37%) |
0.58 |
getResponse Collection |
7952 ops/sec (±3.80%) |
4361 ops/sec (±4.43%) |
0.55 |
get Collection |
5832 ops/sec (±0.43%) |
2691 ops/sec (±0.40%) |
0.46 |
get Query-sorted |
8886 ops/sec (±4.01%) |
5053 ops/sec (±1.85%) |
0.57 |
setLong |
808 ops/sec (±0.50%) |
434 ops/sec (±0.26%) |
0.54 |
setLongWithMerge |
451 ops/sec (±0.47%) |
250 ops/sec (±0.18%) |
0.55 |
setLongWithSimpleMerge |
475 ops/sec (±0.36%) |
268 ops/sec (±0.67%) |
0.56 |
setSmallResponse 500x |
1644 ops/sec (±2.90%) |
906 ops/sec (±1.17%) |
0.55 |
setMany 50x one-per-row |
226 ops/sec (±1.39%) |
145 ops/sec (±1.04%) |
0.64 |
setMany 50 batch |
6233 ops/sec (±1.61%) |
3515 ops/sec (±2.80%) |
0.56 |
setMany 500x one-per-row |
23.05 ops/sec (±0.47%) |
13.98 ops/sec (±0.90%) |
0.61 |
setMany 500 batch |
2870 ops/sec (±0.34%) |
1382 ops/sec (±3.26%) |
0.48 |
This comment was automatically generated by workflow using github-action-benchmark.
ntucker
left a comment
There was a problem hiding this comment.
Reviewed 025e4e4ad365e20c09ccc24af3f9dc068ccc456f. No actionable correctness findings. The shared formatter preserves the replacer's field/value filters and stays inside the development-only block. Independently checked old/new output parity in Node for seven timestamps, including negative and Date-range boundary values. Validation is limited to serializer output and source review; I did not run the browser DevTools integration or reproduce the performance benchmark.
|
Sol review bot (GPT-6.1 Sol, High): I posted this review of commit |
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJNAqsGkg5XHQYV9Bp5DXx
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJNAqsGkg5XHQYV9Bp5DXx
Requested by Nathaniel · project thread
Before: with Redux DevTools open, every store update stalled the page in development. Serializing the store for DevTools took about 20ms with 50 entities and 200ms with 500.
After: each update serializes 40-60x faster, and timestamps still read as
10:42:07.123 AM.The default
serialize.replacerbuilt a newIntl.DateTimeFormatfor everydate/*Atnumber it formatted, and DevTools runs the replacer over the whole state on every action.How: build the formatter once in
DevToolsManager's dev-only module block and reuse it. Output is identical. The v0.19 blog gets a "Faster Redux DevTools" section with aPerfChart.Measurement
JSON.stringify(state, replacer)per store update, median of 5 runs on Node 22. Both replacers produce byte-identical output.Changeset
🤖 Generated with Claude Code
https://claude.ai/code/session_01HJNAqsGkg5XHQYV9Bp5DXx
Note
Low Risk
Dev-only DevTools path with identical serialized output; no production or runtime store behavior changes.
Overview
Fixes development stutter when Redux DevTools is open by reusing a single
Intl.DateTimeFormatfor timestamp fields instead of constructing one on everydate/*Atvalue during DevTools state serialization.DevtoolsManager's dev-onlyserialize.replacernow callsdateFormat.format(value)on a formatter created once at module load. Serialized output is unchanged (still readable times like10:42:07.123 AM), but each store update serializes 40–60× faster on large or frequently updated stores. Production builds are unaffected.Release notes: changeset plus v0.19 blog bullet and Faster Redux DevTools section with benchmark chart.
Reviewed by Cursor Bugbot for commit 3ea4fdb. Bugbot is set up for automated code reviews on this repo. Configure here.