This is an automated email from the ASF dual-hosted git repository.
luoyuxia 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 c108feae4 [lake/hudi] Introduce split index field for tiering splits
(#3503)
c108feae4 is described below
commit c108feae452ddfb4da376c97fa17936b35205f74
Author: fhan <[email protected]>
AuthorDate: Mon Jun 22 10:37:30 2026 +0800
[lake/hudi] Introduce split index field for tiering splits (#3503)
---
.../fluss/lake/writer/WriterInitContext.java | 32 +++++++++
.../fluss/lake/writer/WriterInitContextTest.java | 63 +++++++++++++++++
.../flink/tiering/source/TieringSplitReader.java | 30 ++++++---
.../tiering/source/TieringWriterInitContext.java | 30 +++++++++
.../source/enumerator/TieringSourceEnumerator.java | 21 ++++--
.../tiering/source/split/TieringLogSplit.java | 62 ++++++++++++++++-
.../tiering/source/split/TieringSnapshotSplit.java | 63 ++++++++++++++++-
.../flink/tiering/source/split/TieringSplit.java | 78 +++++++++++++++++++++-
.../source/split/TieringSplitSerializer.java | 13 +++-
.../tiering/source/state/TieringSplitState.java | 10 ++-
.../enumerator/TieringSourceEnumeratorTest.java | 44 ++++++++++++
.../source/split/TieringSplitSerializerTest.java | 53 +++++++++++++--
.../source/state/TieringSplitStateTest.java | 20 ++++--
13 files changed, 483 insertions(+), 36 deletions(-)
diff --git
a/fluss-common/src/main/java/org/apache/fluss/lake/writer/WriterInitContext.java
b/fluss-common/src/main/java/org/apache/fluss/lake/writer/WriterInitContext.java
index 6c9c2f408..318f53f54 100644
---
a/fluss-common/src/main/java/org/apache/fluss/lake/writer/WriterInitContext.java
+++
b/fluss-common/src/main/java/org/apache/fluss/lake/writer/WriterInitContext.java
@@ -34,6 +34,12 @@ import javax.annotation.Nullable;
@PublicEvolving
public interface WriterInitContext {
+ /** Unknown split index for lake writers that do not need split-level
coordination. */
+ int UNKNOWN_SPLIT_INDEX = -1;
+
+ /** Unknown tiering round timestamp for lake writers that do not need
round coordination. */
+ long UNKNOWN_TIERING_ROUND_TIMESTAMP = -1L;
+
/**
* Returns the table path.
*
@@ -62,4 +68,30 @@ public interface WriterInitContext {
* @return the Fluss table info
*/
TableInfo tableInfo();
+
+ /**
+ * Returns the split index in the current tiering round.
+ *
+ * <p>The split whose index is {@code 0} is the first split in the current
tiering round. Lake
+ * writers that need one writer to coordinate round-level metadata can use
the first split as
+ * the coordinator. The value is {@link #UNKNOWN_SPLIT_INDEX} when the
lake writer does not need
+ * this information.
+ *
+ * @return the split index in the current tiering round
+ */
+ default int splitIndex() {
+ return UNKNOWN_SPLIT_INDEX;
+ }
+
+ /**
+ * Returns the timestamp when the current tiering round was generated.
+ *
+ * <p>The value is {@link #UNKNOWN_TIERING_ROUND_TIMESTAMP} when the lake
writer does not need
+ * round-level timestamp coordination.
+ *
+ * @return the tiering round timestamp in milliseconds
+ */
+ default long tieringRoundTimestamp() {
+ return UNKNOWN_TIERING_ROUND_TIMESTAMP;
+ }
}
diff --git
a/fluss-common/src/test/java/org/apache/fluss/lake/writer/WriterInitContextTest.java
b/fluss-common/src/test/java/org/apache/fluss/lake/writer/WriterInitContextTest.java
new file mode 100644
index 000000000..bf0198bd8
--- /dev/null
+++
b/fluss-common/src/test/java/org/apache/fluss/lake/writer/WriterInitContextTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.fluss.lake.writer;
+
+import org.apache.fluss.metadata.TableBucket;
+import org.apache.fluss.metadata.TableInfo;
+import org.apache.fluss.metadata.TablePath;
+
+import org.junit.jupiter.api.Test;
+
+import javax.annotation.Nullable;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Tests for {@link WriterInitContext}. */
+class WriterInitContextTest {
+
+ @Test
+ void testDefaultTieringRoundMetadata() {
+ WriterInitContext context =
+ new WriterInitContext() {
+ @Override
+ public TablePath tablePath() {
+ return null;
+ }
+
+ @Override
+ public TableBucket tableBucket() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public String partition() {
+ return null;
+ }
+
+ @Override
+ public TableInfo tableInfo() {
+ return null;
+ }
+ };
+
+
assertThat(context.splitIndex()).isEqualTo(WriterInitContext.UNKNOWN_SPLIT_INDEX);
+ assertThat(context.tieringRoundTimestamp())
+ .isEqualTo(WriterInitContext.UNKNOWN_TIERING_ROUND_TIMESTAMP);
+ }
+}
diff --git
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/TieringSplitReader.java
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/TieringSplitReader.java
index 324ce8a9e..960c4db7e 100644
---
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/TieringSplitReader.java
+++
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/TieringSplitReader.java
@@ -447,12 +447,15 @@ public class TieringSplitReader<WriteResult>
long lastWrittenTimestamp =
handler.handleRecords(
records,
- () ->
- getOrCreateLakeWriter(
- bucket,
- currentTableSplitsByBucket
- .get(bucket)
- .getPartitionName()),
+ () -> {
+ TieringSplit split =
+
checkNotNull(currentTableSplitsByBucket.get(bucket));
+ return getOrCreateLakeWriter(
+ bucket,
+ split.getPartitionName(),
+ split.getSplitIndex(),
+ split.getTieringRoundTimestamp());
+ },
stoppingOffset);
// The split owns offsets before stoppingOffset only. If the
scanner consumed past
@@ -589,7 +592,11 @@ public class TieringSplitReader<WriteResult>
}
private LakeWriter<WriteResult> getOrCreateLakeWriter(
- TableBucket bucket, @Nullable String partitionName) throws
IOException {
+ TableBucket bucket,
+ @Nullable String partitionName,
+ int splitIndex,
+ long tieringRoundTimestamp)
+ throws IOException {
LakeWriter<WriteResult> lakeWriter = lakeWriters.get(bucket);
if (lakeWriter == null) {
lakeWriter =
@@ -598,7 +605,9 @@ public class TieringSplitReader<WriteResult>
currentTablePath,
bucket,
partitionName,
- currentTable.getTableInfo()));
+ currentTable.getTableInfo(),
+ splitIndex,
+ tieringRoundTimestamp));
lakeWriters.put(bucket, lakeWriter);
}
return lakeWriter;
@@ -683,7 +692,10 @@ public class TieringSplitReader<WriteResult>
if (lakeWriter == null) {
lakeWriter =
getOrCreateLakeWriter(
- bucket,
checkNotNull(currentSnapshotSplit).getPartitionName());
+ bucket,
+
checkNotNull(currentSnapshotSplit).getPartitionName(),
+ currentSnapshotSplit.getSplitIndex(),
+
currentSnapshotSplit.getTieringRoundTimestamp());
}
lakeWriter.write(scanRecord);
if (scanRecord.getSizeInBytes() > 0) {
diff --git
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/TieringWriterInitContext.java
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/TieringWriterInitContext.java
index 121c4fb9c..7dbb6e41b 100644
---
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/TieringWriterInitContext.java
+++
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/TieringWriterInitContext.java
@@ -31,16 +31,36 @@ public class TieringWriterInitContext implements
WriterInitContext {
private final TableBucket tableBucket;
@Nullable private final String partition;
private final TableInfo tableInfo;
+ private final int splitIndex;
+ private final long tieringRoundTimestamp;
public TieringWriterInitContext(
TablePath tablePath,
TableBucket tableBucket,
@Nullable String partition,
TableInfo tableInfo) {
+ this(
+ tablePath,
+ tableBucket,
+ partition,
+ tableInfo,
+ UNKNOWN_SPLIT_INDEX,
+ UNKNOWN_TIERING_ROUND_TIMESTAMP);
+ }
+
+ public TieringWriterInitContext(
+ TablePath tablePath,
+ TableBucket tableBucket,
+ @Nullable String partition,
+ TableInfo tableInfo,
+ int splitIndex,
+ long tieringRoundTimestamp) {
this.tablePath = tablePath;
this.tableBucket = tableBucket;
this.partition = partition;
this.tableInfo = tableInfo;
+ this.splitIndex = splitIndex;
+ this.tieringRoundTimestamp = tieringRoundTimestamp;
}
@Override
@@ -63,4 +83,14 @@ public class TieringWriterInitContext implements
WriterInitContext {
public TableInfo tableInfo() {
return tableInfo;
}
+
+ @Override
+ public int splitIndex() {
+ return splitIndex;
+ }
+
+ @Override
+ public long tieringRoundTimestamp() {
+ return tieringRoundTimestamp;
+ }
}
diff --git
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/enumerator/TieringSourceEnumerator.java
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/enumerator/TieringSourceEnumerator.java
index 7c79596e8..5545e9a50 100644
---
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/enumerator/TieringSourceEnumerator.java
+++
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/enumerator/TieringSourceEnumerator.java
@@ -449,11 +449,11 @@ public class TieringSourceEnumerator
try {
TablePath tablePath = tieringTable.f2;
final TableInfo tableInfo =
flussAdmin.getTableInfo(tablePath).get();
- List<TieringSplit> tieringSplits =
-
populateNumberOfTieringSplits(splitGenerator.generateTableSplits(tableInfo));
+ List<TieringSplit> tieringSplits =
splitGenerator.generateTableSplits(tableInfo);
// shuffle tiering split to avoid splits tiering skew
// after introduce tiering max duration
Collections.shuffle(tieringSplits);
+ tieringSplits = populateTieringRoundMetadata(tieringSplits);
LOG.info(
"Generate Tiering {} splits for table {} with cost {}ms.",
tieringSplits.size(),
@@ -487,11 +487,20 @@ public class TieringSourceEnumerator
}
}
- private List<TieringSplit>
populateNumberOfTieringSplits(List<TieringSplit> tieringSplits) {
+ private List<TieringSplit> populateTieringRoundMetadata(List<TieringSplit>
tieringSplits) {
int numberOfSplits = tieringSplits.size();
- return tieringSplits.stream()
- .map(split -> split.copy(numberOfSplits))
- .collect(Collectors.toList());
+ if (numberOfSplits == 0) {
+ return Collections.emptyList();
+ }
+ long tieringRoundTimestamp = System.currentTimeMillis();
+ List<TieringSplit> splitsWithMetadata = new
ArrayList<>(numberOfSplits);
+ for (int splitIndex = 0; splitIndex < numberOfSplits; splitIndex++) {
+ splitsWithMetadata.add(
+ tieringSplits
+ .get(splitIndex)
+ .copy(numberOfSplits, splitIndex,
tieringRoundTimestamp));
+ }
+ return splitsWithMetadata;
}
@Override
diff --git
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/split/TieringLogSplit.java
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/split/TieringLogSplit.java
index afd8d75f8..a1e983b50 100644
---
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/split/TieringLogSplit.java
+++
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/split/TieringLogSplit.java
@@ -64,6 +64,27 @@ public class TieringLogSplit extends TieringSplit {
false);
}
+ public TieringLogSplit(
+ TablePath tablePath,
+ TableBucket tableBucket,
+ @Nullable String partitionName,
+ long startingOffset,
+ long stoppingOffset,
+ int numberOfSplits,
+ int splitIndex,
+ long tieringRoundTimestamp) {
+ this(
+ tablePath,
+ tableBucket,
+ partitionName,
+ startingOffset,
+ stoppingOffset,
+ numberOfSplits,
+ false,
+ splitIndex,
+ tieringRoundTimestamp);
+ }
+
public TieringLogSplit(
TablePath tablePath,
TableBucket tableBucket,
@@ -72,7 +93,36 @@ public class TieringLogSplit extends TieringSplit {
long stoppingOffset,
int numberOfSplits,
boolean skipCurrentRound) {
- super(tablePath, tableBucket, partitionName, numberOfSplits,
skipCurrentRound);
+ this(
+ tablePath,
+ tableBucket,
+ partitionName,
+ startingOffset,
+ stoppingOffset,
+ numberOfSplits,
+ skipCurrentRound,
+ UNKNOWN_SPLIT_INDEX,
+ UNKNOWN_TIERING_ROUND_TIMESTAMP);
+ }
+
+ public TieringLogSplit(
+ TablePath tablePath,
+ TableBucket tableBucket,
+ @Nullable String partitionName,
+ long startingOffset,
+ long stoppingOffset,
+ int numberOfSplits,
+ boolean skipCurrentRound,
+ int splitIndex,
+ long tieringRoundTimestamp) {
+ super(
+ tablePath,
+ tableBucket,
+ partitionName,
+ numberOfSplits,
+ skipCurrentRound,
+ splitIndex,
+ tieringRoundTimestamp);
this.startingOffset = startingOffset;
this.stoppingOffset = stoppingOffset;
}
@@ -108,11 +158,15 @@ public class TieringLogSplit extends TieringSplit {
+ startingOffset
+ ", stoppingOffset="
+ stoppingOffset
+ + ", splitIndex="
+ + splitIndex
+ + ", tieringRoundTimestamp="
+ + tieringRoundTimestamp
+ '}';
}
@Override
- public TieringLogSplit copy(int numberOfSplits) {
+ public TieringLogSplit copy(int numberOfSplits, int splitIndex, long
tieringRoundTimestamp) {
return new TieringLogSplit(
tablePath,
tableBucket,
@@ -120,7 +174,9 @@ public class TieringLogSplit extends TieringSplit {
startingOffset,
stoppingOffset,
numberOfSplits,
- skipCurrentRound);
+ skipCurrentRound,
+ splitIndex,
+ tieringRoundTimestamp);
}
@Override
diff --git
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/split/TieringSnapshotSplit.java
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/split/TieringSnapshotSplit.java
index e6b37a925..8c3bad364 100644
---
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/split/TieringSnapshotSplit.java
+++
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/split/TieringSnapshotSplit.java
@@ -55,6 +55,27 @@ public class TieringSnapshotSplit extends TieringSplit {
false);
}
+ public TieringSnapshotSplit(
+ TablePath tablePath,
+ TableBucket tableBucket,
+ @Nullable String partitionName,
+ long snapshotId,
+ long logOffsetOfSnapshot,
+ int numberOfSplits,
+ int splitIndex,
+ long tieringRoundTimestamp) {
+ this(
+ tablePath,
+ tableBucket,
+ partitionName,
+ snapshotId,
+ logOffsetOfSnapshot,
+ numberOfSplits,
+ false,
+ splitIndex,
+ tieringRoundTimestamp);
+ }
+
public TieringSnapshotSplit(
TablePath tablePath,
TableBucket tableBucket,
@@ -63,7 +84,36 @@ public class TieringSnapshotSplit extends TieringSplit {
long logOffsetOfSnapshot,
int numberOfSplits,
boolean skipCurrentRound) {
- super(tablePath, tableBucket, partitionName, numberOfSplits,
skipCurrentRound);
+ this(
+ tablePath,
+ tableBucket,
+ partitionName,
+ snapshotId,
+ logOffsetOfSnapshot,
+ numberOfSplits,
+ skipCurrentRound,
+ UNKNOWN_SPLIT_INDEX,
+ UNKNOWN_TIERING_ROUND_TIMESTAMP);
+ }
+
+ public TieringSnapshotSplit(
+ TablePath tablePath,
+ TableBucket tableBucket,
+ @Nullable String partitionName,
+ long snapshotId,
+ long logOffsetOfSnapshot,
+ int numberOfSplits,
+ boolean skipCurrentRound,
+ int splitIndex,
+ long tieringRoundTimestamp) {
+ super(
+ tablePath,
+ tableBucket,
+ partitionName,
+ numberOfSplits,
+ skipCurrentRound,
+ splitIndex,
+ tieringRoundTimestamp);
this.snapshotId = snapshotId;
this.logOffsetOfSnapshot = logOffsetOfSnapshot;
}
@@ -99,11 +149,16 @@ public class TieringSnapshotSplit extends TieringSplit {
+ snapshotId
+ ", logOffsetOfSnapshot="
+ logOffsetOfSnapshot
+ + ", splitIndex="
+ + splitIndex
+ + ", tieringRoundTimestamp="
+ + tieringRoundTimestamp
+ '}';
}
@Override
- public TieringSnapshotSplit copy(int numberOfSplits) {
+ public TieringSnapshotSplit copy(
+ int numberOfSplits, int splitIndex, long tieringRoundTimestamp) {
return new TieringSnapshotSplit(
tablePath,
tableBucket,
@@ -111,7 +166,9 @@ public class TieringSnapshotSplit extends TieringSplit {
snapshotId,
logOffsetOfSnapshot,
numberOfSplits,
- skipCurrentRound);
+ skipCurrentRound,
+ splitIndex,
+ tieringRoundTimestamp);
}
@Override
diff --git
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/split/TieringSplit.java
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/split/TieringSplit.java
index d6827a62c..b0ea25693 100644
---
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/split/TieringSplit.java
+++
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/split/TieringSplit.java
@@ -31,6 +31,8 @@ public abstract class TieringSplit implements SourceSplit {
public static final byte TIERING_SNAPSHOT_SPLIT_FLAG = 1;
public static final byte TIERING_LOG_SPLIT_FLAG = 2;
+ public static final int UNKNOWN_SPLIT_INDEX = -1;
+ public static final long UNKNOWN_TIERING_ROUND_TIMESTAMP = -1L;
protected static final int UNKNOWN_NUMBER_OF_SPLITS = -1;
@@ -40,6 +42,10 @@ public abstract class TieringSplit implements SourceSplit {
// the total number of splits in one round of tiering
protected final int numberOfSplits;
+ // the split index in one round of tiering
+ protected final int splitIndex;
+ // the timestamp when one round of tiering was generated
+ protected final long tieringRoundTimestamp;
/**
* Indicates whether to skip tiering data for this split in the current
round of tiering. When
@@ -53,6 +59,24 @@ public abstract class TieringSplit implements SourceSplit {
@Nullable String partitionName,
int numberOfSplits,
boolean skipCurrentRound) {
+ this(
+ tablePath,
+ tableBucket,
+ partitionName,
+ numberOfSplits,
+ skipCurrentRound,
+ UNKNOWN_SPLIT_INDEX,
+ UNKNOWN_TIERING_ROUND_TIMESTAMP);
+ }
+
+ public TieringSplit(
+ TablePath tablePath,
+ TableBucket tableBucket,
+ @Nullable String partitionName,
+ int numberOfSplits,
+ boolean skipCurrentRound,
+ int splitIndex,
+ long tieringRoundTimestamp) {
this.tablePath = tablePath;
this.tableBucket = tableBucket;
this.partitionName = partitionName;
@@ -63,6 +87,10 @@ public abstract class TieringSplit implements SourceSplit {
}
this.numberOfSplits = numberOfSplits;
this.skipCurrentRound = skipCurrentRound;
+ validateSplitIndex(numberOfSplits, splitIndex);
+ validateTieringRoundTimestamp(tieringRoundTimestamp);
+ this.splitIndex = splitIndex;
+ this.tieringRoundTimestamp = tieringRoundTimestamp;
}
/** Checks whether this split is a primary key table split to tier. */
@@ -116,6 +144,18 @@ public abstract class TieringSplit implements SourceSplit {
return numberOfSplits;
}
+ public int getSplitIndex() {
+ return splitIndex;
+ }
+
+ public boolean isFirstSplit() {
+ return splitIndex == 0;
+ }
+
+ public long getTieringRoundTimestamp() {
+ return tieringRoundTimestamp;
+ }
+
protected static String toSplitId(String splitPrefix, TableBucket
tableBucket) {
if (tableBucket.getPartitionId() != null) {
return splitPrefix
@@ -142,7 +182,12 @@ public abstract class TieringSplit implements SourceSplit {
return partitionName;
}
- public abstract TieringSplit copy(int numberOfSplits);
+ public TieringSplit copy(int numberOfSplits) {
+ return copy(numberOfSplits, splitIndex, tieringRoundTimestamp);
+ }
+
+ public abstract TieringSplit copy(
+ int numberOfSplits, int splitIndex, long tieringRoundTimestamp);
@Override
public boolean equals(Object object) {
@@ -154,12 +199,39 @@ public abstract class TieringSplit implements SourceSplit
{
&& Objects.equals(tableBucket, that.tableBucket)
&& Objects.equals(partitionName, that.partitionName)
&& numberOfSplits == that.numberOfSplits
- && skipCurrentRound == that.skipCurrentRound;
+ && skipCurrentRound == that.skipCurrentRound
+ && splitIndex == that.splitIndex
+ && tieringRoundTimestamp == that.tieringRoundTimestamp;
}
@Override
public int hashCode() {
return Objects.hash(
- tablePath, tableBucket, partitionName, numberOfSplits,
skipCurrentRound);
+ tablePath,
+ tableBucket,
+ partitionName,
+ numberOfSplits,
+ skipCurrentRound,
+ splitIndex,
+ tieringRoundTimestamp);
+ }
+
+ private static void validateSplitIndex(int numberOfSplits, int splitIndex)
{
+ if (splitIndex < UNKNOWN_SPLIT_INDEX) {
+ throw new IllegalArgumentException("Split index must be -1 or
non-negative.");
+ }
+ if (splitIndex != UNKNOWN_SPLIT_INDEX
+ && numberOfSplits != UNKNOWN_NUMBER_OF_SPLITS
+ && (numberOfSplits <= 0 || splitIndex >= numberOfSplits)) {
+ throw new IllegalArgumentException(
+ "Split index must be smaller than the number of splits.");
+ }
+ }
+
+ private static void validateTieringRoundTimestamp(long
tieringRoundTimestamp) {
+ if (tieringRoundTimestamp != UNKNOWN_TIERING_ROUND_TIMESTAMP
+ && tieringRoundTimestamp <= 0) {
+ throw new IllegalArgumentException("Tiering round timestamp must
be -1 or positive.");
+ }
}
}
diff --git
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/split/TieringSplitSerializer.java
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/split/TieringSplitSerializer.java
index 62c4f7886..e336ee467 100644
---
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/split/TieringSplitSerializer.java
+++
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/split/TieringSplitSerializer.java
@@ -86,6 +86,9 @@ public class TieringSplitSerializer implements
SimpleVersionedSerializer<Tiering
out.writeInt(split.getNumberOfSplits());
// write skipCurrentRound
out.writeBoolean(split.shouldSkipCurrentRound());
+ // write split metadata for this tiering round
+ out.writeInt(split.getSplitIndex());
+ out.writeLong(split.getTieringRoundTimestamp());
if (split.isTieringSnapshotSplit()) {
// Snapshot split
TieringSnapshotSplit tieringSnapshotSplit =
split.asTieringSnapshotSplit();
@@ -139,6 +142,8 @@ public class TieringSplitSerializer implements
SimpleVersionedSerializer<Tiering
// deserialize number of splits
int numberOfSplits = in.readInt();
boolean skipCurrentRound = in.readBoolean();
+ int splitIndex = in.readInt();
+ long tieringRoundTimestamp = in.readLong();
if (splitKind == TIERING_SNAPSHOT_SPLIT_FLAG) {
// deserialize snapshot id
@@ -152,7 +157,9 @@ public class TieringSplitSerializer implements
SimpleVersionedSerializer<Tiering
snapshotId,
logOffsetOfSnapshot,
numberOfSplits,
- skipCurrentRound);
+ skipCurrentRound,
+ splitIndex,
+ tieringRoundTimestamp);
} else {
// deserialize starting offset
long startingOffset = in.readLong();
@@ -165,7 +172,9 @@ public class TieringSplitSerializer implements
SimpleVersionedSerializer<Tiering
startingOffset,
stoppingOffset,
numberOfSplits,
- skipCurrentRound);
+ skipCurrentRound,
+ splitIndex,
+ tieringRoundTimestamp);
}
}
}
diff --git
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/state/TieringSplitState.java
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/state/TieringSplitState.java
index 1b5be62b7..0690da628 100644
---
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/state/TieringSplitState.java
+++
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/tiering/source/state/TieringSplitState.java
@@ -45,7 +45,10 @@ public class TieringSplitState {
split.getPartitionName(),
split.getSnapshotId(),
split.getLogOffsetOfSnapshot(),
- split.getNumberOfSplits());
+ split.getNumberOfSplits(),
+ split.shouldSkipCurrentRound(),
+ split.getSplitIndex(),
+ split.getTieringRoundTimestamp());
} else {
final TieringLogSplit split = (TieringLogSplit) tieringSplit;
return new TieringLogSplit(
@@ -54,7 +57,10 @@ public class TieringSplitState {
split.getPartitionName(),
split.getStartingOffset(),
split.getStoppingOffset(),
- split.getNumberOfSplits());
+ split.getNumberOfSplits(),
+ split.shouldSkipCurrentRound(),
+ split.getSplitIndex(),
+ split.getTieringRoundTimestamp());
}
}
}
diff --git
a/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/tiering/source/enumerator/TieringSourceEnumeratorTest.java
b/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/tiering/source/enumerator/TieringSourceEnumeratorTest.java
index 446534688..9e7975d0c 100644
---
a/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/tiering/source/enumerator/TieringSourceEnumeratorTest.java
+++
b/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/tiering/source/enumerator/TieringSourceEnumeratorTest.java
@@ -142,6 +142,8 @@ class TieringSourceEnumeratorTest extends TieringTestBase {
List<TieringSplit> actualLogAssignment = new ArrayList<>();
context.getSplitsAssignmentSequence()
.forEach(a ->
a.assignment().values().forEach(actualLogAssignment::addAll));
+ assertTieringRoundMetadata(actualLogAssignment);
+ clearTieringSplitMetadata(actualLogAssignment);
assertThat(actualLogAssignment)
.containsExactlyInAnyOrderElementsOf(expectedLogAssignment);
}
@@ -185,6 +187,8 @@ class TieringSourceEnumeratorTest extends TieringTestBase {
List<TieringSplit> actualAssignment = new ArrayList<>();
context.getSplitsAssignmentSequence()
.forEach(a ->
a.assignment().values().forEach(actualAssignment::addAll));
+ assertTieringRoundMetadata(actualAssignment);
+ clearTieringSplitMetadata(actualAssignment);
assertThat(actualAssignment)
.containsExactlyInAnyOrderElementsOf(expectedSnapshotAssignment);
@@ -230,6 +234,7 @@ class TieringSourceEnumeratorTest extends TieringTestBase {
List<TieringSplit> actualLogAssignment = new ArrayList<>();
context.getSplitsAssignmentSequence()
.forEach(a ->
a.assignment().values().forEach(actualLogAssignment::addAll));
+ clearTieringSplitMetadata(actualLogAssignment);
assertThat(actualLogAssignment)
.containsExactlyInAnyOrderElementsOf(expectedLogAssignment);
}
@@ -273,6 +278,7 @@ class TieringSourceEnumeratorTest extends TieringTestBase {
context.getSplitsAssignmentSequence()
.forEach(a ->
a.assignment().values().forEach(actualAssignment::addAll));
+ clearTieringSplitMetadata(actualAssignment);
assertThat(actualAssignment).containsExactlyInAnyOrderElementsOf(expectedAssignment);
// mock finished tiered this round, check second round
@@ -316,6 +322,7 @@ class TieringSourceEnumeratorTest extends TieringTestBase {
List<TieringSplit> actualLogAssignment = new ArrayList<>();
context.getSplitsAssignmentSequence()
.forEach(a ->
a.assignment().values().forEach(actualLogAssignment::addAll));
+ clearTieringSplitMetadata(actualLogAssignment);
assertThat(actualLogAssignment)
.containsExactlyInAnyOrderElementsOf(expectedLogAssignment);
}
@@ -416,6 +423,7 @@ class TieringSourceEnumeratorTest extends TieringTestBase {
context.getSplitsAssignmentSequence()) {
splitsAssignment.assignment().values().forEach(actualLogAssignment::addAll);
}
+ clearTieringSplitMetadata(actualLogAssignment);
assertThat(actualLogAssignment)
.containsExactlyInAnyOrderElementsOf(expectedLogAssignment);
}
@@ -472,6 +480,7 @@ class TieringSourceEnumeratorTest extends TieringTestBase {
context.getSplitsAssignmentSequence()) {
splitsAssignment.assignment().values().forEach(actualAssignment::addAll);
}
+ clearTieringSplitMetadata(actualAssignment);
assertThat(actualAssignment).containsExactlyInAnyOrderElementsOf(expectedAssignment);
// mock finished tiered this round, check second round
@@ -539,6 +548,7 @@ class TieringSourceEnumeratorTest extends TieringTestBase {
context.getSplitsAssignmentSequence()) {
splitsAssignment.assignment().values().forEach(actualLogAssignment::addAll);
}
+ clearTieringSplitMetadata(actualLogAssignment);
assertThat(actualLogAssignment)
.containsExactlyInAnyOrderElementsOf(expectedLogAssignment);
}
@@ -579,6 +589,7 @@ class TieringSourceEnumeratorTest extends TieringTestBase {
context.getSplitsAssignmentSequence()
.forEach(a ->
a.assignment().values().forEach(actualAssignment::addAll));
+ clearTieringSplitMetadata(actualAssignment);
assertThat(actualAssignment).containsExactlyInAnyOrderElementsOf(expectedAssignment);
// mock tiering fail by send tiering fail event
@@ -593,6 +604,7 @@ class TieringSourceEnumeratorTest extends TieringTestBase {
List<TieringSplit> actualAssignment1 = new ArrayList<>();
context.getSplitsAssignmentSequence()
.forEach(a ->
a.assignment().values().forEach(actualAssignment1::addAll));
+ clearTieringSplitMetadata(actualAssignment1);
assertThat(actualAssignment1).containsExactlyInAnyOrderElementsOf(expectedAssignment);
}
}
@@ -673,6 +685,38 @@ class TieringSourceEnumeratorTest extends TieringTestBase {
}
// --------------------- Test Utils ---------------------
+ private static void assertTieringRoundMetadata(List<TieringSplit>
tieringSplits) {
+ assertThat(tieringSplits).isNotEmpty();
+
assertThat(tieringSplits).filteredOn(TieringSplit::isFirstSplit).hasSize(1);
+ assertThat(tieringSplits)
+ .extracting(TieringSplit::getSplitIndex)
+ .containsExactlyInAnyOrderElementsOf(
+ java.util.stream.IntStream.range(0,
tieringSplits.size())
+ .boxed()
+ .collect(Collectors.toList()));
+ long tieringRoundTimestamp =
tieringSplits.get(0).getTieringRoundTimestamp();
+ assertThat(tieringRoundTimestamp).isPositive();
+ assertThat(tieringSplits)
+ .allSatisfy(
+ split -> {
+
assertThat(split.getNumberOfSplits()).isEqualTo(tieringSplits.size());
+ assertThat(split.getTieringRoundTimestamp())
+ .isEqualTo(tieringRoundTimestamp);
+ });
+ }
+
+ private static void clearTieringSplitMetadata(List<TieringSplit>
tieringSplits) {
+ for (int i = 0; i < tieringSplits.size(); i++) {
+ TieringSplit tieringSplit = tieringSplits.get(i);
+ tieringSplits.set(
+ i,
+ tieringSplit.copy(
+ tieringSplit.getNumberOfSplits(),
+ TieringSplit.UNKNOWN_SPLIT_INDEX,
+ TieringSplit.UNKNOWN_TIERING_ROUND_TIMESTAMP));
+ }
+ }
+
private void registerReaderAndHandleSplitRequests(
FlussMockSplitEnumeratorContext<TieringSplit> context,
TieringSourceEnumerator enumerator,
diff --git
a/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/tiering/source/split/TieringSplitSerializerTest.java
b/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/tiering/source/split/TieringSplitSerializerTest.java
index 45cbd76a8..ef7de7f08 100644
---
a/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/tiering/source/split/TieringSplitSerializerTest.java
+++
b/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/tiering/source/split/TieringSplitSerializerTest.java
@@ -69,8 +69,8 @@ class TieringSplitSerializerTest {
String expectedSplitString =
isPartitionedTable
- ?
"TieringSnapshotSplit{tablePath=test_db.test_partitioned_table,
tableBucket=TableBucket{tableId=1, partitionId=100, bucket=2},
partitionName='1024', numberOfSplits=30, skipCurrentRound=false, snapshotId=0,
logOffsetOfSnapshot=200}"
- : "TieringSnapshotSplit{tablePath=test_db.test_table,
tableBucket=TableBucket{tableId=1, bucket=2}, partitionName='null',
numberOfSplits=30, skipCurrentRound=false, snapshotId=0,
logOffsetOfSnapshot=200}";
+ ?
"TieringSnapshotSplit{tablePath=test_db.test_partitioned_table,
tableBucket=TableBucket{tableId=1, partitionId=100, bucket=2},
partitionName='1024', numberOfSplits=30, skipCurrentRound=false, snapshotId=0,
logOffsetOfSnapshot=200, splitIndex=-1, tieringRoundTimestamp=-1}"
+ : "TieringSnapshotSplit{tablePath=test_db.test_table,
tableBucket=TableBucket{tableId=1, bucket=2}, partitionName='null',
numberOfSplits=30, skipCurrentRound=false, snapshotId=0,
logOffsetOfSnapshot=200, splitIndex=-1, tieringRoundTimestamp=-1}";
assertThat(new TieringSnapshotSplit(path, bucket, partitionName, 0L,
200L, 30).toString())
.isEqualTo(expectedSplitString);
}
@@ -103,8 +103,8 @@ class TieringSplitSerializerTest {
String expectedSplitString =
isPartitionedTable
- ?
"TieringLogSplit{tablePath=test_db.test_partitioned_table,
tableBucket=TableBucket{tableId=1, partitionId=100, bucket=2},
partitionName='1024', numberOfSplits=2, skipCurrentRound=false,
startingOffset=100, stoppingOffset=200}"
- : "TieringLogSplit{tablePath=test_db.test_table,
tableBucket=TableBucket{tableId=1, bucket=2}, partitionName='null',
numberOfSplits=2, skipCurrentRound=false, startingOffset=100,
stoppingOffset=200}";
+ ?
"TieringLogSplit{tablePath=test_db.test_partitioned_table,
tableBucket=TableBucket{tableId=1, partitionId=100, bucket=2},
partitionName='1024', numberOfSplits=2, skipCurrentRound=false,
startingOffset=100, stoppingOffset=200, splitIndex=-1,
tieringRoundTimestamp=-1}"
+ : "TieringLogSplit{tablePath=test_db.test_table,
tableBucket=TableBucket{tableId=1, bucket=2}, partitionName='null',
numberOfSplits=2, skipCurrentRound=false, startingOffset=100,
stoppingOffset=200, splitIndex=-1, tieringRoundTimestamp=-1}";
assertThat(new TieringLogSplit(path, bucket, partitionName, 100, 200,
2).toString())
.isEqualTo(expectedSplitString);
}
@@ -151,4 +151,49 @@ class TieringSplitSerializerTest {
(TieringLogSplit)
serializer.deserialize(serializer.getVersion(), serialized);
assertThat(deserializedLogSplit).isEqualTo(logSplit);
}
+
+ @Test
+ void testTieringRoundMetadataSerde() throws Exception {
+ TieringSnapshotSplit snapshotSplit =
+ new TieringSnapshotSplit(tablePath, tableBucket, null, 0L,
200L, 10, 0, 1000L);
+ byte[] serialized = serializer.serialize(snapshotSplit);
+ TieringSnapshotSplit deserializedSnapshotSplit =
+ (TieringSnapshotSplit)
serializer.deserialize(serializer.getVersion(), serialized);
+ assertThat(deserializedSnapshotSplit.getSplitIndex()).isZero();
+ assertThat(deserializedSnapshotSplit.isFirstSplit()).isTrue();
+
assertThat(deserializedSnapshotSplit.getTieringRoundTimestamp()).isEqualTo(1000L);
+
+ TieringLogSplit logSplit =
+ new TieringLogSplit(tablePath, tableBucket, null, 100, 200,
40, 2, 2000L);
+ serialized = serializer.serialize(logSplit);
+ TieringLogSplit deserializedLogSplit =
+ (TieringLogSplit)
serializer.deserialize(serializer.getVersion(), serialized);
+ assertThat(deserializedLogSplit.getSplitIndex()).isEqualTo(2);
+ assertThat(deserializedLogSplit.isFirstSplit()).isFalse();
+
assertThat(deserializedLogSplit.getTieringRoundTimestamp()).isEqualTo(2000L);
+ }
+
+ @Test
+ void testTieringRoundMetadataParticipatesInSplitIdentity() {
+ TieringSnapshotSplit snapshotSplit =
+ new TieringSnapshotSplit(tablePath, tableBucket, null, 0L,
200L, 10, 0, 1000L);
+ TieringSnapshotSplit snapshotSplitWithDifferentMetadata =
+ new TieringSnapshotSplit(tablePath, tableBucket, null, 0L,
200L, 10, 1, 2000L);
+
assertThat(snapshotSplit).isNotEqualTo(snapshotSplitWithDifferentMetadata);
+
+ TieringLogSplit logSplit =
+ new TieringLogSplit(tablePath, tableBucket, null, 100, 200,
40, 0, 1000L);
+ TieringLogSplit logSplitWithDifferentMetadata =
+ new TieringLogSplit(tablePath, tableBucket, null, 100, 200,
40, 1, 2000L);
+ assertThat(logSplit).isNotEqualTo(logSplitWithDifferentMetadata);
+ }
+
+ @Test
+ void testLogSplitDefaultTieringRoundMetadataIsUnknown() {
+ TieringLogSplit logSplit = new TieringLogSplit(tablePath, tableBucket,
null, 100, 200);
+
+
assertThat(logSplit.getSplitIndex()).isEqualTo(TieringSplit.UNKNOWN_SPLIT_INDEX);
+ assertThat(logSplit.getTieringRoundTimestamp())
+ .isEqualTo(TieringSplit.UNKNOWN_TIERING_ROUND_TIMESTAMP);
+ }
}
diff --git
a/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/tiering/source/state/TieringSplitStateTest.java
b/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/tiering/source/state/TieringSplitStateTest.java
index e51072872..ebc08553e 100644
---
a/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/tiering/source/state/TieringSplitStateTest.java
+++
b/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/tiering/source/state/TieringSplitStateTest.java
@@ -36,9 +36,16 @@ class TieringSplitStateTest {
// verify conversion between TieringSnapshotSplitState and
TieringSnapshotSplit
TieringSnapshotSplit tieringSnapshotSplit =
- new TieringSnapshotSplit(tablePath, tableBucket, "partition1",
0L, 200L, 10);
+ new TieringSnapshotSplit(
+ tablePath, tableBucket, "partition1", 0L, 200L, 10, 0,
1000L);
+ tieringSnapshotSplit.skipCurrentRound();
TieringSplitState tieringSnapshotSplitState = new
TieringSplitState(tieringSnapshotSplit);
-
assertThat(tieringSnapshotSplitState.toSourceSplit()).isEqualTo(tieringSnapshotSplit);
+ TieringSnapshotSplit restoredSplit =
+ (TieringSnapshotSplit)
tieringSnapshotSplitState.toSourceSplit();
+ assertThat(restoredSplit).isEqualTo(tieringSnapshotSplit);
+ assertThat(restoredSplit.shouldSkipCurrentRound()).isTrue();
+ assertThat(restoredSplit.getSplitIndex()).isZero();
+ assertThat(restoredSplit.getTieringRoundTimestamp()).isEqualTo(1000L);
}
@Test
@@ -48,8 +55,13 @@ class TieringSplitStateTest {
// verify conversion between TieringLogSplitState and TieringLogSplit
TieringLogSplit tieringLogSplit =
- new TieringLogSplit(tablePath, tableBucket, "partition1",
100L, 200L, 20);
+ new TieringLogSplit(tablePath, tableBucket, "partition1",
100L, 200L, 20, 1, 2000L);
+ tieringLogSplit.skipCurrentRound();
TieringSplitState tieringLogSplitState = new
TieringSplitState(tieringLogSplit);
-
assertThat(tieringLogSplitState.toSourceSplit()).isEqualTo(tieringLogSplit);
+ TieringLogSplit restoredSplit = (TieringLogSplit)
tieringLogSplitState.toSourceSplit();
+ assertThat(restoredSplit).isEqualTo(tieringLogSplit);
+ assertThat(restoredSplit.shouldSkipCurrentRound()).isTrue();
+ assertThat(restoredSplit.getSplitIndex()).isEqualTo(1);
+ assertThat(restoredSplit.getTieringRoundTimestamp()).isEqualTo(2000L);
}
}