Skip to content

pulsaradmin: ProducerConfig cannot express PIP-401 batchingConfig #1528

Description

@david-streamlio

Is your feature request related to a problem? Please describe.

pulsaradmin's utils.ProducerConfig cannot express PIP-401's producer batching configuration, so no Go-based tool can set it on a Pulsar Function, Source, or Sink.

PIP-401 (apache/pulsar#23860, merged 2025-03-12) added a BatchingConfig to the Java admin API's ProducerConfig and wired it through to the ProducerBuilder. The Java class today is:

// pulsar-client-admin-api/src/main/java/org/apache/pulsar/common/functions/ProducerConfig.java
public class ProducerConfig {
    private Integer maxPendingMessages;
    private Integer maxPendingMessagesAcrossPartitions;
    private Boolean useThreadLocalProducers;
    private CryptoConfig cryptoConfig;
    private String batchBuilder;
    private CompressionType compressionType;
    private BatchingConfig batchingConfig;   // <-- added by PIP-401
}

pulsaradmin/pkg/utils/producer_config.go on master has every field except the last one:

type ProducerConfig struct {
	MaxPendingMessages int `json:"maxPendingMessages" yaml:"maxPendingMessages"`
	//nolint
	MaxPendingMessagesAcrossPartitions int `json:"maxPendingMessagesAcrossPartitions" yaml:"maxPendingMessagesAcrossPartitions"`

	UseThreadLocalProducers bool          `json:"useThreadLocalProducers" yaml:"useThreadLocalProducers"`
	CryptoConfig            *CryptoConfig `json:"cryptoConfig" yaml:"cryptoConfig"`
	BatchBuilder            string        `json:"batchBuilder" yaml:"batchBuilder"`
	CompressionType         string        `json:"compressionType" yaml:"compressionType"`
}

Since ProducerConfig is a typed struct serialized to JSON with no passthrough field, there is no workaround: a Go caller simply cannot put batchingConfig on the wire, even though the broker accepts it and the Java pulsar-admin CLI sends it happily via functions create --producer-config '<json>'.

The concrete impact is on infrastructure-as-code. The Terraform provider (streamnative/terraform-provider-pulsar) imports this package, so producer batching is unreachable from Terraform for every function runtime — including Java, where the broker and runtime support have been complete since PIP-401. pulsarctl has the same ceiling.

The user-visible symptom this traces back to is publish latency: BatchingConfig.batchingMaxPublishDelayMs defaults to 10ms, and below roughly one message per 10ms per instance every message pays that full linger while each batch still contains exactly one message. Tuning or disabling it is exactly what batchingConfig exists for.

Describe the solution you'd like

Add a BatchingConfig type mirroring the Java class and reference it from ProducerConfig:

// pulsaradmin/pkg/utils/batching_config.go
type BatchingConfig struct {
	Enabled                   bool `json:"enabled" yaml:"enabled"`
	BatchingMaxPublishDelayMs *int `json:"batchingMaxPublishDelayMs,omitempty" yaml:"batchingMaxPublishDelayMs"`
	//nolint
	RoundRobinRouterBatchingPartitionSwitchFrequency *int `json:"roundRobinRouterBatchingPartitionSwitchFrequency,omitempty" yaml:"roundRobinRouterBatchingPartitionSwitchFrequency"`
	BatchingMaxMessages *int   `json:"batchingMaxMessages,omitempty" yaml:"batchingMaxMessages"`
	BatchingMaxBytes    *int   `json:"batchingMaxBytes,omitempty" yaml:"batchingMaxBytes"`
	BatchBuilder        string `json:"batchBuilder,omitempty" yaml:"batchBuilder"`
}

and in ProducerConfig:

	BatchingConfig *BatchingConfig `json:"batchingConfig,omitempty" yaml:"batchingConfig"`

Two details worth a maintainer's eye:

  1. Pointer-typed numerics. The Java fields are boxed (Integer), and BatchingConfig has non-zero defaults — enabled = true and batchingMaxPublishDelayMs = 10. Plain int with omitempty would make "explicitly 0" indistinguishable from "unset", which matters most for batchingMaxPublishDelayMs, where 0 is the meaningful value for latency-sensitive callers who want the linger gone. *int keeps that distinction. Enabled is a plain bool because Java declares it unboxed with a true default; a caller wanting to disable batching sets false explicitly, which must serialize — hence no omitempty on it.
  2. ProducerConfig as a pointer field. BatchingConfig should be *BatchingConfig so an unset batching config is omitted entirely rather than sent as a zero-valued object, which would read to the broker as enabled: false.

ProducerConfig is already referenced by FunctionConfig, SourceConfig, and SinkConfig, so a single addition covers all three resource types.

Describe alternatives you've considered

  • A free-form passthrough field on ProducerConfig — rejected. It would diverge from the Java model and push JSON assembly onto every caller.
  • Doing this in the Terraform provider instead — not possible. The provider serializes through this struct; there is no seam to inject an extra JSON key.
  • customRuntimeOptions — not a workaround. It maps to a different FunctionDetails field that the worker passes to the runtime, not into producerSpec.

Additional context

Runtime support for batchingSpec varies, which affects whether setting this has an effect but not whether it can be set:

To be explicit, since it would be easy to read the above as more settled than it is: neither runtime PR has been reviewed or merged, and this request does not depend on either landing. The gap described here applies today to Java functions, where the runtime has honoured batchingSpec since PIP-401 merged in March 2025 — a Go caller still cannot send the configuration that runtime already supports. The Python and Go runtime work would widen who benefits; it is not a prerequisite.

Downstream, streamnative/terraform-provider-pulsar#220 tracks exposing producerConfig on pulsar_function and is blocked on this issue for the batching portion.

Happy to open a PR for this if the shape above looks right — in particular I would like a maintainer's read on the pointer-vs-value choice before writing it.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions