Skip to content

perf(read-state): load peer journals without a strict writeback - #1448

Merged
kalvinnchau merged 1 commit into
mainfrom
peon/read-refresh-m
Oct 26, 2026
Merged

kalvinnchau merged 1 commit into
mainfrom
peon/read-refresh-m

Conversation

@kalvinnchau

@kalvinnchau kalvinnchau commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

On a BroadcastChannel change notice, each read-state owner refreshed with save((current) => current). That is a strict readwrite transaction: it does a get, validates the journal twice and puts the unchanged record back, in every listening window, for every notice. The peer's save was already durable, so the writeback did no useful work, and the readwrite lock made it contend with the writing window.

Now the notice handler calls load():

  • load() reads through a new optional ReadStateStorage.read(). That is one readonly get with the same readJournal validation.
  • A missing record (first use, or cleared elsewhere) still goes through save, which creates the journal as before. This is the only case where refresh still writes. Every real change has already been written by the window that made it. readJournal normalization is deterministic, and ready and the next mutation persist it.
  • Unchanged: ready, flush's locked reload and every mutation keep their strict readwrite transactions. A corrupt record is still rejected without being replaced.
  • Ordering: loads run on the owner's existing queue, so a late load cannot displace later local intent. A disposed owner applies nothing.
  • Why read is optional: test fakes that implement only update keep the previous refresh path.

Evidence

Paired base/candidate runs on a dedicated macOS 26.6 host (Apple M4 Pro VM, 6 CPUs, load ≤4). The harness is a Playwright source fixture with real IndexedDB and BroadcastChannel in Chromium and WebKit:

  • 12 rounds in alternating order, with a fresh context per case. 816 cases passed, with no signing attempts and no error states.
  • The base is the parent main commit. The candidate differs only in this commit; its production blobs are identical to this branch's.

Transactions:

  • Peer/listener refreshes: base ran 7,632 strict readwrite transactions with 7,632 puts. The candidate ran 7,632 readonly transactions with 0 puts.
  • Writer mutations: identical on both sides, 3,744 strict readwrite.

Medians at reserve 5,000 (365 KB journal). The candidate was lower in 12 of 12 rounds unless noted:

Chromium WebKit
20-notice refresh drain, two windows, same partition 406 → 123 ms 152 → 50 ms
same, different partitions 417 → 123 ms 155 → 49 ms
same, one window 211 → 114 ms 82 → 47 ms
serial local commit with a live peer 27.1 → 16.0 ms 19.0 → 6.2 ms
serial peer convergence 42.8 → 21.9 ms 27.2 → 9.0 ms
12 back-to-back writes, writer total 284 → 238 ms 115 → 99 ms
one-window controls (unchanged path) 15.5 → 15.2 ms serial; 181.5 → 183.0 ms back-to-back 6.0 → 6.0 ms; 76.0 → 75.5 ms
  • With a live peer, local commit drops to the one-window level. Different partition keys do not avoid the base contention, because all partitions share one object store.
  • At reserve 0 the 20-notice drain is 9.9 → 2.9 ms (Chromium) and 16.5 → 6.0 ms (WebKit).
  • Foreground typing in the peer window during matched writes did not change materially. Keydown delay p95 is ≤1 ms on both sides, and frame p95 is unchanged.

Correctness controls:

  • Unit tests:

    • A peer load does not write.
    • A missing record still saves.
    • A corrupt record reports an error without being replaced.
    • A held late load can neither displace later local writes nor write back a deleted record.
    • A disposed owner applies nothing.
  • Revocation boundary (external diagnostic, 192/192 cases, base and candidate):

    1. A peer refresh is held in the leaving window.
    2. The window runs the departure order from createCommunities: dispose the read owner, then purgeCommunityDeviceState.
    3. The refresh is released behind an owner-queue barrier.
    4. Checks: the owner state is unchanged and nothing is emitted. The IndexedDB record stays absent after release and after a reload. A reopened owner starts empty.

    This covers the disposal/purge boundary, not the membership-removal service end to end.

