@@ -57,13 +57,9 @@ private GroupAssignment doAssign(final GroupSpec groupSpec, final TopologyDescri
57
57
final LinkedList <TaskId > activeTasks = taskIds (topologyDescriber , true );
58
58
assignActive (activeTasks );
59
59
60
- //standby
61
- final int numStandbyReplicas =
62
- groupSpec .assignmentConfigs ().isEmpty () ? 0
63
- : Integer .parseInt (groupSpec .assignmentConfigs ().get ("num.standby.replicas" ));
64
- if (numStandbyReplicas > 0 ) {
60
+ if (localState .numStandbyReplicas > 0 ) {
65
61
final LinkedList <TaskId > statefulTasks = taskIds (topologyDescriber , false );
66
- assignStandby (statefulTasks , numStandbyReplicas );
62
+ assignStandby (statefulTasks );
67
63
}
68
64
69
65
return buildGroupAssignment (groupSpec .members ().keySet ());
@@ -84,13 +80,24 @@ private LinkedList<TaskId> taskIds(final TopologyDescriber topologyDescriber, fi
84
80
85
81
private void initialize (final GroupSpec groupSpec , final TopologyDescriber topologyDescriber ) {
86
82
localState = new LocalState ();
87
- localState .allTasks = 0 ;
83
+ localState .numStandbyReplicas =
84
+ groupSpec .assignmentConfigs ().isEmpty () ? 0
85
+ : Integer .parseInt (groupSpec .assignmentConfigs ().get ("num.standby.replicas" ));
86
+
87
+ // Helpers for computing active tasks per member, and tasks per member
88
+ localState .totalActiveTasks = 0 ;
89
+ localState .totalTasks = 0 ;
88
90
for (final String subtopology : topologyDescriber .subtopologies ()) {
89
91
final int numberOfPartitions = topologyDescriber .maxNumInputPartitions (subtopology );
90
- localState .allTasks += numberOfPartitions ;
92
+ localState .totalTasks += numberOfPartitions ;
93
+ localState .totalActiveTasks += numberOfPartitions ;
94
+ if (topologyDescriber .isStateful (subtopology ))
95
+ localState .totalTasks += numberOfPartitions * localState .numStandbyReplicas ;
91
96
}
92
- localState .totalCapacity = groupSpec .members ().size ();
93
- localState .tasksPerMember = computeTasksPerMember (localState .allTasks , localState .totalCapacity );
97
+ localState .totalMembersWithActiveTaskCapacity = groupSpec .members ().size ();
98
+ localState .totalMembersWithTaskCapacity = groupSpec .members ().size ();
99
+ localState .activeTasksPerMember = computeTasksPerMember (localState .totalActiveTasks , localState .totalMembersWithActiveTaskCapacity );
100
+ localState .tasksPerMember = computeTasksPerMember (localState .totalTasks , localState .totalMembersWithTaskCapacity );
94
101
95
102
localState .processIdToState = new HashMap <>();
96
103
localState .activeTaskToPrevMember = new HashMap <>();
@@ -175,11 +182,13 @@ private void assignActive(final LinkedList<TaskId> activeTasks) {
175
182
for (final Iterator <TaskId > it = activeTasks .iterator (); it .hasNext ();) {
176
183
final TaskId task = it .next ();
177
184
final Member prevMember = localState .activeTaskToPrevMember .get (task );
178
- if (prevMember != null && hasUnfulfilledQuota ( prevMember ) ) {
185
+ if (prevMember != null ) {
179
186
final ProcessState processState = localState .processIdToState .get (prevMember .processId );
180
- processState .addTask (prevMember .memberId , task , true );
181
- maybeUpdateTasksPerMember (processState .activeTaskCount ());
182
- it .remove ();
187
+ if (hasUnfulfilledActiveTaskQuota (processState , prevMember )) {
188
+ processState .addTask (prevMember .memberId , task , true );
189
+ maybeUpdateActiveTasksPerMember (processState .memberToTaskCounts ().get (prevMember .memberId ));
190
+ it .remove ();
191
+ }
183
192
}
184
193
}
185
194
@@ -188,11 +197,13 @@ private void assignActive(final LinkedList<TaskId> activeTasks) {
188
197
final TaskId task = it .next ();
189
198
final ArrayList <Member > prevMembers = localState .standbyTaskToPrevMember .get (task );
190
199
final Member prevMember = findPrevMemberWithLeastLoad (prevMembers , null );
191
- if (prevMember != null && hasUnfulfilledQuota ( prevMember ) ) {
200
+ if (prevMember != null ) {
192
201
final ProcessState processState = localState .processIdToState .get (prevMember .processId );
193
- processState .addTask (prevMember .memberId , task , true );
194
- maybeUpdateTasksPerMember (processState .activeTaskCount ());
195
- it .remove ();
202
+ if (hasUnfulfilledActiveTaskQuota (processState , prevMember )) {
203
+ processState .addTask (prevMember .memberId , task , true );
204
+ maybeUpdateActiveTasksPerMember (processState .memberToTaskCounts ().get (prevMember .memberId ));
205
+ it .remove ();
206
+ }
196
207
}
197
208
}
198
209
@@ -206,24 +217,32 @@ private void assignActive(final LinkedList<TaskId> activeTasks) {
206
217
final TaskId task = it .next ();
207
218
final ProcessState processWithLeastLoad = processByLoad .poll ();
208
219
if (processWithLeastLoad == null ) {
209
- throw new TaskAssignorException ("No process available to assign active task {}." + task );
220
+ throw new TaskAssignorException (String . format ( "No process available to assign active task %s." , task ) );
210
221
}
211
222
final String member = memberWithLeastLoad (processWithLeastLoad );
212
223
if (member == null ) {
213
- throw new TaskAssignorException ("No member available to assign active task {}." + task );
224
+ throw new TaskAssignorException (String . format ( "No member available to assign active task %s." , task ) );
214
225
}
215
226
processWithLeastLoad .addTask (member , task , true );
216
227
it .remove ();
217
- maybeUpdateTasksPerMember (processWithLeastLoad .activeTaskCount ( ));
228
+ maybeUpdateActiveTasksPerMember (processWithLeastLoad .memberToTaskCounts (). get ( member ));
218
229
processByLoad .add (processWithLeastLoad ); // Add it back to the queue after updating its state
219
230
}
220
231
}
221
232
222
- private void maybeUpdateTasksPerMember (final int activeTasksNo ) {
223
- if (activeTasksNo == localState .tasksPerMember ) {
224
- localState .totalCapacity --;
225
- localState .allTasks -= activeTasksNo ;
226
- localState .tasksPerMember = computeTasksPerMember (localState .allTasks , localState .totalCapacity );
233
+ private void maybeUpdateActiveTasksPerMember (final int activeTasksNo ) {
234
+ if (activeTasksNo == localState .activeTasksPerMember ) {
235
+ localState .totalMembersWithActiveTaskCapacity --;
236
+ localState .totalActiveTasks -= activeTasksNo ;
237
+ localState .activeTasksPerMember = computeTasksPerMember (localState .totalActiveTasks , localState .totalMembersWithActiveTaskCapacity );
238
+ }
239
+ }
240
+
241
+ private void maybeUpdateTasksPerMember (final int taskNo ) {
242
+ if (taskNo == localState .tasksPerMember ) {
243
+ localState .totalMembersWithTaskCapacity --;
244
+ localState .totalTasks -= taskNo ;
245
+ localState .tasksPerMember = computeTasksPerMember (localState .totalTasks , localState .totalMembersWithTaskCapacity );
227
246
}
228
247
}
229
248
@@ -298,43 +317,49 @@ private String memberWithLeastLoad(final ProcessState processWithLeastLoad) {
298
317
return memberWithLeastLoad .orElse (null );
299
318
}
300
319
301
- private boolean hasUnfulfilledQuota ( final Member member ) {
302
- return localState . processIdToState . get ( member . processId ). memberToTaskCounts ().get (member .memberId ) < localState .tasksPerMember ;
320
+ private boolean hasUnfulfilledActiveTaskQuota ( final ProcessState process , final Member member ) {
321
+ return process . memberToTaskCounts ().get (member .memberId ) < localState .activeTasksPerMember ;
303
322
}
304
323
305
- private void assignStandby (final LinkedList <TaskId > standbyTasks , int numStandbyReplicas ) {
306
- final ArrayList <StandbyToAssign > toLeastLoaded = new ArrayList <>(standbyTasks .size () * numStandbyReplicas );
324
+ private boolean hasUnfulfilledTaskQuota (final ProcessState process , final Member member ) {
325
+ return process .memberToTaskCounts ().get (member .memberId ) < localState .tasksPerMember ;
326
+ }
307
327
328
+ private void assignStandby (final LinkedList <TaskId > standbyTasks ) {
329
+ final ArrayList <StandbyToAssign > toLeastLoaded = new ArrayList <>(standbyTasks .size () * localState .numStandbyReplicas );
330
+
308
331
// Assuming our current assignment is range-based, we want to sort by partition first.
309
332
standbyTasks .sort (Comparator .comparing (TaskId ::partition ).thenComparing (TaskId ::subtopologyId ).reversed ());
310
333
311
334
for (TaskId task : standbyTasks ) {
312
- for (int i = 0 ; i < numStandbyReplicas ; i ++) {
335
+ for (int i = 0 ; i < localState . numStandbyReplicas ; i ++) {
313
336
314
337
// prev active task
315
- final Member prevMember = localState .activeTaskToPrevMember .get (task );
316
- if (prevMember != null ) {
317
- final ProcessState prevMemberProcessState = localState .processIdToState .get (prevMember .processId );
318
- if (!prevMemberProcessState .hasTask (task ) && isLoadBalanced (prevMemberProcessState )) {
319
- prevMemberProcessState .addTask (prevMember .memberId , task , false );
338
+ final Member prevActiveMember = localState .activeTaskToPrevMember .get (task );
339
+ if (prevActiveMember != null ) {
340
+ final ProcessState prevActiveMemberProcessState = localState .processIdToState .get (prevActiveMember .processId );
341
+ if (!prevActiveMemberProcessState .hasTask (task ) && hasUnfulfilledTaskQuota (prevActiveMemberProcessState , prevActiveMember )) {
342
+ prevActiveMemberProcessState .addTask (prevActiveMember .memberId , task , false );
343
+ maybeUpdateTasksPerMember (prevActiveMemberProcessState .memberToTaskCounts ().get (prevActiveMember .memberId ));
320
344
continue ;
321
345
}
322
346
}
323
347
324
348
// prev standby tasks
325
- final ArrayList <Member > prevMembers = localState .standbyTaskToPrevMember .get (task );
326
- if (prevMembers != null && !prevMembers .isEmpty ()) {
327
- final Member prevMember2 = findPrevMemberWithLeastLoad (prevMembers , task );
328
- if (prevMember2 != null ) {
329
- final ProcessState prevMemberProcessState = localState .processIdToState .get (prevMember2 .processId );
330
- if (isLoadBalanced (prevMemberProcessState )) {
331
- prevMemberProcessState .addTask (prevMember2 .memberId , task , false );
349
+ final ArrayList <Member > prevStandbyMembers = localState .standbyTaskToPrevMember .get (task );
350
+ if (prevStandbyMembers != null && !prevStandbyMembers .isEmpty ()) {
351
+ final Member prevStandbyMember = findPrevMemberWithLeastLoad (prevStandbyMembers , task );
352
+ if (prevStandbyMember != null ) {
353
+ final ProcessState prevStandbyMemberProcessState = localState .processIdToState .get (prevStandbyMember .processId );
354
+ if (hasUnfulfilledTaskQuota (prevStandbyMemberProcessState , prevStandbyMember )) {
355
+ prevStandbyMemberProcessState .addTask (prevStandbyMember .memberId , task , false );
356
+ maybeUpdateTasksPerMember (prevStandbyMemberProcessState .memberToTaskCounts ().get (prevStandbyMember .memberId ));
332
357
continue ;
333
358
}
334
359
}
335
360
}
336
361
337
- toLeastLoaded .add (new StandbyToAssign (task , numStandbyReplicas - i ));
362
+ toLeastLoaded .add (new StandbyToAssign (task , localState . numStandbyReplicas - i ));
338
363
break ;
339
364
}
340
365
}
@@ -350,7 +375,7 @@ private void assignStandby(final LinkedList<TaskId> standbyTasks, int numStandby
350
375
if (!assignStandbyToMemberWithLeastLoad (processByLoad , toAssign .taskId )) {
351
376
log .warn ("{} There is not enough available capacity. " +
352
377
"You should increase the number of threads and/or application instances to maintain the requested number of standby replicas." ,
353
- errorMessage (numStandbyReplicas , i , toAssign .taskId ));
378
+ errorMessage (localState . numStandbyReplicas , i , toAssign .taskId ));
354
379
break ;
355
380
}
356
381
}
@@ -362,13 +387,6 @@ private String errorMessage(final int numStandbyReplicas, final int i, final Tas
362
387
" of " + numStandbyReplicas + " standby tasks for task [" + task + "]." ;
363
388
}
364
389
365
- private boolean isLoadBalanced (final ProcessState process ) {
366
- final double load = process .load ();
367
- final boolean isLeastLoadedProcess = localState .processIdToState .values ().stream ()
368
- .allMatch (p -> p .load () >= load );
369
- return process .hasCapacity () || isLeastLoadedProcess ;
370
- }
371
-
372
390
private static int computeTasksPerMember (final int numberOfTasks , final int numberOfMembers ) {
373
391
if (numberOfMembers == 0 ) {
374
392
return 0 ;
@@ -406,8 +424,12 @@ private static class LocalState {
406
424
Map <TaskId , ArrayList <Member >> standbyTaskToPrevMember ;
407
425
Map <String , ProcessState > processIdToState ;
408
426
409
- int allTasks ;
410
- int totalCapacity ;
427
+ int numStandbyReplicas ;
428
+ int totalActiveTasks ;
429
+ int totalTasks ;
430
+ int totalMembersWithActiveTaskCapacity ;
431
+ int totalMembersWithTaskCapacity ;
432
+ int activeTasksPerMember ;
411
433
int tasksPerMember ;
412
434
}
413
435
}
0 commit comments