Forces Flink to register avro classes in kryo serializer.
Important: Make sure to include the flink-avro module. Otherwise, nothing will be registered. For backward compatibility, the default value is empty to conform to the behavior of the older version. That is, always register avro with kryo, and if flink-avro is not in the class path, register a dummy serializer. In Flink-2.0, we will set the default value to true.
+
+
pipeline.forward-edge.parallelism-mismatch-mode
+
REBALANCE
+
Enum
+
Determines how the runtime handles a FORWARD (pointwise) edge whose producer and consumer parallelism no longer match, which can happen when the parallelism of connected operators is changed independently (for example via the AdaptiveScheduler or pipeline.jobvertex-parallelism-overrides). A FORWARD edge is only valid at equal parallelism; on a mismatch one of the following strategies is applied:
REBALANCE: replace the forward partitioner with a rebalance (round-robin) partitioner. This preserves throughput but reorders records, which is safe for append-only streams but corrupts order-sensitive (changelog) streams.
FAIL: fail the job with an exception instead of silently changing the data distribution.
KEEP_FORWARD: keep the forward partitioner. Record order is preserved but the records are funneled to a single consumer subtask, so the remaining consumer subtasks stay idle.
Possible values:
"REBALANCE"
"FAIL"
"KEEP_FORWARD"
+
pipeline.generic-types
true
diff --git a/flink-core/src/main/java/org/apache/flink/configuration/PipelineOptions.java b/flink-core/src/main/java/org/apache/flink/configuration/PipelineOptions.java
index 4742b6dbca859..d366b63ce0ed8 100644
--- a/flink-core/src/main/java/org/apache/flink/configuration/PipelineOptions.java
+++ b/flink-core/src/main/java/org/apache/flink/configuration/PipelineOptions.java
@@ -305,6 +305,68 @@ public enum VertexDescriptionMode {
CASCADING
}
+ public static final ConfigOption
+ FORWARD_EDGE_PARALLELISM_MISMATCH_MODE =
+ key("pipeline.forward-edge.parallelism-mismatch-mode")
+ .enumType(ForwardEdgeParallelismMismatchMode.class)
+ .defaultValue(ForwardEdgeParallelismMismatchMode.REBALANCE)
+ .withDescription(
+ Description.builder()
+ .text(
+ "Determines how the runtime handles a FORWARD (pointwise) edge whose"
+ + " producer and consumer parallelism no longer match, which can happen"
+ + " when the parallelism of connected operators is changed"
+ + " independently (for example via the AdaptiveScheduler or %s). A"
+ + " FORWARD edge is only valid at equal parallelism; on a mismatch one"
+ + " of the following strategies is applied:",
+ code(PARALLELISM_OVERRIDES.key()))
+ .list(
+ text(
+ "%s: replace the forward partitioner with a rebalance (round-robin)"
+ + " partitioner. This preserves throughput but reorders records,"
+ + " which is safe for append-only streams but corrupts"
+ + " order-sensitive (changelog) streams.",
+ code(
+ ForwardEdgeParallelismMismatchMode
+ .REBALANCE
+ .name())),
+ text(
+ "%s: fail the job with an exception instead of silently changing"
+ + " the data distribution.",
+ code(
+ ForwardEdgeParallelismMismatchMode
+ .FAIL
+ .name())),
+ text(
+ "%s: keep the forward partitioner. Record order is preserved but"
+ + " the records are funneled to a single consumer subtask, so the"
+ + " remaining consumer subtasks stay idle.",
+ code(
+ ForwardEdgeParallelismMismatchMode
+ .KEEP_FORWARD
+ .name())))
+ .build());
+
+ /**
+ * The strategy applied when a FORWARD (pointwise) edge connects a producer and consumer with
+ * mismatched parallelism.
+ */
+ @PublicEvolving
+ public enum ForwardEdgeParallelismMismatchMode {
+ /**
+ * Replace the forward partitioner with a rebalance partitioner. Reorders records; safe only
+ * for append-only streams.
+ */
+ REBALANCE,
+ /** Fail the job with an exception. */
+ FAIL,
+ /**
+ * Keep the forward partitioner. Preserves record order but funnels records to a single
+ * consumer subtask.
+ */
+ KEEP_FORWARD
+ }
+
public static final ConfigOption VERTEX_NAME_INCLUDE_INDEX_PREFIX =
key("pipeline.vertex-name-include-index-prefix")
.booleanType()
diff --git a/flink-libraries/flink-state-processing-api/src/main/java/org/apache/flink/state/api/runtime/SavepointEnvironment.java b/flink-libraries/flink-state-processing-api/src/main/java/org/apache/flink/state/api/runtime/SavepointEnvironment.java
index dbd918da7bfdc..fe9009cb081f8 100644
--- a/flink-libraries/flink-state-processing-api/src/main/java/org/apache/flink/state/api/runtime/SavepointEnvironment.java
+++ b/flink-libraries/flink-state-processing-api/src/main/java/org/apache/flink/state/api/runtime/SavepointEnvironment.java
@@ -319,6 +319,11 @@ public ResultPartitionWriter getWriter(int index) {
throw new UnsupportedOperationException(ERROR_MSG);
}
+ @Override
+ public int getWriterConsumerParallelism(int index) {
+ throw new UnsupportedOperationException(ERROR_MSG);
+ }
+
@Override
public ResultPartitionWriter[] getAllWriters() {
return new ResultPartitionWriter[0];
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/deployment/ResultPartitionDeploymentDescriptor.java b/flink-runtime/src/main/java/org/apache/flink/runtime/deployment/ResultPartitionDeploymentDescriptor.java
index 6cca01c788f69..79b3910f007a9 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/deployment/ResultPartitionDeploymentDescriptor.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/deployment/ResultPartitionDeploymentDescriptor.java
@@ -28,6 +28,7 @@
import java.io.Serializable;
+import static org.apache.flink.util.Preconditions.checkArgument;
import static org.apache.flink.util.Preconditions.checkNotNull;
/**
@@ -45,14 +46,31 @@ public class ResultPartitionDeploymentDescriptor implements Serializable {
private final int maxParallelism;
+ /** Parallelism of the consuming job vertices, or {@link #UNKNOWN_CONSUMER_PARALLELISM}. */
+ private final int consumerParallelism;
+
+ /**
+ * The consumer parallelism is not decided yet when the producer is deployed. This only happens
+ * in dynamic graphs (AdaptiveBatchScheduler), where the parallelism of a consumer vertex may be
+ * decided after its producers are deployed. Every other partition carries the actual consumer
+ * parallelism.
+ */
+ public static final int UNKNOWN_CONSUMER_PARALLELISM = -1;
+
public ResultPartitionDeploymentDescriptor(
PartitionDescriptor partitionDescriptor,
ShuffleDescriptor shuffleDescriptor,
- int maxParallelism) {
+ int maxParallelism,
+ int consumerParallelism) {
this.partitionDescriptor = checkNotNull(partitionDescriptor);
this.shuffleDescriptor = checkNotNull(shuffleDescriptor);
KeyGroupRangeAssignment.checkParallelismPreconditions(maxParallelism);
this.maxParallelism = maxParallelism;
+ checkArgument(
+ consumerParallelism > 0 || consumerParallelism == UNKNOWN_CONSUMER_PARALLELISM,
+ "Invalid consumer parallelism %s.",
+ consumerParallelism);
+ this.consumerParallelism = consumerParallelism;
}
public IntermediateDataSetID getResultId() {
@@ -88,6 +106,16 @@ public int getMaxParallelism() {
return maxParallelism;
}
+ /**
+ * Returns the parallelism of the job vertices consuming this partition, or {@link
+ * #UNKNOWN_CONSUMER_PARALLELISM} if it is not decided yet. Unlike {@link
+ * #getNumberOfSubpartitions()}, this is the actual consumer parallelism for every distribution
+ * pattern.
+ */
+ public int getConsumerParallelism() {
+ return consumerParallelism;
+ }
+
public ShuffleDescriptor getShuffleDescriptor() {
return shuffleDescriptor;
}
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/execution/Environment.java b/flink-runtime/src/main/java/org/apache/flink/runtime/execution/Environment.java
index 4b449a4d12950..d989f67477433 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/execution/Environment.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/execution/Environment.java
@@ -32,6 +32,7 @@
import org.apache.flink.runtime.checkpoint.TaskStateSnapshot;
import org.apache.flink.runtime.checkpoint.channel.ChannelStateWriteRequestExecutorFactory;
import org.apache.flink.runtime.checkpoint.channel.ChannelStateWriter;
+import org.apache.flink.runtime.deployment.ResultPartitionDeploymentDescriptor;
import org.apache.flink.runtime.executiongraph.ExecutionAttemptID;
import org.apache.flink.runtime.externalresource.ExternalResourceInfoProvider;
import org.apache.flink.runtime.io.disk.iomanager.IOManager;
@@ -248,6 +249,13 @@ void acknowledgeCheckpoint(
ResultPartitionWriter getWriter(int index);
+ /**
+ * Returns the parallelism of the job vertices consuming the partition written by {@link
+ * #getWriter(int)}, or {@link ResultPartitionDeploymentDescriptor#UNKNOWN_CONSUMER_PARALLELISM}
+ * if it is not decided yet.
+ */
+ int getWriterConsumerParallelism(int index);
+
ResultPartitionWriter[] getAllWriters();
IndexedInputGate getInputGate(int index);
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/Execution.java b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/Execution.java
index 430cd9ee76da4..7e27d0f0cc7cb 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/Execution.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/Execution.java
@@ -565,7 +565,10 @@ private static ResultPartitionDeploymentDescriptor createResultPartitionDeployme
IntermediateResultPartition partition,
ShuffleDescriptor shuffleDescriptor) {
return new ResultPartitionDeploymentDescriptor(
- partitionDescriptor, shuffleDescriptor, getPartitionMaxParallelism(partition));
+ partitionDescriptor,
+ shuffleDescriptor,
+ getPartitionMaxParallelism(partition),
+ partition.getIntermediateResult().getConsumersParallelism());
}
/**
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/taskmanager/RuntimeEnvironment.java b/flink-runtime/src/main/java/org/apache/flink/runtime/taskmanager/RuntimeEnvironment.java
index 69df0116802cd..131af5bc3351c 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/taskmanager/RuntimeEnvironment.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/taskmanager/RuntimeEnvironment.java
@@ -58,6 +58,7 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
+import static org.apache.flink.util.Preconditions.checkArgument;
import static org.apache.flink.util.Preconditions.checkNotNull;
import static org.apache.flink.util.Preconditions.checkState;
@@ -93,6 +94,7 @@ public class RuntimeEnvironment implements Environment {
private final Map> distCacheEntries;
private final ResultPartitionWriter[] writers;
+ private final int[] writerConsumerParallelisms;
private final IndexedInputGate[] inputGates;
private final TaskEventDispatcher taskEventDispatcher;
@@ -145,6 +147,7 @@ public RuntimeEnvironment(
InputSplitProvider splitProvider,
Map> distCacheEntries,
ResultPartitionWriter[] writers,
+ int[] writerConsumerParallelisms,
IndexedInputGate[] inputGates,
TaskEventDispatcher taskEventDispatcher,
CheckpointResponder checkpointResponder,
@@ -177,6 +180,8 @@ public RuntimeEnvironment(
this.splitProvider = checkNotNull(splitProvider);
this.distCacheEntries = checkNotNull(distCacheEntries);
this.writers = checkNotNull(writers);
+ this.writerConsumerParallelisms = checkNotNull(writerConsumerParallelisms);
+ checkArgument(writers.length == writerConsumerParallelisms.length);
this.inputGates = checkNotNull(inputGates);
this.taskEventDispatcher = checkNotNull(taskEventDispatcher);
this.checkpointResponder = checkNotNull(checkpointResponder);
@@ -306,6 +311,11 @@ public ResultPartitionWriter getWriter(int index) {
return writers[index];
}
+ @Override
+ public int getWriterConsumerParallelism(int index) {
+ return writerConsumerParallelisms[index];
+ }
+
@Override
public ResultPartitionWriter[] getAllWriters() {
return writers;
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/taskmanager/Task.java b/flink-runtime/src/main/java/org/apache/flink/runtime/taskmanager/Task.java
index 249f29af1be66..82e7780275be8 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/taskmanager/Task.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/taskmanager/Task.java
@@ -230,6 +230,9 @@ public class Task
private final ResultPartitionWriter[] partitionWriters;
+ /** Consumer parallelism of each partition in {@link #partitionWriters}, in the same order. */
+ private final int[] partitionConsumerParallelisms;
+
private final IndexedInputGate[] inputGates;
/** Connection to the task manager. */
@@ -423,6 +426,10 @@ public Task(
.toArray(new ResultPartitionWriter[] {});
this.partitionWriters = resultPartitionWriters;
+ this.partitionConsumerParallelisms =
+ resultPartitionDeploymentDescriptors.stream()
+ .mapToInt(ResultPartitionDeploymentDescriptor::getConsumerParallelism)
+ .toArray();
// consumed intermediate result partitions
final IndexedInputGate[] gates =
@@ -731,6 +738,7 @@ private void doRun() {
inputSplitProvider,
distributedCacheEntries,
partitionWriters,
+ partitionConsumerParallelisms,
inputGates,
taskEventDispatcher,
checkpointResponder,
diff --git a/flink-runtime/src/main/java/org/apache/flink/streaming/runtime/tasks/StreamTask.java b/flink-runtime/src/main/java/org/apache/flink/streaming/runtime/tasks/StreamTask.java
index 7938a1ef278b7..5f5dc8864b923 100644
--- a/flink-runtime/src/main/java/org/apache/flink/streaming/runtime/tasks/StreamTask.java
+++ b/flink-runtime/src/main/java/org/apache/flink/streaming/runtime/tasks/StreamTask.java
@@ -25,6 +25,8 @@
import org.apache.flink.configuration.CheckpointingOptions;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.configuration.NettyShuffleEnvironmentOptions;
+import org.apache.flink.configuration.PipelineOptions;
+import org.apache.flink.configuration.PipelineOptions.ForwardEdgeParallelismMismatchMode;
import org.apache.flink.configuration.TaskManagerOptions;
import org.apache.flink.core.execution.RecoveryClaimMode;
import org.apache.flink.core.fs.AutoCloseableRegistry;
@@ -46,6 +48,7 @@
import org.apache.flink.runtime.checkpoint.channel.InputChannelInfo;
import org.apache.flink.runtime.checkpoint.channel.SequentialChannelStateReader;
import org.apache.flink.runtime.checkpoint.filemerging.FileMergingSnapshotManager;
+import org.apache.flink.runtime.deployment.ResultPartitionDeploymentDescriptor;
import org.apache.flink.runtime.execution.CancelTaskException;
import org.apache.flink.runtime.execution.Environment;
import org.apache.flink.runtime.io.AvailabilityProvider;
@@ -1816,13 +1819,64 @@ List>>> createRecordWriters
private static void replaceForwardPartitionerIfConsumerParallelismDoesNotMatch(
Environment environment, NonChainedOutput streamOutput, int outputIndex) {
- if (streamOutput.getPartitioner() instanceof ForwardPartitioner
- && environment.getWriter(outputIndex).getNumberOfSubpartitions()
- != environment.getTaskInfo().getNumberOfParallelSubtasks()) {
- LOG.debug(
- "Replacing forward partitioner with rebalance for {}",
- environment.getTaskInfo().getTaskNameWithSubtasks());
- streamOutput.setPartitioner(new RebalancePartitioner<>());
+ final int producerParallelism = environment.getTaskInfo().getNumberOfParallelSubtasks();
+ // The number of subpartitions isn't the consumer parallelism on a POINTWISE edge, so the
+ // actual consumer parallelism is passed through the deployment descriptor.
+ final int consumerParallelism = environment.getWriterConsumerParallelism(outputIndex);
+ if (!(streamOutput.getPartitioner() instanceof ForwardPartitioner)
+ // An undecided consumer parallelism is not a mismatch.
+ || consumerParallelism
+ == ResultPartitionDeploymentDescriptor.UNKNOWN_CONSUMER_PARALLELISM
+ || consumerParallelism == producerParallelism) {
+ return;
+ }
+
+ final ForwardEdgeParallelismMismatchMode mode =
+ environment
+ .getJobConfiguration()
+ .get(PipelineOptions.FORWARD_EDGE_PARALLELISM_MISMATCH_MODE);
+ final String taskNameWithSubtasks = environment.getTaskInfo().getTaskNameWithSubtasks();
+ switch (mode) {
+ case REBALANCE:
+ LOG.warn(
+ "Replacing forward partitioner with rebalance for {} "
+ + "(producer parallelism {} != consumer parallelism {}). This may "
+ + "reorder records and produce incorrect results for order-sensitive "
+ + "(changelog) streams. Set '{}' to {} or {} to avoid it.",
+ taskNameWithSubtasks,
+ producerParallelism,
+ consumerParallelism,
+ PipelineOptions.FORWARD_EDGE_PARALLELISM_MISMATCH_MODE.key(),
+ ForwardEdgeParallelismMismatchMode.FAIL,
+ ForwardEdgeParallelismMismatchMode.KEEP_FORWARD);
+ streamOutput.setPartitioner(new RebalancePartitioner<>());
+ break;
+ case KEEP_FORWARD:
+ LOG.warn(
+ "Keeping forward partitioner for {} despite a parallelism mismatch "
+ + "(producer parallelism {} != consumer parallelism {}). Record order "
+ + "is preserved but records are funneled to a single consumer subtask, "
+ + "leaving the remaining consumer subtasks idle.",
+ taskNameWithSubtasks,
+ producerParallelism,
+ consumerParallelism);
+ break;
+ case FAIL:
+ throw new FlinkRuntimeException(
+ String.format(
+ "Forward partitioning cannot be preserved across a parallelism change "
+ + "for %s (producer parallelism %d != consumer parallelism %d). "
+ + "Silently downgrading a FORWARD edge to a redistributing "
+ + "exchange can reorder records and corrupt order-sensitive "
+ + "(changelog) results. Set %s to %s or %s to allow it.",
+ taskNameWithSubtasks,
+ producerParallelism,
+ consumerParallelism,
+ PipelineOptions.FORWARD_EDGE_PARALLELISM_MISMATCH_MODE.key(),
+ ForwardEdgeParallelismMismatchMode.REBALANCE,
+ ForwardEdgeParallelismMismatchMode.KEEP_FORWARD));
+ default:
+ throw new IllegalStateException("Unhandled mode: " + mode);
}
}
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/deployment/ResultPartitionDeploymentDescriptorTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/deployment/ResultPartitionDeploymentDescriptorTest.java
index eddb41d6c7848..181f807fff80b 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/deployment/ResultPartitionDeploymentDescriptorTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/deployment/ResultPartitionDeploymentDescriptorTest.java
@@ -51,6 +51,8 @@ class ResultPartitionDeploymentDescriptorTest {
private static final ResultPartitionType partitionType = ResultPartitionType.PIPELINED;
private static final int numberOfSubpartitions = 24;
+
+ private static final int consumerParallelism = 12;
private static final int connectionIndex = 10;
private static final boolean isBroadcast = false;
private static final boolean isAllToAllDistribution = true;
@@ -112,7 +114,10 @@ void testSerializationWithNettyShuffleDescriptor() throws IOException {
ShuffleDescriptor shuffleDescriptor) throws IOException {
ResultPartitionDeploymentDescriptor orig =
new ResultPartitionDeploymentDescriptor(
- partitionDescriptor, shuffleDescriptor, numberOfSubpartitions);
+ partitionDescriptor,
+ shuffleDescriptor,
+ numberOfSubpartitions,
+ consumerParallelism);
ResultPartitionDeploymentDescriptor copy = CommonTestUtils.createCopySerializable(orig);
verifyResultPartitionDeploymentDescriptorCopy(copy);
return copy;
@@ -125,5 +130,6 @@ private static void verifyResultPartitionDeploymentDescriptorCopy(
assertThat(partitionId).isEqualTo(copy.getPartitionId());
assertThat(partitionType).isEqualTo(copy.getPartitionType());
assertThat(numberOfSubpartitions).isEqualTo(copy.getNumberOfSubpartitions());
+ assertThat(consumerParallelism).isEqualTo(copy.getConsumerParallelism());
}
}
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/deployment/ShuffleDescriptorTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/deployment/ShuffleDescriptorTest.java
index df4291170ca2c..79cae3fb573b9 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/deployment/ShuffleDescriptorTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/deployment/ShuffleDescriptorTest.java
@@ -274,6 +274,7 @@ private static ResultPartitionDeploymentDescriptor createResultPartitionDeployme
.registerPartitionWithProducer(
jobID, partitionDescriptor, producerDescriptor)
.get();
- return new ResultPartitionDeploymentDescriptor(partitionDescriptor, shuffleDescriptor, 1);
+ return new ResultPartitionDeploymentDescriptor(
+ partitionDescriptor, shuffleDescriptor, 1, 1);
}
}
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/io/network/partition/AbstractPartitionTrackerTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/io/network/partition/AbstractPartitionTrackerTest.java
index 6f90313cea846..a49075b76dbdc 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/io/network/partition/AbstractPartitionTrackerTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/io/network/partition/AbstractPartitionTrackerTest.java
@@ -87,6 +87,7 @@ public Optional storesLocalResourcesOn() {
: Optional.empty();
}
},
+ 1,
1);
}
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/io/network/partition/PartitionTestUtils.java b/flink-runtime/src/test/java/org/apache/flink/runtime/io/network/partition/PartitionTestUtils.java
index 20547adc2c77c..1e921f5b3bf91 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/io/network/partition/PartitionTestUtils.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/io/network/partition/PartitionTestUtils.java
@@ -137,7 +137,8 @@ public static ResultPartitionDeploymentDescriptor createPartitionDeploymentDescr
.setPartitionId(shuffleDescriptor.getResultPartitionID().getPartitionId())
.setPartitionType(partitionType)
.build();
- return new ResultPartitionDeploymentDescriptor(partitionDescriptor, shuffleDescriptor, 1);
+ return new ResultPartitionDeploymentDescriptor(
+ partitionDescriptor, shuffleDescriptor, 1, 1);
}
public static PartitionedFile createPartitionedFile(
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/io/network/partition/ResultPartitionFactoryTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/io/network/partition/ResultPartitionFactoryTest.java
index bd8156634df1c..95907f5ab54db 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/io/network/partition/ResultPartitionFactoryTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/io/network/partition/ResultPartitionFactoryTest.java
@@ -193,6 +193,7 @@ private static ResultPartition createResultPartition(
.setIsBroadcast(isBroadcast)
.build(),
NettyShuffleDescriptorBuilder.newBuilder().buildLocal(),
+ 1,
1);
// guard our test assumptions
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/operators/testutils/DummyEnvironment.java b/flink-runtime/src/test/java/org/apache/flink/runtime/operators/testutils/DummyEnvironment.java
index fe291c1b4e5f3..367b28d3d30cf 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/operators/testutils/DummyEnvironment.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/operators/testutils/DummyEnvironment.java
@@ -264,6 +264,11 @@ public ResultPartitionWriter getWriter(int index) {
return null;
}
+ @Override
+ public int getWriterConsumerParallelism(int index) {
+ return taskInfo.getNumberOfParallelSubtasks();
+ }
+
@Override
public ResultPartitionWriter[] getAllWriters() {
return new ResultPartitionWriter[0];
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/operators/testutils/MockEnvironment.java b/flink-runtime/src/test/java/org/apache/flink/runtime/operators/testutils/MockEnvironment.java
index c50eface312b0..4ccb3a0e9d753 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/operators/testutils/MockEnvironment.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/operators/testutils/MockEnvironment.java
@@ -331,6 +331,12 @@ public ResultPartitionWriter getWriter(int index) {
return outputs.get(index);
}
+ /** Outputs are consumed with the same parallelism as this task. */
+ @Override
+ public int getWriterConsumerParallelism(int index) {
+ return taskInfo.getNumberOfParallelSubtasks();
+ }
+
@Override
public ResultPartitionWriter[] getAllWriters() {
return outputs.toArray(new ResultPartitionWriter[outputs.size()]);
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/shuffle/NettyShuffleUtilsTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/shuffle/NettyShuffleUtilsTest.java
index 76f3bf38341c9..575d7addf238c 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/shuffle/NettyShuffleUtilsTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/shuffle/NettyShuffleUtilsTest.java
@@ -194,7 +194,8 @@ private ResultPartition createResultPartition(
true,
false);
ResultPartitionDeploymentDescriptor resultPartitionDeploymentDescriptor =
- new ResultPartitionDeploymentDescriptor(partitionDescriptor, shuffleDescriptor, 1);
+ new ResultPartitionDeploymentDescriptor(
+ partitionDescriptor, shuffleDescriptor, 1, 1);
ExecutionAttemptID consumerID = createExecutionAttemptId();
Collection resultPartitions =
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/taskexecutor/TaskExecutorSubmissionTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/taskexecutor/TaskExecutorSubmissionTest.java
index 225629876b09a..481e9c4fe4ea4 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/taskexecutor/TaskExecutorSubmissionTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/taskexecutor/TaskExecutorSubmissionTest.java
@@ -342,7 +342,7 @@ void testGetPartitionWithMetrics() throws Exception {
.build();
ResultPartitionDeploymentDescriptor resultPartitionDeploymentDescriptor =
- new ResultPartitionDeploymentDescriptor(partitionDescriptor, sdd, 1);
+ new ResultPartitionDeploymentDescriptor(partitionDescriptor, sdd, 1, 1);
TaskDeploymentDescriptor tdd =
createTestTaskDeploymentDescriptor(
"task",
@@ -637,7 +637,8 @@ private TaskDeploymentDescriptor createSender(
.setPartitionId(shuffleDescriptor.getResultPartitionID().getPartitionId())
.build();
ResultPartitionDeploymentDescriptor resultPartitionDeploymentDescriptor =
- new ResultPartitionDeploymentDescriptor(partitionDescriptor, shuffleDescriptor, 1);
+ new ResultPartitionDeploymentDescriptor(
+ partitionDescriptor, shuffleDescriptor, 1, 1);
return createTestTaskDeploymentDescriptor(
"Sender",
shuffleDescriptor.getResultPartitionID().getProducerId(),
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/taskmanager/TaskTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/taskmanager/TaskTest.java
index 36a248934e14b..eaa99ef366424 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/taskmanager/TaskTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/taskmanager/TaskTest.java
@@ -316,7 +316,8 @@ public void testExecutionFailsInNetworkRegistrationForPartitions() throws Except
final ShuffleDescriptor shuffleDescriptor =
NettyShuffleDescriptorBuilder.newBuilder().buildLocal();
final ResultPartitionDeploymentDescriptor dummyPartition =
- new ResultPartitionDeploymentDescriptor(partitionDescriptor, shuffleDescriptor, 1);
+ new ResultPartitionDeploymentDescriptor(
+ partitionDescriptor, shuffleDescriptor, 1, 1);
testExecutionFailsInNetworkRegistration(
Collections.singletonList(dummyPartition), Collections.emptyList());
}
diff --git a/flink-runtime/src/test/java/org/apache/flink/streaming/runtime/tasks/StreamMockEnvironment.java b/flink-runtime/src/test/java/org/apache/flink/streaming/runtime/tasks/StreamMockEnvironment.java
index dd4ef8994c2e5..06bce51026ad1 100644
--- a/flink-runtime/src/test/java/org/apache/flink/streaming/runtime/tasks/StreamMockEnvironment.java
+++ b/flink-runtime/src/test/java/org/apache/flink/streaming/runtime/tasks/StreamMockEnvironment.java
@@ -90,7 +90,7 @@ public class StreamMockEnvironment implements Environment {
private final JobInfo jobInfo;
- private final TaskInfo taskInfo;
+ private TaskInfo taskInfo;
private final MemoryManager memManager;
@@ -108,6 +108,9 @@ public class StreamMockEnvironment implements Environment {
private List outputs;
+ /** Consumer parallelism of all outputs, equal to the parallelism of this task by default. */
+ private int writerConsumerParallelism;
+
private final ExecutionAttemptID executionAttemptID;
private final BroadcastVariableManager bcVarManager = new BroadcastVariableManager();
@@ -193,6 +196,7 @@ public StreamMockEnvironment(
this.taskConfiguration = taskConfig;
this.inputs = new LinkedList<>();
this.outputs = new LinkedList();
+ this.writerConsumerParallelism = taskInfo.getNumberOfParallelSubtasks();
this.memManager =
MemoryManagerBuilder.newBuilder().setMemorySize(offHeapMemorySize).build();
this.sharedResources = new SharedResources();
@@ -324,6 +328,27 @@ public ResultPartitionWriter getWriter(int index) {
return outputs.get(index);
}
+ @Override
+ public int getWriterConsumerParallelism(int index) {
+ return writerConsumerParallelism;
+ }
+
+ public void setWriterConsumerParallelism(int writerConsumerParallelism) {
+ this.writerConsumerParallelism = writerConsumerParallelism;
+ }
+
+ public void setNumberOfParallelSubtasks(int numberOfParallelSubtasks) {
+ this.taskInfo =
+ new TaskInfoImpl(
+ taskInfo.getTaskName(),
+ Math.max(
+ taskInfo.getMaxNumberOfParallelSubtasks(),
+ numberOfParallelSubtasks),
+ taskInfo.getIndexOfThisSubtask(),
+ numberOfParallelSubtasks,
+ taskInfo.getAttemptNumber());
+ }
+
@Override
public ResultPartitionWriter[] getAllWriters() {
return outputs.toArray(new ResultPartitionWriter[outputs.size()]);
diff --git a/flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/tasks/StreamTaskITCase.java b/flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/tasks/StreamTaskITCase.java
index 1dac2e2d8018b..74d1e024a7886 100644
--- a/flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/tasks/StreamTaskITCase.java
+++ b/flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/tasks/StreamTaskITCase.java
@@ -132,6 +132,7 @@ private TestTaskBuilder taskBuilderWithConfiguredRecordWriter(
new ResultPartitionDeploymentDescriptor(
PartitionDescriptorBuilder.newBuilder().build(),
NettyShuffleDescriptorBuilder.newBuilder().buildLocal(),
+ 1,
1);
return new TestTaskBuilder(shuffleEnvironment)
.setInvokable(NoOpStreamTask.class)
diff --git a/flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/tasks/StreamTaskTest.java b/flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/tasks/StreamTaskTest.java
index 1b42449f539ee..8ca889c033ab5 100644
--- a/flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/tasks/StreamTaskTest.java
+++ b/flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/tasks/StreamTaskTest.java
@@ -30,6 +30,8 @@
import org.apache.flink.configuration.Configuration;
import org.apache.flink.configuration.IllegalConfigurationException;
import org.apache.flink.configuration.NettyShuffleEnvironmentOptions;
+import org.apache.flink.configuration.PipelineOptions;
+import org.apache.flink.configuration.PipelineOptions.ForwardEdgeParallelismMismatchMode;
import org.apache.flink.configuration.ReadableConfig;
import org.apache.flink.core.execution.SavepointFormatType;
import org.apache.flink.core.fs.FSDataInputStream;
@@ -154,6 +156,8 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -1838,8 +1842,10 @@ void testMailboxMetricsMeasurement() throws Exception {
}
}
- @Test
- void testForwardPartitionerIsConvertedToRebalanceOnParallelismChanges() throws Exception {
+ @ParameterizedTest(name = "producer parallelism {0}, consumer parallelism {1}")
+ @CsvSource({"1, 2", "2, 1"})
+ void testForwardPartitionerIsConvertedToRebalanceOnParallelismChanges(
+ int producerParallelism, int consumerParallelism) throws Exception {
StreamTaskMailboxTestHarnessBuilder builder =
new StreamTaskMailboxTestHarnessBuilder<>(
OneInputStreamTask::new, BasicTypeInfo.INT_TYPE_INFO)
@@ -1861,16 +1867,18 @@ void testForwardPartitionerIsConvertedToRebalanceOnParallelismChanges() throws E
.getChannelSelector())
.isInstanceOf(ForwardPartitioner.class);
- // Simulate changed downstream task parallelism (1->2)
+ // Simulate changed producer and consumer parallelism
List newOutputs = new ArrayList<>();
newOutputs.add(
new MockResultPartitionWriter() {
@Override
public int getNumberOfSubpartitions() {
- return 2;
+ return consumerParallelism;
}
});
harness.streamMockEnvironment.setOutputs(newOutputs);
+ harness.streamMockEnvironment.setNumberOfParallelSubtasks(producerParallelism);
+ harness.streamMockEnvironment.setWriterConsumerParallelism(consumerParallelism);
// Re-create outputs
recordWriterDelegate =
@@ -1887,6 +1895,82 @@ public int getNumberOfSubpartitions() {
}
}
+ @Test
+ void testForwardPartitionerFailsOnParallelismChangeWhenModeIsFail() throws Exception {
+ StreamTaskMailboxTestHarnessBuilder builder =
+ new StreamTaskMailboxTestHarnessBuilder<>(
+ OneInputStreamTask::new, BasicTypeInfo.INT_TYPE_INFO)
+ .addInput(BasicTypeInfo.INT_TYPE_INFO)
+ .addJobConfig(
+ PipelineOptions.FORWARD_EDGE_PARALLELISM_MISMATCH_MODE,
+ ForwardEdgeParallelismMismatchMode.FAIL)
+ .setOutputPartitioner(new ForwardPartitioner<>())
+ .setupOutputForSingletonOperatorChain(
+ new TestBoundedOneInputStreamOperator());
+
+ try (StreamTaskMailboxTestHarness harness = builder.build()) {
+ // Simulate changed downstream task parallelism (1->2)
+ List newOutputs = new ArrayList<>();
+ newOutputs.add(
+ new MockResultPartitionWriter() {
+ @Override
+ public int getNumberOfSubpartitions() {
+ return 2;
+ }
+ });
+ harness.streamMockEnvironment.setOutputs(newOutputs);
+ harness.streamMockEnvironment.setWriterConsumerParallelism(2);
+
+ // With FAIL mode the job is rejected instead of silently downgrading the edge.
+ assertThatThrownBy(
+ () ->
+ harness.streamTask.createRecordWriterDelegate(
+ harness.streamTask.configuration,
+ harness.streamMockEnvironment))
+ .isInstanceOf(FlinkRuntimeException.class)
+ .hasMessageContaining("Forward partitioning cannot be preserved");
+ }
+ }
+
+ @Test
+ void testForwardPartitionerIsKeptOnParallelismChangeWhenModeIsKeepForward() throws Exception {
+ StreamTaskMailboxTestHarnessBuilder builder =
+ new StreamTaskMailboxTestHarnessBuilder<>(
+ OneInputStreamTask::new, BasicTypeInfo.INT_TYPE_INFO)
+ .addInput(BasicTypeInfo.INT_TYPE_INFO)
+ .addJobConfig(
+ PipelineOptions.FORWARD_EDGE_PARALLELISM_MISMATCH_MODE,
+ ForwardEdgeParallelismMismatchMode.KEEP_FORWARD)
+ .setOutputPartitioner(new ForwardPartitioner<>())
+ .setupOutputForSingletonOperatorChain(
+ new TestBoundedOneInputStreamOperator());
+
+ try (StreamTaskMailboxTestHarness harness = builder.build()) {
+ // Simulate changed downstream task parallelism (1->2)
+ List newOutputs = new ArrayList<>();
+ newOutputs.add(
+ new MockResultPartitionWriter() {
+ @Override
+ public int getNumberOfSubpartitions() {
+ return 2;
+ }
+ });
+ harness.streamMockEnvironment.setOutputs(newOutputs);
+ harness.streamMockEnvironment.setWriterConsumerParallelism(2);
+
+ RecordWriterDelegate>> recordWriterDelegate =
+ harness.streamTask.createRecordWriterDelegate(
+ harness.streamTask.configuration, harness.streamMockEnvironment);
+ // With KEEP_FORWARD mode the forward partitioner is preserved despite the mismatch.
+ assertThat(
+ ((ChannelSelectorRecordWriter)
+ ((SingleRecordWriter) recordWriterDelegate)
+ .getRecordWriter(0))
+ .getChannelSelector())
+ .isInstanceOf(ForwardPartitioner.class);
+ }
+ }
+
@Test
void testGetAndCheckMaxTraverseSize() {
Configuration config = new Configuration();
diff --git a/flink-tests/src/test/java/org/apache/flink/test/scheduling/ForwardEdgeParallelismMismatchITCaseBase.java b/flink-tests/src/test/java/org/apache/flink/test/scheduling/ForwardEdgeParallelismMismatchITCaseBase.java
new file mode 100644
index 0000000000000..fd62d6954cbc0
--- /dev/null
+++ b/flink-tests/src/test/java/org/apache/flink/test/scheduling/ForwardEdgeParallelismMismatchITCaseBase.java
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.test.scheduling;
+
+import org.apache.flink.api.common.RuntimeExecutionMode;
+import org.apache.flink.api.common.eventtime.WatermarkStrategy;
+import org.apache.flink.api.connector.source.Source;
+import org.apache.flink.configuration.CheckpointingOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.JobManagerOptions;
+import org.apache.flink.configuration.JobManagerOptions.SchedulerType;
+import org.apache.flink.configuration.PipelineOptions;
+import org.apache.flink.configuration.PipelineOptions.ForwardEdgeParallelismMismatchMode;
+import org.apache.flink.configuration.RestOptions;
+import org.apache.flink.configuration.RestartStrategyOptions;
+import org.apache.flink.runtime.jobgraph.JobGraph;
+import org.apache.flink.runtime.jobgraph.JobVertexID;
+import org.apache.flink.runtime.minicluster.MiniCluster;
+import org.apache.flink.runtime.minicluster.MiniClusterConfiguration;
+import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
+import org.apache.flink.streaming.api.functions.sink.v2.DiscardingSink;
+import org.apache.flink.streaming.runtime.tasks.StreamTask;
+import org.apache.flink.testutils.junit.extensions.parameterized.Parameter;
+import org.apache.flink.testutils.junit.extensions.parameterized.ParameterizedTestExtension;
+import org.apache.flink.testutils.logging.LoggerAuditingExtension;
+import org.apache.flink.util.ExceptionUtils;
+
+import org.junit.jupiter.api.TestTemplate;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.slf4j.event.Level;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static org.apache.flink.configuration.RestartStrategyOptions.RestartStrategyType.NO_RESTART_STRATEGY;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Verifies which branch of {@link PipelineOptions#FORWARD_EDGE_PARALLELISM_MISMATCH_MODE} a {@code
+ * source -> forward -> sink} job takes after its parallelism is changed.
+ *
+ *
The branch is decided per producer job vertex from the real producer and consumer parallelism:
+ * if they are equal no branch is taken; otherwise every producer subtask either logs the configured
+ * branch (REBALANCE, KEEP_FORWARD) or fails the job (FAIL). Subclasses run the jobs and use the
+ * helpers of this class to build them and to assert the branch.
+ */
+@ExtendWith(ParameterizedTestExtension.class)
+abstract class ForwardEdgeParallelismMismatchITCaseBase {
+
+ private static final int NUM_SLOTS = 8;
+
+ private static final String REBALANCE_LOG = "Replacing forward partitioner";
+ private static final String KEEP_FORWARD_LOG = "Keeping forward partitioner";
+ private static final String FAIL_MESSAGE = "Forward partitioning cannot be preserved";
+
+ static final List PARALLELISM_CHANGES =
+ Arrays.asList(
+ new ParallelismChange(4, 4, 4),
+ new ParallelismChange(4, 5, 5),
+ new ParallelismChange(4, 3, 3),
+ new ParallelismChange(1, 1, 2),
+ new ParallelismChange(2, 2, 4),
+ new ParallelismChange(2, 2, 3),
+ new ParallelismChange(4, 4, 2),
+ new ParallelismChange(3, 3, 2),
+ new ParallelismChange(2, 2, 1),
+ new ParallelismChange(4, 2, 3),
+ new ParallelismChange(2, 4, 2));
+
+ @Parameter(0)
+ public SchedulerType scheduler;
+
+ @Parameter(1)
+ public ParallelismChange parallelismChange;
+
+ @Parameter(2)
+ public ForwardEdgeParallelismMismatchMode mode;
+
+ @RegisterExtension
+ private final LoggerAuditingExtension streamTaskLogs =
+ new LoggerAuditingExtension(StreamTask.class, Level.DEBUG);
+
+ /**
+ * Runs a job with the initial parallelism and changes it to the new parallelism.
+ *
+ * @return the failure of the job with the new parallelism, if any.
+ */
+ abstract Optional runAndRescale(MiniCluster miniCluster) throws Exception;
+
+ @TestTemplate
+ void testForwardEdgeParallelismMismatch() throws Exception {
+ final Optional failure;
+ try (MiniCluster miniCluster = startMiniCluster()) {
+ failure = runAndRescale(miniCluster);
+ }
+ assertExpectedBranch(failure);
+ }
+
+ /** Combines the given schedulers with every parallelism change and mode. */
+ static List