Repository navigation
Conversation
Redis subscriptions ran a blocking XREAD in the loop's default executor, one thread per open downlink, and aget/aset/apublish used the same executor. At a dozen downlinks per worker every publish queued behind the parked reads for 5-15 s, so on uvicorn --workers 4 a quarter to half of streams failed at 25 browsers. The Redis backend now has a native redis.asyncio path: one client per event loop, loop-native key/value and publish, and one reader task per loop that serves every async subscription with a single multi-stream XREAD (woken for new subscriptions through a private wake stream). Diskcache's async subscriptions poll without blocking and sleep on the loop. The ASGI downlink writes its connection record with the async calls, and its closed record from a task of its own so a client disconnect can't skip it.
Contributor
Dash performance benchmarks✅ all within thresholds
growth = late-third / early-third per-op time; ~1 is flat, a large value means the per-op cost scales with accumulated state. machine scale vs baseline: 0.94x - divided out of the baseline ratios so they compare like for like (the absolute warn/fail ceilings are left un-scaled); calibrated on |
Split the reader's dispatch and failure paths out of its run loop, drop copies of subscriber sets that are never mutated while iterated, keep one possible raise per pytest.raises block, and silence pylint's import-error on the redis.asyncio import, which CI's lint environment can't resolve.
|
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.



On uvicorn
--workers 4withRedisSharedStorage, FastAPI and Quart failed 24-57% of streaming callbacks at only 25 browsers, with p95 frame latency of 10-35 s. The same apps were fine on one worker with local storage, and Flask/gunicorn was fine on the same Redis.Cause
Redis subscriptions used
PollingSubscription, whose async iterator ran a blockingXREAD(5 s block) in the loop's default executor: one thread per open downlink, out of 12 on an 8-core machine.aget/aset/apublishused the same executor, so at a dozen downlinks per worker every publish queued behind the parked reads for 5-15 s and streams ran into the client's timeout. Instrumenting the workers showed nothing else: no stream was cancelled by the cross-worker connection-record check.Changes
redis.asynciopath, one client per event loop.aget/aset/adelete/apublishare loop-native. One reader task per loop serves every async subscription with a single multi-streamXREAD, so it is one Redis connection per worker, not one per browser. A new subscription wakes the read through a private wake stream. Gaps are detected by sequence contiguity; a stream reset under a subscriber is caught on quiet cycles. The sync path is unchanged.Shared storage and streaming are unreleased (#3930, #3931), so no CHANGELOG entry.
Numbers
benchmarks/streamingload sweep (#4055), Redis, 4 workers, 8-core laptop:Before: 24-57% errors at 25 browsers.
Tests
tests/streaming/test_stream_asgi_redis.py: uvicorn--workers 2on Redis, 80 idle downlinks open, then a real browser stream must finish in under 4 s (FastAPI and Quart). Before the change it was stuck at the first frame after 15 s.tests/shared_storage/test_redis_backend.py: async subscriptions hold no executor threads (fails before), replay, gap, close from another thread, a passed client, a subscriber joining behind a read in flight (no false gap), per-loop clients dropped once their loop closes.tests/shared_storage/test_diskcache_backend.py: async subscriptions hold no executor threads (fails before).tests/streaming/test_stream_transport.py: a cancelled async downlink still records itself closed.Not in this PR
--workers N, uvicorn binds its socket with protocol 0, so asyncio never setsTCP_NODELAYand every first frame waits ~40 ms on Nagle plus delayed ACK (3 ms on one worker). That is uvicorn, not Dash.