Skip to content

AxonOps — AI-Native Control Plane for Open Source Data Platforms

Kafka Client Load Balancing

Unlike traditional databases where a load balancer routes requests, Kafka clients perform their own load balancing. The client determines which broker to contact based on partition ownership. This guide covers partition-based routing, producer partitioning strategies, and consumer load distribution.

Load balancer routing compared with partition-aware Kafka clientsLoad balancer routing compared with partition-aware Kafka clientsTraditional ModelServersKafka ModelBrokersClientLoad BalancerS1S2S3Client(Partition-Aware)B1(P0,P3)B2(P1,P4)B3(P2,P5)P0,P3 requestsP1,P4 requestsP2,P5 requests

Producers distribute messages across partitions using a partitioner. This determines which broker receives each message.

Producer partitioner selecting a partition and its leader brokerProducerPartitionerMetadataProducerProducerPartitionerPartitionerMetadataMetadatapartition(topic, key, value)alt[Key is not null]hash(key) % numPartitionspartition based on key[Key is null]alt[Sticky Partitioning (Kafka 2.4+)]stick to current partitionuntil batch full[Round-Robin (Legacy)]next partitionpartitiongetLeader(partition)broker
StrategyWhen UsedDistributionOrdering
Key-basedKey providedBy key hashPer-key ordering
StickyNo key (Kafka 2.4+)Batch-basedNone
Round-robinNo key (legacy)EvenNone
CustomCustom partitionerUser-definedUser-defined
// Messages with same key go to same partition
producer.send(new ProducerRecord<>("orders", "customer-123", orderJson));
producer.send(new ProducerRecord<>("orders", "customer-123", anotherOrder));
// Both go to same partition → ordering guaranteed for customer-123

Hash Function:

// Default partitioner (murmur2)
partition = Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;

Key Distribution

Poorly distributed keys cause hot partitions. Avoid using boolean values, enum values with few options, or timestamps as keys.

Round-robin partitioning compared with sticky partitioningRound-robin partitioning compared with sticky partitioningLegacy Round-RobinSticky PartitionerMsg 1 → P0Msg 2 → P1Msg 3 → P2Msg 4 → P0Each message todifferent partitionmany small batchesMsgs 1-100 → P0Msgs 101-200 → P1Msgs 201-300 → P2Fill batch beforeswitching partitionfewer, larger batches

Benefits of sticky partitioning:

  • Larger batches → better compression
  • Fewer requests → lower overhead
  • Higher throughput
public class GeoPartitioner implements Partitioner {
@Override
public int partition(String topic, Object key, byte[] keyBytes,
Object value, byte[] valueBytes, Cluster cluster) {
List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
int numPartitions = partitions.size();
// Route by geographic region
String region = extractRegion(value);
switch (region) {
case "US": return 0;
case "EU": return 1;
case "APAC": return 2;
default: return Math.abs(region.hashCode()) % numPartitions;
}
}
}
partitioner.class=com.example.GeoPartitioner

Partition assignment across consumers in a groupPartition assignment across consumers in a groupTopic: orders (6 partitions)Consumer GroupP0P1P2P3P4P5Consumer 1Consumer 2Consumer 3Each partition assignedto exactly one consumerin the group
StrategyClassDistributionRebalance Impact
RangeRangeAssignorConsecutive per topicMay unbalance
RoundRobinRoundRobinAssignorEven distributionModerate movement
StickyStickyAssignorEven + minimize movementMinimal movement
CooperativeStickyCooperativeStickyAssignorSame + incrementalLowest impact
# Recommended for production
partition.assignment.strategy=org.apache.kafka.clients.consumer.CooperativeStickyAssignor
Range Assignment (2 topics × 3 partitions, 2 consumers)Range Assignment (2 topics × 3 partitions, 2 consumers)Topic ATopic BP0P1P2P0P1P2Consumer 1Consumer 2Consumer 1: 4 partitionsConsumer 2: 2 partitions(unbalanced!)
RoundRobin Assignment (2 topics × 3 partitions, 2 consumers)RoundRobin Assignment (2 topics × 3 partitions, 2 consumers)All Partitions (sorted)A-P0, A-P1, A-P2, B-P0, B-P1, B-P2Consumer 1A-P0, A-P2, B-P1Consumer 2A-P1, B-P0, B-P2Evenly distributed:3 partitions eachodd positionseven positions

Minimizes partition movement during rebalance:

