Skip to content

Commit 3d317e1

Browse files
author
Nathan Hartwell
committed
1 parent 0c4ba61 commit 3d317e1

File tree

2 files changed

+53
-21
lines changed

2 files changed

+53
-21
lines changed

balance_strategy.go

+16-7
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,27 @@ type BalanceStrategy interface {
5858
// --------------------------------------------------------------------
5959

6060
// BalanceStrategyRange is the default and assigns partitions as ranges to consumer group members.
61-
// Example with one topic T with six partitions (0..5) and two members (M1, M2):
62-
// M1: {T: [0, 1, 2]}
63-
// M2: {T: [3, 4, 5]}
61+
// This follows the same logic as
62+
// https://kafka.apache.org/31/javadoc/org/apache/kafka/clients/consumer/RangeAssignor.html
63+
//
64+
// Example with two topics T1 and T2 with six partitions each (0..5) and two members (M1, M2):
65+
// M1: {T1: [0, 1, 2], T2: [0, 1, 2]}
66+
// M2: {T2: [3, 4, 5], T2: [3, 4, 5]}
6467
var BalanceStrategyRange = &balanceStrategy{
6568
name: RangeBalanceStrategyName,
6669
coreFn: func(plan BalanceStrategyPlan, memberIDs []string, topic string, partitions []int32) {
67-
step := float64(len(partitions)) / float64(len(memberIDs))
70+
partitionsPerConsumer := len(partitions) / len(memberIDs)
71+
consumersWithExtraPartition := len(partitions) % len(memberIDs)
72+
73+
sort.Strings(memberIDs)
6874

6975
for i, memberID := range memberIDs {
70-
pos := float64(i)
71-
min := int(math.Floor(pos*step + 0.5))
72-
max := int(math.Floor((pos+1)*step + 0.5))
76+
min := i*partitionsPerConsumer + int(math.Min(float64(consumersWithExtraPartition), float64(i)))
77+
extra := 0
78+
if i < consumersWithExtraPartition {
79+
extra = 1
80+
}
81+
max := min + partitionsPerConsumer + extra
7382
plan.Add(memberID, topic, partitions[min:max]...)
7483
}
7584
},

balance_strategy_test.go

+37-14
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,48 @@ import (
1313

1414
func TestBalanceStrategyRange(t *testing.T) {
1515
tests := []struct {
16+
name string
1617
members map[string][]string
1718
topics map[string][]int32
1819
expected BalanceStrategyPlan
1920
}{
2021
{
22+
name: "2 members, 2 topics, 4 partitions each",
2123
members: map[string][]string{"M1": {"T1", "T2"}, "M2": {"T1", "T2"}},
2224
topics: map[string][]int32{"T1": {0, 1, 2, 3}, "T2": {0, 1, 2, 3}},
2325
expected: BalanceStrategyPlan{
24-
"M1": map[string][]int32{"T1": {0, 1}, "T2": {2, 3}},
25-
"M2": map[string][]int32{"T1": {2, 3}, "T2": {0, 1}},
26+
"M1": map[string][]int32{"T1": {0, 1}, "T2": {0, 1}},
27+
"M2": map[string][]int32{"T1": {2, 3}, "T2": {2, 3}},
2628
},
2729
},
2830
{
31+
name: "2 members, 2 topics, 4 partitions each (different member ids)",
32+
members: map[string][]string{"M3": {"T1", "T2"}, "M4": {"T1", "T2"}},
33+
topics: map[string][]int32{"T1": {0, 1, 2, 3}, "T2": {0, 1, 2, 3}},
34+
expected: BalanceStrategyPlan{
35+
"M3": map[string][]int32{"T1": {0, 1}, "T2": {0, 1}},
36+
"M4": map[string][]int32{"T1": {2, 3}, "T2": {2, 3}},
37+
},
38+
},
39+
{
40+
name: "3 members, 1 topic, 1 partition each",
41+
members: map[string][]string{"M1": {"T1"}, "M2": {"T1"}, "M3": {"T1"}},
42+
topics: map[string][]int32{"T1": {0}},
43+
expected: BalanceStrategyPlan{
44+
"M1": map[string][]int32{"T1": {0}},
45+
},
46+
},
47+
{
48+
name: "2 members, 2 topics, 3 partitions each",
2949
members: map[string][]string{"M1": {"T1", "T2"}, "M2": {"T1", "T2"}},
3050
topics: map[string][]int32{"T1": {0, 1, 2}, "T2": {0, 1, 2}},
3151
expected: BalanceStrategyPlan{
32-
"M1": map[string][]int32{"T1": {0, 1}, "T2": {2}},
33-
"M2": map[string][]int32{"T1": {2}, "T2": {0, 1}},
52+
"M1": map[string][]int32{"T1": {0, 1}, "T2": {0, 1}},
53+
"M2": map[string][]int32{"T1": {2}, "T2": {2}},
3454
},
3555
},
3656
{
57+
name: "2 members, 2 topics, different subscriptions",
3758
members: map[string][]string{"M1": {"T1"}, "M2": {"T1", "T2"}},
3859
topics: map[string][]int32{"T1": {0, 1}, "T2": {0, 1}},
3960
expected: BalanceStrategyPlan{
@@ -49,17 +70,19 @@ func TestBalanceStrategyRange(t *testing.T) {
4970
}
5071

5172
for _, test := range tests {
52-
members := make(map[string]ConsumerGroupMemberMetadata)
53-
for memberID, topics := range test.members {
54-
members[memberID] = ConsumerGroupMemberMetadata{Topics: topics}
55-
}
73+
t.Run(test.name, func(t *testing.T) {
74+
members := make(map[string]ConsumerGroupMemberMetadata)
75+
for memberID, topics := range test.members {
76+
members[memberID] = ConsumerGroupMemberMetadata{Topics: topics}
77+
}
5678

57-
actual, err := strategy.Plan(members, test.topics)
58-
if err != nil {
59-
t.Errorf("Unexpected error %v", err)
60-
} else if !reflect.DeepEqual(actual, test.expected) {
61-
t.Errorf("Plan does not match expectation\nexpected: %#v\nactual: %#v", test.expected, actual)
62-
}
79+
actual, err := strategy.Plan(members, test.topics)
80+
if err != nil {
81+
t.Errorf("Unexpected error %v", err)
82+
} else if !reflect.DeepEqual(actual, test.expected) {
83+
t.Errorf("Plan does not match expectation\nexpected: %#v\nactual: %#v", test.expected, actual)
84+
}
85+
})
6386
}
6487
}
6588

0 commit comments

Comments
 (0)