Skip to content

[Go Functions] retainOrdering and retainKeyOrdering are ignored, so key ordering is silently not preserved #26405

Description

@david-streamlio

Master Issue: #26404

Search before reporting

  • I searched in the issues and found nothing similar.

Motivation

The Go runtime picks the subscription type from one field only:

// pulsar-function-go/pf/instance.go:307-310
func (gi *goInstance) setupConsumer() (chan pulsar.ConsumerMessage, error) {
	subscriptionType := pulsar.Shared
	if int32(gi.context.instanceConf.funcDetails.Source.SubscriptionType) == pb.SubscriptionType_value["FAILOVER"] {
		subscriptionType = pulsar.Failover
	}

RetainOrdering and RetainKeyOrdering appear nowhere in pulsar-function-go outside the generated pb package — git grep -i "RetainOrdering\|RetainKeyOrdering" -- 'pulsar-function-go/**/*.go' returns nothing once pb/ is excluded.

The Python runtime applies both:

# pulsar-functions/instance/src/main/python/python_instance.py:147-151
if self.instance_config.function_details.retainOrdering or \
  self.instance_config.function_details.processingGuarantees == Function_pb2.ProcessingGuarantees.Value("EFFECTIVELY_ONCE"):
  mode = pulsar._pulsar.ConsumerType.Failover
elif self.instance_config.function_details.retainKeyOrdering:
  mode = pulsar._pulsar.ConsumerType.KeyShared

So this is accepted and has no effect:

pulsar-admin functions create --go fn --retain-key-ordering ...

functions get reports retainKeyOrdering: true, and the function runs on a Shared subscription. Messages sharing a key are distributed across instances and processed concurrently, which is precisely the guarantee the flag exists to provide. --retain-ordering is ignored the same way, so a function that should run on Failover runs Shared.

This is the one gap in #26404 with a live correctness consequence rather than a missing capability: the function does not fail, it produces subtly wrong results, and nothing in the configuration, the admin output or the logs indicates why.

Solution

Apply both fields when selecting the subscription type, matching Python and the Java runtime:

subscriptionType := pulsar.Shared
if int32(funcDetails.Source.SubscriptionType) == pb.SubscriptionType_value["FAILOVER"] {
	subscriptionType = pulsar.Failover
}
if funcDetails.RetainOrdering {
	subscriptionType = pulsar.Failover
} else if funcDetails.RetainKeyOrdering {
	subscriptionType = pulsar.KeyShared
}

Two points for review:

  1. Precedence. Python applies retainOrdering (and EFFECTIVELY_ONCE) before retainKeyOrdering, so ordering wins when both are set. Worth matching deliberately rather than by accident.
  2. EFFECTIVELY_ONCE needs no arm here. The Go runtime already panics on it at pulsar-function-go/pf/instanceConf.go:137, so it cannot reach this code.

Also worth considering: a dead letter policy requires a Shared or KeyShared subscription, so this interacts with the sibling issue on retryDetails. Whichever lands second should check the combination.

Alternatives

Refusing at startup — panicking the way EFFECTIVELY_ONCE does — would at least make the gap visible, and is strictly better than the current silence. But KeyShared and Failover are both available in the Go client, so there is no reason not to implement the behaviour.

Anything else?

Verified against origin/master.

Are you willing to submit a PR?

  • I'm willing to submit a PR!

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/functiontype/bugThe PR fixed a bug or issue reported a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions