[fix][fn] Honour producerSpec batching configuration in the Python function runtime - #26392
Conversation
…nction runtime ### Motivation PIP-401 (apache#23860) made producer batching configurable for Pulsar Functions. The setting travels as `ProducerConfig.batchingConfig` -> `ProducerSpec.batchingSpec` in `FunctionDetails`, and the Java runtime applies it in `ProducerBuilderFactory`. The Python runtime never reads it. Both producers it creates hardcode `batching_enabled=True` and `batching_max_publish_delay_ms=10` as literals, so `batchingSpec` arrives in the instance and is silently dropped. Every Python function is pinned to a 10ms publish-latency floor that no configuration can change, and a function whose per-instance rate is below one message per 10ms pays that delay on every message while every batch still contains exactly one message. `maxPendingMessages` and `maxPendingMessagesAcrossPartitions` were ignored too, and `context.publish()` additionally ignored `batchBuilder`, which the sink producer already honoured. Fixes apache#26390 ### Modifications - Add `util.producer_config_from_spec()` / `producer_config_from_function_details()`, translating a `ProducerSpec` into `Client.create_producer()` keyword arguments. - Apply it in `python_instance.setup_producer()` (sink producer) and in `contextimpl.publish()` (context.publish producers). The translation follows the same rules as the Java runtime: - Unset or non-positive spec fields are omitted so the client default applies. - A sink with no `producerSpec`, or a `producerSpec` with no `batchingSpec`, keeps batching enabled with a 10ms delay, matching `BatchingUtils.convertFromSpec(null)`. Existing deployments are unaffected. - `batchingSpec.batchBuilder` overrides `ProducerSpec.batchBuilder`, matching the order in which `ProducerBuilderFactory` applies them. Omitting `batching_type` when no batchBuilder is configured is behaviour preserving: the Python client already defaults it to `BatchingType.Default`, which is what the previous unconditional argument passed. `roundRobinRouterBatchingPartitionSwitchFrequency` has no equivalent in the Python client and is ignored. `block_if_queue_full` stays fixed at `True`; the Java runtime hardcodes `blockIfQueueFull(true)` as well and exposes no configuration for it, so making it configurable would need a new proto field and belongs in a separate change. ### Verifying this change 19 unit tests added to `test_python_instance.py`, covering the spec-to-kwargs translation directly and asserting the resulting `create_producer()` call for both producer paths, including the unchanged defaults. 16 of them fail against the unfixed runtime.
|
The apache/pulsar-site#1215 ("Document Flagging it because For context on the impact: at low throughput (~27 msg/s per instance) a function instance never fills a batch, so every message waits the full hardcoded batching delay and then flushes alone — a flat per-message latency tax that the |
freeznet
left a comment
There was a problem hiding this comment.
Verified the ProducerSpec/BatchingSpec mapping against FunctionConfigUtils, BatchingUtils, ProducerBuilderFactory, and pulsar-client 3.13.0. Both producer paths preserve the existing defaults and apply the configured limits and batcher precedence. The Python runtime tests pass locally.
Fixes #26390
Motivation
PIP-401 (#23860) made producer batching configurable for Pulsar Functions. The setting travels as
ProducerConfig.batchingConfig→ProducerSpec.batchingSpecinFunctionDetails, and the Java runtime applies it inProducerBuilderFactory.The Python runtime never reads it. Both producers it creates hardcode
batching_enabled=Trueandbatching_max_publish_delay_ms=10as literals, sobatchingSpecarrives in the instance and is silently dropped. Every Python function is pinned to a 10ms publish-latency floor that no configuration can change.Below roughly one message per 10ms per instance, that inverts the point of batching: every message opens a batch, waits the full linger for companions that never arrive, and flushes alone. At 27 msg/s per instance, messages arrive every ~37ms, so each one pays a flat 10ms tax while every batch still contains exactly one message.
maxPendingMessagesandmaxPendingMessagesAcrossPartitionswere ignored too, andcontext.publish()additionally ignoredbatchBuilder, which the sink producer already honoured.Modifications
util.producer_config_from_spec()/producer_config_from_function_details(), translating aProducerSpecintoClient.create_producer()keyword arguments.python_instance.setup_producer()(sink producer) andcontextimpl.publish()(context.publish()producers).The translation follows the same rules as the Java runtime:
producerSpec, or aproducerSpecwith nobatchingSpec, keeps batching enabled with a 10ms delay, matchingBatchingUtils.convertFromSpec(null).batchingSpec.batchBuilderoverridesProducerSpec.batchBuilder, matching the orderProducerBuilderFactoryapplies them in.Omitting
batching_typewhen no batchBuilder is configured is behaviour preserving: the Python client already defaults it toBatchingType.Default, which is what the previous unconditional argument passed.Out of scope, deliberately:
block_if_queue_fullstays fixed atTrue. The Java runtime hardcodesblockIfQueueFull(true)too and exposes no configuration for it, so making it configurable means adding a newProducerSpecfield — a config-surface change that belongs in its own PR.roundRobinRouterBatchingPartitionSwitchFrequencyhas no equivalent in the Python client and is ignored.Documentation
This PR updates
pulsar-functions/instance/src/main/python/README.mdwith aProducerSpec→create_producer()mapping table and the parity rules above, but that is contributor-facing documentation only.The user-facing docs still need a change in apache/pulsar-site, and I have not made it. Concretely:
batchingConfigwithout noting which runtimes honour it. Before this PR it was Java-only in practice; after it, Java and Python. Readers had no way to know their Python function was ignoring the setting, which is what made [Functions] Python function runtime hardcodes producer batching config, ignoring producerSpec.batchingSpec #26390 hard to spot in the first place.batchingSpec([Functions] Go function runtime hardcodes producer batching config, ignoring producerSpec.batchingSpec #26391). Either scope the note to the runtimes that honour it today, or hold the site change until the Go fix lands and document both at once.Marking this
doc-requiredrather thandoc-completefor that reason. Happy to open the pulsar-site PR as a follow-up — flagging it here so it does not get lost, and so a reviewer can tell me whether they would rather wait for #26391 and do one combined docs update.Verifying this change
This change added tests and can be verified as follows:
test_python_instance.py, covering the spec-to-kwargs translation directly and asserting the resultingcreate_producer()call for both producer paths.producerSpecstill getsbatching_enabled=True,batching_max_publish_delay_ms=10,block_if_queue_full=True.pulsar-functions/instance/src/scripts/run_python_instance_tests.sh../gradlew quickCheckpasses.Does this pull request potentially affect one of the following parts:
If the box was checked, please highlight the changes
No box checked: no configuration surface is added or changed, and defaults are unchanged. This makes the Python runtime honour configuration that already exists and is already accepted from users today.