Skip to content

perf(bindx-react): batch list refresh notifications - #111

Open
matej21 wants to merge 4 commits into
mainfrom
perf/batch-list-refresh-notifications
Open

perf(bindx-react): batch list refresh notifications#111
matej21 wants to merge 4 commits into
mainfrom
perf/batch-list-refresh-notifications

Conversation

@matej21

@matej21 matej21 commented Sep 10, 2026

Copy link
Copy Markdown
Member

Motivation

A production performance trace showed repeated dirty-state scans taking about 635 ms of an 801 ms main-thread task. Each row in a list response dispatched a server refresh and synchronously notified global subscribers; usePersist then scanned the store again for each row.

Changes

  • Add synchronous, nestable SnapshotStore.batchNotifications, backed by the subscription manager.
  • Keep snapshot/global versions and ancestor propagation immediate, but defer callbacks until the outer batch finishes. Deduplicate callbacks across affected entity, relation and global subscriptions.
  • Batch list-response writes together with publication of the ready list state, so subscribers see the completed response.
  • Preserve unsubscribe behavior and resolve queued keys after rekeys. Flush completed writes in finally; batching does not introduce rollback or change undo transaction boundaries.

Validation

  • Full unit/React suite: 2,078 passed.
  • Typecheck and changed-file ESLint passed.
  • A 200-row React regression test verifies one global notification for the loaded response and complete data in entity callbacks. It fails when list batching is disabled.
  • Store tests cover nesting, local edits, ancestor/version visibility, callback deduplication, unsubscribe, rekeys, clear, failure cleanup and reentrant writes.

The trace identifies the removed source of repeated work; end-to-end browser speed after this change has not yet been measured.

matej21 and others added 4 commits September 10, 2026 16:45
- A throwing subscriber no longer drops the rest of a flush. Delivery
  finishes, then the first subscriber error propagates and the rest are
  logged. When the batch callback itself throws, its error wins.
- Queued keys move with their subscribers on rekey, so a flush no longer
  reads the redirect map, which clear() does not reset.
- batchNotifications rejects async callbacks at compile time.
- Rows under a shared ancestor walk it once per batch. The walked set is
  reset whenever a relation write changes the edges.
- applyJournalImages and importPartialSnapshot run in a batch: an undo
  that removes K creates notifies once, after the restore completes.
- notifyGlobal() is removed; it was identical to notify().

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DQrj6jnat2NHRCAR4vgmWk
Each of these wrote row by row and sent one global notification per
row, so every mounted usePersist ran a dirty scan per row:

- HasManyDataGrid page loads
- BatchPersister claim, post-persist reconciliation, and release + sweep
- EntityLoader.loadMany
- useEntity: refreshed data and the success state now arrive together

List and grid rows are validated before any write, without `as` casts;
a row without a string id now fails the load.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DQrj6jnat2NHRCAR4vgmWk
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