Sticky Assignment During RebalanceSticky Assignment During RebalanceBefore: 2 consumersAfter: 3 consumersC1: P0, P1, P2C2: P3, P4, P5C1: P0, P1C2: P3, P4C3: P2, P5C3 joinsOnly P2 and P5 moved(minimal disruption)

Well-distributed leadership ensures even broker load:

Ideal Leader DistributionIdeal Leader DistributionBroker 1Broker 2Broker 3Leader: P0, P3, P6Follower: P1, P4, P7Leader: P1, P4, P7Follower: P2, P5, P8Leader: P2, P5, P8Follower: P0, P3, P6Each broker leads 3 partitionsBalanced read/write load
Terminal window
# Check partition distribution (leaders per broker)
kafka-topics.sh --describe --topic orders \
--bootstrap-server localhost:9092
# Trigger preferred leader election
kafka-leader-election.sh --bootstrap-server localhost:9092 \
--election-type PREFERRED --all-topic-partitions

Rack-aware consumer fetching from a local follower replicaRack-aware consumer fetching from a local follower replicaRack ARack BConsumer(client.rack=rack-a)FollowerBroker 2LeaderBroker 1FollowerBroker 3KIP-392: Consumer fetchesfrom rack-local replicaLower latencyReduced cross-rack trafficFetch fromlocal followerReplicate

Consumer:

# Consumer's rack identity
client.rack=rack-a

Broker:

# Broker's rack identity
broker.rack=rack-a
# Allow follower fetching
replica.selector.class=org.apache.kafka.common.replica.RackAwareReplicaSelector

Kafka clients don't use connection load balancing in the traditional sense. Instead:

AspectBehavior
Connection targetDetermined by partition leader
Number of connectionsOne per broker needed (per client instance)
Request routingClient routes to correct broker
Why Not Use External Load BalancersWhy Not Use External Load BalancersBrokersBroker 1(Leader P0)Broker 2(Leader P1)ClientLoad BalancerLoad balancer doesn't knowpartition leaders→ Requests go to wrong broker→ Broker returns NOT_LEADER_OR_FOLLOWER→ Client refreshes metadata and retries→ Extra latency + overheadWrite to P0Routes to wrong broker!

When load balancers may be needed:

  • Security boundary crossing
  • Service discovery in Kubernetes
  • Must be TCP (layer 4), not HTTP

Hot Partition ScenarioHot Partition ScenarioTopic: eventsBrokersP0: 100K msgs/sP1: 10K msgs/sP2: 10K msgs/sP3: 10K msgs/sBroker 1(P0 Leader)Overloaded!Broker 2(P1 Leader)Broker 3(P2,P3 Leader)
CauseSolution
Single popular keyAdd secondary key component
Timestamp as keyUse business key instead
Too few partitionsIncrease partition count
Uneven custom partitionerFix partitioner logic
// Problem: Single customer generates massive traffic
producer.send(new ProducerRecord<>("orders", "big-customer", order));
// Solution: Add salt to distribute load
int salt = random.nextInt(10); // 0-9
String saltedKey = "big-customer-" + salt;
producer.send(new ProducerRecord<>("orders", saltedKey, order));
// Note: Ordering now spread across 10 partitions
// May need aggregation on consumer side

MetricDescriptionImbalance Indicator
record-send-rateRecords sent/secBy broker comparison
byte-rateBytes sent/secBy broker comparison
request-rateRequests/secBy broker comparison
batch-size-avgAverage batch sizeLow = partitioning issue
MetricDescriptionImbalance Indicator
records-consumed-rateRecords/secBy partition comparison
fetch-rateFetches/secBy broker comparison
records-lagMessages behindHigh on single partition
Terminal window
# Check messages per partition
kafka-run-class.sh kafka.tools.GetOffsetShell \
--broker-list localhost:9092 \
--topic orders
# Per-broker request rate
kafka-run-class.sh kafka.tools.JmxTool \
--object-name kafka.network:type=RequestMetrics,name=RequestsPerSec,request=Produce

PracticeRationale
Choose meaningful keysEnable key-based routing
Avoid low-cardinality keysPrevent hot partitions
Use sticky partitioner for keylessBetter batching
Monitor partition distributionDetect imbalances early
PracticeRationale
Use CooperativeStickyAssignorMinimize rebalance impact
Match consumers to partitionsFull parallelism
Configure client.rackEnable local fetching
Monitor lag per partitionDetect bottlenecks
ThroughputPartitions per Topic
< 10 MB/s6-12
10-100 MB/s12-50
> 100 MB/s50+ (consider multiple topics)