Skip to content

Solid renderer rework - huge performance gains#1609

Open
fezproof wants to merge 3 commits into
TanStack:mainfrom
fezproof:solid-renderer-rework
Open

Solid renderer rework - huge performance gains#1609
fezproof wants to merge 3 commits into
TanStack:mainfrom
fezproof:solid-renderer-rework

Conversation

@fezproof

@fezproof fezproof commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

🎯 Changes

Changes how the solid-js renderer updates data to be much more performant in all cases.
This PR manages to keep the API interface exactly the same, while improving change updates + mounting performance.

Currently whenever any data changes, the entire store is reset. This is reliable to keep the data consistent, but for huge collections that only have one field change it is a bit excessive. The changes handle different cases in a few different ways with a huge focus on performance and memory improvements.

  • It keeps avoids cloning any data in the solid renderer, it will only reference the data already in the solid store
    • It also handles the double data set on mount that was happening with the old renderer
  • Uses $key for reconcile
  • It handles singleResult queries much more cleanly
  • If the changes are only just updates that will not change the data order, it will update the rows in place
  • It avoids creating new objects and arrays at all costs, reusing temp arrays so that memory only needs to grow once
  • It lazily mounts state if it is accessed. State is rarely used in reality and can probably be removed, but in the interest of compatibility I just made it faster

All this adds together to a much more reliable and performant library that keeps the promised performance by default of both solid-js and tanstack-db.

I have also extensively tested this in a real world app, and no problems where witnessed.

Benchmarks

When writing this PR I wrote extensive benchmarks that compared this version with the version currently in main. Here are the results.

JSDOM Benchmarks

Initial All-Row Mount

Case Current Main Result
10 rows 0.08ms 0.12ms 1.42× faster
1,000 rows 4.77ms 7.97ms 1.67× faster
10,000 rows 48.2ms 86.2ms 1.79× faster

Single-Row Update in All-Row Query

Case Current Main Result
10 rows 1.71ms 1.75ms 1.03× faster
1,000 rows 7.37ms 12.4ms 1.69× faster
10,000 rows 57.3ms 123.3ms 2.15× faster

10% Row Batch Update in All-Row Query

Case Current Main Result
10 rows 1.71ms 1.74ms 1.02× faster
1,000 rows 17.5ms 21.9ms 1.26× faster
10,000 rows 200.8ms 276.0ms 1.37× faster

Non-Matching Update in Single-Row Query

Case Current Main Result
10 rows 0.61ms 0.60ms 1.00× slower
1,000 rows 0.66ms 0.66ms 1.00× faster
10,000 rows 1.73ms 1.76ms 1.02× faster

Remount After Update

Case Current Main Result
10 rows 2.41ms 2.49ms 1.03× faster
1,000 rows 12.4ms 20.8ms 1.68× faster
10,000 rows 110.6ms 210.6ms 1.90× faster

Sorted Query Moves One Row

Case Current Main Result
10 rows 0.12ms 0.15ms 1.20× faster
1,000 rows 7.28ms 10.7ms 1.47× faster
10,000 rows 152.8ms 194.4ms 1.27× faster

Single-Row Query Update

Case Current Main Result
10 rows 1.88ms 1.81ms 1.04× slower
1,000 rows 2.01ms 1.86ms 1.08× slower
10,000 rows 2.76ms 2.71ms 1.02× slower

Library-Unfavourable Repeated Update Commits

Case Current Main Result
10k rows, 200 single-row update commits 1594.3ms 7728.8ms 4.85× faster
1k rows, indexed single-row query, 100 matching update commits 26.3ms 30.3ms 1.15× faster
1k rows, indexed single-row query, 100 non-matching update commits 14.5ms 14.4ms 1.01× slower

Large Change Sets

Case Current Main Result
1k rows, 100% row batch update in all-row query 62.4ms 68.1ms 1.09× faster

Deep Row Cloning

Case Current Main Result
1k deep rows, single-row update in all-row query 11.2ms 25.3ms 2.26× faster
1k deep rows, 3-level nested update in all-row query 11.2ms 25.2ms 2.26× faster

Real browser benchmarks

Initial Table Render

Chrome

Case Current Main Result
100 rows initial table render 8.35ms 8.33ms 1.00× slower
1,000 rows initial table render 28.7ms 31.1ms 1.09× faster

Firefox

Case Current Main Result
100 rows initial table render 8.33ms 8.33ms 1.00× faster
1,000 rows initial table render 38.9ms 42.7ms 1.10× faster

Safari

Case Current Main Result
100 rows initial table render 16.7ms 16.7ms 1.00× faster
1,000 rows initial table render 32.1ms 33.3ms 1.04× faster

Single-Row Update Commits

Chrome

Case Current Main Result
1,000 rows table, 25 single-row update commits 53.5ms 120.7ms 2.26× faster

Firefox

Case Current Main Result
1,000 rows table, 25 single-row update commits 55.4ms 126.4ms 2.28× faster

Safari

Case Current Main Result
1,000 rows table, 25 single-row update commits 51.8ms 88.6ms 1.71× faster

Batch Update

Chrome

Case Current Main Result
1,000 rows table, 100-row batch update 40.8ms 45.7ms 1.12× faster

Firefox

Case Current Main Result
1,000 rows table, 100-row batch update 53.8ms 54.8ms 1.02× faster

Safari

Case Current Main Result
1,000 rows table, 100-row batch update 43.2ms 46.2ms 1.07× faster

Sorted Reorder

Chrome

Case Current Main Result
1,000 rows sorted table, update reorders one row 33.3ms 34.1ms 1.02× faster

