Skip to content

perf(signals): witness coarse-read machinery — one node per record subtree - #3275

Draft
ryansolid wants to merge 8 commits into
nextfrom
perf/witness-coarse-reads
Draft

perf(signals): witness coarse-read machinery — one node per record subtree#3275
ryansolid wants to merge 8 commits into
nextfrom
perf/witness-coarse-reads

Conversation

@ryansolid

Copy link
Copy Markdown
Member

What

The coarse-read stack from the create-floor isolation battery (O5–O7), landed as internal machinery with zero public API. Three pieces, one semantic unit:

1. witnessNext — the coarse read (O5)

Subscribe to ONE record's deep-witness node (dk) and serve the raw backing. No child walk, no key-set subscription, no snapshot, no leaf nodes, no wrap caches. This is the coarse-row read model: a row effect witnesses its record and reads values raw — one node + one link per record instead of one per touched leaf.

Not exported from solid-js or @solidjs/signals. The consumer is the compiler-emitted coarse-row track (the unified-For scan emission); user code never calls it. Tests import it through the internal module path.

2. Deep-witness bubbling (O6)

bumpDeep now notifies witnesses on the record AND its ancestors, so one witness covers a record subtree. Gated by a live-witness counter: apps with no deep()/witness subscribers pay one number check per change, never a chain walk. Ancestor bumps are authoritative (never lane cargo) — bisected during the battery: bubbling into an optimistic root's armed dk mid-action created an unintended override that wedged the family's generator.

3. Under-witness identity (O7) — the R18 amendment

Keyed reconcile's adoption walk threads an under-witness flag: records beneath a live deep witness keep key-matched proxy identity without materializing per-record nodes — the witness IS the subscription for its subtree. The reviewer-facing semantic surface:

  • Never-wrapped records are still skipped always, even under a witness — cost stays O(wrapped), never O(store).
  • Unwitnessed captures still detach (R18's "subscribing buys liveness" is unchanged where no witness exists) — narrow-subpath pruning in large store diffs is byte-for-byte today's behavior.
  • Since deep() also creates dk nodes, deep() subscribers get bubbling + subtree identity too. This is a strengthening, not a break.

All three behaviors are pinned by the new reconcile-pruning-contract.test.ts (uses the __adoptVisits probe to assert exact walk-entry counts across unwitnessed / witnessed / witness-released scenarios).

Measured

Isolation battery (solo on clean next, 5 canonical fixtures, correctness gates green). Hand-compiled dbmon coarse rows vs fine-grained:

  • mount 0.70x, unmount 0.61x, tick 0.90x
  • deep dbmon: one witness per row replaces one per nested record
  • Signal-only fixtures untouched (all of it tree-shakes out of storeless graphs; the bubble gate is one counter check)

Size

  • + createStore: 14.16 → 14.30 KB (+140 B: witnessNext + bubbling + the reconcile flag)
  • hydrating + store families: 26.25 → 26.31 KB (+51 B)
  • Core floor, signal-only, CSR, frames: unchanged/under

Tests

signals 1472 / web 677 / solid 585, all green. Changesets included (patch, prerelease).

Made with Cursor

ryansolid and others added 8 commits September 4, 2026 03:39
…itness node, serve raw

The coarse-row read model (create-floor): a row effect witnesses its
record(s) — one value-less dk node + one link each — and reads values RAW,
materializing zero leaf nodes/links/wrap caches. Vue Vapor's dep economics
(value-less anchors, raw data as the only value store) with Solid's
semantics. Hand-compiled dbmon fixture: mount 0.70x, remount 0.77x,
unmount 0.61x, tick 0.90x vs the fine-grained row. PROTOTYPE export.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…btree

bumpDeep bumps the record AND every witnessed ancestor (u-chain walk gated
by a live-witness counter; unwitnessed apps pay one number check). Ancestor
bumps are AUTHORITATIVE — bisected: routing them through the lane machinery
armed an optimistic root's dk mid-action and wedged the family generator.
Fold/write gates widen so dk-less records under witnessed ancestors still
bump. Full signals suite green (1461).

Co-authored-by: Cursor <cursoragent@cursor.com>
…g in keyed reconcile

The R18 pruning contract descends only where subscriptions exist (ct.d) —
subscribing buys liveness. A deep witness on a record or ancestor IS that
subscription for the whole subtree (deep() always bought it via per-record
nodes; witness+bubbling claims the same observation with ONE node), so the
adoption walk threads an under-witness flag: witnessed subtrees keep
key-matched proxy identity without materializing any nodes. Unwitnessed
captures still detach (R18 pinned tests green; full suite 1461).

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…act amendment

Four pins with the __adoptVisits probe (TEST-only): (1) never-wrapped rows
skip ALWAYS, witness or not — O(wrapped), never O(store); (2) unwitnessed
wrapped-but-unsubscribed rows detach (R18 stale captures, unchanged);
(3) under a witness, wrapped rows adopt + keep identity with zero per-row
subscriptions (the amendment, and its exact cost bound); (4) releasing the
witness restores pruning. The React-like diff-only-what-is-listened-to
property is now a permanent suite invariant.

Co-authored-by: Cursor <cursoragent@cursor.com>
… the compiler track consumes it

Co-authored-by: Cursor <cursoragent@cursor.com>
…inery

Co-authored-by: Cursor <cursoragent@cursor.com>
@changeset-bot

changeset-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 09b52fb

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
@solidjs/signals Patch
test-integration Patch
@solidjs/web Patch
@solidjs/babel-plugin Patch
@solidjs/compiler Patch
@solidjs/diagnostics Patch
@solidjs/element Patch
@solidjs/h Patch
@solidjs/html Patch
solid-js Patch
@solidjs/universal Patch

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

@ryansolid
ryansolid marked this pull request as draft September 4, 2026 15:39
@ryansolid

Copy link
Copy Markdown
Member Author

Marked draft deliberately: this machinery is inert without its consumer (witnessNext has zero callers; bubbling/under-witness identity only activate through deep()'s dk nodes). Holding the merge until the compiler-emitted coarse-row track lands — the PR exists now so the R18-amendment semantics review can happen decoupled from the unified-For review. The one standalone behavior change if merged early: records captured under a deep() subscription keep key-matched identity through keyed reconcile instead of detaching.

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