Skip to content

Commit 35b9b70

Browse files
authored
Fix IoTConsensusV2 progress for split LOAD TsFiles (#18927)
1 parent c462a97 commit 35b9b70

2 files changed

Lines changed: 80 additions & 1 deletion

File tree

‎iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/TsFileWriterManager.java‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.apache.iotdb.db.conf.IoTDBDescriptor;
3232
import org.apache.iotdb.db.exception.load.LoadFileException;
3333
import org.apache.iotdb.db.i18n.StorageEngineMessages;
34+
import org.apache.iotdb.db.pipe.agent.PipeDataNodeAgent;
3435
import org.apache.iotdb.db.queryengine.plan.planner.plan.node.load.LoadTsFileConsensusNode;
3536
import org.apache.iotdb.db.queryengine.plan.planner.plan.node.load.LoadTsFileConsensusOp;
3637
import org.apache.iotdb.db.schemaengine.table.DataNodeTableCache;
@@ -532,7 +533,7 @@ void prepare(
532533

533534
/** Formally imports sealed staged TsFiles into the target storage engine data regions. */
534535
void loadAll(final boolean isGeneratedByPipe, final boolean deleteStagedSource)
535-
throws LoadFileException {
536+
throws IOException, LoadFileException {
536537
for (final Map.Entry<TTimePartitionSlot, TsFileResource> entry :
537538
timePartition2Resource.entrySet()) {
538539
final DataRegion targetDataRegion = dataRegion;
@@ -547,6 +548,14 @@ void loadAll(final boolean isGeneratedByPipe, final boolean deleteStagedSource)
547548
uuidDirName(tsFileResource.getTsFile())));
548549
}
549550

551+
if (LoadConsensusCompat.usesIoTConsensusV2()) {
552+
// V2 only replicates files carrying this node's progress. The coordinator's index may
553+
// belong to another node and precede writes between PREPARE and COMMIT. Assign progress
554+
// at import, and persist it so recovery keeps the file eligible for replication.
555+
PipeDataNodeAgent.runtime().assignProgressIndexForTsFileLoad(tsFileResource);
556+
tsFileResource.serialize();
557+
}
558+
550559
targetDataRegion.loadNewTsFile(
551560
tsFileResource, deleteStagedSource, isGeneratedByPipe, false, Optional.empty());
552561

‎iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/load/LoadTsFileManagerTest.java‎

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,22 @@
2020
package org.apache.iotdb.db.storageengine.load;
2121

2222
import org.apache.iotdb.common.rpc.thrift.TTimePartitionSlot;
23+
import org.apache.iotdb.commons.consensus.index.ProgressIndex;
24+
import org.apache.iotdb.commons.consensus.index.impl.RecoverProgressIndex;
2325
import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeId;
2426
import org.apache.iotdb.consensus.iot.log.ConsensusReqReader;
2527
import org.apache.iotdb.db.conf.IoTDBConfig;
2628
import org.apache.iotdb.db.conf.IoTDBDescriptor;
2729
import org.apache.iotdb.db.exception.load.LoadFileException;
30+
import org.apache.iotdb.db.pipe.agent.PipeDataNodeAgent;
31+
import org.apache.iotdb.db.pipe.event.common.tsfile.PipeTsFileInsertionEvent;
32+
import org.apache.iotdb.db.pipe.processor.iotconsensusv2.IoTConsensusV2Processor;
2833
import org.apache.iotdb.db.queryengine.plan.planner.plan.node.load.LoadTsFileConsensusNode;
2934
import org.apache.iotdb.db.queryengine.plan.planner.plan.node.load.LoadTsFileConsensusOp;
3035
import org.apache.iotdb.db.queryengine.plan.scheduler.load.ChunkOffsetCalculator;
3136
import org.apache.iotdb.db.storageengine.dataregion.DataRegion;
3237
import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource;
38+
import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResourceStatus;
3339
import org.apache.iotdb.db.storageengine.dataregion.wal.node.IWALNode;
3440
import org.apache.iotdb.db.storageengine.dataregion.wal.node.WALNode;
3541
import org.apache.iotdb.db.storageengine.dataregion.wal.utils.WALMode;
@@ -798,6 +804,70 @@ public void testStagedDirectoryWithoutTerminalRecordIsKeptOnRecovery() throws Ex
798804
assertFalse(manager.prepare(prepareNode("in-progress-load"), Collections.emptyMap()));
799805
}
800806

807+
@Test
808+
public void testIoTConsensusV2CommitUsesReceivingNodeProgress() throws Exception {
809+
assertCommittedProgress(true, config.getDataNodeId() + 1);
810+
}
811+
812+
@Test
813+
public void testIoTConsensusV2CommitAdvancesPreparedProgress() throws Exception {
814+
assertCommittedProgress(true, config.getDataNodeId());
815+
}
816+
817+
@Test
818+
public void testOtherConsensusCommitPreservesCoordinatorProgress() throws Exception {
819+
assertCommittedProgress(false, config.getDataNodeId() + 1);
820+
}
821+
822+
private void assertCommittedProgress(final boolean useIoTConsensusV2, final int coordinatorNodeId)
823+
throws Exception {
824+
LoadConsensusCompat.setIoTConsensusV2ForTest(useIoTConsensusV2);
825+
final String loadId = "load-progress";
826+
final LoadTsFileManager manager = new LoadTsFileManager(dataRegion);
827+
try {
828+
emulateStagedFileImport();
829+
final LoadTsFileConsensusNode piece = stagedPiece(loadId, laidOutChunk());
830+
manager.writePiece(piece);
831+
final File staged = new File(tempDir, piece.getPieceRefs().get(0).getRelativePath());
832+
final RecoverProgressIndex preparedLocalProgress =
833+
PipeDataNodeAgent.runtime().getNextProgressIndexForTsFileLoad();
834+
final ProgressIndex coordinatorProgress =
835+
new RecoverProgressIndex(
836+
coordinatorNodeId,
837+
preparedLocalProgress.getDataNodeId2LocalIndex().get(config.getDataNodeId()));
838+
assertTrue(
839+
manager.prepare(
840+
prepareNode(loadId),
841+
Collections.singletonMap(new TTimePartitionSlot(0L), coordinatorProgress)));
842+
843+
// A local write after PREPARE must precede the progress assigned when the file is imported.
844+
final ProgressIndex interveningWrite =
845+
PipeDataNodeAgent.runtime().getNextProgressIndexForTsFileLoad();
846+
final LoadTsFileConsensusNode commit = commitNode(loadId);
847+
assertTrue(manager.loadAll(commit, Collections.emptyMap()));
848+
849+
final TsFileResource imported =
850+
new TsFileResource(new File(new File(tempDir, "data"), staged.getName()));
851+
imported.deserialize();
852+
imported.setStatus(TsFileResourceStatus.NORMAL);
853+
final ProgressIndex persistedProgress = imported.getMaxProgressIndexAfterClose();
854+
final PipeTsFileInsertionEvent event = Mockito.mock(PipeTsFileInsertionEvent.class);
855+
Mockito.when(event.forceGetProgressIndex()).thenReturn(persistedProgress);
856+
assertEquals(
857+
"A V2 LOAD must remain eligible for consensus replication after resource recovery",
858+
useIoTConsensusV2,
859+
IoTConsensusV2Processor.isShouldReplicate(event));
860+
if (useIoTConsensusV2) {
861+
assertTrue(persistedProgress.isAfter(interveningWrite));
862+
} else {
863+
assertEquals(coordinatorProgress, persistedProgress);
864+
}
865+
} finally {
866+
manager.stop();
867+
LoadConsensusCompat.setIoTConsensusV2ForTest(null);
868+
}
869+
}
870+
801871
@Test
802872
public void testCommitKeepsTheStagedBytesWhileAReplicaMayStillNeedThem() throws Exception {
803873
// The watermark lags behind the COMMIT command, so a replica that catches up from this node's

0 commit comments

Comments
 (0)