This is an automated email from the ASF dual-hosted git repository.
loserwang1024 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fluss.git
The following commit(s) were added to refs/heads/main by this push:
new bff97fec3 [flink] introduce batched splits assignment mechanism (#3288)
bff97fec3 is described below
commit bff97fec3b0b3cf2b66bfdea25014d9ad9780a36
Author: Junfan Zhang <[email protected]>
AuthorDate: Wed May 13 19:11:44 2026 +0800
[flink] introduce batched splits assignment mechanism (#3288)
---
.../apache/fluss/flink/FlinkConnectorOptions.java | 9 ++
.../fluss/flink/catalog/FlinkTableFactory.java | 10 ++
.../fluss/flink/source/BinlogFlinkTableSource.java | 27 ++++
.../flink/source/ChangelogFlinkTableSource.java | 27 ++++
.../org/apache/fluss/flink/source/FlinkSource.java | 73 ++++++++++
.../fluss/flink/source/FlinkTableSource.java | 44 ++++++
.../org/apache/fluss/flink/source/FlussSource.java | 30 +++++
.../fluss/flink/source/FlussSourceBuilder.java | 20 +++
.../source/enumerator/FlinkSourceEnumerator.java | 150 ++++++++++++++++++++-
.../flink/utils/FlinkConnectorOptionsUtils.java | 12 ++
.../fluss/flink/catalog/FlinkTableFactoryTest.java | 12 ++
.../enumerator/FlinkSourceEnumeratorTest.java | 97 +++++++++++--
.../flink/utils/FlinkConnectorOptionsUtilTest.java | 16 +++
13 files changed, 515 insertions(+), 12 deletions(-)
diff --git
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/FlinkConnectorOptions.java
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/FlinkConnectorOptions.java
index c7a3b44c2..23d8f0b2e 100644
---
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/FlinkConnectorOptions.java
+++
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/FlinkConnectorOptions.java
@@ -150,6 +150,15 @@ public class FlinkConnectorOptions {
+ "as a small value would cause frequent
requests and increase server load. In the future, "
+ "once list partitions is optimized, the
default value of this parameter can be reduced.");
+ public static final ConfigOption<Integer> SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE
=
+ ConfigOptions.key("scan.split.assignment.batch-size")
+ .intType()
+ .defaultValue(Integer.MAX_VALUE)
+ .withDescription(
+ "The maximum number of Fluss source splits
assigned to a reader in "
+ + "one assignment request. The value must
be positive. By default, "
+ + "all pending splits for a reader are
assigned in one request.");
+
public static final ConfigOption<Boolean> SINK_IGNORE_DELETE =
ConfigOptions.key("sink.ignore-delete")
.booleanType()
diff --git
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/catalog/FlinkTableFactory.java
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/catalog/FlinkTableFactory.java
index 408a70305..1b082855e 100644
---
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/catalog/FlinkTableFactory.java
+++
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/catalog/FlinkTableFactory.java
@@ -146,6 +146,8 @@ public class FlinkTableFactory implements
DynamicTableSourceFactory, DynamicTabl
tableOptions
.get(FlinkConnectorOptions.SCAN_PARTITION_DISCOVERY_INTERVAL)
.toMillis();
+ int splitAssignmentBatchSize =
+
tableOptions.get(FlinkConnectorOptions.SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE);
LeaseContext leaseContext = LeaseContext.fromConf(tableOptions);
return new FlinkTableSource(
@@ -163,6 +165,7 @@ public class FlinkTableFactory implements
DynamicTableSourceFactory, DynamicTabl
tableOptions.get(FlinkConnectorOptions.LOOKUP_INSERT_IF_NOT_EXISTS),
cache,
partitionDiscoveryIntervalMs,
+ splitAssignmentBatchSize,
tableOptions.get(toFlinkOption(ConfigOptions.TABLE_DATALAKE_ENABLED)),
tableOptions.get(toFlinkOption(ConfigOptions.TABLE_MERGE_ENGINE)),
context.getCatalogTable().getOptions(),
@@ -234,6 +237,7 @@ public class FlinkTableFactory implements
DynamicTableSourceFactory, DynamicTabl
FlinkConnectorOptions.SCAN_STARTUP_MODE,
FlinkConnectorOptions.SCAN_STARTUP_TIMESTAMP,
FlinkConnectorOptions.SCAN_PARTITION_DISCOVERY_INTERVAL,
+
FlinkConnectorOptions.SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE,
FlinkConnectorOptions.SCAN_KV_SNAPSHOT_LEASE_ID,
FlinkConnectorOptions.SCAN_KV_SNAPSHOT_LEASE_DURATION,
FlinkConnectorOptions.LOOKUP_ASYNC,
@@ -365,6 +369,8 @@ public class FlinkTableFactory implements
DynamicTableSourceFactory, DynamicTabl
tableOptions
.get(FlinkConnectorOptions.SCAN_PARTITION_DISCOVERY_INTERVAL)
.toMillis();
+ int splitAssignmentBatchSize =
+
tableOptions.get(FlinkConnectorOptions.SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE);
return new ChangelogFlinkTableSource(
TablePath.of(tableIdentifier.getDatabaseName(), baseTableName),
@@ -374,6 +380,7 @@ public class FlinkTableFactory implements
DynamicTableSourceFactory, DynamicTabl
isStreamingMode,
startupOptions,
partitionDiscoveryIntervalMs,
+ splitAssignmentBatchSize,
catalogTableOptions);
}
@@ -412,6 +419,8 @@ public class FlinkTableFactory implements
DynamicTableSourceFactory, DynamicTabl
tableOptions
.get(FlinkConnectorOptions.SCAN_PARTITION_DISCOVERY_INTERVAL)
.toMillis();
+ int splitAssignmentBatchSize =
+
tableOptions.get(FlinkConnectorOptions.SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE);
return new BinlogFlinkTableSource(
TablePath.of(tableIdentifier.getDatabaseName(), baseTableName),
@@ -421,6 +430,7 @@ public class FlinkTableFactory implements
DynamicTableSourceFactory, DynamicTabl
isStreamingMode,
startupOptions,
partitionDiscoveryIntervalMs,
+ splitAssignmentBatchSize,
catalogTableOptions);
}
}
diff --git
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/BinlogFlinkTableSource.java
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/BinlogFlinkTableSource.java
index 901995a65..93261e843 100644
---
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/BinlogFlinkTableSource.java
+++
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/BinlogFlinkTableSource.java
@@ -19,6 +19,7 @@ package org.apache.fluss.flink.source;
import org.apache.fluss.client.initializer.OffsetsInitializer;
import org.apache.fluss.config.Configuration;
+import org.apache.fluss.flink.FlinkConnectorOptions;
import org.apache.fluss.flink.source.deserializer.BinlogDeserializationSchema;
import org.apache.fluss.flink.source.reader.LeaseContext;
import org.apache.fluss.flink.utils.FlinkConnectorOptionsUtils;
@@ -51,6 +52,7 @@ public class BinlogFlinkTableSource implements
ScanTableSource {
private final boolean streaming;
private final FlinkConnectorOptionsUtils.StartupOptions startupOptions;
private final long scanPartitionDiscoveryIntervalMs;
+ private final int splitPerAssignmentBatchSize;
private final Map<String, String> tableOptions;
// Projection pushdown
@@ -68,6 +70,28 @@ public class BinlogFlinkTableSource implements
ScanTableSource {
FlinkConnectorOptionsUtils.StartupOptions startupOptions,
long scanPartitionDiscoveryIntervalMs,
Map<String, String> tableOptions) {
+ this(
+ tablePath,
+ flussConfig,
+ binlogOutputType,
+ isPartitioned,
+ streaming,
+ startupOptions,
+ scanPartitionDiscoveryIntervalMs,
+
FlinkConnectorOptions.SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE.defaultValue(),
+ tableOptions);
+ }
+
+ public BinlogFlinkTableSource(
+ TablePath tablePath,
+ Configuration flussConfig,
+ org.apache.flink.table.types.logical.RowType binlogOutputType,
+ boolean isPartitioned,
+ boolean streaming,
+ FlinkConnectorOptionsUtils.StartupOptions startupOptions,
+ long scanPartitionDiscoveryIntervalMs,
+ int splitPerAssignmentBatchSize,
+ Map<String, String> tableOptions) {
this.tablePath = tablePath;
this.flussConfig = flussConfig;
this.binlogOutputType = binlogOutputType;
@@ -75,6 +99,7 @@ public class BinlogFlinkTableSource implements
ScanTableSource {
this.streaming = streaming;
this.startupOptions = startupOptions;
this.scanPartitionDiscoveryIntervalMs =
scanPartitionDiscoveryIntervalMs;
+ this.splitPerAssignmentBatchSize = splitPerAssignmentBatchSize;
this.tableOptions = tableOptions;
// Extract data columns from the 'before' nested ROW type (index 3)
@@ -129,6 +154,7 @@ public class BinlogFlinkTableSource implements
ScanTableSource {
null,
offsetsInitializer,
scanPartitionDiscoveryIntervalMs,
+ splitPerAssignmentBatchSize,
new BinlogDeserializationSchema(),
streaming,
partitionFilters,
@@ -148,6 +174,7 @@ public class BinlogFlinkTableSource implements
ScanTableSource {
streaming,
startupOptions,
scanPartitionDiscoveryIntervalMs,
+ splitPerAssignmentBatchSize,
tableOptions);
copy.producedDataType = producedDataType;
copy.projectedFields = projectedFields;
diff --git
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/ChangelogFlinkTableSource.java
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/ChangelogFlinkTableSource.java
index f58fac3df..4304b271b 100644
---
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/ChangelogFlinkTableSource.java
+++
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/ChangelogFlinkTableSource.java
@@ -19,6 +19,7 @@ package org.apache.fluss.flink.source;
import org.apache.fluss.client.initializer.OffsetsInitializer;
import org.apache.fluss.config.Configuration;
+import org.apache.fluss.flink.FlinkConnectorOptions;
import
org.apache.fluss.flink.source.deserializer.ChangelogDeserializationSchema;
import org.apache.fluss.flink.source.reader.LeaseContext;
import org.apache.fluss.flink.utils.FlinkConnectorOptionsUtils;
@@ -57,6 +58,7 @@ public class ChangelogFlinkTableSource implements
ScanTableSource {
private final boolean streaming;
private final FlinkConnectorOptionsUtils.StartupOptions startupOptions;
private final long scanPartitionDiscoveryIntervalMs;
+ private final int splitPerAssignmentBatchSize;
private final Map<String, String> tableOptions;
// Projection pushdown
@@ -81,6 +83,28 @@ public class ChangelogFlinkTableSource implements
ScanTableSource {
FlinkConnectorOptionsUtils.StartupOptions startupOptions,
long scanPartitionDiscoveryIntervalMs,
Map<String, String> tableOptions) {
+ this(
+ tablePath,
+ flussConfig,
+ changelogOutputType,
+ partitionKeyIndexes,
+ streaming,
+ startupOptions,
+ scanPartitionDiscoveryIntervalMs,
+
FlinkConnectorOptions.SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE.defaultValue(),
+ tableOptions);
+ }
+
+ public ChangelogFlinkTableSource(
+ TablePath tablePath,
+ Configuration flussConfig,
+ org.apache.flink.table.types.logical.RowType changelogOutputType,
+ int[] partitionKeyIndexes,
+ boolean streaming,
+ FlinkConnectorOptionsUtils.StartupOptions startupOptions,
+ long scanPartitionDiscoveryIntervalMs,
+ int splitPerAssignmentBatchSize,
+ Map<String, String> tableOptions) {
this.tablePath = tablePath;
this.flussConfig = flussConfig;
// The changelogOutputType already includes metadata columns from
FlinkCatalog
@@ -89,6 +113,7 @@ public class ChangelogFlinkTableSource implements
ScanTableSource {
this.streaming = streaming;
this.startupOptions = startupOptions;
this.scanPartitionDiscoveryIntervalMs =
scanPartitionDiscoveryIntervalMs;
+ this.splitPerAssignmentBatchSize = splitPerAssignmentBatchSize;
this.tableOptions = tableOptions;
// Extract data columns by filtering out metadata columns by name
@@ -166,6 +191,7 @@ public class ChangelogFlinkTableSource implements
ScanTableSource {
null,
offsetsInitializer,
scanPartitionDiscoveryIntervalMs,
+ splitPerAssignmentBatchSize,
new ChangelogDeserializationSchema(),
streaming,
partitionFilters,
@@ -185,6 +211,7 @@ public class ChangelogFlinkTableSource implements
ScanTableSource {
streaming,
startupOptions,
scanPartitionDiscoveryIntervalMs,
+ splitPerAssignmentBatchSize,
tableOptions);
copy.producedDataType = producedDataType;
copy.projectedFields = projectedFields;
diff --git
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlinkSource.java
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlinkSource.java
index efa426e5f..8535a881a 100644
---
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlinkSource.java
+++
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlinkSource.java
@@ -19,6 +19,7 @@ package org.apache.fluss.flink.source;
import org.apache.fluss.client.initializer.OffsetsInitializer;
import org.apache.fluss.config.Configuration;
+import org.apache.fluss.flink.FlinkConnectorOptions;
import org.apache.fluss.flink.source.deserializer.DeserializerInitContextImpl;
import org.apache.fluss.flink.source.deserializer.FlussDeserializationSchema;
import org.apache.fluss.flink.source.emitter.FlinkRecordEmitter;
@@ -67,6 +68,7 @@ public class FlinkSource<OUT>
@Nullable private final int[] projectedFields;
protected final OffsetsInitializer offsetsInitializer;
protected final long scanPartitionDiscoveryIntervalMs;
+ protected final int splitPerAssignmentBatchSize;
private final boolean streaming;
private final FlussDeserializationSchema<OUT> deserializationSchema;
@Nullable private final Predicate partitionFilters;
@@ -99,6 +101,7 @@ public class FlinkSource<OUT>
logRecordBatchFilter,
offsetsInitializer,
scanPartitionDiscoveryIntervalMs,
+
FlinkConnectorOptions.SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE.defaultValue(),
deserializationSchema,
streaming,
partitionFilters,
@@ -121,6 +124,73 @@ public class FlinkSource<OUT>
@Nullable Predicate partitionFilters,
@Nullable LakeSource<LakeSplit> lakeSource,
LeaseContext leaseContext) {
+ this(
+ flussConf,
+ tablePath,
+ hasPrimaryKey,
+ isPartitioned,
+ sourceOutputType,
+ projectedFields,
+ logRecordBatchFilter,
+ offsetsInitializer,
+ scanPartitionDiscoveryIntervalMs,
+
FlinkConnectorOptions.SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE.defaultValue(),
+ deserializationSchema,
+ streaming,
+ partitionFilters,
+ lakeSource,
+ leaseContext);
+ }
+
+ public FlinkSource(
+ Configuration flussConf,
+ TablePath tablePath,
+ boolean hasPrimaryKey,
+ boolean isPartitioned,
+ RowType sourceOutputType,
+ @Nullable int[] projectedFields,
+ @Nullable Predicate logRecordBatchFilter,
+ OffsetsInitializer offsetsInitializer,
+ long scanPartitionDiscoveryIntervalMs,
+ int splitPerAssignmentBatchSize,
+ FlussDeserializationSchema<OUT> deserializationSchema,
+ boolean streaming,
+ @Nullable Predicate partitionFilters,
+ LeaseContext leaseContext) {
+ this(
+ flussConf,
+ tablePath,
+ hasPrimaryKey,
+ isPartitioned,
+ sourceOutputType,
+ projectedFields,
+ logRecordBatchFilter,
+ offsetsInitializer,
+ scanPartitionDiscoveryIntervalMs,
+ splitPerAssignmentBatchSize,
+ deserializationSchema,
+ streaming,
+ partitionFilters,
+ null,
+ leaseContext);
+ }
+
+ public FlinkSource(
+ Configuration flussConf,
+ TablePath tablePath,
+ boolean hasPrimaryKey,
+ boolean isPartitioned,
+ RowType sourceOutputType,
+ @Nullable int[] projectedFields,
+ @Nullable Predicate logRecordBatchFilter,
+ OffsetsInitializer offsetsInitializer,
+ long scanPartitionDiscoveryIntervalMs,
+ int splitPerAssignmentBatchSize,
+ FlussDeserializationSchema<OUT> deserializationSchema,
+ boolean streaming,
+ @Nullable Predicate partitionFilters,
+ @Nullable LakeSource<LakeSplit> lakeSource,
+ LeaseContext leaseContext) {
this.flussConf = flussConf;
this.tablePath = tablePath;
this.hasPrimaryKey = hasPrimaryKey;
@@ -130,6 +200,7 @@ public class FlinkSource<OUT>
this.logRecordBatchFilter = logRecordBatchFilter;
this.offsetsInitializer = offsetsInitializer;
this.scanPartitionDiscoveryIntervalMs =
scanPartitionDiscoveryIntervalMs;
+ this.splitPerAssignmentBatchSize = splitPerAssignmentBatchSize;
this.deserializationSchema = deserializationSchema;
this.streaming = streaming;
this.partitionFilters = partitionFilters;
@@ -153,6 +224,7 @@ public class FlinkSource<OUT>
splitEnumeratorContext,
offsetsInitializer,
scanPartitionDiscoveryIntervalMs,
+ splitPerAssignmentBatchSize,
streaming,
partitionFilters,
lakeSource,
@@ -175,6 +247,7 @@ public class FlinkSource<OUT>
sourceEnumeratorState.getRemainingHybridLakeFlussSplits(),
offsetsInitializer,
scanPartitionDiscoveryIntervalMs,
+ splitPerAssignmentBatchSize,
streaming,
partitionFilters,
lakeSource,
diff --git
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlinkTableSource.java
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlinkTableSource.java
index 0da1ba766..859ef7cfa 100644
---
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlinkTableSource.java
+++
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlinkTableSource.java
@@ -139,6 +139,7 @@ public class FlinkTableSource
@Nullable private final LookupCache cache;
private final long scanPartitionDiscoveryIntervalMs;
+ private final int splitPerAssignmentBatchSize;
private final boolean isDataLakeEnabled;
private final LeaseContext leaseContext;
@@ -194,6 +195,46 @@ public class FlinkTableSource
@Nullable MergeEngineType mergeEngineType,
Map<String, String> tableOptions,
LeaseContext leaseContext) {
+ this(
+ tablePath,
+ flussConfig,
+ tableConfig,
+ tableOutputType,
+ primaryKeyIndexes,
+ bucketKeyIndexes,
+ partitionKeyIndexes,
+ streaming,
+ startupOptions,
+ lookupAsync,
+ insertIfNotExists,
+ cache,
+ scanPartitionDiscoveryIntervalMs,
+
FlinkConnectorOptions.SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE.defaultValue(),
+ isDataLakeEnabled,
+ mergeEngineType,
+ tableOptions,
+ leaseContext);
+ }
+
+ public FlinkTableSource(
+ TablePath tablePath,
+ Configuration flussConfig,
+ TableConfig tableConfig,
+ org.apache.flink.table.types.logical.RowType tableOutputType,
+ int[] primaryKeyIndexes,
+ int[] bucketKeyIndexes,
+ int[] partitionKeyIndexes,
+ boolean streaming,
+ FlinkConnectorOptionsUtils.StartupOptions startupOptions,
+ boolean lookupAsync,
+ boolean insertIfNotExists,
+ @Nullable LookupCache cache,
+ long scanPartitionDiscoveryIntervalMs,
+ int splitPerAssignmentBatchSize,
+ boolean isDataLakeEnabled,
+ @Nullable MergeEngineType mergeEngineType,
+ Map<String, String> tableOptions,
+ LeaseContext leaseContext) {
this.tablePath = tablePath;
this.flussConfig = flussConfig;
this.tableOutputType = tableOutputType;
@@ -209,6 +250,7 @@ public class FlinkTableSource
this.cache = cache;
this.scanPartitionDiscoveryIntervalMs =
scanPartitionDiscoveryIntervalMs;
+ this.splitPerAssignmentBatchSize = splitPerAssignmentBatchSize;
this.isDataLakeEnabled = isDataLakeEnabled;
this.leaseContext = leaseContext;
this.mergeEngineType = mergeEngineType;
@@ -370,6 +412,7 @@ public class FlinkTableSource
logRecordBatchFilter,
offsetsInitializer,
scanPartitionDiscoveryIntervalMs,
+ splitPerAssignmentBatchSize,
new RowDataDeserializationSchema(),
streaming,
partitionFilters,
@@ -478,6 +521,7 @@ public class FlinkTableSource
insertIfNotExists,
cache,
scanPartitionDiscoveryIntervalMs,
+ splitPerAssignmentBatchSize,
isDataLakeEnabled,
mergeEngineType,
tableOptions,
diff --git
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlussSource.java
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlussSource.java
index 53cabcca7..795e85082 100644
---
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlussSource.java
+++
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlussSource.java
@@ -20,6 +20,7 @@ package org.apache.fluss.flink.source;
import org.apache.fluss.annotation.VisibleForTesting;
import org.apache.fluss.client.initializer.OffsetsInitializer;
import org.apache.fluss.config.Configuration;
+import org.apache.fluss.flink.FlinkConnectorOptions;
import org.apache.fluss.flink.source.deserializer.FlussDeserializationSchema;
import org.apache.fluss.flink.source.reader.LeaseContext;
import org.apache.fluss.metadata.TablePath;
@@ -71,6 +72,34 @@ public class FlussSource<OUT> extends FlinkSource<OUT> {
long scanPartitionDiscoveryIntervalMs,
FlussDeserializationSchema<OUT> deserializationSchema,
boolean streaming) {
+ this(
+ flussConf,
+ tablePath,
+ hasPrimaryKey,
+ isPartitioned,
+ sourceOutputType,
+ projectedFields,
+ logRecordBatchFilter,
+ offsetsInitializer,
+ scanPartitionDiscoveryIntervalMs,
+
FlinkConnectorOptions.SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE.defaultValue(),
+ deserializationSchema,
+ streaming);
+ }
+
+ FlussSource(
+ Configuration flussConf,
+ TablePath tablePath,
+ boolean hasPrimaryKey,
+ boolean isPartitioned,
+ RowType sourceOutputType,
+ @Nullable int[] projectedFields,
+ @Nullable Predicate logRecordBatchFilter,
+ OffsetsInitializer offsetsInitializer,
+ long scanPartitionDiscoveryIntervalMs,
+ int splitPerAssignmentBatchSize,
+ FlussDeserializationSchema<OUT> deserializationSchema,
+ boolean streaming) {
// TODO: Support partition pushDown in datastream
super(
flussConf,
@@ -82,6 +111,7 @@ public class FlussSource<OUT> extends FlinkSource<OUT> {
logRecordBatchFilter,
offsetsInitializer,
scanPartitionDiscoveryIntervalMs,
+ splitPerAssignmentBatchSize,
deserializationSchema,
streaming,
null,
diff --git
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlussSourceBuilder.java
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlussSourceBuilder.java
index 87efc01cd..1e6549134 100644
---
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlussSourceBuilder.java
+++
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlussSourceBuilder.java
@@ -71,6 +71,7 @@ public class FlussSourceBuilder<OUT> {
private String[] projectedFieldNames;
private Predicate logRecordBatchFilter;
private Long scanPartitionDiscoveryIntervalMs;
+ private Integer splitPerAssignmentBatchSize;
private OffsetsInitializer offsetsInitializer;
private FlussDeserializationSchema<OUT> deserializationSchema;
@@ -133,6 +134,20 @@ public class FlussSourceBuilder<OUT> {
return this;
}
+ /**
+ * Sets the maximum number of splits assigned to a reader in one
assignment request.
+ *
+ * <p>If not specified, the default value from {@link
+ * FlinkConnectorOptions#SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE} is used.
+ *
+ * @param splitPerAssignmentBatchSize maximum splits per assignment request
+ * @return this builder
+ */
+ public FlussSourceBuilder<OUT> setSplitPerAssignmentBatchSize(int
splitPerAssignmentBatchSize) {
+ this.splitPerAssignmentBatchSize = splitPerAssignmentBatchSize;
+ return this;
+ }
+
/**
* Sets the starting offsets strategy for the Fluss source.
*
@@ -241,6 +256,10 @@ public class FlussSourceBuilder<OUT> {
.defaultValue()
.toMillis();
}
+ if (splitPerAssignmentBatchSize == null) {
+ splitPerAssignmentBatchSize =
+
FlinkConnectorOptions.SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE.defaultValue();
+ }
if (this.flussConf == null) {
this.flussConf = new Configuration();
@@ -317,6 +336,7 @@ public class FlussSourceBuilder<OUT> {
logRecordBatchFilter,
offsetsInitializer,
scanPartitionDiscoveryIntervalMs,
+ splitPerAssignmentBatchSize,
deserializationSchema,
true);
}
diff --git
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/enumerator/FlinkSourceEnumerator.java
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/enumerator/FlinkSourceEnumerator.java
index 1a6521f57..e04989cbb 100644
---
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/enumerator/FlinkSourceEnumerator.java
+++
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/enumerator/FlinkSourceEnumerator.java
@@ -29,6 +29,7 @@ import org.apache.fluss.client.metadata.KvSnapshots;
import org.apache.fluss.config.ConfigOptions;
import org.apache.fluss.config.Configuration;
import org.apache.fluss.exception.UnsupportedVersionException;
+import org.apache.fluss.flink.FlinkConnectorOptions;
import org.apache.fluss.flink.lake.LakeSplitGenerator;
import org.apache.fluss.flink.lake.split.LakeSnapshotAndFlussLogSplit;
import org.apache.fluss.flink.lake.split.LakeSnapshotSplit;
@@ -52,6 +53,7 @@ import org.apache.fluss.predicate.Predicate;
import org.apache.fluss.row.BinaryString;
import org.apache.fluss.row.GenericRow;
import org.apache.fluss.row.InternalRow;
+import org.apache.fluss.shaded.guava32.com.google.common.collect.Lists;
import org.apache.fluss.utils.ExceptionUtils;
import org.apache.flink.annotation.Internal;
@@ -83,6 +85,7 @@ import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
+import static org.apache.fluss.utils.Preconditions.checkArgument;
import static org.apache.fluss.utils.Preconditions.checkNotNull;
import static org.apache.fluss.utils.Preconditions.checkState;
@@ -183,6 +186,8 @@ public class FlinkSourceEnumerator
@Nullable private final LakeSource<LakeSplit> lakeSource;
+ private final int splitPerAssignmentBatchSize;
+
public FlinkSourceEnumerator(
TablePath tablePath,
Configuration flussConf,
@@ -196,6 +201,36 @@ public class FlinkSourceEnumerator
@Nullable LakeSource<LakeSplit> lakeSource,
LeaseContext leaseContext,
boolean checkpointTriggeredBefore) {
+ this(
+ tablePath,
+ flussConf,
+ hasPrimaryKey,
+ isPartitioned,
+ context,
+ startingOffsetsInitializer,
+ scanPartitionDiscoveryIntervalMs,
+
FlinkConnectorOptions.SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE.defaultValue(),
+ streaming,
+ partitionFilters,
+ lakeSource,
+ leaseContext,
+ checkpointTriggeredBefore);
+ }
+
+ public FlinkSourceEnumerator(
+ TablePath tablePath,
+ Configuration flussConf,
+ boolean hasPrimaryKey,
+ boolean isPartitioned,
+ SplitEnumeratorContext<SourceSplitBase> context,
+ OffsetsInitializer startingOffsetsInitializer,
+ long scanPartitionDiscoveryIntervalMs,
+ int splitPerAssignmentBatchSize,
+ boolean streaming,
+ @Nullable Predicate partitionFilters,
+ @Nullable LakeSource<LakeSplit> lakeSource,
+ LeaseContext leaseContext,
+ boolean checkpointTriggeredBefore) {
this(
tablePath,
flussConf,
@@ -207,6 +242,7 @@ public class FlinkSourceEnumerator
null,
startingOffsetsInitializer,
scanPartitionDiscoveryIntervalMs,
+ splitPerAssignmentBatchSize,
streaming,
partitionFilters,
lakeSource,
@@ -241,6 +277,43 @@ public class FlinkSourceEnumerator
pendingHybridLakeFlussSplits,
startingOffsetsInitializer,
scanPartitionDiscoveryIntervalMs,
+
FlinkConnectorOptions.SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE.defaultValue(),
+ streaming,
+ partitionFilters,
+ lakeSource,
+ leaseContext,
+ checkpointTriggeredBefore);
+ }
+
+ public FlinkSourceEnumerator(
+ TablePath tablePath,
+ Configuration flussConf,
+ boolean hasPrimaryKey,
+ boolean isPartitioned,
+ SplitEnumeratorContext<SourceSplitBase> context,
+ Set<TableBucket> assignedTableBuckets,
+ Map<Long, String> assignedPartitions,
+ List<SourceSplitBase> pendingHybridLakeFlussSplits,
+ OffsetsInitializer startingOffsetsInitializer,
+ long scanPartitionDiscoveryIntervalMs,
+ int splitPerAssignmentBatchSize,
+ boolean streaming,
+ @Nullable Predicate partitionFilters,
+ @Nullable LakeSource<LakeSplit> lakeSource,
+ LeaseContext leaseContext,
+ boolean checkpointTriggeredBefore) {
+ this(
+ tablePath,
+ flussConf,
+ hasPrimaryKey,
+ isPartitioned,
+ context,
+ assignedTableBuckets,
+ assignedPartitions,
+ pendingHybridLakeFlussSplits,
+ startingOffsetsInitializer,
+ scanPartitionDiscoveryIntervalMs,
+ splitPerAssignmentBatchSize,
streaming,
partitionFilters,
lakeSource,
@@ -266,6 +339,48 @@ public class FlinkSourceEnumerator
WorkerExecutor workerExecutor,
LeaseContext leaseContext,
boolean checkpointTriggeredBefore) {
+ this(
+ tablePath,
+ flussConf,
+ hasPrimaryKey,
+ isPartitioned,
+ context,
+ assignedTableBuckets,
+ assignedPartitions,
+ pendingHybridLakeFlussSplits,
+ startingOffsetsInitializer,
+ scanPartitionDiscoveryIntervalMs,
+
FlinkConnectorOptions.SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE.defaultValue(),
+ streaming,
+ partitionFilters,
+ lakeSource,
+ workerExecutor,
+ leaseContext,
+ checkpointTriggeredBefore);
+ }
+
+ FlinkSourceEnumerator(
+ TablePath tablePath,
+ Configuration flussConf,
+ boolean hasPrimaryKey,
+ boolean isPartitioned,
+ SplitEnumeratorContext<SourceSplitBase> context,
+ Set<TableBucket> assignedTableBuckets,
+ Map<Long, String> assignedPartitions,
+ List<SourceSplitBase> pendingHybridLakeFlussSplits,
+ OffsetsInitializer startingOffsetsInitializer,
+ long scanPartitionDiscoveryIntervalMs,
+ int splitPerAssignmentBatchSize,
+ boolean streaming,
+ @Nullable Predicate partitionFilters,
+ @Nullable LakeSource<LakeSplit> lakeSource,
+ WorkerExecutor workerExecutor,
+ LeaseContext leaseContext,
+ boolean checkpointTriggeredBefore) {
+ checkArgument(
+ splitPerAssignmentBatchSize > 0,
+ "Split assignment batch size must be positive, but was %s.",
+ splitPerAssignmentBatchSize);
this.tablePath = checkNotNull(tablePath);
this.flussConf = checkNotNull(flussConf);
this.hasPrimaryKey = hasPrimaryKey;
@@ -288,6 +403,7 @@ public class FlinkSourceEnumerator
this.workerExecutor = workerExecutor;
this.leaseContext = leaseContext;
this.checkpointTriggeredBefore = checkpointTriggeredBefore;
+ this.splitPerAssignmentBatchSize = splitPerAssignmentBatchSize;
}
@Override
@@ -926,8 +1042,38 @@ public class FlinkSourceEnumerator
// Assign pending splits to readers
if (!incrementalAssignment.isEmpty()) {
- LOG.info("Assigning splits to readers {}", incrementalAssignment);
- context.assignSplits(new
SplitsAssignment<>(incrementalAssignment));
+ int totalSplits =
incrementalAssignment.values().stream().mapToInt(List::size).sum();
+ Map<Integer, Integer> batchesPerReader =
+ incrementalAssignment.entrySet().stream()
+ .collect(
+ Collectors.toMap(
+ Map.Entry::getKey,
+ entry ->
+ (entry.getValue().size()
+ +
splitPerAssignmentBatchSize
+ - 1)
+ /
splitPerAssignmentBatchSize));
+ LOG.info(
+ "Assigning splits to {} readers: totalSplits={},
batchesPerReader={}",
+ incrementalAssignment.size(),
+ totalSplits,
+ batchesPerReader);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Assigning splits to readers {}",
incrementalAssignment);
+ }
+ for (Map.Entry<Integer, List<SourceSplitBase>> entry :
+ incrementalAssignment.entrySet()) {
+ int readerId = entry.getKey();
+ List<SourceSplitBase> splits = entry.getValue();
+ Lists.partition(splits, splitPerAssignmentBatchSize).stream()
+ .forEach(
+ batchSplits -> {
+ context.assignSplits(
+ new SplitsAssignment<>(
+ Collections.singletonMap(
+ readerId,
batchSplits)));
+ });
+ }
}
if (noMoreNewSplits) {
diff --git
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/utils/FlinkConnectorOptionsUtils.java
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/utils/FlinkConnectorOptionsUtils.java
index b9306e53a..249e1be08 100644
---
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/utils/FlinkConnectorOptionsUtils.java
+++
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/utils/FlinkConnectorOptionsUtils.java
@@ -43,6 +43,7 @@ import java.util.stream.Collectors;
import static org.apache.flink.configuration.CoreOptions.TMP_DIRS;
import static org.apache.fluss.config.ConfigOptions.CLIENT_SCANNER_IO_TMP_DIR;
+import static
org.apache.fluss.flink.FlinkConnectorOptions.SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE;
import static org.apache.fluss.flink.FlinkConnectorOptions.SCAN_STARTUP_MODE;
import static
org.apache.fluss.flink.FlinkConnectorOptions.SCAN_STARTUP_TIMESTAMP;
import static
org.apache.fluss.flink.FlinkConnectorOptions.ScanStartupMode.TIMESTAMP;
@@ -61,6 +62,7 @@ public class FlinkConnectorOptionsUtils {
public static void validateTableSourceOptions(ReadableConfig tableOptions)
{
validateScanStartupMode(tableOptions);
+ validateScanSplitAssignmentBatchSize(tableOptions);
}
/**
@@ -154,6 +156,16 @@ public class FlinkConnectorOptionsUtils {
}
}
+ private static void validateScanSplitAssignmentBatchSize(ReadableConfig
tableOptions) {
+ int batchSize = tableOptions.get(SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE);
+ if (batchSize <= 0) {
+ throw new ValidationException(
+ String.format(
+ "'%s' must be positive, but was %s.",
+ SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE.key(),
batchSize));
+ }
+ }
+
/**
* Parses timestamp String to Long.
*
diff --git
a/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/catalog/FlinkTableFactoryTest.java
b/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/catalog/FlinkTableFactoryTest.java
index c1d136cb4..57bf8e8da 100644
---
a/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/catalog/FlinkTableFactoryTest.java
+++
b/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/catalog/FlinkTableFactoryTest.java
@@ -93,6 +93,18 @@ abstract class FlinkTableFactoryTest {
FlinkConnectorOptions.SCAN_STARTUP_TIMESTAMP.key(),
"2023-12-09 23:09:12");
createTableSource(schema, scanModeProperties);
+ // test split assignment batch size
+ Map<String, String> splitAssignmentBatchProperties = getBasicOptions();
+ splitAssignmentBatchProperties.put(
+ FlinkConnectorOptions.SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE.key(),
"0");
+ assertThatThrownBy(() -> createTableSource(schema,
splitAssignmentBatchProperties))
+ .isInstanceOf(ValidationException.class)
+ .hasMessageContaining(
+ "'scan.split.assignment.batch-size' must be positive,
but was 0.");
+ splitAssignmentBatchProperties.put(
+ FlinkConnectorOptions.SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE.key(),
"1");
+ createTableSource(schema, splitAssignmentBatchProperties);
+
// test datalake options
Map<String, String> datalakeProperties = getBasicOptions();
datalakeProperties.put("table.datalake.format", "paimon");
diff --git
a/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/source/enumerator/FlinkSourceEnumeratorTest.java
b/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/source/enumerator/FlinkSourceEnumeratorTest.java
index ca4722c9e..6d239f965 100644
---
a/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/source/enumerator/FlinkSourceEnumeratorTest.java
+++
b/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/source/enumerator/FlinkSourceEnumeratorTest.java
@@ -79,6 +79,7 @@ import static
org.apache.fluss.client.table.scanner.log.LogScanner.EARLIEST_OFFS
import static org.apache.fluss.record.TestData.DEFAULT_REMOTE_DATA_DIR;
import static org.apache.fluss.testutils.DataTestUtils.row;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
/** Unit tests for {@link FlinkSourceEnumerator}. */
class FlinkSourceEnumeratorTest extends FlinkTestBase {
@@ -137,12 +138,78 @@ class FlinkSourceEnumeratorTest extends FlinkTestBase {
expectedAssignment.put(i,
Collections.singletonList(genLogSplit(tableId, i)));
}
- Map<Integer, List<SourceSplitBase>> actualAssignment =
- getLastReadersAssignments(context);
+ Map<Integer, List<SourceSplitBase>> actualAssignment =
getReadersAssignments(context);
assertThat(actualAssignment).isEqualTo(expectedAssignment);
}
}
+ @Test
+ void testSplitAssignmentBatchSize() throws Throwable {
+ long tableId = createTable(DEFAULT_TABLE_PATH,
DEFAULT_PK_TABLE_DESCRIPTOR);
+ try (MockSplitEnumeratorContext<SourceSplitBase> context =
+ new MockSplitEnumeratorContext<>(1)) {
+ FlinkSourceEnumerator enumerator =
+ new FlinkSourceEnumerator(
+ DEFAULT_TABLE_PATH,
+ flussConf,
+ true,
+ false,
+ context,
+ OffsetsInitializer.full(),
+ DEFAULT_SCAN_PARTITION_DISCOVERY_INTERVAL_MS,
+ 2,
+ streaming,
+ null,
+ null,
+ LeaseContext.DEFAULT,
+ false);
+
+ enumerator.start();
+ registerReader(context, enumerator, 0);
+ context.runNextOneTimeCallable();
+
+ List<SplitsAssignment<SourceSplitBase>> assignments =
+ context.getSplitsAssignmentSequence();
+ assertThat(assignments).hasSize(2);
+ assertThat(assignments.get(0).assignment().get(0)).hasSize(2);
+ assertThat(assignments.get(1).assignment().get(0)).hasSize(1);
+
+ List<SourceSplitBase> assignedSplits = new ArrayList<>();
+ assignments.forEach(
+ assignment ->
assignedSplits.addAll(assignment.assignment().get(0)));
+ assertThat(assignedSplits)
+ .containsExactly(
+ genLogSplit(tableId, 0),
+ genLogSplit(tableId, 1),
+ genLogSplit(tableId, 2));
+ }
+ }
+
+ @Test
+ void testInvalidSplitAssignmentBatchSize() throws Exception {
+ try (MockSplitEnumeratorContext<SourceSplitBase> context =
+ new MockSplitEnumeratorContext<>(1)) {
+ assertThatThrownBy(
+ () ->
+ new FlinkSourceEnumerator(
+ DEFAULT_TABLE_PATH,
+ flussConf,
+ true,
+ false,
+ context,
+ OffsetsInitializer.full(),
+
DEFAULT_SCAN_PARTITION_DISCOVERY_INTERVAL_MS,
+ 0,
+ streaming,
+ null,
+ null,
+ LeaseContext.DEFAULT,
+ false))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Split assignment batch size must be
positive");
+ }
+ }
+
@Test
void testPkTableWithSnapshotSplits() throws Throwable {
long tableId = createTable(DEFAULT_TABLE_PATH,
DEFAULT_PK_TABLE_DESCRIPTOR);
@@ -177,8 +244,7 @@ class FlinkSourceEnumeratorTest extends FlinkTestBase {
// make enumerate to get splits and assign
context.runNextOneTimeCallable();
- Map<Integer, List<SourceSplitBase>> actualAssignment =
- getLastReadersAssignments(context);
+ Map<Integer, List<SourceSplitBase>> actualAssignment =
getReadersAssignments(context);
Map<Integer, List<SourceSplitBase>> expectedAssignment = new
HashMap<>();
@@ -264,8 +330,7 @@ class FlinkSourceEnumeratorTest extends FlinkTestBase {
new LogSplit(new TableBucket(tableId, i),
null, -2L)));
}
- Map<Integer, List<SourceSplitBase>> actualAssignment =
- getLastReadersAssignments(context);
+ Map<Integer, List<SourceSplitBase>> actualAssignment =
getReadersAssignments(context);
assertThat(actualAssignment).isEqualTo(expectedAssignment);
}
}
@@ -477,10 +542,11 @@ class FlinkSourceEnumeratorTest extends FlinkTestBase {
createPartitions(zooKeeperClient, DEFAULT_TABLE_PATH,
newPartitions);
/// invoke partition discovery callable again and there should
assignments.
+ int assignmentStart = context.getSplitsAssignmentSequence().size();
runPeriodicPartitionDiscovery(workExecutor);
expectedAssignment = expectAssignments(enumerator, tableId,
newPartitionNameIds);
- actualAssignments = getLastReadersAssignments(context);
+ actualAssignments = getReadersAssignments(context,
assignmentStart);
checkAssignmentIgnoreOrder(actualAssignments, expectedAssignment);
// drop + create partitions;
@@ -493,6 +559,7 @@ class FlinkSourceEnumeratorTest extends FlinkTestBase {
createPartitions(zooKeeperClient, DEFAULT_TABLE_PATH,
newPartitions);
// invoke partition discovery callable again
+ assignmentStart = context.getSplitsAssignmentSequence().size();
runPeriodicPartitionDiscovery(workExecutor);
// there should be partition removed events
@@ -513,7 +580,7 @@ class FlinkSourceEnumeratorTest extends FlinkTestBase {
// check new assignments.
expectedAssignment = expectAssignments(enumerator, tableId,
newPartitionNameIds);
- actualAssignments = getLastReadersAssignments(context);
+ actualAssignments = getReadersAssignments(context,
assignmentStart);
checkAssignmentIgnoreOrder(actualAssignments, expectedAssignment);
Map<Long, String> assignedPartitions =
@@ -916,11 +983,21 @@ class FlinkSourceEnumeratorTest extends FlinkTestBase {
private Map<Integer, List<SourceSplitBase>> getReadersAssignments(
MockSplitEnumeratorContext<SourceSplitBase> context) {
+ return getReadersAssignments(context, 0);
+ }
+
+ private Map<Integer, List<SourceSplitBase>> getReadersAssignments(
+ MockSplitEnumeratorContext<SourceSplitBase> context, int
startIndex) {
List<SplitsAssignment<SourceSplitBase>> splitsAssignments =
context.getSplitsAssignmentSequence();
Map<Integer, List<SourceSplitBase>> assignment = new HashMap<>();
- for (SplitsAssignment<SourceSplitBase> splitAssignment :
splitsAssignments) {
- assignment.putAll(splitAssignment.assignment());
+ for (int i = startIndex; i < splitsAssignments.size(); i++) {
+ for (Map.Entry<Integer, List<SourceSplitBase>> splitAssignment :
+ splitsAssignments.get(i).assignment().entrySet()) {
+ assignment
+ .computeIfAbsent(splitAssignment.getKey(), key -> new
ArrayList<>())
+ .addAll(splitAssignment.getValue());
+ }
}
return assignment;
}
diff --git
a/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/utils/FlinkConnectorOptionsUtilTest.java
b/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/utils/FlinkConnectorOptionsUtilTest.java
index 259a8ce6e..0e06dc101 100644
---
a/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/utils/FlinkConnectorOptionsUtilTest.java
+++
b/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/utils/FlinkConnectorOptionsUtilTest.java
@@ -27,6 +27,7 @@ import java.util.TimeZone;
import static org.apache.flink.configuration.CoreOptions.TMP_DIRS;
import static org.apache.fluss.config.ConfigOptions.CLIENT_SCANNER_IO_TMP_DIR;
+import static
org.apache.fluss.flink.FlinkConnectorOptions.SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE;
import static
org.apache.fluss.flink.FlinkConnectorOptions.SCAN_STARTUP_TIMESTAMP;
import static
org.apache.fluss.flink.utils.FlinkConnectorOptionsUtils.parseTimestamp;
import static org.assertj.core.api.Assertions.assertThat;
@@ -63,6 +64,21 @@ class FlinkConnectorOptionsUtilTest {
+ "You can config like: '2023-12-09 23:09:12'
or '1678883047356'.");
}
+ @Test
+ void testValidateSplitAssignmentBatchSize() {
+ org.apache.flink.configuration.Configuration tableOptions =
+ new org.apache.flink.configuration.Configuration();
+
+ tableOptions.set(SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE, 1);
+ FlinkConnectorOptionsUtils.validateTableSourceOptions(tableOptions);
+
+ tableOptions.set(SCAN_SPLIT_ASSIGNMENT_BATCH_SIZE, 0);
+ assertThatThrownBy(
+ () ->
FlinkConnectorOptionsUtils.validateTableSourceOptions(tableOptions))
+ .isInstanceOf(ValidationException.class)
+ .hasMessage("'scan.split.assignment.batch-size' must be
positive, but was 0.");
+ }
+
@Test
void testGetClientScannerIoTmpDir() {
Configuration flussConfig =