You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flink.apache.org by ch...@apache.org on 2023/02/16 13:51:11 UTC

[flink] branch master updated: [FLINK-30895][coordination] Dynamically adjust slot distribution

This is an automated email from the ASF dual-hosted git repository.

chesnay pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git


The following commit(s) were added to refs/heads/master by this push:
     new 97e1dbc90cc [FLINK-30895][coordination] Dynamically adjust slot distribution
97e1dbc90cc is described below

commit 97e1dbc90cc3ea6e6e48e901813ab3bfbb693c6a
Author: Chesnay Schepler <ch...@apache.org>
AuthorDate: Thu Feb 16 14:51:03 2023 +0100

    [FLINK-30895][coordination] Dynamically adjust slot distribution
---
 docs/content.zh/docs/deployment/elastic_scaling.md |  1 -
 docs/content/docs/deployment/elastic_scaling.md    |  1 -
 .../scheduler/adaptive/JobGraphJobInformation.java |  1 +
 .../adaptive/allocator/JobInformation.java         |  2 +
 .../allocator/SlotSharingSlotAllocator.java        | 55 +++++++++++++++++++---
 .../allocator/SlotSharingSlotAllocatorTest.java    | 40 ++++++++++++++++
 6 files changed, 91 insertions(+), 9 deletions(-)

