Repository navigation
fix(redis): default max_records to 1 when retry_on_idle_ms is set - #304
Merged
Merged
Conversation
Without a COUNT, one XREADGROUP hands the consumer every waiting entry. They all enter its PEL at once and are handled one by one, so an entry queued behind slow handlers goes idle past retry_on_idle_ms before it starts, and the reclaimer re-delivers it through .retry: the handler runs it twice and _retry_count climbs without a failure. Read one entry at a time when the reclaimer is on (not with batch=True). The retry subscription inherits it; an explicit max_records is kept. Fixes #303 Co-Authored-By: Claude Opus 5.5 <[email protected]>
afiodorov
marked this pull request as ready for review
October 1, 2026 17:30
Contributor
QualOps Code Quality AnalysisStatus: Summary
🟠 High Issues (1)
🟡 Medium Issues (1)
📊 Full ReportPowered by QualOps |
sebastianwessel
pushed a commit
that referenced
this pull request
Oct 5, 2026
The max_records default applies unless a number is given: an explicit max_records=None is treated like an unset one (as in #304), which 'unless set' did not say. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Contributor
Author
|
Re QualOps high issue ( |
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.
Description
Problem (#303). With
retry_on_idle_msset andmax_records=None(the default), FastStream'sXREADGROUPsends noCOUNT. When a backlog is waiting, one read puts every entry in the consumer's PEL, and FastStream handles them one by one. An entry's idle time starts at delivery, so an entry queued behind slow handlers goes idle pastretry_on_idle_msbefore it starts. The reclaimer then moves it to.retry, and the handler runs it twice: once from the local list, once from the retry stream. With 4 queued entries, a 1 s handler andretry_on_idle_ms=1000, main runs the handler 8 times:[1, 2, 2, 3, 3, 4, 4, 4].Fix. When
retry_on_idle_msis set,batch=Falseandmax_recordsis not set,max_recordsdefaults to 1. The recursive.retrysubscribe already forwardsmax_records, so it inherits it. An explicitmax_recordsis kept, andbatch=Trueis left alone.Type of Change
Related Issue
Fixes #303
Changes Made
RedisTransport.subscribe: themax_recordsdefault described above, next to the otherretry_on_idle_mschecks. Themax_recordsdocstring says why, and that an explicit value should stay at or below what the handler gets through withinretry_on_idle_ms.docs/docs/sdk/redis-transport.md: one line under the retry guidelines.sdk/CHANGELOG.md: under Fixed, including the behaviour change.Behaviour change
A subscription with
retry_on_idle_msnow does oneXREADGROUPper entry instead of one per backlog. For slow handlers, which is where the reclaimer matters, this costs nothing. Setups with small, fast messages that relied on the unbounded read for throughput can setmax_recordsexplicitly. Subscriptions withoutretry_on_idle_msare unchanged.Relation to #299
#299 already defaults
max_recordsto 1 underrenew_lease=Trueand renews prefetched entries. This PR makes the default apply to everyretry_on_idle_mssubscription, so the lease isn't needed just to avoid this case. Whichever PR merges second needs a small rebase insubscribe(): #299'sif not batch and max_records is None: max_records = 1underrenew_leasebecomes redundant. (#299'sscan_prefetchedstill matters for an explicitmax_records > 1and formax_workers > 1.)Out of scope here, and listed as open questions in #303: making
renew_leasethe default withretry_on_idle_ms, a warning for an explicitmax_records > 1without the lease, and skipping live consumers viaXINFO CONSUMERS.Testing
Against a throwaway
redis:7-alpine, FastStream 0.7.5, Python 3.12:test_retry_on_idle_ms_does_not_reclaim_entries_queued_behind_a_slow_handler(integration): 4 entries published before start,group_start="0", 1 s handler,retry_on_idle_ms=1000. It checks that 0.5 s in, only 1 entry is pending and 3 are still in the stream (lag), and that each entry is handled exactly once. On main it fails (4 pending; with the PEL checks skipped, the runs are[1, 2, 2, 3, 3, 4, 4, 4]).test_retry_on_idle_ms_defaults_max_records_to_one(unit): 1 on the main and the retry subscriber, an explicitmax_records=10kept on both,batch=Trueand no-reclaimer subscriptions unchanged.ruff check,ruff format --check,mypy(locked versions) clean.Changelog
sdk/CHANGELOG.mdunder[Unreleased]sectionChecklist
make lintpasses)make formatapplied)🤖 Generated with Claude Code