Repository navigation
Conversation
memory_limit= reached Stream.snapshot, scan, sql and live, but not the snapshot connect(catch_up=True) reads its gap through, which sized itself at DuckDB's default of 80% of what the process may use; passing it to connect was refused by the websocket. Carried on Catcher to the snapshot's ReadCache, and checked by DuckDB at connect (#162). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
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.
Closes #162. Closes #160: its two reads were already bounded on main by #161, #164 and #172, and this PR measures it below.
#162:
connect(catch_up=True, memory_limit=)memory_limit=(#161) reachedStream.snapshot,scan,sqlandlive, but not the snapshotconnect(catch_up=True)opens to read a gap.connectwas refused from insidecreate_connection, becauseconnect's**kwargsgo to the websocket.Change:
connect(..., memory_limit: str | None = None)is carried onCatcherand given to the catch-up's_snapshot.snapshot(cache=ReadCache(memory_limit=...)). That is the settingStream.snapshottakes, so readers asking for different limits read through different databases.connect. A limit DuckDB cannot read is aValueErrorat the call (_published.check_memory_limit, which asks DuckDB on a throwaway in-memory database). Otherwise the DuckDB error would surface insideCatcher._open, whose handling reports any failure to open a table as a credentials problem.catch_up=Trueit means nothing and is ignored, ascatch_up_retriesis.#160: already bounded on main, measured
#160 named two reads.
recvheld up to 1M rows of the gap. fix(catchup): read the gap in pages; memory_limit on snapshot readers #161 and feat(catchup): page a catch-up by a byte budget, not an offset count #164 page the gap: each page is one query over at mostpage_bytesof Arrow (64 MiB, litelink's row group), the first page is one row, and each later page is sized from the last.Table.opentake the extent and row count from the manifests.Measured on main (
26e903d) with #160's reproduction, adapted to record whether the extent came from the manifests or from the full scan. The run used a localfile://table of 80,000 rows of about 0.5 KB in 120 data files,max_replay=100, undersystemd-run --user --scope -p MemoryMax=1200M.recvOn 0.21.1, #160 measured case B's first
recvat 4.80 s and +452 MiB, and a connect that scanned all 200,000 offsets.The tests that hold this are already on main:
tests/test_catchup_pages.py, for exampletest_connect,test_narrow_rows_take_a_probe_then_one_pageandtest_wide_rows_stay_within_it;tests/test_published.py::test_the_extent_comes_from_the_manifests_and_agrees_with_a_scan.Test that fails without the fix:
tests/test_catchup_memory_limit.pyA local stream of 2,000 published rows, served with
max_replay=10, so a consumer at offset 1 catches up from the published table. A fixture records thecurrent_setting('memory_limit')of every database the read goes through. The tests take about 4 s.test_the_catch_up_reads_through_a_database_with_the_limitmemory_limit="77MB": every database the catch-up read through reports DuckDB's rendering of 77MBtest_without_it_the_catch_up_leaves_duckdbs_defaulttest_a_limit_duckdb_cannot_read_is_refused_at_the_callmemory_limit="plenty"is aValueErroratconnectOn unfixed main (
26e903d):Falsified. Each break below made tests fail; each was restored and passes:
Catcheropens withReadCache()connectpassesmemory_limit=NoneconnectFull suite, locally:
STREAMCAST_REQUIRE_S3=1 STREAMCAST_REQUIRE_INTEROP=1 STREAMCAST_REQUIRE_POSTGRES=1 pytestgave 958 passed, 1 failed, 2 skipped.test_maintain.py::TestFlushing::test_a_served_row_reaches_the_published_table. It fails the same way on unmodifiedmainon this 2-vCPU box, passes in CI, and does not touch the catch-up.test_node.pyis left to CI.No critical review was run: this is keyword plumbing on the reader side, into a setting #161 already reviewed. It touches no durable state, no frames and no ordering.
Downstream
New keyword, not breaking. Under the repository's release rule a new keyword is a minor release, 0.24.0; the user decides. Consumers'
<0.24pins move to use it.🤖 Generated with Claude Code