This is an automated email from the ASF dual-hosted git repository.
jt2594838 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new 35b9b708687 Fix IoTConsensusV2 progress for split LOAD TsFiles (#18927)
35b9b708687 is described below
commit 35b9b708687cd64f8f7c8b78da8da7ba41486b47
Author: Caideyipi <[email protected]>
AuthorDate: Tue Oct 20 14:57:17 2026 +0800
Fix IoTConsensusV2 progress for split LOAD TsFiles (#18927)
---
.../db/storageengine/load/TsFileWriterManager.java | 11 +++-
.../storageengine/load/LoadTsFileManagerTest.java | 70 ++++++++++++++++++++++
2 files changed, 80 insertions(+), 1 deletion(-)
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/TsFileWriterManager.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/TsFileWriterManager.java
index fc8f38b8502..d3d9efb1345 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/TsFileWriterManager.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/TsFileWriterManager.java
@@ -31,6 +31,7 @@ import org.apache.iotdb.commons.utils.RetryUtils;
import org.apache.iotdb.db.conf.IoTDBDescriptor;
import org.apache.iotdb.db.exception.load.LoadFileException;
import org.apache.iotdb.db.i18n.StorageEngineMessages;
+import org.apache.iotdb.db.pipe.agent.PipeDataNodeAgent;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.load.LoadTsFileConsensusNode;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.load.LoadTsFileConsensusOp;
import org.apache.iotdb.db.schemaengine.table.DataNodeTableCache;
@@ -532,7 +533,7 @@ final class TsFileWriterManager {
/** Formally imports sealed staged TsFiles into the target storage engine
data regions. */
void loadAll(final boolean isGeneratedByPipe, final boolean
deleteStagedSource)
- throws LoadFileException {
+ throws IOException, LoadFileException {
for (final Map.Entry<TTimePartitionSlot, TsFileResource> entry :
timePartition2Resource.entrySet()) {
final DataRegion targetDataRegion = dataRegion;
@@ -547,6 +548,14 @@ final class TsFileWriterManager {
uuidDirName(tsFileResource.getTsFile())));
}
+ if (LoadConsensusCompat.usesIoTConsensusV2()) {
+ // V2 only replicates files carrying this node's progress. The
coordinator's index may
+ // belong to another node and precede writes between PREPARE and
COMMIT. Assign progress
+ // at import, and persist it so recovery keeps the file eligible for
replication.
+
PipeDataNodeAgent.runtime().assignProgressIndexForTsFileLoad(tsFileResource);
+ tsFileResource.serialize();
+ }
+
targetDataRegion.loadNewTsFile(
tsFileResource, deleteStagedSource, isGeneratedByPipe, false,
Optional.empty());
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/load/LoadTsFileManagerTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/load/LoadTsFileManagerTest.java
index 360fa83c6af..a3d24733167 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/load/LoadTsFileManagerTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/load/LoadTsFileManagerTest.java
@@ -20,16 +20,22 @@
package org.apache.iotdb.db.storageengine.load;
import org.apache.iotdb.common.rpc.thrift.TTimePartitionSlot;
+import org.apache.iotdb.commons.consensus.index.ProgressIndex;
+import org.apache.iotdb.commons.consensus.index.impl.RecoverProgressIndex;
import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeId;
import org.apache.iotdb.consensus.iot.log.ConsensusReqReader;
import org.apache.iotdb.db.conf.IoTDBConfig;
import org.apache.iotdb.db.conf.IoTDBDescriptor;
import org.apache.iotdb.db.exception.load.LoadFileException;
+import org.apache.iotdb.db.pipe.agent.PipeDataNodeAgent;
+import org.apache.iotdb.db.pipe.event.common.tsfile.PipeTsFileInsertionEvent;
+import
org.apache.iotdb.db.pipe.processor.iotconsensusv2.IoTConsensusV2Processor;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.load.LoadTsFileConsensusNode;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.load.LoadTsFileConsensusOp;
import
org.apache.iotdb.db.queryengine.plan.scheduler.load.ChunkOffsetCalculator;
import org.apache.iotdb.db.storageengine.dataregion.DataRegion;
import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource;
+import
org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResourceStatus;
import org.apache.iotdb.db.storageengine.dataregion.wal.node.IWALNode;
import org.apache.iotdb.db.storageengine.dataregion.wal.node.WALNode;
import org.apache.iotdb.db.storageengine.dataregion.wal.utils.WALMode;
@@ -798,6 +804,70 @@ public class LoadTsFileManagerTest {
assertFalse(manager.prepare(prepareNode("in-progress-load"),
Collections.emptyMap()));
}
+ @Test
+ public void testIoTConsensusV2CommitUsesReceivingNodeProgress() throws
Exception {
+ assertCommittedProgress(true, config.getDataNodeId() + 1);
+ }
+
+ @Test
+ public void testIoTConsensusV2CommitAdvancesPreparedProgress() throws
Exception {
+ assertCommittedProgress(true, config.getDataNodeId());
+ }
+
+ @Test
+ public void testOtherConsensusCommitPreservesCoordinatorProgress() throws
Exception {
+ assertCommittedProgress(false, config.getDataNodeId() + 1);
+ }
+
+ private void assertCommittedProgress(final boolean useIoTConsensusV2, final
int coordinatorNodeId)
+ throws Exception {
+ LoadConsensusCompat.setIoTConsensusV2ForTest(useIoTConsensusV2);
+ final String loadId = "load-progress";
+ final LoadTsFileManager manager = new LoadTsFileManager(dataRegion);
+ try {
+ emulateStagedFileImport();
+ final LoadTsFileConsensusNode piece = stagedPiece(loadId,
laidOutChunk());
+ manager.writePiece(piece);
+ final File staged = new File(tempDir,
piece.getPieceRefs().get(0).getRelativePath());
+ final RecoverProgressIndex preparedLocalProgress =
+ PipeDataNodeAgent.runtime().getNextProgressIndexForTsFileLoad();
+ final ProgressIndex coordinatorProgress =
+ new RecoverProgressIndex(
+ coordinatorNodeId,
+
preparedLocalProgress.getDataNodeId2LocalIndex().get(config.getDataNodeId()));
+ assertTrue(
+ manager.prepare(
+ prepareNode(loadId),
+ Collections.singletonMap(new TTimePartitionSlot(0L),
coordinatorProgress)));
+
+ // A local write after PREPARE must precede the progress assigned when
the file is imported.
+ final ProgressIndex interveningWrite =
+ PipeDataNodeAgent.runtime().getNextProgressIndexForTsFileLoad();
+ final LoadTsFileConsensusNode commit = commitNode(loadId);
+ assertTrue(manager.loadAll(commit, Collections.emptyMap()));
+
+ final TsFileResource imported =
+ new TsFileResource(new File(new File(tempDir, "data"),
staged.getName()));
+ imported.deserialize();
+ imported.setStatus(TsFileResourceStatus.NORMAL);
+ final ProgressIndex persistedProgress =
imported.getMaxProgressIndexAfterClose();
+ final PipeTsFileInsertionEvent event =
Mockito.mock(PipeTsFileInsertionEvent.class);
+
Mockito.when(event.forceGetProgressIndex()).thenReturn(persistedProgress);
+ assertEquals(
+ "A V2 LOAD must remain eligible for consensus replication after
resource recovery",
+ useIoTConsensusV2,
+ IoTConsensusV2Processor.isShouldReplicate(event));
+ if (useIoTConsensusV2) {
+ assertTrue(persistedProgress.isAfter(interveningWrite));
+ } else {
+ assertEquals(coordinatorProgress, persistedProgress);
+ }
+ } finally {
+ manager.stop();
+ LoadConsensusCompat.setIoTConsensusV2ForTest(null);
+ }
+ }
+
@Test
public void testCommitKeepsTheStagedBytesWhileAReplicaMayStillNeedThem()
throws Exception {
// The watermark lags behind the COMMIT command, so a replica that catches
up from this node's