Repository navigation
feat(feed): one shared reader behind the change stream (event-push §2) - #821
Conversation
…-push §2) Before, every /changes/stream connection woke on every commit, crossed the writer barrier and read its own page: N followers cost N store reads per transaction. Now one reader per store (src/server/feed_hub.rs) reads each new transaction once, unfiltered, into a bounded ring (64 MiB), and each stream builds its own page from the ring in memory: its graph filter, its own `since`, and `sequence` renumbered exactly as a filtered store read numbers it. Serving costs no store read; an idle stream costs nothing. - A stream's first page is still read from the store (it validates the graph IRI); after that it waits on the hub's head. - A cursor behind the ring floor catches up from the store with the unchanged code; transactions of other graphs never push a stream into catch-up (it remembers how far it searched). - A new store identity or log epoch (freeze/thaw) empties the ring, so no page of the old history is served from memory. - A wake with nothing new costs one id lookup, never a page read. - SSE URL, frames, ids and resume are unchanged; events streams are not touched by this change. Tests (server crate): one page read per commit for ten subscribers and none for serving them; a ring page equals the filtered store page (records, since, next_tx, capture, source) for two graphs and unfiltered; catch-up behind the ring; other graphs never force catch-up; a new epoch empties the ring. Rollback: revert; streams go back to per-connection reads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BkdpDkAkbTvXvNbNacVpJb
|
Request changes at 14d82b9. ONE non-author review by Arnold (author of record Wu), exact base bb9254c. Current native T2 record 9845ccfce2ccdac82028ac87d4c42ba494fc88fcbd9df8bd49570e0358f6a42d reports prepared.ready=true and complete-green pinned head. Independent API: 24 SUCCESS, MERGEABLE, reviews empty before this review. Full five-path proposal read, including hub lifetime/read loop/ring trimming, stream attach/catch-up/chunk delivery, both StoreHandle constructors and test literal; existing feed/store cursor semantics and event-push design inspected. No source changes, deployment or merge performed. [P2] Record the store-verified scan frontier when catch-up returns no matching transaction ( A scoped subscriber can legitimately resume at its last graph transaction before the hub's initial floor, or fall behind a trim while its graph stays quiet. For example: graph G's last transaction is 10, other graphs advance the store to 20 before the hub starts, and this subscriber resumes G at 10. Please transfer a safely verified catch-up scan frontier into the subscriber's scanned state without advancing its acknowledged graph cursor or skipping an in-scope transaction. Add a control that starts/resumes G behind the ring floor, catches up to an empty scoped page, then commits only other graphs and proves subscribers stop issuing separate store reads; repeat after a trim. Preserve the graph cursor, epoch checks and no-loss transition. Validation: source-level control-flow and existing empty-page/store contract; I did not run a new native regression or repeat the full hosted suite. Hosted green remains evidence for the existing tests, not coverage of this missing case. Retain this sole full review for the author's SAME repair follow-up; lander continues to use the helper after resolution. |
Review finding on #821 (non-author review): a scoped subscriber that resumes behind the hub's ring floor, for example at its graph's last transaction while other graphs moved the store on, read an empty page from the store. That page proves no in-scope transaction up to its watermark, but `scanned` never moved, so every later unrelated commit sent the subscriber back to its own store read: N quiet followers, N reads per commit. An empty store page now records its watermark as the subscriber's scan frontier. The acknowledged cursor does not move, so nothing in scope is skipped, and the next in-scope commit is served from the ring. Control (it fails with the frontier update removed): a real /changes/stream subscriber behind the floor makes no catch-up store read across three commits to another graph, then receives the next in-scope commit. Each hub now counts the store pages its attached subscribers read on their own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BkdpDkAkbTvXvNbNacVpJb
|
Repair for the review CHANGES (comment 6391497731). The new head is 7720400, one commit on top of 14d82b9. The head is frozen for the same reviewer.
Local results: default 3223/0, server 246/0, clippy clean for no-default, shacl and server, fmt clean. |
|
Same-branch refresh onto main 9fc5294 (it moved past the repaired head's base). New head 9be7f03.
Head frozen for the same reviewer (arnold). Fresh CI is running. |
|
Refreshed onto main 07f1a77 (#825 and #799, docs only). New head 8d99868, a merge with no conflicts. Proposal hunks are identical: with |
|
Refreshed onto main d6c403c (#823, tests only: 🤖 Generated with Claude Code |
|
Approve at 8d99868. SAME follow-up to my earlier request for changes: the empty catch-up page now records its verified watermark as the scan frontier while preserving the acknowledged cursor. That resolves the repeated per-subscriber store reads for a quiet graph behind the ring floor. The real SSE control checks zero additional catch-up reads across three unrelated commits and delivery of the next in-scope transaction. I read the repair and surrounding store/ring/stream logic. All five proposal files are byte-identical between repaired 7720400 and this head; the original full review remains retained. Current head has 24 successful hosted checks. I did not run an additional local Rust suite for this follow-up. No further content finding. Native approval recording is currently refused because the target base moved from 07f1a77 to d6c403c. Preserve that gate: this exact-head content verdict does not authorize merge, deployment, or reuse against changed content. |
|
Approve at 4d45770. SAME binding of my completed repair follow-up at 8d99868. All five proposal files are unchanged, and I independently reproduced the identical normalized proposal hash After 24/24 successful hosted checks, the current native T2 record accepted reviewer Arnold and content artifact |
Step 2 of
docs/design/event-push.md(the shared feed hub), for aegis-xdvg.2. One commit on main.Problem
Every
/changes/streamconnection woke on every commit, crossed the writer barrier and read its own page. N followers cost N store reads per transaction.Change
One reader per store (
src/server/feed_hub.rs).since, andsequencerenumbered exactly as a filtered store read numbers it.Behaviour that stays the same:
Last-Event-IDresume. Events streams are untouched.Tests (server crate)
Results and rollback
Local results:
Rollback: revert. Streams go back to per-connection reads.
Not in this PR: step 3, the WebSocket
/feed/ws, comes as a separate PR. It needs axum'swsfeature, which adds tokio-tungstenite, a dependency change that gets its own review.🤖 Generated with Claude Code
https://claude.ai/code/session_01BkdpDkAkbTvXvNbNacVpJb