diff --git a/docs/content.zh/docs/deployment/elastic_scaling.md b/docs/content.zh/docs/deployment/elastic_scaling.md
index c65c974a35b..1df55d738db 100644
--- a/docs/content.zh/docs/deployment/elastic_scaling.md
+++ b/docs/content.zh/docs/deployment/elastic_scaling.md
@@ -145,7 +145,6 @@ Adaptive 调度器可以通过[所有在名字包含 `adaptive-scheduler` 的配
 - **只支持流式 Job**:Adaptive 调度器的第一个版本仅支持流式 Job。当提交的是一个批处理 Job 时,我们会自动换回默认调度器。
 - **不支持[本地恢复]({{< ref "docs/ops/state/large_state_tuning">}}#task-local-recovery)**:本地恢复是将 Task 调度到状态尽可能的被重用的机器上的功能。不支持这个功能意味着 Adaptive 调度器需要每次从 Checkpoint 的存储中下载整个 State。
 - **不支持部分故障恢复**: 部分故障恢复意味着调度器可以只重启失败 Job 其中某一部分(在 Flink 的内部结构中被称之为 Region)而不是重启整个 Job。这个限制只会影响那些独立并行(Embarrassingly Parallel)Job的恢复时长,默认的调度器可以重启失败的部分,然而 Adaptive 将需要重启整个 Job。
-- **空闲 Slot**: 如果 Slot 共享组的最大并行度不相等,提供给 Adaptive 调度器所使用的的 Slot 可能不会被使用。
 - 扩缩容事件会触发 Job 和 Task 重启,Task 重试的次数也会增加。
 
 ## Adaptive Batch Scheduler
diff --git a/docs/content/docs/deployment/elastic_scaling.md b/docs/content/docs/deployment/elastic_scaling.md
index b11e459f294..0acf86ad910 100644
--- a/docs/content/docs/deployment/elastic_scaling.md
+++ b/docs/content/docs/deployment/elastic_scaling.md
@@ -147,7 +147,6 @@ The behavior of Adaptive Scheduler is configured by [all configuration options c
 - **Streaming jobs only**: The first version of Adaptive Scheduler runs with streaming jobs only. When submitting a batch job, we will automatically fall back to the default scheduler.
 - **No support for [local recovery]({{< ref "docs/ops/state/large_state_tuning">}}#task-local-recovery)**: Local recovery is a feature that schedules tasks to machines so that the state on that machine gets re-used if possible. The lack of this feature means that Adaptive Scheduler will always need to download the entire state from the checkpoint storage.
 - **No support for partial failover**: Partial failover means that the scheduler is able to restart parts ("regions" in Flink's internals) of a failed job, instead of the entire job. This limitation impacts only recovery time of embarrassingly parallel jobs: Flink's default scheduler can restart failed parts, while Adaptive Scheduler will restart the entire job.
-- **Unused slots**: If the max parallelism for slot sharing groups is not equal, slots offered to Adaptive Scheduler might be unused.
 - Scaling events trigger job and task restarts, which will increase the number of Task attempts.
 
 ## Adaptive Batch Scheduler
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/scheduler/adaptive/JobGraphJobInformation.java b/flink-runtime/src/main/java/org/apache/flink/runtime/scheduler/adaptive/JobGraphJobInformation.java
index 1e265fb9352..1b0bb60e7e6 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/scheduler/adaptive/JobGraphJobInformation.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/scheduler/adaptive/JobGraphJobInformation.java
@@ -73,6 +73,7 @@ public class JobGraphJobInformation implements JobInformation {
         return jobGraph.getCheckpointingSettings();
     }
 
+    @Override
     public Iterable<JobInformation.VertexInformation> getVertices() {
         return Iterables.transform(
                 jobGraph.getVertices(), (vertex) -> getVertexInformation(vertex.getID()));
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/scheduler/adaptive/allocator/JobInformation.java b/flink-runtime/src/main/java/org/apache/flink/runtime/scheduler/adaptive/allocator/JobInformation.java
index 074dd6d15c5..99b8d48c051 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/scheduler/adaptive/allocator/JobInformation.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/scheduler/adaptive/allocator/JobInformation.java
@@ -36,6 +36,8 @@ public interface JobInformation {
 
     VertexInformation getVertexInformation(JobVertexID jobVertexId);
 
+    Iterable<VertexInformation> getVertices();
+
     /** Information about a single vertex. */
     interface VertexInformation {
         JobVertexID getJobVertexID();
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/scheduler/adaptive/allocator/SlotSharingSlotAllocator.java b/flink-runtime/src/main/java/org/apache/flink/runtime/scheduler/adaptive/allocator/SlotSharingSlotAllocator.java
index edc3b7f05c8..ce4c6bfb1af 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/scheduler/adaptive/allocator/SlotSharingSlotAllocator.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/scheduler/adaptive/allocator/SlotSharingSlotAllocator.java
@@ -34,6 +34,7 @@ import javax.annotation.Nonnull;
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -94,15 +95,15 @@ public class SlotSharingSlotAllocator implements SlotAllocator {
     @Override
     public Optional<VertexParallelismWithSlotSharing> determineParallelism(
             JobInformation jobInformation, Collection<? extends SlotInfo> freeSlots) {
-        // TODO: This can waste slots if the max parallelism for slot sharing groups is not equal
-        final int slotsPerSlotSharingGroup =
-                freeSlots.size() / jobInformation.getSlotSharingGroups().size();
 
-        if (slotsPerSlotSharingGroup == 0) {
-            // => less slots than slot-sharing groups
+        // => less slots than slot-sharing groups
+        if (jobInformation.getSlotSharingGroups().size() > freeSlots.size()) {
             return Optional.empty();
         }
 
+        final Map<SlotSharingGroupId, Integer> slotSharingGroupParallelism =
+                determineSlotsPerSharingGroup(jobInformation, freeSlots.size());
+
         final Iterator<? extends SlotInfo> slotIterator = freeSlots.iterator();
 
         final Collection<ExecutionSlotSharingGroupAndSlot> assignments = new ArrayList<>();
@@ -115,7 +116,10 @@ public class SlotSharingSlotAllocator implements SlotAllocator {
                             .collect(Collectors.toList());
 
             final Map<JobVertexID, Integer> vertexParallelism =
-                    determineParallelism(containedJobVertices, slotsPerSlotSharingGroup);
+                    determineVertexParallelism(
+                            containedJobVertices,
+                            slotSharingGroupParallelism.get(
+                                    slotSharingGroup.getSlotSharingGroupId()));
 
             final Iterable<ExecutionSlotSharingGroup> sharedSlotToVertexAssignment =
                     createExecutionSlotSharingGroups(vertexParallelism);
@@ -133,7 +137,44 @@ public class SlotSharingSlotAllocator implements SlotAllocator {
         return Optional.of(new VertexParallelismWithSlotSharing(allVertexParallelism, assignments));
     }
 
-    private static Map<JobVertexID, Integer> determineParallelism(
+    /**
+     * Distributes free slots across the slot-sharing groups of the job. Slots are distributed as
+     * evenly as possible. If a group requires less than an even share of slots the remainder is
+     * distributed over the remaining groups.
+     */
+    private static Map<SlotSharingGroupId, Integer> determineSlotsPerSharingGroup(
+            JobInformation jobInformation, int freeSlots) {
+        int numUnassignedSlots = freeSlots;
+        int numUnassignedSlotSharingGroups = jobInformation.getSlotSharingGroups().size();
+
+        final Map<SlotSharingGroupId, Integer> slotSharingGroupParallelism = new HashMap<>();
+
+        for (Map.Entry<SlotSharingGroupId, Integer> slotSharingGroup :
+                sortSlotSharingGroupsByDesiredParallelism(jobInformation)) {
+            final int groupParallelism =
+                    Math.min(
+                            slotSharingGroup.getValue(),
+                            numUnassignedSlots / numUnassignedSlotSharingGroups);
+
+            slotSharingGroupParallelism.put(slotSharingGroup.getKey(), groupParallelism);
+
+            numUnassignedSlots -= groupParallelism;
+            numUnassignedSlotSharingGroups--;
+        }
+
+        return slotSharingGroupParallelism;
+    }
+
+    private static List<Map.Entry<SlotSharingGroupId, Integer>>
+            sortSlotSharingGroupsByDesiredParallelism(JobInformation jobInformation) {
+
+        return getMaxParallelismForSlotSharingGroups(jobInformation.getVertices()).entrySet()
+                .stream()
+                .sorted(Comparator.comparingInt(Map.Entry::getValue))
+                .collect(Collectors.toList());
+    }
+
+    private static Map<JobVertexID, Integer> determineVertexParallelism(
             Collection<JobInformation.VertexInformation> containedJobVertices, int availableSlots) {
         final Map<JobVertexID, Integer> vertexParallelism = new HashMap<>();
         for (JobInformation.VertexInformation jobVertex : containedJobVertices) {
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/scheduler/adaptive/allocator/SlotSharingSlotAllocatorTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/scheduler/adaptive/allocator/SlotSharingSlotAllocatorTest.java
index a8dc02fa49a..8be500919f1 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/scheduler/adaptive/allocator/SlotSharingSlotAllocatorTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/scheduler/adaptive/allocator/SlotSharingSlotAllocatorTest.java
@@ -27,6 +27,7 @@ import org.apache.flink.runtime.scheduler.strategy.ExecutionVertexID;
 import org.apache.flink.runtime.util.ResourceCounter;
 import org.apache.flink.util.TestLogger;
 
+import org.assertj.core.api.Assertions;
 import org.junit.Test;
 
 import java.util.ArrayList;
@@ -134,6 +135,40 @@ public class SlotSharingSlotAllocatorTest extends TestLogger {
                 is(vertex3.getParallelism()));
     }
 
+    @Test
+    public void testDetermineParallelismWithVariedParallelism() {
+        final SlotSharingSlotAllocator slotAllocator =
+                SlotSharingSlotAllocator.createSlotSharingSlotAllocator(
+                        TEST_RESERVE_SLOT_FUNCTION,
+                        TEST_FREE_SLOT_FUNCTION,
+                        TEST_IS_SLOT_FREE_FUNCTION);
+        final SlotSharingGroup slotSharingGroup1 = new SlotSharingGroup();
+        final JobInformation.VertexInformation vertex11 =
+                new TestVertexInformation(new JobVertexID(), 4, slotSharingGroup1);
+        final JobInformation.VertexInformation vertex12 =
+                new TestVertexInformation(new JobVertexID(), 1, slotSharingGroup1);
+        final JobInformation.VertexInformation vertex2 =
+                new TestVertexInformation(new JobVertexID(), 2, new SlotSharingGroup());
+
+        TestJobInformation testJobInformation =
+                new TestJobInformation(Arrays.asList(vertex11, vertex12, vertex2));
+
+        Map<JobVertexID, Integer> maxParallelismForVertices =
+                slotAllocator
+                        .determineParallelism(
+                                testJobInformation,
+                                getSlots(vertex11.getParallelism() + vertex2.getParallelism()))
+                        .get()
+                        .getMaxParallelismForVertices();
+
+        Assertions.assertThat(maxParallelismForVertices.get(vertex11.getJobVertexID()))
+                .isEqualTo(vertex11.getParallelism());
+        Assertions.assertThat(maxParallelismForVertices.get(vertex12.getJobVertexID()))
+                .isEqualTo(vertex12.getParallelism());
+        Assertions.assertThat(maxParallelismForVertices.get(vertex2.getJobVertexID()))
+                .isEqualTo(vertex2.getParallelism());
+    }
+
     @Test
     public void testDetermineParallelismUnsuccessfulWithLessSlotsThanSlotSharingGroups() {
         final SlotSharingSlotAllocator slotAllocator =
@@ -243,6 +278,11 @@ public class SlotSharingSlotAllocatorTest extends TestLogger {
         public VertexInformation getVertexInformation(JobVertexID jobVertexId) {
             return vertexIdToInformation.get(jobVertexId);
         }
+
+        @Override
+        public Iterable<VertexInformation> getVertices() {
+            return vertexIdToInformation.values();
+        }
     }
 
     private static class TestVertexInformation implements JobInformation.VertexInformation {