Skip to content

[fix][fn] Honour producerSpec batching configuration in the Python function runtime - #26392

Merged
david-streamlio merged 1 commit into
apache:masterfrom
david-streamlio:fix-python-fn-producer-batching
Aug 26, 2026
Merged

[fix][fn] Honour producerSpec batching configuration in the Python function runtime#26392
david-streamlio merged 1 commit into
apache:masterfrom
david-streamlio:fix-python-fn-producer-batching

Conversation

@david-streamlio

Copy link
Copy Markdown
Contributor

Fixes #26390

Motivation

PIP-401 (#23860) made producer batching configurable for Pulsar Functions. The setting travels as ProducerConfig.batchingConfigProducerSpec.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.

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.

maxPendingMessages and maxPendingMessagesAcrossPartitions were ignored too, and context.publish() additionally ignored batchBuilder, which the sink producer already honoured.

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 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).
  • batchingSpec.batchBuilder overrides ProducerSpec.batchBuilder, matching the order ProducerBuilderFactory applies them in.

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.

Out of scope, deliberately:

Documentation

This PR updates pulsar-functions/instance/src/main/python/README.md with a ProducerSpeccreate_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:

Marking this doc-required rather than doc-complete for 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

  • Make sure that the change passes the CI checks.

This change added tests and can be verified as follows:

  • 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.
  • Explicit backwards-compatibility cases assert that a function with no producerSpec still gets batching_enabled=True, batching_max_publish_delay_ms=10, block_if_queue_full=True.
  • Reverting the three production files and re-running the suite fails 16 of the 19, confirming the tests pin the new behaviour rather than passing vacuously.
  • Full suite: 23/23 pass (4 pre-existing + 19 new), via pulsar-functions/instance/src/scripts/run_python_instance_tests.sh.
  • ./gradlew quickCheck passes.

Does this pull request potentially affect one of the following parts:

If the box was checked, please highlight the changes

  • Dependencies (add or upgrade a dependency)
  • The public API
  • The schema
  • The default values of configurations
  • The threading model
  • The binary protocol
  • The REST endpoints
  • The admin CLI options
  • The metrics
  • Anything that affects deployment

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.

…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.
@david-streamlio

Copy link
Copy Markdown
Contributor Author

The doc-required label on this PR is stale — the documentation has already merged.

apache/pulsar-site#1215 ("Document producerConfig.batchingConfig for Pulsar Functions") merged on 25 Aug and covers exactly the surface this PR implements in the Python runtime. Nothing further is owed on the docs side, so I have relabelled to doc-complete; happy to revert if a reviewer sees it differently.

Flagging it because doc-required reads as "the author still owes work", and this PR has had no review activity since it was opened on 20 Aug despite being green.

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 producerSpec settings are meant to let operators tune away. Today those settings are silently ignored by the runtime.

@david-streamlio david-streamlio added doc-complete Your PR changes impact docs and the related docs have been already added. and removed doc-required Your PR changes impact docs and you will update later. labels Aug 26, 2026

@freeznet freeznet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

@david-streamlio
david-streamlio merged commit 7d2a0bd into apache:master Aug 26, 2026
43 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/function doc-complete Your PR changes impact docs and the related docs have been already added.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Functions] Python function runtime hardcodes producer batching config, ignoring producerSpec.batchingSpec

2 participants