Checks at this head (on the measurement host with Hermit active, plus hosted CI):

  • pnpm check passes.
  • vitest run: 708 files and 10,953 tests pass.
  • ./bin/node --test tests/integration/*.test.mjs: 224 pass, 2 fail, 3 skipped. Clean base gives the same result.
    • The 2 failures (plugin-cli, plugin-manager-lock) come from their cargo build --locked step.
    • The host's cargo registry mirror lacks the locked webpki-roots 1.0.10.
    • The hosted "Rust and tool integration" job runs the same suite and passes.
  • The read-related browser specs (read-reserve, unread, sidebar-unread, thread-unread, inbox-reading, navigation-mute-read) pass 58/58 in Chromium and WebKit.

Limits:

  • This is the source fixture, not the native WKWebView host.
  • The reserve is synthetic.
  • Samples within one context are dependent.
  • WebKit's timer resolution is 1 ms.

Peer refresh burst drain, base vs candidate, Chromium and WebKit

Local read with a live peer window, base vs candidate

a peer broadcast refreshed the local journal with save(identity), a strict
readwrite transaction that re-validated and re-put an unchanged record in
every listening window. load it through a readonly get with the same
journal validation instead. a missing record still saves a new journal,
and ready, flush and every mutation keep strict readwrite transactions.

Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
@kalvinnchau
kalvinnchau marked this pull request as ready for review October 21, 2026 20:40
@kalvinnchau
kalvinnchau requested a review from a team as a code owner October 21, 2026 20:40

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source review via Wes’s account, integrating independent Rocket (storage/performance/lifecycle), Mantis (correctness/concurrency) and Groot (unread behavior) reviews.

No actionable production defects found. Source assessment: 9/10 for scope, ownership and correctness. Reviewed head 0c521a8 against base/merge-base 9366357.

The readonly peer load retains journal validation and the owner’s serial queue. Actual mutations, initialization and publisher reloads retain strict readwrite transactions. Missing-record and update-only adapter fallbacks preserve the existing behavior. I traced corrupt-record errors, pending/failed sync reconciliation, session disposal before community purge, and the unchanged diagnostics retry controls. The post-await closed guard prevents an already-started read from applying after disposal; closing the database itself does not cancel an existing transaction.

Optional test improvement: in src/features/relay/read-state.test.ts:624-626, polling a.revision() does not explicitly prove that disposed owner b has drained its held load. Await a completion barrier on b’s queue before the negative assertions (and release held work in finally). This strengthens the regression evidence; I found no corresponding production defect.

Reviewed the public description, both attached charts and feature-commit metadata; no disclosure finding. The reported performance numbers and test results were not independently reproduced. This was source-only: no tests, builds, app launch, PR-code execution or CI assessment. Real IndexedDB/multi-window behavior, native lifecycle, rendered focus/accessibility, performance and human acceptance remain unverified. This COMMENT is not approval or merge authorization.

@kalvinnchau
kalvinnchau merged commit f9586e3 into main Oct 26, 2026
37 checks passed
@kalvinnchau
kalvinnchau deleted the peon/read-refresh-m branch October 26, 2026 16:12
johnmatthewtennant added a commit that referenced this pull request Oct 26, 2026
…r-emoji-size

* origin/main: (26 commits)
  perf(profiles): notify once per batch of owner attestations (#1650)
  feat(pi): load Pi durable extensions and providers from Pi packages (#1610)
  Teach agents memory links and source metadata; frame memory as information (#1562)
  Make avatar choices visible in agent creation (#1631)
  fix(pi): take plugin wakes instead of ignoring them (#1612)
  perf(agents): parse activity records once per snapshot (#1449)
  perf(terminal): parse hidden terminal output in slices (#1459)
  perf(composer): reuse the document projection for editor reads (#1463)
  perf(read-state): load peer journals without a strict writeback (#1448)
  perf(channels): keep openLink stable across tab changes (#1525)
  fix: name the Cargo package Buzz so the dev build shows "Buzz" (#789)
  test(browser): re-type a dropped page name in the search palette (#1635)
  test(browser): fix two main CI timing failures (#1567)
  feat(agents): deliver Attention watches and timers to conversations (#1578)
  fix(inbox): stop listing tag-only composer state as drafts (#1531)
  Choose Builderlab agent models at creation and in Settings (#1533)
  feat(communities): sync known communities through Builderlab accounts (#644)
  Tag the last-tagged agent when a Solo chat composer is shown empty (#1615)
  perf(usage): read Claude usage once, then only what changed (#1586)
  feat(chats): ask to share access when sending a chat link into a DM (#1614)
  ...
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.

2 participants