Skip to content

fix(asyncio): keep pipeline packing off the event loop - #4210

Draft
lh0156 wants to merge 1 commit into
redis:masterfrom
lh0156:agent/fix-async-pipeline-packing-3416
Draft

fix(asyncio): keep pipeline packing off the event loop#4210
lh0156 wants to merge 1 commit into
redis:masterfrom
lh0156:agent/fix-async-pipeline-packing-3416

Conversation

@lh0156

@lh0156 lh0156 commented Jul 26, 2026

Copy link
Copy Markdown

Summary

Large async pipelines currently call Connection.pack_commands() synchronously from the event loop. Packing a large transaction or pipeline can therefore block unrelated coroutines for seconds.

This change:

  • offloads command packing for transactional and non-transactional async pipelines with asyncio.to_thread
  • preserves the existing wire packing and response handling
  • adds a deterministic regression test covering both pipeline paths

Fixes #3416.

Validation

  • TDD regression test was red before the production change and green after it
  • pytest -q tests/test_asyncio/test_pipeline.py -k 'pipeline_packing_does_not_block_event_loop or TestAsyncPipelineOperationDurationMetricsRecording' — 7 passed
  • RESP3 async pipeline suite — 71 passed, 2 skipped
  • RESP2 async pipeline suite — 71 passed, 2 skipped
  • invoke linters — passed

The full Docker/fixed-client matrix was not completed because the local Docker credential helper and required cluster/maintenance services were unavailable.

Move async pipeline command packing to a worker thread so large transactions and pipelines do not monopolize the event loop.\n\nRefs redis#3416

@petyaslavova petyaslavova left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tackling this — the underlying issue in #3416 is real: for large async pipelines/transactions, pack_commands runs synchronously on the event loop and can block it for seconds, and moving packing off the loop is a reasonable direction.

There is, though, one design point to settle: the offload is applied unconditionally, so every async pipeline — including small ones — now pays a to_thread dispatch and uses the loop's shared default executor.
It will be better to gate the offload behind a batch-size threshold (so the common small-pipeline path keeps its current cost) or share a quick benchmark showing the small-pipeline overhead is negligible.

It would also help to note whether async cluster pipelines (redis/asyncio/cluster.py, which pack the same way) are in scope here or a follow-up.

Once that's decided and the PR is out of draft, it should be ready for another review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pipeline execute in async blocks as it packs the commands

2 participants