Repository navigation
Coalesce committed WAL transactions into one generation per poll - #47
Merged
Merged
Conversation
The bounded Postgres worker published one generation per WAL transaction (~13 gen/s) while the native contention writer commits 20/s, so its backlog grew until catch_up timed out at 30 s. PostgresSource.peek_many reads up to max_coalesce (default 64, 1..1024) complete transactions already committed to the slot, bounded by the per-poll byte and event limits. ProductionCatalog.apply_many ingests them in order under one checkpoint restore and publishes one generation, one signed head and one slot acknowledgement at the last transaction's LSN. A binding freezes at the first transaction it cannot apply, exactly as it would per transaction. A single transaction keeps its old id wal:<lsn>:<xid>; a group is wal:<lsn>:<xid0>..<xidN>:<n> with a hash over the per-transaction hashes, so a replayed group is a duplicate. Native evidence (local PG16 + wal2json + Redis cluster): test_production_read_contention_during_publication[bounded] passed 3/3 at ~30.8 s, commit_to_visible p95 0.59-0.69 s; with max_coalesce=1 the same test still fails "did not catch up within 30 seconds" (p95 13.7 s). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0194kAmYv4pLMyrspzKEWExZ
This was referenced Nov 11, 2026
dbccpage
marked this pull request as ready for review
November 11, 2026 10:07
dbccpage
pushed a commit
that referenced
this pull request
Nov 11, 2026
# Conflicts: # smplcache/production_postgres.py
dbccpage
pushed a commit
that referenced
this pull request
Nov 11, 2026
peek_many now reports whether its batch drained the slot through the frontier (every decoded row admitted and fewer than max_coalesce rows), and the worker passes the poll's observation time to apply_many only then. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0194kAmY4QqQGabdoB3xGFsr
dbccpage
pushed a commit
that referenced
this pull request
Nov 11, 2026
dbccpage
added a commit
that referenced
this pull request
Nov 11, 2026
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.
🤖 Generated with Claude Code
Why
test_production_read_contention_during_publication[bounded]was flaky on main. The bounded worker published one generation per WAL transaction, about 13 per second, while the writer commits 20 per second. The backlog kept growing until the 30 scatch_uplimit ran out.What
PostgresSource.peek_many(identity, offset, max_transactions)reads up to N complete, already committed transactions from the slot in onepg_logical_slot_peek_changescall. It is bounded by:octet_lengthsum; the first row is always read, so an oversized row is still refused by name);Offsets must strictly increase, or it raises
source_gap_or_reorder.peek()is nowpeek_many(..., 1).ProductionCatalog.apply_many(...):A binding freezes at the first transaction it cannot apply, which matches per-transaction behaviour.
apply()is now a one-elementapply_many.Identity.
wal:<lsn>:<xid>and its old hash.wal:<lsn>:<xid0>..<xidN>:<n>, with a hash taken over the ordered(offset, txn-hash)list. So a replayed group is a duplicate, not a new generation.PostgresWorker(max_coalesce=64)accepts values from 1 to 1024. Anything else raisesinvalid_coalesce_limit.Trade-off: intermediate commits inside a group are never published. Readers see the group's end state, which is the strict worker's behaviour too.
Evidence (local; no Actions minutes spent)
tests/test_production_coalesce.py(new)pytest --ignore=tests/production_native.pymypy smplcacheblack --checktest_production_read_contention_during_publicationon PG16 + wal2json + Redis clustercommit_to_visiblep95 0.59–0.69 smax_coalesce=1https://claude.ai/code/session_0194kAmYv4pLMyrspzKEWExZ
Generated by Claude Code