Skip to content

perf(table-core): write-epoch fast path for memoized APIs - #6570

Draft
KevinVandy wants to merge 1 commit into
perf/shape-stable-instancesfrom
perf/memo-write-epoch
Draft

perf(table-core): write-epoch fast path for memoized APIs#6570
KevinVandy wants to merge 1 commit into
perf/shape-stable-instancesfrom
perf/memo-write-epoch

Conversation

@KevinVandy

Copy link
Copy Markdown
Member

Summary

Stacked on #6569 (shape-stable instances). Adds a table-wide write epoch that lets memoized APIs skip their dependency check entirely when nothing could have changed.

  • table._epoch is bumped on every state, options, and external-atom write. The patched atom set wraps the updater and bumps at value-resolution time: after the user updater runs (updater reads still see the pre-write epoch) and immediately before the store assigns and notifies (synchronous listeners see new value and new epoch together). Bumping before or after set instead leaves a window where epoch and value disagree for code running inside the write; the react cell-selection per-row <Subscribe> selectors caught exactly that.
  • memo() gains epochSource: unchanged epoch + same depArgs identity returns the cached result without running memoDeps. The win is not per-instance memos on a state-change render (those revalidate anyway, measured at parity); it is the high-fanin table memos (getAllLeafColumns, getRowModel, getRowsInDisplayOrder, selection bounds) that every row's deps call once per row, and any read pass without an intervening write.
  • Opt-in per reactivity binding via TableReactivityBindings.supportsWriteEpoch, enabled where verified by the adapter's full e2e subset: store (vanilla), react/preact/lit/octane (render-phase preset), vue, angular, alpine. Off for solid and svelte (read-tracking reactivity subscribes to whatever a memoized call reads during execution; the fast path registers no dependencies, so templates never invalidate) and ember (live pull-based options getter). Their path in is an epoch-as-signal provided by the binding, which is also the shape a transactional/async state model wants (epoch = transaction version, bumped per commit).
  • Contract change: memoized APIs revalidate once per table write epoch. Row models only mutate structure inside epoch-advancing writes; a manual row.subRows = ... outside any write surfaces after the next write (constructRow.test updated).

Results

Full benchmark campaign against 9.1.2 (shape PR + this PR; v8 re-measured in the same runs):

Row model (geomean of all cases) vs 9.1.2
selection (10) -46%
expanding (4) -39%
grouping (36) -35%
filtering (48) -30%
faceting (15) -29%
sorting (48) -14%
core (4) +3% (sub-ms cases; core at 100k is -33%)
all 165 cases -27%
Headline case v8 9.1.2 this chain
grouping sum, 100k 116 ms 62.1 ms 27.0 ms
selection grouped 10%, 100k 9.0 ms 4.4 ms 1.3 ms
filtering includesString, 400k 109 ms 94.7 ms 64.0 ms
heap, paginated 1M rows 2710 MB 379 MB 299 MB

Node: steady-state reads -52%, same-epoch repeats -57%, worst case (a write before every read pass) at parity. Full monorepo CI and all 388 example e2e projects pass; each adapter subset was verified individually with the flag in its final state.

Notes for review

  • Probing trap for anyone debugging this: react's useTable returns a spread copy of the table (react-compiler workaround), so _epoch / options on the returned object are frozen snapshots. Probe the real table via row.table.
  • Tables without _epoch (mock tables in tests) and bindings without the flag always run the plain dependency check.

🤖 Generated with Claude Code

Add `table._epoch`, bumped at value-resolution time on every state,
options, and external-atom write (the patched atom `set` wraps the updater
and bumps after the user updater runs, immediately before the store assigns
and notifies, so epoch and value change atomically for updaters and
synchronous listeners alike). `memo()` gains an `epochSource`: when the
epoch and depArgs identity are unchanged since the last validation, the
cached result returns without running `memoDeps`, collapsing the nested
dependency cascade that high-fanin table memos (`getAllLeafColumns`,
`getRowModel`, selection bounds) otherwise re-run once per consumer call.

Opt-in per reactivity binding via `TableReactivityBindings.supportsWriteEpoch`:
enabled for store, react/preact/lit/octane (render-phase preset), vue,
angular, alpine (each verified by its full e2e subset). Solid and svelte stay
off: their read-tracking reactivity subscribes to whatever a memoized call
reads, and the fast path registers no dependencies. Ember stays off: its
live options getter changes options without any set. Both have a documented
epoch-as-signal path in.

Contract change: memoized APIs revalidate once per table write epoch, so a
manual structural mutation (e.g. `row.subRows = ...` outside any write)
surfaces after the next write.

Full bench campaign vs 9.1.2 (with the shape refactor): all 165 row-model
cases -27% geomean (grouping -35%, selection -46%, filtering -30%);
grouping sum 100k 62.1 -> 27.0 ms; Node steady reads -52%, worst case
(write before every read pass) at parity.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cd6eb1ca-9439-4f6a-8e49-b6a30c520cd2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@nx-cloud

nx-cloud Bot commented Aug 21, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 74f824b

Command Status Duration Result
nx affected --targets=test:eslint,test:sherif,t... ✅ Succeeded 7m 48s View ↗
nx run-many --targets=build --exclude=examples/** ✅ Succeeded 42s View ↗

☁️ Nx Cloud last updated this comment at 2026-08-21 17:47:12 UTC

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

No changeset entries found. Merging this PR will not cause a version bump for any packages.

@pkg-pr-new

pkg-pr-new Bot commented Aug 21, 2026

Copy link
Copy Markdown
More templates

@tanstack/alpine-table

npm i https://pkg.pr.new/@tanstack/alpine-table@6570

@tanstack/angular-table

npm i https://pkg.pr.new/@tanstack/angular-table@6570

@tanstack/angular-table-devtools

npm i https://pkg.pr.new/@tanstack/angular-table-devtools@6570

@tanstack/ember-table

npm i https://pkg.pr.new/@tanstack/ember-table@6570

@tanstack/lit-table

npm i https://pkg.pr.new/@tanstack/lit-table@6570

@tanstack/match-sorter-utils

npm i https://pkg.pr.new/@tanstack/match-sorter-utils@6570

@tanstack/octane-table

npm i https://pkg.pr.new/@tanstack/octane-table@6570

@tanstack/preact-table

npm i https://pkg.pr.new/@tanstack/preact-table@6570

@tanstack/preact-table-devtools

npm i https://pkg.pr.new/@tanstack/preact-table-devtools@6570

@tanstack/react-table

npm i https://pkg.pr.new/@tanstack/react-table@6570

@tanstack/react-table-devtools

npm i https://pkg.pr.new/@tanstack/react-table-devtools@6570

@tanstack/solid-table

npm i https://pkg.pr.new/@tanstack/solid-table@6570

@tanstack/solid-table-devtools

npm i https://pkg.pr.new/@tanstack/solid-table-devtools@6570

@tanstack/svelte-table

npm i https://pkg.pr.new/@tanstack/svelte-table@6570

@tanstack/table-core

npm i https://pkg.pr.new/@tanstack/table-core@6570

@tanstack/table-devtools

npm i https://pkg.pr.new/@tanstack/table-devtools@6570

@tanstack/vue-table

npm i https://pkg.pr.new/@tanstack/vue-table@6570

@tanstack/vue-table-devtools

npm i https://pkg.pr.new/@tanstack/vue-table-devtools@6570

commit: 74f824b

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