Firefox

Case Current Main Result
1,000 rows sorted table, update reorders one row 46.5ms 54.8ms 1.18× faster

Safari

Case Current Main Result
1,000 rows sorted table, update reorders one row 47.5ms 47.0ms 1.01× slower

Query Filter Changes

Chrome

Case Current Main Result
1,000 rows table, query filter changes after mount 17.1ms 18.7ms 1.10× faster

Firefox

Case Current Main Result
1,000 rows table, query filter changes after mount 23.2ms 34.1ms 1.47× faster

Safari

Case Current Main Result
1,000 rows table, query filter changes after mount 33.3ms 33.3ms 1.00× faster

Deep Nested Update Commits

Chrome

Case Current Main Result
1,000 deep rows table, 25 deep nested update commits 52.4ms 219.8ms 4.20× faster

Firefox

Case Current Main Result
1,000 deep rows table, 25 deep nested update commits 77.2ms 217.8ms 2.82× faster

Safari

Case Current Main Result
1,000 deep rows table, 25 deep nested update commits 55.3ms 166.1ms 3.00× faster

I am happy to provide the benchmark code if wanted, its just a tad noisy and adds playwright as a dependency which could slow down other runners. Can clean it up and add some actions that show benchmarks in comparison with main when creating PRs possibly

✅ Checklist

  • I have tested this code locally with pnpm test.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • Performance
    • Improved SolidJS live query performance for more efficient result synchronization and reduced unnecessary recomputation.
  • Bug Fixes
    • Fixed live query behavior when switching between collections to ensure results resync correctly and avoid stale row indexing.
  • Tests
    • Added a regression test covering live query resynchronization after switching active collections.

@fezproof fezproof changed the title Solid renderer rework Solid renderer rework - huge performance gains Jun 22, 2026
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 51e0e7be-8d67-4aa2-8efa-04888a196eab

📥 Commits

Reviewing files that changed from the base of the PR and between c90c950 and d9641a6.

📒 Files selected for processing (1)
  • packages/solid-db/src/useLiveQuery.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/solid-db/src/useLiveQuery.ts

📝 Walkthrough

Walkthrough

useLiveQuery is refactored to use scratch storage (rowIndex, syncRows), reconcile-based store updates, and granular patch paths (patchArrayChanges, patchSingleResultChanges) that avoid full rebuilds for update-only change batches. The .state getter is made lazy, subscription handling is rewritten with adjusted hydration behavior, and a regression test for collection-switching is added.

Changes

useLiveQuery SolidJS renderer rework

Layer / File(s) Summary
Imports, reconciliation constants, and scratch state
packages/solid-db/src/useLiveQuery.ts
Adds createStore/reconcile imports from solid-js/store and ReactiveMap from @solid-primitives/map. Defines RECONCILE_KEY/RECONCILE_DEEP constants and AnyCollection/AnyChange types. Introduces scratch variables (rowIndex, syncRows, singleRowKey, stateAccessed, syncedCollection, stateSyncedCollection) plus isSingleResult, patchStoreRow, and syncStateFromCollection helpers.
Core sync and patch logic
packages/solid-db/src/useLiveQuery.ts
Replaces syncDataFromCollection with a reconcile-based version using rowIndex for in-place patching of both single-result and array collections. Adds syncDataOnlyFromCollection, patchArrayChanges, and patchSingleResultChanges that apply granular changes to .state and .data or fall back to authoritative resync when membership/order may change. Updates createResource to reconcile only on collection reference change.
Subscription handling and public accessors
packages/solid-db/src/useLiveQuery.ts
Rewrites createEffect subscription to clear scratch on null collection, sets includeInitialState: false, detects hydration race for full resync, and dispatches to patch paths otherwise. Updates getData single-result fast path with conditional resource tracking and isSingleResult helper. Makes .state getter lazy via syncStateFromCollection and stateAccessed tracking.
Regression test and test callback fixes
packages/solid-db/tests/useLiveQuery.test.tsx, .changeset/small-doors-raise.md
Adds a collection-switching regression test with controlled toArrayWhenReady blocking to assert correct resync behavior when the active collection changes. Updates no-op handler bodies in five existing isLoading tests to use block syntax. Adds a minor changeset entry for the SolidJS renderer performance rework.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 Hop! The old sync path was slow as mud,
Now scratch arrays and reconcile thud.
Patch only what changed, skip the rebuild,
Four times faster — the warren is thrilled!
.state waits lazily 'til you peek,
The renderer rework is at its peak. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change - a rework of the solid renderer with performance improvements - matching the core objective of the PR.
Description check ✅ Passed The description covers the Changes section thoroughly with detailed explanations, and includes a completed Release Impact checklist confirming a changeset was generated.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/solid-db/src/useLiveQuery.ts`:
- Line 520: The reconcile call in the if (!needsResync) block is missing the
reconciliation options used elsewhere in the code. Update the reconcile(row)
call to match the same pattern used in patchStoreRow by adding the
RECONCILE_DEEP option and merge: true parameters. This ensures consistent
behavior and preserves fine-grained field subscriptions to prevent unnecessary
re-renders for unchanged fields in single-result queries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 97063a35-6dcc-4e9c-b2bb-dcecc6cb68d8

📥 Commits

Reviewing files that changed from the base of the PR and between 9a5e8e5 and c90c950.

📒 Files selected for processing (3)
  • .changeset/small-doors-raise.md
  • packages/solid-db/src/useLiveQuery.ts
  • packages/solid-db/tests/useLiveQuery.test.tsx

Comment thread packages/solid-db/src/useLiveQuery.ts Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant