Improve Redis stream reclaim and prefetch handling - #129
Draft
vvanglro wants to merge 1 commit into
Draft
Conversation
vvanglro
force-pushed
the
improve/stream-reclaim-prefetch
branch
from
July 29, 2026 09:40
13d4bf1 to
2503827
Compare
RedisStreamBroker reclaim was driven by a fixed idle_timeout and a broker-side Redis lock, which both double-ran long tasks and recovered short crashed tasks slowly. Replace it with per-task-deadline reclaim: - Resolve reclaim deadline from the message's `timeout` label (via formatter.loads) plus reclaim_timeout_grace, falling back to idle_timeout for messages without a timeout label. - Drop the autoclaim Redis lock; rely on XCLAIM min-idle-time for server-side atomic dedup (unacknowledged_lock_timeout is deprecated and ignored). - Protect only messages held by the current listener instance from reclaim (tracked via a local delivered set), so a worker sharing a consumer_name with a dead predecessor still recovers its pending. - Gate reclaim sweeps with reclaim_interval (default 30s) to avoid scanning pending on every listen iteration. - Enforce prefetch backpressure: do not XREADGROUP while xread_count delivered-but-unacked messages are outstanding. - Claim broker-local buffered entries to an internal abandoned consumer on listener close, so the next reclaim sweep can recover not-yet-yielded messages immediately. - Recreate a missing consumer group on NOGROUP during xpending/xreadgroup. Tests cover timeout-label reclaim, idle_timeout reclaim, shared consumer_name reclaim, prefetch backpressure, buffered-message handoff on listener close, and NOGROUP self-heal. README documents the new reclaim/backpressure/close behavior.
vvanglro
force-pushed
the
improve/stream-reclaim-prefetch
branch
from
July 29, 2026 09:41
2503827 to
ee60f08
Compare
vvanglro
marked this pull request as draft
July 29, 2026 09:59
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.
Summary
This PR improves
RedisStreamBrokerreliability and fairness by adopting stronger Redis Streams handling patterns: per-task-deadline reclaim, lock-freeXCLAIM, listener-local prefetch backpressure, and faster handoff of buffered messages on listener close.What changed
XCLAIMrecovery.timeoutlabel plusreclaim_timeout_grace, falling back toidle_timeoutfor messages without a timeout label.unacknowledged_lock_timeout; RedisXCLAIMmin_idle_timenow provides the atomic claim guard.reclaim_intervalso pending-message scans are throttled instead of running on every listen loop.xread_countalso caps delivered-but-unacknowledged messages for this listener.consumer_namecan still recover pending messages from its predecessor.abandonedconsumer and stamp them as very idle, so the next reclaim sweep can recover them immediately.NOGROUPduring stream reads/reclaim.Why
The previous reclaim path used a single global
idle_timeoutplus a broker-side Redis lock. That can reclaim long tasks too early (for example, a task with a 30m timeout reclaimed at the default 10midle_timeout) and recover short crashed tasks slowly (for example, a 5s task waiting for the default 10midle_timeout). The lock also adds a failure point (unacknowledged_lock_timeout) and extra round-trips, while RedisXCLAIMmin_idle_timealready deduplicates claims atomically on the server.The close-time handoff is deliberately limited to the broker's internal buffer: messages already yielded to taskiq may be executing, so they are not abandoned immediately to avoid duplicate execution during shutdown.
Behavior notes
XREADGROUPwithin a listen iteration, so overdue pending messages take priority over new messages when a sweep is due.reclaim_intervaldefaults to 30000 ms; set it to0to scan on every iteration.consumer_name) are recoverable.xread_count=Nonedisables the prefetch cap.abandonedconsumer on listener close; already-yielded entries still follow their normal ack/reclaim lifecycle.Tests
Added coverage for:
consumer_namexread_countbackpressureNOGROUPself-healingValidation run locally:
uv run ruff check taskiq_redis/redis_broker.py tests/test_broker.py uv run mypy taskiq_redis/redis_broker.py tests/test_broker.py uv run pytest tests/test_broker.py -k "not cluster and not sentinel" -qResult:
13 passed, 7 deselectedfor the single-node Redis broker tests. Cluster/sentinel tests are unchanged and were not run locally because no Redis cluster/sentinel service is available in this environment.Reference
The reclaim/backpressure design was informed by the Redis Streams broker implementation in
sylvinus/dramatiq-redis-streams, especially its use of deadline-aware pending recovery, bounded local prefetching, and abandoned-buffer handoff on close.