Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

1.1 The Problem: Scaling Kafka-to-Kafka Pipelines Today

Currently, Kafka Connect sink connectors consume from Kafka topics using rely on traditional consumer groups that enforce a strict 1:1 mapping between partitions and tasks. T

In this his model , each partition is exclusively assigned to one task. This creates two problems for Kafka-to-Kafka (and Kafka-to-external) pipelinesis often incompatible with unordered message processing and creates three primary bottlenecks for task queue workloads:

1. Partition-Coupled Scaling is coupled to : Parallelism is hard-limited by the partition count.

If a topic has 12 partitions, you can run at most 12 sink tasks. I

...



2. Head-of-Line Blocking: Because partition ownership is exclusive, a single slow task—often caused by downstream latency—stalls all subsequent records in its assigned partitions

3. Rebalance-Driven Gaps: Adding or removing tasks triggers "rebalance storms."

If one sink task is slow (e.g., network latency to a downstream system), the records on its assigned partitions back up.

...

When tasks are added, removed, or crash, consumer group rebalances revoke and reassign partitions.

During a rebalance, no task processes records from revoked partitions. With cooperative sticky rebalancing this is mitigated but not eliminated.

1.2 How Share Groups Solve This

...

3.1.1 Worker-level configuration (`connect-distributed.properties`)

PropertyTypeDefaultDescription
consumer.group.protocolstringconsumerExisting property. When set to share, the Connect worker creates a KafkaShareConsumer instead of a KafkaConsumer for sink tasks.


3.1.2 Connector-level configuration (per-connector JSON)


PropertyTypeDefaultDescription
consumer.override.group.protocolstring(inherited from worker)Per-connector override. Set to share to opt a single connector into queue semantics.
share.group.idstringconnect-<connector-name>The Share Group ID. Defaults to the same naming convention as consumer groups.
share.acknowledgement.modestringexplicitexplicit: worker calls acknowledge(ACCEPT) after task.put() succeeds. implicit: acknowledgments are sent on the next poll() (simpler, lower latency, weaker guarantee).
share.acquisition.lock.timeout.msint30000Maximum time a record remains in ACQUIRED state before the broker releases it for re-delivery. Must be greater than the expected task.put() latency.
share.delivery.semanticsstringat-least-onceat-least-once or exactly-once. Exactly-once requires KIP-1289 and a transactional producer.
share.max.delivery.countint5Maximum number of times a record can be re-delivered before being sent to the Dead Letter Queue (if configured). Maps to Share Group's group.share.record.lock.partition.limit.

3.2 New / Modified Java Interfaces

...

The difference is entirely in the worker runtime:

AspectWorkerSinkTask (today)WorkerShareSinkTask (proposed)
ConsumerKafkaConsumerKafkaShareConsumer
Subscriptionconsumer.subscribe(topics, rebalanceListener)shareConsumer.subscribe(topics)
Pollconsumer.poll()shareConsumer.poll()
Offset trackingcurrentOffsets map + consumer.commitSync()Per-record shareConsumer.acknowledge(record, ACCEPT) + shareConsumer.commitSync()
RebalanceConsumerRebalanceListener calling task.open()/close()No rebalances. task.open() called once at startup for all subscribed topics.
Failure handlingRetriableException -> pause consumer, retry batchRetriableException -> acknowledge(RELEASE) for batch, records re-delivered by broker

3.2.2`Worker.baseConsumerConfigs()` (modified)

...

This keeps them co-located with the existing `sink-record-read-total`, `sink-record-send-total`, etc. and avoids a separate metric namespace.

Sensor NameMetric NameTypeTraditional Consumer (group.protocol=consumer)Share Consumer (group.protocol=share)
sink-record-acquiresink-record-acquire-rateRatenot registeredRecords/sec acquired from the share group

sink-record-acquire-totalCumulativeSumnot registeredTotal records acquired from the share group
sink-record-acknowledgesink-record-acknowledge-rateRatenot registeredRecords/sec acknowledged (ACCEPT)

sink-record-acknowledge-totalCumulativeSumnot registeredTotal records acknowledged (ACCEPT)
sink-record-releasesink-record-release-rateRatenot registeredRecords/sec released (RELEASE) for re-delivery

sink-record-release-totalCumulativeSumnot registeredTotal records released for re-delivery
sink-record-rejectsink-record-reject-rateRatenot registeredRecords/sec rejected (REJECT) to DLQ

sink-record-reject-totalCumulativeSumnot registeredTotal records rejected to DLQ
acknowledge-timeacknowledge-time-maxMaxnot registeredMax time (ms) between poll() and acknowledge()

acknowledge-time-avgAvgnot registeredAvg time (ms) between poll() and acknowledge()
sink-record-redeliverysink-record-redelivery-totalCumulativeSumnot registeredTotal records with delivery count > 1

Conversely, the following existing `WorkerSinkTask` sensors have no Share Group equivalent and are not registered by `WorkerShareSinkTask`:

Existing SensorWhy not applicable to Share Groups
partition-countShare Groups don't assign partitions exclusively to tasks. All tasks consume from all subscribed partitions.
offset-seq-numberShare Groups don't use consumer offsets. Acknowledgments replace offset commits.
offset-commit-completionNo offset commits in Share Groups. Replaced by sink-record-acknowledge.
offset-commit-completion-skipNo offset commits to skip.


The existing sensors that are shared between both task types:

SensorBehavior
sink-record-readRegistered by both. Counts records polled (same semantics).
sink-record-sendRegistered by both. Counts records delivered to task.put().
sink-record-active-countRegistered by both. In Share Groups, this is the number of records currently ACQUIRED but not yet acknowledged.
put-batch-timeRegistered by both. Time spent in task.put().

Proposed Changes

At‑Least‑Once (Share Group → SinkTask → External Sink)

...