-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-12309 The revocation algorithm produces uneven distributions #10077
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
87b7be6
e014b78
c128949
b758fe3
84e19b4
a0b98aa
95d5e78
f177888
3830d35
4f2e8c8
261bf0b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -553,7 +553,6 @@ private Map<String, ConnectorsAndTasks> performTaskRevocation(ConnectorsAndTasks | |
| wl.worker(), wl.connectorsSize(), wl.tasksSize())); | ||
| } | ||
|
|
||
| Map<String, ConnectorsAndTasks> revoking = new HashMap<>(); | ||
| // If there are no new workers, or no existing workers to revoke tasks from return early | ||
| // after logging the status | ||
| if (!(newWorkersNum > 0 && existingWorkersNum > 0)) { | ||
|
|
@@ -562,50 +561,63 @@ private Map<String, ConnectorsAndTasks> performTaskRevocation(ConnectorsAndTasks | |
| existingWorkersNum, newWorkersNum, totalWorkersNum); | ||
| // This is intentionally empty but mutable, because the map is used to include deleted | ||
| // connectors and tasks as well | ||
| return revoking; | ||
| return Collections.emptyMap(); | ||
| } | ||
|
|
||
| log.debug("Task revocation is required; workers with existing load: {} workers with " | ||
| + "no load {} total workers {}", | ||
| existingWorkersNum, newWorkersNum, totalWorkersNum); | ||
|
|
||
| Map<String, ConnectorsAndTasks> revoking = new HashMap<>(); | ||
| // We have at least one worker assignment (the leader itself) so totalWorkersNum can't be 0 | ||
| log.debug("Previous rounded down (floor) average number of connectors per worker {}", totalActiveConnectorsNum / existingWorkersNum); | ||
| int floorConnectors = totalActiveConnectorsNum / totalWorkersNum; | ||
| int ceilConnectors = floorConnectors + ((totalActiveConnectorsNum % totalWorkersNum == 0) ? 0 : 1); | ||
| log.debug("New average number of connectors per worker rounded down (floor) {} and rounded up (ceil) {}", floorConnectors, ceilConnectors); | ||
|
|
||
| computeRevoked( | ||
| revoking, | ||
| existingWorkers, | ||
| totalWorkersNum, | ||
| totalActiveConnectorsNum, | ||
| false | ||
| ); | ||
|
|
||
| log.debug("Previous rounded down (floor) average number of tasks per worker {}", totalActiveTasksNum / existingWorkersNum); | ||
| int floorTasks = totalActiveTasksNum / totalWorkersNum; | ||
| int ceilTasks = floorTasks + ((totalActiveTasksNum % totalWorkersNum == 0) ? 0 : 1); | ||
| log.debug("New average number of tasks per worker rounded down (floor) {} and rounded up (ceil) {}", floorTasks, ceilTasks); | ||
| int numToRevoke; | ||
| computeRevoked( | ||
| revoking, | ||
| existingWorkers, | ||
| totalWorkersNum, | ||
| totalActiveTasksNum, | ||
| true | ||
| ); | ||
|
|
||
| for (WorkerLoad existing : existingWorkers) { | ||
| Iterator<String> connectors = existing.connectors().iterator(); | ||
| numToRevoke = existing.connectorsSize() - ceilConnectors; | ||
| for (int i = existing.connectorsSize(); i > floorConnectors && numToRevoke > 0; --i, --numToRevoke) { | ||
| ConnectorsAndTasks resources = revoking.computeIfAbsent( | ||
| existing.worker(), | ||
| w -> new ConnectorsAndTasks.Builder().build()); | ||
| resources.connectors().add(connectors.next()); | ||
| } | ||
| } | ||
| return revoking; | ||
| } | ||
|
|
||
| static void computeRevoked(Map<String, ConnectorsAndTasks> revoking, | ||
| Collection<WorkerLoad> existingWorkers, | ||
| int numberOfTotalWorks, | ||
| int numberOfActives, | ||
| boolean forTask) { | ||
| int floor = numberOfActives / numberOfTotalWorks; | ||
| int numberOfBiggers = numberOfActives % numberOfTotalWorks; | ||
|
chia7712 marked this conversation as resolved.
Outdated
|
||
| int ceil = numberOfBiggers == 0 ? floor : floor + 1; | ||
| for (WorkerLoad existing : existingWorkers) { | ||
| Iterator<ConnectorTaskId> tasks = existing.tasks().iterator(); | ||
| numToRevoke = existing.tasksSize() - ceilTasks; | ||
| log.debug("Tasks on worker {} is higher than ceiling, so revoking {} tasks", existing, numToRevoke); | ||
| for (int i = existing.tasksSize(); i > floorTasks && numToRevoke > 0; --i, --numToRevoke) { | ||
| int currentSize = forTask ? existing.tasksSize() : existing.connectorsSize(); | ||
| int expectedSize; | ||
| if (existingWorkers.size() == 1 || currentSize == 1) expectedSize = ceil; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need this condition? And why under this case, we don't need to have
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is a specify case as the node having single task is already in balance. We can move the job to another node but it does not balance anything but it brings extra down-time. I will add comments for that case. |
||
| else if (currentSize >= ceil && numberOfBiggers > 0) { | ||
| expectedSize = ceil; | ||
| numberOfBiggers--; | ||
| } else expectedSize = floor; | ||
| Iterator<?> elements = forTask ? existing.tasks().iterator() : existing.connectors().iterator(); | ||
| int numToRevoke = currentSize - expectedSize; | ||
| while (elements.hasNext() && numToRevoke > 0) { | ||
| ConnectorsAndTasks resources = revoking.computeIfAbsent( | ||
| existing.worker(), | ||
| w -> new ConnectorsAndTasks.Builder().build()); | ||
| resources.tasks().add(tasks.next()); | ||
| if (forTask) resources.tasks().add((ConnectorTaskId) elements.next()); | ||
| else resources.connectors().add((String) elements.next()); | ||
| numToRevoke--; | ||
| } | ||
| } | ||
|
|
||
| return revoking; | ||
| } | ||
|
|
||
| private Map<String, ExtendedAssignment> fillAssignments(Collection<String> members, short error, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -359,7 +359,9 @@ private boolean assertConnectorAndTasksAreUniqueAndBalanced() { | |
| tasks.values().size(), | ||
| tasks.values().stream().distinct().collect(Collectors.toList()).size()); | ||
| assertTrue("Connectors are imbalanced: " + formatAssignment(connectors), maxConnectors - minConnectors < 2); | ||
| if (minConnectors > 1) assertEquals("Some workers have no connectors", connectors.size(), connect.workers().size()); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. make sure there is no idle worker |
||
| assertTrue("Tasks are imbalanced: " + formatAssignment(tasks), maxTasks - minTasks < 2); | ||
| if (minTasks > 1) assertEquals("Some workers have no tasks", tasks.size(), connect.workers().size()); | ||
| return true; | ||
| } catch (Exception e) { | ||
| log.error("Could not check connector state info.", e); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.