Add transport-aware batch publishing with Redis pipelines - #2572
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces a new, public, transport-neutral batching API on kombu.Producer (Producer.batch()), and adds a Redis transport implementation that defers the final Redis commands using a non-transactional redis-py pipeline to reduce round trips while preserving existing message preparation/routing behavior.
Changes:
- Add
Producer.batch()context manager with nested/abort/flush semantics andProducer.supports_batch_publishcapability detection. - Implement Redis-side batching via a
PublishBatchthat buffersLPUSH/PEXPIRE/PUBLISHintoclient.pipeline(transaction=False)and flushes on exit / size thresholds. - Add
BatchPublishError, documentation, changelog entry, unit + integration coverage, and a benchmark script.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
kombu/messaging.py |
Adds the public Producer.batch() API, batching state machine, and publish-path integration. |
kombu/transport/redis.py |
Implements Redis PublishBatch and hooks _put/_put_fanout to buffer final Redis commands when batching. |
kombu/transport/base.py |
Adds the batch_publish capability flag to default transport capabilities. |
kombu/exceptions.py |
Introduces BatchPublishError for uncertain batch delivery outcomes. |
t/unit/test_messaging.py |
Adds unit tests for batch context semantics (nesting, abort, flush, scoping). |
t/unit/transport/test_redis.py |
Adds unit tests validating Redis pipeline usage and command buffering behavior. |
t/integration/test_redis.py |
Adds real-Redis integration tests for batched direct/topic/fanout behavior. |
docs/userguide/producers.rst |
Documents batch publishing API, semantics, and Redis behavior/limitations. |
Changelog.rst |
Adds a “Next release” changelog entry for the new API. |
benchmarks/redis_publish_batch.py |
Adds a benchmark script to compare ordinary vs batched Redis publication under simulated latency. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2572 +/- ##
==========================================
+ Coverage 82.91% 83.15% +0.23%
==========================================
Files 79 79
Lines 10408 10626 +218
Branches 1199 1241 +42
==========================================
+ Hits 8630 8836 +206
- Misses 1577 1584 +7
- Partials 201 206 +5 ☔ View full report in Codecov by Harness. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
a988c99 to
fd947e2
Compare
Summary
Add a public, transport-neutral
Producer.batch()context and implement it forthe Redis transport with
client.pipeline(transaction=False).Normal
Producer.publish()calls still own serialization, message preparation,declarations, routing, queue priority selection, queue expiration, and fanout.
Only the final Redis
LPUSH,PEXPIRE, andPUBLISHcommands are deferred.Transports that do not implement batching retain immediate publication.
This is motivated by Apache Airflow #8854.
Airflow may publish hundreds of Celery tasks in one scheduler iteration. Its
local prototype demonstrated the performance benefit, but had to replace
Kombu's private
_put()and_put_fanout()methods.Public API
max_size.operations that have not already been flushed.
producer.supports_batch_publishadvertises actual transport support.Design decision
The selected design combines public
Producer.batch()ergonomics with atransport-owned channel session created by
create_publish_batch().This keeps the API usable by Celery, which already passes a Kombu producer into
Task.apply_async(), while leaving broker command ownership with the transport.Alternatives considered:
own Redis queue names, priority keys, wire encoding, or fanout topics.
callers would still need transport knowledge and Celery publishes through
the producer API.
publish_many(requests)was rejected because it would duplicate the largeProducer.publish()argument surface and force callers to construct a fulllist instead of using their existing publication loop.
Failure semantics
Redis pipeline execution is non-transactional. If the response to
pipeline.execute()is lost, Redis may have accepted none, some, or all of thecommands.
Kombu raises
BatchPublishErrorand does not automatically replay the batch,including when an individual publication used
retry=True. Applications maystart a new batch and retry explicitly when at-least-once delivery and possible
duplicates are acceptable.
max_sizebounds buffered Redis commands. Long-running contexts can usebatch.flush()for an elapsed-time boundary, and Redissocket_timeoutboundsnetwork blocking.
Validation
1611 passed, 178 skipped64 passedglobal_keyprefixandexercises direct routing, topic routing, priorities/FIFO, and fanout Pub/Sub.
Task.apply_async(..., producer=producer)calls remained deferred inside the context and all three appeared in Redis
on successful exit.
-Wbuild stillfails on unrelated warnings already present on
main.The included benchmark publishes 100 messages with 10 ms simulated per-request
latency over seven alternating rounds:
Observed speedup: 75.1x.
Compatibility
Producer.batch()is used.inheritance/capability paths have unit coverage.
supported or tested here.