Closed
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
This was referenced Jan 22, 2026
github-merge-queue bot
pushed a commit
that referenced
this pull request
Feb 12, 2026
) ## Immutable `applyChange` with O(N + K) Batching Makes `applyChange` return new objects instead of mutating in place. Unchanged rows keep their object reference, changed rows get new references. This lets React.memo and Solid reactivity actually work. Right now Zero deep clones every snapshot because `applyChange` mutates in place. Every row gets a new reference on every sync, even if nothing changed. ``` Current behavior (mutable + deepClone): DB: UPDATE row A Consumer receives snapshot ┌──────────────┐ ┌──────────────────────────┐ │ A = "new" │ │ A' ← new ref (changed) │ │ B = "same" │ ────► │ B' ← new ref (unchanged) │ ← React.memo fails │ C = "same" │ │ C' ← new ref (unchanged) │ ← everything re-renders └──────────────┘ └──────────────────────────┘ ``` With immutable `applyChange`, unchanged rows keep their reference: ``` New behavior (immutable): DB: UPDATE row A Consumer receives snapshot ┌──────────────┐ ┌──────────────────────────┐ │ A = "new" │ │ A' ← new ref (changed) │ ← re-render │ B = "same" │ ────► │ B ← same ref │ ← memo skips │ C = "same" │ │ C ← same ref │ ← memo skips └──────────────┘ └──────────────────────────┘ ``` ## Performance Started with a naive immutable implementation. @tantaman pointed out it was O(K × N) for K changes to an N-item array: ``` Naive immutable (O(K × N)): push(change₁) ──► [...array] ─┐ push(change₂) ──► [...array] │ K rebuilds push(change₃) ──► [...array] │ of N items ... │ push(changeₖ) ──► [...array] ─┘ ``` Ran benchmarks, it was ~4x slower at scale. Still under 0.1ms but felt like we could do better. Tried batching but hit a blocker: tests expected `.data` to reflect changes immediately after `push()`. Solution: auto-flush in the `.data` getter. ``` Batched immutable (O(N + K)): push(change₁) ──► buffer ─┐ push(change₂) ──► buffer │ O(K) to buffer push(change₃) ──► buffer │ ... │ push(changeₖ) ──► buffer ─┘ │ ▼ flush() ─────► sorted merge ──► one new array │ O(N + K log K) ``` The `.data` getter auto-flushes if there are pending changes, so existing code just works. ``` Benchmarks (time in ms): N=2000, K=200 ───────────────────────────────────────────────────────── Mutable O(K) │████████████████████████ 0.023ms Immutable O(K×N) │████████████████████████████████████████████████████████ 0.109ms Batched O(N+K) │███████████████ 0.015ms ◄── faster than mutable! ───────────────────────────────────────────────────────── ``` At larger scales batched beats mutable because we do not have to copy the objects over and over ## Changes ``` applyChange() ├── returns Entry instead of void ├── uses toSpliced()/with() for immutable array ops └── uses spread for immutable object ops ArrayView ├── push() buffers changes ├── flush() applies in one sorted merge pass └── .data getter auto-flushes for backwards compat Consumers ├── React: removes deepClone └── Solid: uses produce with in-place mutation ``` ## Manual Testing: React (zbugs) To verify React.memo actually works with stable references, I modified zbugs' list page: 1. Extracted `IssueRow` outside the component and wrapped with `React.memo` 2. Added custom comparator (default shallow compare fails because `style` is new object each render) 3. Added render count tracking to each row ```tsx const areIssueRowPropsEqual = (prev, next) => { if (prev.issue !== next.issue) return false; // reference check if (prev.style?.transform !== next.style?.transform) // value check return false; // ... other props return true; }; const IssueRow = memo(function IssueRow({ issue, style, ... }) { // render count tracking for testing }, areIssueRowPropsEqual); ``` Then ran the app and updated rows directly via psql while watching console: ``` ┌───────────────────────────────────────────────────────────────────────┐ │ Test 1: Update title only (no sort position change) │ ├───────────────────────────────────────────────────────────────────────┤ │ │ │ psql> UPDATE issue SET title = 'TITLE ONLY UPDATE' │ │ WHERE id = '_6cIk1pH24n6nmJ5sI6Ar'; │ │ │ │ Console output after Zero sync: │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ [render #3] IssueRow: _6cIk1pH - "TITLE ONLY UPDATE" │ │ │ │ (sameRef: false) │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ │ Result: ONLY the updated row re-rendered. Other 99 rows skipped. │ │ │ └───────────────────────────────────────────────────────────────────────┘ ┌───────────────────────────────────────────────────────────────────────┐ │ Test 2: Update title + modified (causes sort position change) │ ├───────────────────────────────────────────────────────────────────────┤ │ │ │ psql> UPDATE issue SET title = 'POSITION CHANGE', │ │ modified = 1769210037000 │ │ WHERE id = 'dSHOxE86nkbGafajdd1ew'; │ │ │ │ Console output after Zero sync: │ │ ┌────────────────────────────────────────────────────────────────┐ │ │ │ [render #3] IssueRow: dSHOxE86 - "POSITION CHANGE" │ │ │ │ [render #3] IssueRow: oGP3NonM - "Some other issue" │ │ │ └────────────────────────────────────────────────────────────────┘ │ │ │ │ Result: Updated row + displaced row re-rendered. Expected behavior │ │ since style.transform changed for rows that moved. │ │ │ └───────────────────────────────────────────────────────────────────────┘ ``` Key insight: Zero maintains stable object references for unchanged issues. The `sameRef: true/false` logging confirmed this. When only the title changes, that row gets a new reference, but all other rows keep their original reference. ## Manual Testing: Solid (zbugs-lite-solid) (this is no longer part of the PR) Created a minimal Solid app that imports Zero schema/queries from zbugs. Added render count badges to each row. ``` ┌────────────────────────────────────────────────────────────────────────┐ │ Test: Update one issue while watching render counts │ ├────────────────────────────────────────────────────────────────────────┤ │ │ │ psql> UPDATE issue SET title = 'MODIFIED BY PSQL' │ │ WHERE id = 'HdpMkgbH...'; │ │ │ │ Before: After: │ │ ┌──────────────────────────┐ ┌──────────────────────────┐ │ │ │ [1] Leaking listeners... │ │ [1] MODIFIED BY PSQL │ ← +0 │ │ │ [1] Sort and remove... │ │ [1] Sort and remove... │ │ │ │ [1] UnknownError: Int... │ │ [1] UnknownError: Int... │ │ │ │ [1] Fix: race condition │ │ [1] Fix: race condition │ │ │ │ [1] Add support for... │ │ [1] Add support for... │ │ │ └──────────────────────────┘ └──────────────────────────┘ │ │ │ │ Items keep their reference so Solid never rerenders. │ │ All other rows stayed at 1. │ │ │ └────────────────────────────────────────────────────────────────────────┘ ``` Solid's fine-grained reactivity handles the in-place updates efficiently via produce. No special memo config needed. ## Related - Follows up on #5455 - Revives #3688 (original immutable attempt) - Fixes zero/3036 --------- Co-authored-by: Alp <alp@Alps-MacBook-Pro.local> Co-authored-by: Alp <alp@Alps-MBP.localdomain> Co-authored-by: Alp <alp@mac.mynetworksettings.com> Co-authored-by: Matt Wonlaw <matt.wonlaw@gmail.com> Co-authored-by: Erik Arvidsson <arv@roci.dev> Co-authored-by: Erik Arvidsson <erik.arvidsson@gmail.com>
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.
This is an old branch that I had that changes applyChange to treat the data as immutable and it copies the minimal amount of objects to create a new object.
It has bit rotted a bit but the goal is to remove the deepClone which was initially only used in React but now it is used in Solid too. Doing a deepClone copies everything. This branch only copies the objects that change.