An idle store is not read on every poll - #193
Merged
Merged
Conversation
A follower polls every store it ships, once a second, and most are idle on most polls. Each poll parsed the store's whole .rings, and serve() also loaded its whole .grain, to find that there was nothing to send. The shipper leaves a store out of the read when its cursor sits exactly at the end of the flushed chunks, no write-ahead entries are pending and no .sap.seal handoff is in flight: the read also follows the live edge, so a chunk-level test alone would miss a wal store. The stores returned are the ones read, because the parse pairs them with the buffer. The frames sender skips serve() when the store has no chunk at or after the resume seq, decided from the index header and its last record. serve() no longer copies the store's selection twice before looking at max_chunks, and reads only the grain pages it will send instead of loading the whole grain: grain::read_pages walks the length prefixes with a bounded buffer. Three stores of 300,000 chunks, idle under feed: 48 MB/s read and 128 MB peak RSS before, 0 MB/s and 7 MB after. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second of the changes from the full-read sweep: the polling loops. A follower polls every store it ships once a second, and most are idle on most polls. Each poll parsed the store's whole
.rings(andserve()loaded its whole.grain) to find that there was nothing to send.Change
ship.rs): a store is left out of the read when its cursor sits exactly at the end of the flushed chunks, no write-ahead entries are pending, and no.sap.sealhandoff is in flight. The read also follows the live edge (query_entriesopens aLiveTailfor a store with a position), so a chunk-level test alone would be wrong for a wal store; both guards are tested. Anything unreadable is read, so the error surfaces. The stores returned bypoll_excludingare the ones read, becausetakepairs the list with the buffer.ship_turn): skipsserve()when the store has no chunk at or after the resume seq, from the index header and its last record (serve::nothing_from).serve(): no longer copies the selection twice (about 64 B per chunk of the store) before looking atmax_chunks; selects by position instead. It reads only the grain pages it will send (grain::read_pages: length prefixes walked with a bounded buffer) instead of loading the whole grain, which used to happen even when nothing was selected.Measured
Three stores of 300,000 chunks each, idle under
timberfs feed --follow-from end:Tests
The skip: exactly the tape end, one byte either side, pending
.sapentries, a.sap.seal, an unreadable store. A shipper that has caught up leaves both stores out of the read and picks the grown one up alone. Mutating the skip to always-idle fails 4 tests (it would lose data); to never-idle fails 2 (the optimisation would vanish).read_pagesequals the pagesloadreturns for every range, stops at a torn tail, and is empty past the end. A mid-storeservewithmax_chunkssends the pages of its own chunks.Not here, and not verified
query --follow's own poll still re-reads the index; it is entangled with the live-tail reconcile and the retention-overtaken note, so it is a separate change..rings, and aserveturn with data still walks the grain from the front to its first page. Both want the reader-side index view planned separately.feedrun: the baseline binary does not deliver in my ad-hoc setup either (a consumer needs protocol progress reports), so that run says nothing about this change. The unit test above is the evidence.Behaviour change
Shipper::pollno longer returns slices for idle stores. Nothing else consumespoll_raw/poll_excluding.🤖 Generated with Claude Code