This is an automated email from the ASF dual-hosted git repository.
jt2594838 pushed a commit to branch dev/1.3
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/dev/1.3 by this push:
new 064fd76e3f3 Pipe: order historical TsFiles by query priority (#18088)
(#18165)
064fd76e3f3 is described below
commit 064fd76e3f38d8d65a516d864b35e5c58a27e52a
Author: Caideyipi <[email protected]>
AuthorDate: Mon Aug 24 17:19:23 2026 +0800
Pipe: order historical TsFiles by query priority (#18088) (#18165)
* Pipe: order historical TsFiles by flush time
* Pipe: order historical TsFiles by query priority
* Pipe: report safe query-order historical progress
* Pipe: report time-partition historical progress
(cherry picked from commit 4c1f833adf1748d6f39bb8d88c3e446f680f1706)
---
.../common/tablet/PipeRawTabletInsertionEvent.java | 13 +
.../common/tsfile/PipeTsFileInsertionEvent.java | 20 ++
.../source/dataregion/IoTDBDataRegionSource.java | 12 +
.../PipeHistoricalDataRegionTsFileSource.java | 383 +++++++++++++++++----
.../pipe/event/PipeTabletInsertionEventTest.java | 59 ++++
.../PipeHistoricalDataRegionTsFileSourceTest.java | 380 +++++++++++++++++++-
.../TsFileResourceProgressIndexTest.java | 42 +++
.../commons/consensus/index/ProgressIndexType.java | 8 +-
.../index/impl/TimePartitionProgressIndex.java | 301 ++++++++++++++++
.../pipe/config/constant/PipeSourceConstant.java | 5 +
.../iotdb/commons/pipe/task/PipeMetaDeSerTest.java | 13 +
11 files changed, 1148 insertions(+), 88 deletions(-)
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tablet/PipeRawTabletInsertionEvent.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tablet/PipeRawTabletInsertionEvent.java
index f47544ab64f..2a3923800a3 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tablet/PipeRawTabletInsertionEvent.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tablet/PipeRawTabletInsertionEvent.java
@@ -92,6 +92,7 @@ public class PipeRawTabletInsertionEvent extends EnrichedEvent
this.treeModelDatabaseName = treeModelDatabaseName;
this.sourceEvent = sourceEvent;
this.needToReport = needToReport;
+ inheritSourceEventReportSkippingIfNecessary();
// Allocate empty memory block, will be resized later.
this.allocatedMemoryBlock =
@@ -342,6 +343,18 @@ public class PipeRawTabletInsertionEvent extends
EnrichedEvent
});
}
this.needToReport = true;
+ inheritSourceEventReportSkippingIfNecessary();
+ }
+
+ private void inheritSourceEventReportSkippingIfNecessary() {
+ if (needToReport && shouldSkipReportOnCommitBecauseOfSourceEvent()) {
+ skipReportOnCommit();
+ }
+ }
+
+ private boolean shouldSkipReportOnCommitBecauseOfSourceEvent() {
+ return sourceEvent instanceof PipeTsFileInsertionEvent
+ && !((PipeTsFileInsertionEvent)
sourceEvent).shouldReportGeneratedEventsOnCommit();
}
// This getter is reserved for user-defined plugins
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/PipeTsFileInsertionEvent.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/PipeTsFileInsertionEvent.java
index ae3708fde58..2692000689d 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/PipeTsFileInsertionEvent.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/PipeTsFileInsertionEvent.java
@@ -106,6 +106,8 @@ public class PipeTsFileInsertionEvent extends EnrichedEvent
protected volatile ProgressIndex overridingProgressIndex;
private Set<String> tableNames;
+ // False when generated tablet events should wait for an external progress
report.
+ private volatile boolean shouldReportGeneratedEventsOnCommit = true;
private String tsFileParser;
public PipeTsFileInsertionEvent(final TsFileResource resource, final boolean
isLoaded) {
@@ -432,6 +434,23 @@ public class PipeTsFileInsertionEvent extends EnrichedEvent
return resource.getMaxProgressIndex();
}
+ public PipeTsFileInsertionEvent skipReportOnCommitAndGeneratedEvents() {
+ return setShouldReportGeneratedEventsOnCommit(false);
+ }
+
+ public boolean shouldReportGeneratedEventsOnCommit() {
+ return shouldReportGeneratedEventsOnCommit;
+ }
+
+ private PipeTsFileInsertionEvent setShouldReportGeneratedEventsOnCommit(
+ final boolean shouldReportGeneratedEventsOnCommit) {
+ this.shouldReportGeneratedEventsOnCommit =
shouldReportGeneratedEventsOnCommit;
+ if (!shouldReportGeneratedEventsOnCommit) {
+ skipReportOnCommit();
+ }
+ return this;
+ }
+
public void eliminateProgressIndex() {
if (Objects.isNull(overridingProgressIndex) && Objects.nonNull(resource)) {
PipeTsFileEpochProgressIndexKeeper.getInstance()
@@ -469,6 +488,7 @@ public class PipeTsFileInsertionEvent extends EnrichedEvent
startTime,
endTime,
isTsFileSealed);
+
copiedEvent.setShouldReportGeneratedEventsOnCommit(shouldReportGeneratedEventsOnCommit);
copiedEvent.setTsFileParser(tsFileParser);
return copiedEvent;
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/IoTDBDataRegionSource.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/IoTDBDataRegionSource.java
index 5c82af0d459..7dcd9d972f4 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/IoTDBDataRegionSource.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/IoTDBDataRegionSource.java
@@ -57,6 +57,7 @@ import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.E
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.EXTRACTOR_HISTORY_ENABLE_KEY;
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.EXTRACTOR_HISTORY_END_TIME_KEY;
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.EXTRACTOR_HISTORY_START_TIME_KEY;
+import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.EXTRACTOR_HISTORY_TSFILE_ORDER_BY_QUERY_PRIORITY_KEY;
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.EXTRACTOR_PATTERN_FORMAT_IOTDB_VALUE;
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.EXTRACTOR_PATTERN_FORMAT_KEY;
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.EXTRACTOR_PATTERN_FORMAT_PREFIX_VALUE;
@@ -79,6 +80,7 @@ import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.S
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.SOURCE_HISTORY_ENABLE_KEY;
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.SOURCE_HISTORY_END_TIME_KEY;
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.SOURCE_HISTORY_START_TIME_KEY;
+import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.SOURCE_HISTORY_TSFILE_ORDER_BY_QUERY_PRIORITY_KEY;
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.SOURCE_PATTERN_FORMAT_KEY;
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.SOURCE_REALTIME_ENABLE_KEY;
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.SOURCE_REALTIME_MODE_KEY;
@@ -155,6 +157,16 @@ public class IoTDBDataRegionSource extends IoTDBSource {
SOURCE_HISTORY_ENABLE_KEY, true, Boolean.TRUE.toString(),
Boolean.FALSE.toString())
.validateAttributeValueRange(
SOURCE_REALTIME_ENABLE_KEY, true, Boolean.TRUE.toString(),
Boolean.FALSE.toString())
+ .validateAttributeValueRange(
+ EXTRACTOR_HISTORY_TSFILE_ORDER_BY_QUERY_PRIORITY_KEY,
+ true,
+ Boolean.TRUE.toString(),
+ Boolean.FALSE.toString())
+ .validateAttributeValueRange(
+ SOURCE_HISTORY_TSFILE_ORDER_BY_QUERY_PRIORITY_KEY,
+ true,
+ Boolean.TRUE.toString(),
+ Boolean.FALSE.toString())
.validate(
args -> (boolean) args[0] || (boolean) args[1],
"Should not set both history.enable and realtime.enable to false.",
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/historical/PipeHistoricalDataRegionTsFileSource.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/historical/PipeHistoricalDataRegionTsFileSource.java
index 25ace9387b1..d0dd6549f07 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/historical/PipeHistoricalDataRegionTsFileSource.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/historical/PipeHistoricalDataRegionTsFileSource.java
@@ -21,7 +21,11 @@ package
org.apache.iotdb.db.pipe.source.dataregion.historical;
import org.apache.iotdb.commons.consensus.DataRegionId;
import org.apache.iotdb.commons.consensus.index.ProgressIndex;
+import org.apache.iotdb.commons.consensus.index.ProgressIndexType;
+import org.apache.iotdb.commons.consensus.index.impl.HybridProgressIndex;
+import org.apache.iotdb.commons.consensus.index.impl.MinimumProgressIndex;
import org.apache.iotdb.commons.consensus.index.impl.StateProgressIndex;
+import
org.apache.iotdb.commons.consensus.index.impl.TimePartitionProgressIndex;
import
org.apache.iotdb.commons.consensus.index.impl.TimeWindowStateProgressIndex;
import org.apache.iotdb.commons.exception.IllegalPathException;
import org.apache.iotdb.commons.pipe.agent.task.PipeTaskAgent;
@@ -38,6 +42,7 @@ import
org.apache.iotdb.db.pipe.source.dataregion.DataRegionListeningFilter;
import org.apache.iotdb.db.storageengine.StorageEngine;
import org.apache.iotdb.db.storageengine.dataregion.DataRegion;
import org.apache.iotdb.db.storageengine.dataregion.memtable.TsFileProcessor;
+import
org.apache.iotdb.db.storageengine.dataregion.read.reader.common.MergeReaderPriority;
import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileManager;
import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource;
import org.apache.iotdb.db.utils.DateTimeUtils;
@@ -58,6 +63,7 @@ import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -79,6 +85,8 @@ import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.E
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.EXTRACTOR_HISTORY_LOOSE_RANGE_PATH_VALUE;
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.EXTRACTOR_HISTORY_LOOSE_RANGE_TIME_VALUE;
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.EXTRACTOR_HISTORY_START_TIME_KEY;
+import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.EXTRACTOR_HISTORY_TSFILE_ORDER_BY_QUERY_PRIORITY_DEFAULT_VALUE;
+import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.EXTRACTOR_HISTORY_TSFILE_ORDER_BY_QUERY_PRIORITY_KEY;
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.EXTRACTOR_MODS_ENABLE_DEFAULT_VALUE;
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.EXTRACTOR_MODS_ENABLE_KEY;
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.EXTRACTOR_START_TIME_KEY;
@@ -89,6 +97,7 @@ import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.S
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.SOURCE_HISTORY_END_TIME_KEY;
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.SOURCE_HISTORY_LOOSE_RANGE_KEY;
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.SOURCE_HISTORY_START_TIME_KEY;
+import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.SOURCE_HISTORY_TSFILE_ORDER_BY_QUERY_PRIORITY_KEY;
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.SOURCE_MODS_ENABLE_KEY;
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.SOURCE_START_TIME_KEY;
import static
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant.SOURCE_TSFILE_PARSER_KEY;
@@ -115,6 +124,8 @@ public class PipeHistoricalDataRegionTsFileSource
implements PipeHistoricalDataR
private boolean sloppyTimeRange; // true to disable time range filter after
extraction
private boolean sloppyPattern; // true to disable pattern filter after
extraction
+ private boolean shouldOrderHistoricalTsFileByQueryPriority =
+ EXTRACTOR_HISTORY_TSFILE_ORDER_BY_QUERY_PRIORITY_DEFAULT_VALUE;
private Pair<Boolean, Boolean> listeningOptionPair;
private boolean shouldExtractInsertion;
@@ -130,6 +141,11 @@ public class PipeHistoricalDataRegionTsFileSource
implements PipeHistoricalDataR
private Queue<TsFileResource> pendingQueue;
private final Set<TsFileResource> filteredTsFileResources = new HashSet<>();
+ private final Set<TsFileResource> historicalProgressReportResources = new
HashSet<>();
+ private ProgressIndex maxHistoricalProgressIndex =
MinimumProgressIndex.INSTANCE;
+ private ProgressIndex maxSuppliedHistoricalProgressReportIndex =
MinimumProgressIndex.INSTANCE;
+ private ProgressIndex pendingHistoricalProgressIndexToReport;
+ private boolean shouldReportMaxHistoricalProgressIndex = false;
@Override
public void validate(final PipeParameterValidator validator) {
@@ -143,6 +159,13 @@ public class PipeHistoricalDataRegionTsFileSource
implements PipeHistoricalDataR
throw new PipeParameterNotValidException(e.getMessage());
}
+ shouldOrderHistoricalTsFileByQueryPriority =
+ parameters.getBooleanOrDefault(
+ Arrays.asList(
+ EXTRACTOR_HISTORY_TSFILE_ORDER_BY_QUERY_PRIORITY_KEY,
+ SOURCE_HISTORY_TSFILE_ORDER_BY_QUERY_PRIORITY_KEY),
+ EXTRACTOR_HISTORY_TSFILE_ORDER_BY_QUERY_PRIORITY_DEFAULT_VALUE);
+
final String extractorHistoryLooseRangeValue =
parameters
.getStringOrDefault(
@@ -254,6 +277,12 @@ public class PipeHistoricalDataRegionTsFileSource
implements PipeHistoricalDataR
final PipeParameters parameters, final PipeExtractorRuntimeConfiguration
configuration)
throws IllegalPathException {
shouldExtractInsertion = listeningOptionPair.getLeft();
+ shouldOrderHistoricalTsFileByQueryPriority =
+ parameters.getBooleanOrDefault(
+ Arrays.asList(
+ EXTRACTOR_HISTORY_TSFILE_ORDER_BY_QUERY_PRIORITY_KEY,
+ SOURCE_HISTORY_TSFILE_ORDER_BY_QUERY_PRIORITY_KEY),
+ EXTRACTOR_HISTORY_TSFILE_ORDER_BY_QUERY_PRIORITY_DEFAULT_VALUE);
// Do nothing if only extract deletion
if (!shouldExtractInsertion) {
return;
@@ -326,6 +355,11 @@ public class PipeHistoricalDataRegionTsFileSource
implements PipeHistoricalDataR
return;
}
hasBeenStarted = true;
+ maxHistoricalProgressIndex = MinimumProgressIndex.INSTANCE;
+ maxSuppliedHistoricalProgressReportIndex = MinimumProgressIndex.INSTANCE;
+ pendingHistoricalProgressIndexToReport = null;
+ shouldReportMaxHistoricalProgressIndex = false;
+ historicalProgressReportResources.clear();
final DataRegion dataRegion =
StorageEngine.getInstance().getDataRegion(new
DataRegionId(dataRegionId));
@@ -344,7 +378,8 @@ public class PipeHistoricalDataRegionTsFileSource
implements PipeHistoricalDataR
// Since a large number of consensus pipes are not created at the same
time, resulting in no
// serious waiting for locks. Therefore, the flush operation is always
performed for the
// consensus pipe, and the lastFlushed timestamp is not updated here.
- if (pipeName.startsWith(PipeStaticMeta.CONSENSUS_PIPE_PREFIX)) {
+ if (pipeName.startsWith(PipeStaticMeta.CONSENSUS_PIPE_PREFIX)
+ || shouldUseHistoricalTsFileQueryPriorityOrder()) {
dataRegion.syncCloseAllWorkingTsFileProcessors();
} else {
dataRegion.asyncCloseAllWorkingTsFileProcessors();
@@ -398,11 +433,13 @@ public class PipeHistoricalDataRegionTsFileSource
implements PipeHistoricalDataR
}
});
- originalResourceList.sort(
- (o1, o2) ->
- Objects.nonNull(getTimeWindowStateProgressIndex(startIndex))
- ? Long.compare(o1.getFileStartTime(),
o2.getFileStartTime())
- :
o1.getMaxProgressIndex().topologicalCompareTo(o2.getMaxProgressIndex()));
+ if (shouldUseHistoricalTsFileQueryPriorityOrder()) {
+
prepareResourcesForHistoricalTsFileQueryPriorityOrder(originalResourceList);
+ }
+ sortExtractedResources(originalResourceList);
+ if (shouldUseHistoricalTsFileQueryPriorityOrder()) {
+
prepareProgressReportResourcesForHistoricalTsFileQueryPriorityOrder(originalResourceList);
+ }
pendingQueue = new ArrayDeque<>(originalResourceList);
PipeTerminateEvent.initializeHistoricalTransferSummary(
pipeName, creationTime, dataRegionId,
filteredTsFileResources.size(), 0);
@@ -427,6 +464,109 @@ public class PipeHistoricalDataRegionTsFileSource
implements PipeHistoricalDataR
}
}
+ private boolean shouldUseHistoricalTsFileQueryPriorityOrder() {
+ return shouldOrderHistoricalTsFileByQueryPriority &&
shouldExtractInsertion;
+ }
+
+ private void prepareResourcesForHistoricalTsFileQueryPriorityOrder(
+ final List<TsFileResource> resourceList) {
+ // Query-priority order is intentionally not compatible with progressIndex
order, so only
+ // selected historical TsFiles should participate in query-order progress
reports.
+ resourceList.removeIf(resource ->
!filteredTsFileResources.contains(resource));
+ updateMaxHistoricalProgressIndex(resourceList);
+ shouldReportMaxHistoricalProgressIndex = !resourceList.isEmpty();
+ }
+
+ private void
prepareProgressReportResourcesForHistoricalTsFileQueryPriorityOrder(
+ final List<TsFileResource> resourceList) {
+ historicalProgressReportResources.clear();
+ final Map<Long, List<ProgressIndex>>
timePartitionId2RemainingMinimalProgressIndexes =
+ new HashMap<>();
+ for (int i = resourceList.size() - 1; i >= 0; --i) {
+ final TsFileResource resource = resourceList.get(i);
+ final ProgressIndex progressIndex = resource.getMaxProgressIndex();
+ if (Objects.isNull(progressIndex)) {
+ continue;
+ }
+
+ final List<ProgressIndex> remainingMinimalProgressIndexes =
+ timePartitionId2RemainingMinimalProgressIndexes.computeIfAbsent(
+ resource.getTimePartition(), ignored -> new ArrayList<>());
+ // A query-priority report is persisted as a time-partition-scoped
progress index. Recovery
+ // only uses it to cover TsFiles from the same partition, so it does not
rely on any global
+ // ordering guarantee between partitions.
+ if
(remainingMinimalProgressIndexes.stream().noneMatch(progressIndex::isEqualOrAfter))
{
+ historicalProgressReportResources.add(resource);
+ }
+ updateRemainingMinimalProgressIndexes(remainingMinimalProgressIndexes,
progressIndex);
+ }
+ }
+
+ private void updateRemainingMinimalProgressIndexes(
+ final List<ProgressIndex> remainingMinimalProgressIndexes,
+ final ProgressIndex progressIndex) {
+ if
(remainingMinimalProgressIndexes.stream().anyMatch(progressIndex::isEqualOrAfter))
{
+ return;
+ }
+
+ // Keep only suffix minimal progress indexes. They are sufficient to test
whether a new
+ // progress index covers any remaining resource.
+ remainingMinimalProgressIndexes.removeIf(
+ minimalProgressIndex ->
minimalProgressIndex.isEqualOrAfter(progressIndex));
+ remainingMinimalProgressIndexes.add(progressIndex);
+ }
+
+ private void updateMaxHistoricalProgressIndex(final List<TsFileResource>
resourceList) {
+ for (final TsFileResource resource : resourceList) {
+ final ProgressIndex progressIndex = resource.getMaxProgressIndex();
+ if (Objects.nonNull(progressIndex)) {
+ maxHistoricalProgressIndex =
+
maxHistoricalProgressIndex.updateToMinimumEqualOrIsAfterProgressIndex(progressIndex);
+ }
+ }
+ }
+
+ private void sortExtractedResources(final List<TsFileResource> resourceList)
{
+ if (shouldUseHistoricalTsFileQueryPriorityOrder()) {
+ // Send TsFiles from lower query/compaction priority to higher priority.
For duplicated
+ // points, covered files are loaded first on the receiver and covering
files are loaded later
+ // to preserve overwrite semantics.
+ resourceList.sort(this::compareTsFileResourcesByQueryPriority);
+ return;
+ }
+
+ resourceList.sort(
+ (o1, o2) ->
+ getInnerProgressIndex(startIndex) instanceof
TimeWindowStateProgressIndex
+ ? Long.compare(o1.getFileStartTime(), o2.getFileStartTime())
+ :
o1.getMaxProgressIndex().topologicalCompareTo(o2.getMaxProgressIndex()));
+ }
+
+ private int compareTsFileResourcesByQueryPriority(
+ final TsFileResource resource1, final TsFileResource resource2) {
+ int result =
+ new MergeReaderPriority(
+ resource1.getTsFileID().timestamp, resource1.getVersion(), 0,
resource1.isSeq())
+ .compareTo(
+ new MergeReaderPriority(
+ resource2.getTsFileID().timestamp,
+ resource2.getVersion(),
+ 0,
+ resource2.isSeq()));
+ if (result != 0) {
+ return result;
+ }
+
+ result =
+ Long.compare(
+ resource1.getTsFileID().compactionVersion,
resource2.getTsFileID().compactionVersion);
+ if (result != 0) {
+ return result;
+ }
+
+ return resource1.getTsFilePath().compareTo(resource2.getTsFilePath());
+ }
+
private boolean shouldExtractTsFileResource(final TsFileResource resource) {
if (!isHistoricalSourceEnabled) {
return false;
@@ -468,28 +608,67 @@ public class PipeHistoricalDataRegionTsFileSource
implements PipeHistoricalDataR
}
private boolean mayTsFileContainUnprocessedData(final TsFileResource
resource) {
- final TimeWindowStateProgressIndex timeWindowStateProgressIndex =
- getTimeWindowStateProgressIndex(startIndex);
- if (Objects.nonNull(timeWindowStateProgressIndex)) {
+ final ProgressIndex innerStartIndex = getInnerProgressIndex(startIndex);
+ if (innerStartIndex instanceof TimeWindowStateProgressIndex) {
// The resource is closed thus the TsFileResource#getFileEndTime() is
safe to use
- return timeWindowStateProgressIndex.getMinTime() <=
resource.getFileEndTime();
+ return ((TimeWindowStateProgressIndex) innerStartIndex).getMinTime()
+ <= resource.getFileEndTime();
}
- if (startIndex instanceof StateProgressIndex) {
- startIndex = ((StateProgressIndex) startIndex).getInnerProgressIndex();
+ final ProgressIndex resourceProgressIndex = resource.getMaxProgressIndex();
+ if (innerStartIndex.isEqualOrAfter(resourceProgressIndex)
+ || isProgressIndexCoveredByTimePartitionProgressIndex(
+ resource, resourceProgressIndex, innerStartIndex)) {
+ return false;
}
- if (!startIndex.isEqualOrAfter(resource.getMaxProgressIndex())) {
- LOGGER.info(
- "Pipe {}@{}: file {} meets mayTsFileContainUnprocessedData
condition, extractor progressIndex: {}, resource ProgressIndex: {}",
- pipeName,
- dataRegionId,
- resource.getTsFilePath(),
- startIndex,
- resource.getMaxProgressIndex());
- return true;
+ LOGGER.info(
+ "Pipe {}@{}: file {} meets mayTsFileContainUnprocessedData condition,
extractor progressIndex: {}, resource ProgressIndex: {}",
+ pipeName,
+ dataRegionId,
+ resource.getTsFilePath(),
+ innerStartIndex,
+ resourceProgressIndex);
+ return true;
+ }
+
+ private ProgressIndex getInnerProgressIndex(final ProgressIndex
progressIndex) {
+ return progressIndex instanceof StateProgressIndex
+ ? ((StateProgressIndex) progressIndex).getInnerProgressIndex()
+ : Objects.isNull(progressIndex) ? MinimumProgressIndex.INSTANCE :
progressIndex;
+ }
+
+ private boolean isProgressIndexCoveredByTimePartitionProgressIndex(
+ final TsFileResource resource,
+ final ProgressIndex progressIndex,
+ final ProgressIndex startIndex) {
+ final TimePartitionProgressIndex timePartitionProgressIndex =
+ getTimePartitionProgressIndex(startIndex);
+ // Keep this check strictly partition-local, matching the reporting side.
This is what makes
+ // query-priority historical transfer restart-safe even when different
partitions are sent in an
+ // order that conflicts with the global ProgressIndex order.
+ return Objects.nonNull(timePartitionProgressIndex)
+ && timePartitionProgressIndex.isProgressIndexEqualOrAfter(
+ resource.getTimePartition(), progressIndex);
+ }
+
+ private TimePartitionProgressIndex getTimePartitionProgressIndex(
+ final ProgressIndex progressIndex) {
+ final ProgressIndex innerProgressIndex =
getInnerProgressIndex(progressIndex);
+ if (innerProgressIndex instanceof TimePartitionProgressIndex) {
+ return (TimePartitionProgressIndex) innerProgressIndex;
+ }
+
+ if (innerProgressIndex instanceof HybridProgressIndex) {
+ final ProgressIndex timePartitionProgressIndex =
+ ((HybridProgressIndex) innerProgressIndex)
+ .getType2Index()
+ .get(ProgressIndexType.TIME_PARTITION_PROGRESS_INDEX.getType());
+ if (timePartitionProgressIndex instanceof TimePartitionProgressIndex) {
+ return (TimePartitionProgressIndex) timePartitionProgressIndex;
+ }
}
- return false;
+ return null;
}
private TimeWindowStateProgressIndex getTimeWindowStateProgressIndex(
@@ -527,16 +706,6 @@ public class PipeHistoricalDataRegionTsFileSource
implements PipeHistoricalDataR
deviceID -> pipePattern.mayOverlapWithDevice(((PlainDeviceID)
deviceID).toStringID()));
}
- private boolean isTsFileResourceOverlappedWithTimeRange(final TsFileResource
resource) {
- return !(resource.getFileEndTime() < historicalDataExtractionStartTime
- || historicalDataExtractionEndTime < resource.getFileStartTime());
- }
-
- private boolean isTsFileResourceCoveredByTimeRange(final TsFileResource
resource) {
- return historicalDataExtractionStartTime <= resource.getFileStartTime()
- && historicalDataExtractionEndTime >= resource.getFileEndTime();
- }
-
private boolean isTsFileResourceCoveredByPattern(final TsFileResource
resource) {
final Set<IDeviceID> deviceSet;
try {
@@ -555,64 +724,128 @@ public class PipeHistoricalDataRegionTsFileSource
implements PipeHistoricalDataR
deviceID -> pipePattern.coversDevice(((PlainDeviceID)
deviceID).toStringID()));
}
+ private boolean isTsFileResourceOverlappedWithTimeRange(final TsFileResource
resource) {
+ return !(resource.getFileEndTime() < historicalDataExtractionStartTime
+ || historicalDataExtractionEndTime < resource.getFileStartTime());
+ }
+
+ private boolean isTsFileResourceCoveredByTimeRange(final TsFileResource
resource) {
+ return historicalDataExtractionStartTime <= resource.getFileStartTime()
+ && historicalDataExtractionEndTime >= resource.getFileEndTime();
+ }
+
@Override
public synchronized Event supply() {
if (!hasBeenStarted &&
StorageEngine.getInstance().isReadyForNonReadWriteFunctions()) {
start();
}
+ if (Objects.nonNull(pendingHistoricalProgressIndexToReport)) {
+ final ProgressIndex progressIndex =
pendingHistoricalProgressIndexToReport;
+ pendingHistoricalProgressIndexToReport = null;
+ return supplyHistoricalProgressReportEvent(progressIndex);
+ }
+
if (Objects.isNull(pendingQueue)) {
return null;
}
- final TsFileResource resource = pendingQueue.poll();
+ while (true) {
+ final TsFileResource resource = pendingQueue.peek();
+ if (resource == null) {
+ if (shouldReportMaxHistoricalProgressIndex) {
+ shouldReportMaxHistoricalProgressIndex = false;
+ if (!maxSuppliedHistoricalProgressReportIndex.isEqualOrAfter(
+ maxHistoricalProgressIndex)) {
+ return
supplyHistoricalProgressReportEvent(maxHistoricalProgressIndex);
+ }
+ }
+ return supplyTerminateEvent();
+ }
- if (resource == null) {
- final PipeTerminateEvent.HistoricalTransferSummary
historicalTransferSummary =
- PipeTerminateEvent.snapshotHistoricalTransferSummary(
- pipeName, creationTime, dataRegionId);
- if (Objects.nonNull(historicalTransferSummary)) {
- LOGGER.info(
- "Pipe {}@{}: historical source has supplied all events, emitting
terminate event. {}",
- pipeName,
- dataRegionId,
- historicalTransferSummary.toReportMessage());
+ pendingQueue.poll();
+ if (!filteredTsFileResources.contains(resource)) {
+ if (shouldUseHistoricalTsFileQueryPriorityOrder()) {
+ if (shouldReportHistoricalProgressAfterResource(resource)) {
+ return supplyHistoricalProgressReportEvent(
+ getHistoricalProgressIndexAfterResource(resource));
+ }
+ continue;
+ }
+ return supplyProgressReportEvent(resource.getMaxProgressIndex());
}
- final PipeTerminateEvent terminateEvent =
- new PipeTerminateEvent(
- pipeName,
- creationTime,
- pipeTaskMeta,
- dataRegionId,
- shouldTerminatePipeOnAllHistoricalEventsConsumed);
- if (!terminateEvent.increaseReferenceCount(
- PipeHistoricalDataRegionTsFileSource.class.getName())) {
- LOGGER.warn(
- "Pipe {}@{}: failed to increase reference count for terminate
event, will resend it",
- pipeName,
- dataRegionId);
- return null;
+ final Event event = supplyTsFileEvent(resource);
+ if (Objects.nonNull(event) &&
shouldReportHistoricalProgressAfterResource(resource)) {
+ pendingHistoricalProgressIndexToReport =
getHistoricalProgressIndexAfterResource(resource);
}
- isTerminateSignalSent = true;
- return terminateEvent;
+ return event;
}
+ }
- if (!filteredTsFileResources.contains(resource)) {
- final ProgressReportEvent progressReportEvent =
- new ProgressReportEvent(pipeName, creationTime, pipeTaskMeta);
- progressReportEvent.bindProgressIndex(resource.getMaxProgressIndex());
- final boolean isReferenceCountIncreased =
- progressReportEvent.increaseReferenceCount(
- PipeHistoricalDataRegionTsFileSource.class.getName());
- if (!isReferenceCountIncreased) {
- LOGGER.warn(
- "The reference count of the event {} cannot be increased, skipping
it.",
- progressReportEvent);
- }
- return isReferenceCountIncreased ? progressReportEvent : null;
+ private boolean shouldReportHistoricalProgressAfterResource(final
TsFileResource resource) {
+ return shouldUseHistoricalTsFileQueryPriorityOrder()
+ && historicalProgressReportResources.remove(resource);
+ }
+
+ private ProgressIndex getHistoricalProgressIndexAfterResource(final
TsFileResource resource) {
+ return new TimePartitionProgressIndex(
+ resource.getTimePartition(), resource.getMaxProgressIndex());
+ }
+
+ private Event supplyTerminateEvent() {
+ final PipeTerminateEvent.HistoricalTransferSummary
historicalTransferSummary =
+ PipeTerminateEvent.snapshotHistoricalTransferSummary(pipeName,
creationTime, dataRegionId);
+ if (Objects.nonNull(historicalTransferSummary)) {
+ LOGGER.info(
+ "Pipe {}@{}: historical source has supplied all events, emitting
terminate event. {}",
+ pipeName,
+ dataRegionId,
+ historicalTransferSummary.toReportMessage());
}
+ final PipeTerminateEvent terminateEvent =
+ new PipeTerminateEvent(
+ pipeName,
+ creationTime,
+ pipeTaskMeta,
+ dataRegionId,
+ shouldTerminatePipeOnAllHistoricalEventsConsumed);
+ if (!terminateEvent.increaseReferenceCount(
+ PipeHistoricalDataRegionTsFileSource.class.getName())) {
+ LOGGER.warn(
+ "Pipe {}@{}: failed to increase reference count for terminate event,
will resend it",
+ pipeName,
+ dataRegionId);
+ return null;
+ }
+ isTerminateSignalSent = true;
+ return terminateEvent;
+ }
+
+ private Event supplyProgressReportEvent(final ProgressIndex progressIndex) {
+ final ProgressReportEvent progressReportEvent =
+ new ProgressReportEvent(pipeName, creationTime, pipeTaskMeta);
+ progressReportEvent.bindProgressIndex(progressIndex);
+ final boolean isReferenceCountIncreased =
+ progressReportEvent.increaseReferenceCount(
+ PipeHistoricalDataRegionTsFileSource.class.getName());
+ if (!isReferenceCountIncreased) {
+ LOGGER.warn(
+ "The reference count of the event {} cannot be increased, skipping
it.",
+ progressReportEvent);
+ }
+ return isReferenceCountIncreased ? progressReportEvent : null;
+ }
+
+ private Event supplyHistoricalProgressReportEvent(final ProgressIndex
progressIndex) {
+ maxSuppliedHistoricalProgressReportIndex =
+
maxSuppliedHistoricalProgressReportIndex.updateToMinimumEqualOrIsAfterProgressIndex(
+ progressIndex);
+ return supplyProgressReportEvent(progressIndex);
+ }
+
+ private Event supplyTsFileEvent(final TsFileResource resource) {
filteredTsFileResources.remove(resource);
final PipeTsFileInsertionEvent event =
new PipeTsFileInsertionEvent(
@@ -627,6 +860,10 @@ public class PipeHistoricalDataRegionTsFileSource
implements PipeHistoricalDataR
pipePattern,
historicalDataExtractionStartTime,
historicalDataExtractionEndTime);
+ if (shouldUseHistoricalTsFileQueryPriorityOrder()) {
+ event.skipReportOnCommitAndGeneratedEvents();
+ }
+
event.setTsFileParser(tsFileParser);
if (sloppyPattern || isDbNameCoveredByPattern ||
isTsFileResourceCoveredByPattern(resource)) {
event.skipParsingPattern();
@@ -694,5 +931,7 @@ public class PipeHistoricalDataRegionTsFileSource
implements PipeHistoricalDataR
pendingQueue.clear();
pendingQueue = null;
}
+ historicalProgressReportResources.clear();
+ pendingHistoricalProgressIndexToReport = null;
}
}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/event/PipeTabletInsertionEventTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/event/PipeTabletInsertionEventTest.java
index b46a86e9944..53f9a6b3a90 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/event/PipeTabletInsertionEventTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/event/PipeTabletInsertionEventTest.java
@@ -28,6 +28,7 @@ import
org.apache.iotdb.db.pipe.event.common.tablet.PipeInsertNodeTabletInsertio
import
org.apache.iotdb.db.pipe.event.common.tablet.PipeRawTabletInsertionEvent;
import org.apache.iotdb.db.pipe.event.common.tablet.PipeTabletUtils;
import
org.apache.iotdb.db.pipe.event.common.tablet.TabletInsertionDataContainer;
+import org.apache.iotdb.db.pipe.event.common.tsfile.PipeTsFileInsertionEvent;
import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeId;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowNode;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertTabletNode;
@@ -42,6 +43,7 @@ import org.apache.tsfile.write.schema.MeasurementSchema;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.Mockito;
import java.time.LocalDate;
import java.util.ArrayList;
@@ -264,6 +266,63 @@ public class PipeTabletInsertionEventTest {
PipeTabletUtils.compactBitMaps(bitMapsForInsertTabletNode,
times.length);
}
+ @Test
+ public void
markAsNeedToReportShouldInheritSourceTsFileGeneratedReportSkipping()
+ throws Exception {
+ final PipeTsFileInsertionEvent sourceEvent =
Mockito.mock(PipeTsFileInsertionEvent.class);
+
Mockito.when(sourceEvent.shouldReportGeneratedEventsOnCommit()).thenReturn(true);
+ final PipeRawTabletInsertionEvent tabletEvent =
+ new PipeRawTabletInsertionEvent(
+ false,
+ null,
+ null,
+ null,
+ tabletForInsertTabletNode,
+ false,
+ null,
+ 0,
+ null,
+ sourceEvent,
+ false);
+
+ tabletEvent.markAsNeedToReport();
+ Assert.assertTrue(tabletEvent.isShouldReportOnCommit());
+
+
Mockito.when(sourceEvent.shouldReportGeneratedEventsOnCommit()).thenReturn(false);
+ final PipeRawTabletInsertionEvent skippedTabletEvent =
+ new PipeRawTabletInsertionEvent(
+ false,
+ null,
+ null,
+ null,
+ tabletForInsertTabletNode,
+ false,
+ null,
+ 0,
+ null,
+ sourceEvent,
+ false);
+
+ skippedTabletEvent.markAsNeedToReport();
+ Assert.assertFalse(skippedTabletEvent.isShouldReportOnCommit());
+
+ final PipeRawTabletInsertionEvent constructorSkippedTabletEvent =
+ new PipeRawTabletInsertionEvent(
+ false,
+ null,
+ null,
+ null,
+ tabletForInsertTabletNode,
+ false,
+ null,
+ 0,
+ null,
+ sourceEvent,
+ true);
+
+ Assert.assertFalse(constructorSkippedTabletEvent.isShouldReportOnCommit());
+ }
+
@Test
public void convertToTabletForTest() {
TabletInsertionDataContainer container1 =
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/source/dataregion/historical/PipeHistoricalDataRegionTsFileSourceTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/source/dataregion/historical/PipeHistoricalDataRegionTsFileSourceTest.java
index 003d8f09c54..61e55dc5cac 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/source/dataregion/historical/PipeHistoricalDataRegionTsFileSourceTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/source/dataregion/historical/PipeHistoricalDataRegionTsFileSourceTest.java
@@ -22,16 +22,21 @@ package
org.apache.iotdb.db.pipe.source.dataregion.historical;
import org.apache.iotdb.commons.consensus.index.ProgressIndex;
import org.apache.iotdb.commons.consensus.index.impl.HybridProgressIndex;
import org.apache.iotdb.commons.consensus.index.impl.IoTProgressIndex;
+import org.apache.iotdb.commons.consensus.index.impl.MinimumProgressIndex;
import org.apache.iotdb.commons.consensus.index.impl.RecoverProgressIndex;
import org.apache.iotdb.commons.consensus.index.impl.SimpleProgressIndex;
+import
org.apache.iotdb.commons.consensus.index.impl.TimePartitionProgressIndex;
+import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTaskMeta;
import org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant;
import org.apache.iotdb.commons.pipe.config.constant.SystemConstant;
import org.apache.iotdb.commons.pipe.datastructure.pattern.PrefixPipePattern;
+import org.apache.iotdb.commons.pipe.event.ProgressReportEvent;
import org.apache.iotdb.commons.utils.FileUtils;
import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource;
import
org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResourceStatus;
import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameterValidator;
import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters;
+import org.apache.iotdb.pipe.api.event.Event;
import com.google.common.collect.ImmutableMap;
import org.apache.tsfile.file.metadata.PlainDeviceID;
@@ -39,11 +44,18 @@ import org.junit.Assert;
import org.junit.Test;
import java.io.File;
+import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Files;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
public class PipeHistoricalDataRegionTsFileSourceTest {
@@ -111,6 +123,263 @@ public class PipeHistoricalDataRegionTsFileSourceTest {
}
}
+ @Test
+ public void testHistoricalTsFileQueryPriorityOrderDefaultsToTrue() {
+ final PipeHistoricalDataRegionTsFileSource source = new
PipeHistoricalDataRegionTsFileSource();
+
+ source.validate(new PipeParameterValidator(new PipeParameters(new
HashMap<>())));
+
+ Assert.assertTrue(
+ (Boolean) getPrivateField(source,
"shouldOrderHistoricalTsFileByQueryPriority"));
+ }
+
+ @Test
+ public void testHistoricalTsFileQueryPriorityOrderMatchesQueryCoverage()
throws Exception {
+ final PipeHistoricalDataRegionTsFileSource source = new
PipeHistoricalDataRegionTsFileSource();
+ final File tempDir =
Files.createTempDirectory("pipeHistoricalTsFileOrder").toFile();
+
+ try {
+ final TsFileResource seqLowerVersionNewerFileTimestamp =
+ createTsFileResource(tempDir, "300-1-0-0.tsfile");
+ seqLowerVersionNewerFileTimestamp.setSeq(true);
+ final TsFileResource seqSameVersionOlderFileTimestamp =
+ createTsFileResource(tempDir, "100-2-0-0.tsfile");
+ seqSameVersionOlderFileTimestamp.setSeq(true);
+ final TsFileResource seqSameVersionNewerFileTimestamp =
+ createTsFileResource(tempDir, "200-2-0-0.tsfile");
+ seqSameVersionNewerFileTimestamp.setSeq(true);
+ final TsFileResource seqHigherVersionOlderFileTimestamp =
+ createTsFileResource(tempDir, "50-3-0-0.tsfile");
+ seqHigherVersionOlderFileTimestamp.setSeq(true);
+ final TsFileResource unseqLowerVersionOldestFileTimestamp =
+ createTsFileResource(tempDir, "1-1-0-0.tsfile");
+ unseqLowerVersionOldestFileTimestamp.setSeq(false);
+
+ setPrivateField(source, "shouldOrderHistoricalTsFileByQueryPriority",
true);
+ setPrivateField(source, "shouldExtractInsertion", true);
+ setPrivateField(source, "startIndex", MinimumProgressIndex.INSTANCE);
+
+ final List<TsFileResource> resources =
+ new ArrayList<>(
+ Arrays.asList(
+ unseqLowerVersionOldestFileTimestamp,
+ seqHigherVersionOlderFileTimestamp,
+ seqSameVersionNewerFileTimestamp,
+ seqSameVersionOlderFileTimestamp,
+ seqLowerVersionNewerFileTimestamp));
+ sortExtractedResources(source, resources);
+
+ Assert.assertEquals(
+ Arrays.asList(
+ seqLowerVersionNewerFileTimestamp,
+ seqSameVersionOlderFileTimestamp,
+ seqSameVersionNewerFileTimestamp,
+ seqHigherVersionOlderFileTimestamp,
+ unseqLowerVersionOldestFileTimestamp),
+ resources);
+ } finally {
+ FileUtils.deleteFileOrDirectory(tempDir);
+ }
+ }
+
+ @Test
+ public void testHistoricalTsFileQueryPriorityOrderCanBeDisabled() throws
Exception {
+ final PipeHistoricalDataRegionTsFileSource source = new
PipeHistoricalDataRegionTsFileSource();
+ final PipeParameters parameters =
+ new PipeParameters(
+ new HashMap<String, String>() {
+ {
+ put(
+
PipeSourceConstant.SOURCE_HISTORY_TSFILE_ORDER_BY_QUERY_PRIORITY_KEY,
+ Boolean.FALSE.toString());
+ }
+ });
+ final File tempDir =
Files.createTempDirectory("pipeHistoricalTsFileProgressOrder").toFile();
+
+ try {
+ source.validate(new PipeParameterValidator(parameters));
+ final TsFileResource earlierProgressIndex =
createTsFileResource(tempDir, "300-1-0-0.tsfile");
+ earlierProgressIndex.updateProgressIndex(new SimpleProgressIndex(0, 1));
+ final TsFileResource laterProgressIndex = createTsFileResource(tempDir,
"100-1-0-0.tsfile");
+ laterProgressIndex.updateProgressIndex(new SimpleProgressIndex(0, 2));
+
+ setPrivateField(source, "shouldExtractInsertion", true);
+ setPrivateField(source, "startIndex", MinimumProgressIndex.INSTANCE);
+
+ final List<TsFileResource> resources =
+ new ArrayList<>(Arrays.asList(laterProgressIndex,
earlierProgressIndex));
+ sortExtractedResources(source, resources);
+
+ Assert.assertFalse(
+ (Boolean) getPrivateField(source,
"shouldOrderHistoricalTsFileByQueryPriority"));
+ Assert.assertEquals(Arrays.asList(earlierProgressIndex,
laterProgressIndex), resources);
+ } finally {
+ FileUtils.deleteFileOrDirectory(tempDir);
+ }
+ }
+
+ @Test
+ public void
testHistoricalTsFileQueryPriorityOrderCanBeDisabledByExtractorKey() {
+ final PipeHistoricalDataRegionTsFileSource source = new
PipeHistoricalDataRegionTsFileSource();
+ final PipeParameters parameters =
+ new PipeParameters(
+ new HashMap<String, String>() {
+ {
+ put(
+
PipeSourceConstant.EXTRACTOR_HISTORY_TSFILE_ORDER_BY_QUERY_PRIORITY_KEY,
+ Boolean.FALSE.toString());
+ }
+ });
+
+ source.validate(new PipeParameterValidator(parameters));
+
+ Assert.assertFalse(
+ (Boolean) getPrivateField(source,
"shouldOrderHistoricalTsFileByQueryPriority"));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testQueryPriorityOrderProgressOnlyCoversSelectedResources()
throws Exception {
+ final PipeHistoricalDataRegionTsFileSource source = new
PipeHistoricalDataRegionTsFileSource();
+ final File tempDir =
Files.createTempDirectory("pipeHistoricalTsFileSelectedProgress").toFile();
+
+ try {
+ final TsFileResource selectedResource = createTsFileResource(tempDir,
"100-1-0-0.tsfile");
+ selectedResource.updateProgressIndex(new SimpleProgressIndex(0, 1));
+ final TsFileResource filteredResource = createTsFileResource(tempDir,
"200-1-0-0.tsfile");
+ filteredResource.updateProgressIndex(new SimpleProgressIndex(0, 100));
+
+ ((Set<TsFileResource>) getPrivateField(source,
"filteredTsFileResources"))
+ .add(selectedResource);
+
+ final List<TsFileResource> resources =
+ new ArrayList<>(Arrays.asList(filteredResource, selectedResource));
+ prepareResourcesForHistoricalTsFileQueryPriorityOrder(source, resources);
+
+ Assert.assertEquals(Arrays.asList(selectedResource), resources);
+ Assert.assertEquals(
+ new SimpleProgressIndex(0, 1), getPrivateField(source,
"maxHistoricalProgressIndex"));
+ Assert.assertTrue(
+ (Boolean) getPrivateField(source,
"shouldReportMaxHistoricalProgressIndex"));
+ } finally {
+ FileUtils.deleteFileOrDirectory(tempDir);
+ }
+ }
+
+ @Test
+ public void testQueryPriorityOrderPreparesIncrementalSafeProgressReports()
throws Exception {
+ final PipeHistoricalDataRegionTsFileSource source = new
PipeHistoricalDataRegionTsFileSource();
+ final File tempDir =
+
Files.createTempDirectory("pipeHistoricalTsFileIncrementalProgress").toFile();
+
+ try {
+ final TsFileResource progress1 = createTsFileResource(tempDir,
"1.tsfile");
+ progress1.updateProgressIndex(new SimpleProgressIndex(0, 1));
+ final TsFileResource progress2 = createTsFileResource(tempDir,
"2.tsfile");
+ progress2.updateProgressIndex(new SimpleProgressIndex(0, 2));
+ final TsFileResource progress4 = createTsFileResource(tempDir,
"4.tsfile");
+ progress4.updateProgressIndex(new SimpleProgressIndex(0, 4));
+ final TsFileResource progress3 = createTsFileResource(tempDir,
"3.tsfile");
+ progress3.updateProgressIndex(new SimpleProgressIndex(0, 3));
+ final TsFileResource progress5 = createTsFileResource(tempDir,
"5.tsfile");
+ progress5.updateProgressIndex(new SimpleProgressIndex(0, 5));
+
+ final List<TsFileResource> resources =
+ new ArrayList<>(Arrays.asList(progress1, progress2, progress4,
progress3, progress5));
+
prepareProgressReportResourcesForHistoricalTsFileQueryPriorityOrder(source,
resources);
+
+ Assert.assertEquals(
+ new HashSet<>(Arrays.asList(progress1, progress2, progress3,
progress5)),
+ getPrivateField(source, "historicalProgressReportResources"));
+ } finally {
+ FileUtils.deleteFileOrDirectory(tempDir);
+ }
+ }
+
+ @Test
+ public void
testQueryPriorityOrderPreparesSafeProgressReportsByTimePartition() throws
Exception {
+ final PipeHistoricalDataRegionTsFileSource source = new
PipeHistoricalDataRegionTsFileSource();
+ final File tempDir =
+
Files.createTempDirectory("pipeHistoricalTsFilePartitionProgress").toFile();
+
+ try {
+ final TsFileResource partition0Progress100 =
+ createTsFileResource(tempDir, 0L, "100-1-0-0.tsfile");
+ partition0Progress100.updateProgressIndex(new SimpleProgressIndex(0,
100));
+ final TsFileResource partition1Progress20 =
+ createTsFileResource(tempDir, 1L, "20-1-0-0.tsfile");
+ partition1Progress20.updateProgressIndex(new SimpleProgressIndex(0, 20));
+ final List<TsFileResource> resources =
+ new ArrayList<>(Arrays.asList(partition0Progress100,
partition1Progress20));
+
prepareProgressReportResourcesForHistoricalTsFileQueryPriorityOrder(source,
resources);
+
+ Assert.assertEquals(
+ new HashSet<>(Arrays.asList(partition0Progress100,
partition1Progress20)),
+ getPrivateField(source, "historicalProgressReportResources"));
+ } finally {
+ FileUtils.deleteFileOrDirectory(tempDir);
+ }
+ }
+
+ @Test
+ public void
testQueryPriorityOrderReportsProgressAfterAllHistoricalResources() {
+ final PipeHistoricalDataRegionTsFileSource source = new
PipeHistoricalDataRegionTsFileSource();
+ final ProgressIndex expectedProgressIndex = new SimpleProgressIndex(0, 10);
+
+ setPrivateField(source, "hasBeenStarted", true);
+ setPrivateField(source, "pipeName", "pipe");
+ setPrivateField(source, "creationTime", 1L);
+ setPrivateField(source, "dataRegionId", 1);
+ setPrivateField(source, "pipeTaskMeta", new
PipeTaskMeta(MinimumProgressIndex.INSTANCE, 1));
+ setPrivateField(source, "pendingQueue", new ArrayDeque<TsFileResource>());
+ setPrivateField(source, "maxHistoricalProgressIndex",
expectedProgressIndex);
+ setPrivateField(source, "shouldReportMaxHistoricalProgressIndex", true);
+
+ final Event event = source.supply();
+
+ Assert.assertTrue(event instanceof ProgressReportEvent);
+ Assert.assertEquals(expectedProgressIndex, ((ProgressReportEvent)
event).getProgressIndex());
+ Assert.assertFalse((Boolean) getPrivateField(source,
"shouldReportMaxHistoricalProgressIndex"));
+ }
+
+ @Test
+ public void
testMayTsFileContainUnprocessedDataUsesTimePartitionProgressCoverage()
+ throws Exception {
+ final File tempDir =
Files.createTempDirectory("pipeHistoricalPartitionCoverage").toFile();
+
+ try {
+ final ProgressIndex startIndex =
+ new TimePartitionProgressIndex(0L, new SimpleProgressIndex(0, 100));
+ assertMayTsFileContainUnprocessedData(
+ tempDir,
+ 0L,
+ "partition-covered.tsfile",
+ startIndex,
+ new SimpleProgressIndex(0, 50),
+ false);
+ assertMayTsFileContainUnprocessedData(
+ tempDir,
+ 1L,
+ "partition-uncovered.tsfile",
+ startIndex,
+ new SimpleProgressIndex(0, 50),
+ true);
+
+ final ProgressIndex hybridStartIndex =
+ hybridProgressIndex(
+ startIndex, new RecoverProgressIndex(-1, new
SimpleProgressIndex(0, 1)));
+ assertMayTsFileContainUnprocessedData(
+ tempDir,
+ 0L,
+ "hybrid-partition-covered.tsfile",
+ hybridStartIndex,
+ new SimpleProgressIndex(0, 80),
+ false);
+ } finally {
+ FileUtils.deleteFileOrDirectory(tempDir);
+ }
+ }
+
@Test
public void testTsFileResourceCoveredByPattern() throws Exception {
final File tempDir =
Files.createTempDirectory("pipeHistoricalPatternCoverage").toFile();
@@ -150,8 +419,27 @@ public class PipeHistoricalDataRegionTsFileSourceTest {
final ProgressIndex resourceProgressIndex,
final boolean expected)
throws Exception {
- Assert.assertEquals(!expected,
startIndex.isEqualOrAfter(resourceProgressIndex));
+ assertMayTsFileContainUnprocessedData(
+ startIndex, createClosedTsFileResource(tempDir, fileName,
resourceProgressIndex), expected);
+ }
+ private static void assertMayTsFileContainUnprocessedData(
+ final File tempDir,
+ final long timePartitionId,
+ final String fileName,
+ final ProgressIndex startIndex,
+ final ProgressIndex resourceProgressIndex,
+ final boolean expected)
+ throws Exception {
+ assertMayTsFileContainUnprocessedData(
+ startIndex,
+ createClosedTsFileResource(tempDir, timePartitionId, fileName,
resourceProgressIndex),
+ expected);
+ }
+
+ private static void assertMayTsFileContainUnprocessedData(
+ final ProgressIndex startIndex, final TsFileResource resource, final
boolean expected)
+ throws Exception {
final PipeHistoricalDataRegionTsFileSource source = new
PipeHistoricalDataRegionTsFileSource();
setPrivateField(source, "pipeName", "pipe");
setPrivateField(source, "dataRegionId", 1);
@@ -161,10 +449,22 @@ public class PipeHistoricalDataRegionTsFileSourceTest {
PipeHistoricalDataRegionTsFileSource.class.getDeclaredMethod(
"mayTsFileContainUnprocessedData", TsFileResource.class);
method.setAccessible(true);
- Assert.assertEquals(
- expected,
- method.invoke(
- source, createClosedTsFileResource(tempDir, fileName,
resourceProgressIndex)));
+ Assert.assertEquals(expected, method.invoke(source, resource));
+ }
+
+ private static TsFileResource createTsFileResource(final File tempDir, final
String fileName)
+ throws IOException {
+ final File file = new File(tempDir, fileName);
+ Assert.assertTrue(file.createNewFile());
+ return new TsFileResource(file);
+ }
+
+ private static TsFileResource createTsFileResource(
+ final File tempDir, final long timePartitionId, final String fileName)
throws IOException {
+ final File regionDir = new File(tempDir, "1");
+ final File partitionDir = new File(regionDir,
String.valueOf(timePartitionId));
+ Assert.assertTrue(partitionDir.exists() || partitionDir.mkdirs());
+ return createTsFileResource(partitionDir, fileName);
}
private static TsFileResource createClosedTsFileResource(
@@ -179,6 +479,18 @@ public class PipeHistoricalDataRegionTsFileSourceTest {
return resource;
}
+ private static TsFileResource createClosedTsFileResource(
+ final File tempDir,
+ final long timePartitionId,
+ final String fileName,
+ final ProgressIndex progressIndex)
+ throws IOException {
+ final TsFileResource resource = createTsFileResource(tempDir,
timePartitionId, fileName);
+ resource.setStatusForTest(TsFileResourceStatus.NORMAL);
+ resource.updateProgressIndex(progressIndex);
+ return resource;
+ }
+
private static TsFileResource createClosedTsFileResourceWithDevices(
final File tempDir, final String fileName, final String... devices)
throws Exception {
final TsFileResource resource =
@@ -200,19 +512,57 @@ public class PipeHistoricalDataRegionTsFileSourceTest {
return result;
}
- private static void setPrivateField(
- final PipeHistoricalDataRegionTsFileSource source, final String
fieldName, final Object value)
+ private static void sortExtractedResources(
+ final PipeHistoricalDataRegionTsFileSource source, final
List<TsFileResource> resources)
throws ReflectiveOperationException {
- final Field field =
PipeHistoricalDataRegionTsFileSource.class.getDeclaredField(fieldName);
- field.setAccessible(true);
- field.set(source, value);
+ final Method method =
+ PipeHistoricalDataRegionTsFileSource.class.getDeclaredMethod(
+ "sortExtractedResources", List.class);
+ method.setAccessible(true);
+ method.invoke(source, resources);
}
- private static Object getPrivateField(
- final PipeHistoricalDataRegionTsFileSource source, final String
fieldName)
+ private static void prepareResourcesForHistoricalTsFileQueryPriorityOrder(
+ final PipeHistoricalDataRegionTsFileSource source, final
List<TsFileResource> resources)
throws ReflectiveOperationException {
- final Field field =
PipeHistoricalDataRegionTsFileSource.class.getDeclaredField(fieldName);
- field.setAccessible(true);
- return field.get(source);
+ final Method method =
+ PipeHistoricalDataRegionTsFileSource.class.getDeclaredMethod(
+ "prepareResourcesForHistoricalTsFileQueryPriorityOrder",
List.class);
+ method.setAccessible(true);
+ method.invoke(source, resources);
+ }
+
+ private static void
prepareProgressReportResourcesForHistoricalTsFileQueryPriorityOrder(
+ final PipeHistoricalDataRegionTsFileSource source, final
List<TsFileResource> resources)
+ throws ReflectiveOperationException {
+ final Method method =
+ PipeHistoricalDataRegionTsFileSource.class.getDeclaredMethod(
+
"prepareProgressReportResourcesForHistoricalTsFileQueryPriorityOrder",
List.class);
+ method.setAccessible(true);
+ method.invoke(source, resources);
+ }
+
+ private static Object getPrivateField(
+ final PipeHistoricalDataRegionTsFileSource source, final String
fieldName) {
+ try {
+ final Field field =
PipeHistoricalDataRegionTsFileSource.class.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ return field.get(source);
+ } catch (final ReflectiveOperationException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ private static void setPrivateField(
+ final PipeHistoricalDataRegionTsFileSource source,
+ final String fieldName,
+ final Object value) {
+ try {
+ final Field field =
PipeHistoricalDataRegionTsFileSource.class.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ field.set(source, value);
+ } catch (final ReflectiveOperationException e) {
+ throw new AssertionError(e);
+ }
}
}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/TsFileResourceProgressIndexTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/TsFileResourceProgressIndexTest.java
index a0a61e3bbd3..0ad03dc8888 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/TsFileResourceProgressIndexTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/TsFileResourceProgressIndexTest.java
@@ -26,6 +26,7 @@ import
org.apache.iotdb.commons.consensus.index.impl.IoTProgressIndex;
import org.apache.iotdb.commons.consensus.index.impl.MinimumProgressIndex;
import org.apache.iotdb.commons.consensus.index.impl.RecoverProgressIndex;
import org.apache.iotdb.commons.consensus.index.impl.SimpleProgressIndex;
+import
org.apache.iotdb.commons.consensus.index.impl.TimePartitionProgressIndex;
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.tsfile.generator.TsFileNameGenerator;
@@ -254,6 +255,47 @@ public class TsFileResourceProgressIndexTest {
hybridProgressIndex.isAfter(new RecoverProgressIndex(1, new
SimpleProgressIndex(2, 21))));
}
+ @Test
+ public void testTimePartitionProgressIndex() {
+ final TimePartitionProgressIndex partition0Progress100 =
+ new TimePartitionProgressIndex(0L, new SimpleProgressIndex(0, 100));
+
+ Assert.assertTrue(
+ partition0Progress100.isProgressIndexEqualOrAfter(0L, new
SimpleProgressIndex(0, 50)));
+ Assert.assertFalse(
+ partition0Progress100.isProgressIndexEqualOrAfter(1L, new
SimpleProgressIndex(0, 50)));
+
+ final TimePartitionProgressIndex partition0Progress120 =
+ (TimePartitionProgressIndex)
+ partition0Progress100.updateToMinimumEqualOrIsAfterProgressIndex(
+ new TimePartitionProgressIndex(0L, new SimpleProgressIndex(0,
120)));
+ Assert.assertTrue(
+ partition0Progress120.isProgressIndexEqualOrAfter(0L, new
SimpleProgressIndex(0, 120)));
+ Assert.assertFalse(
+ partition0Progress120.isProgressIndexEqualOrAfter(0L, new
SimpleProgressIndex(0, 121)));
+
+ final TimePartitionProgressIndex partition0And1Progress =
+ (TimePartitionProgressIndex)
+ partition0Progress120.updateToMinimumEqualOrIsAfterProgressIndex(
+ new TimePartitionProgressIndex(1L, new SimpleProgressIndex(0,
20)));
+ Assert.assertTrue(
+ partition0And1Progress.isProgressIndexEqualOrAfter(0L, new
SimpleProgressIndex(0, 120)));
+ Assert.assertTrue(
+ partition0And1Progress.isProgressIndexEqualOrAfter(1L, new
SimpleProgressIndex(0, 20)));
+
+ final ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
+ partition0And1Progress.serialize(byteBuffer);
+ byteBuffer.flip();
+ Assert.assertEquals(partition0And1Progress,
ProgressIndexType.deserializeFrom(byteBuffer));
+
+ final ProgressIndex hybridProgressIndex =
+ partition0And1Progress.updateToMinimumEqualOrIsAfterProgressIndex(
+ new IoTProgressIndex(1, 100L));
+ Assert.assertTrue(hybridProgressIndex instanceof HybridProgressIndex);
+
Assert.assertTrue(hybridProgressIndex.isEqualOrAfter(partition0And1Progress));
+ Assert.assertTrue(hybridProgressIndex.isEqualOrAfter(new
IoTProgressIndex(1, 100L)));
+ }
+
@Test
public void testProgressIndexMinimumProgressIndexTopologicalSort() {
List<ProgressIndex> progressIndexList = new ArrayList<>();
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/consensus/index/ProgressIndexType.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/consensus/index/ProgressIndexType.java
index 58548e18c4b..07f1b85205f 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/consensus/index/ProgressIndexType.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/consensus/index/ProgressIndexType.java
@@ -26,6 +26,7 @@ import
org.apache.iotdb.commons.consensus.index.impl.MinimumProgressIndex;
import org.apache.iotdb.commons.consensus.index.impl.RecoverProgressIndex;
import org.apache.iotdb.commons.consensus.index.impl.SimpleProgressIndex;
import org.apache.iotdb.commons.consensus.index.impl.StateProgressIndex;
+import
org.apache.iotdb.commons.consensus.index.impl.TimePartitionProgressIndex;
import
org.apache.iotdb.commons.consensus.index.impl.TimeWindowStateProgressIndex;
import org.apache.tsfile.utils.ReadWriteIOUtils;
@@ -43,7 +44,8 @@ public enum ProgressIndexType {
HYBRID_PROGRESS_INDEX((short) 5),
META_PROGRESS_INDEX((short) 6),
TIME_WINDOW_STATE_PROGRESS_INDEX((short) 7),
- STATE_PROGRESS_INDEX((short) 8);
+ STATE_PROGRESS_INDEX((short) 8),
+ TIME_PARTITION_PROGRESS_INDEX((short) 9);
private final short type;
@@ -82,6 +84,8 @@ public enum ProgressIndexType {
return TimeWindowStateProgressIndex.deserializeFrom(byteBuffer);
case 8:
return StateProgressIndex.deserializeFrom(byteBuffer);
+ case 9:
+ return TimePartitionProgressIndex.deserializeFrom(byteBuffer);
default:
throw new UnsupportedOperationException(
String.format("Unsupported progress index type %s.", indexType));
@@ -107,6 +111,8 @@ public enum ProgressIndexType {
return TimeWindowStateProgressIndex.deserializeFrom(stream);
case 8:
return StateProgressIndex.deserializeFrom(stream);
+ case 9:
+ return TimePartitionProgressIndex.deserializeFrom(stream);
default:
throw new UnsupportedOperationException(
String.format("Unsupported progress index type %s.", indexType));
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/consensus/index/impl/TimePartitionProgressIndex.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/consensus/index/impl/TimePartitionProgressIndex.java
new file mode 100644
index 00000000000..05ce7c86718
--- /dev/null
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/consensus/index/impl/TimePartitionProgressIndex.java
@@ -0,0 +1,301 @@
+/*
+ * 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.iotdb.commons.consensus.index.impl;
+
+import org.apache.iotdb.commons.consensus.index.ProgressIndex;
+import org.apache.iotdb.commons.consensus.index.ProgressIndexType;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.tsfile.utils.RamUsageEstimator;
+import org.apache.tsfile.utils.ReadWriteIOUtils;
+
+import javax.annotation.Nonnull;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.stream.Collectors;
+
+public class TimePartitionProgressIndex extends ProgressIndex {
+
+ private static final long INSTANCE_SIZE =
+ RamUsageEstimator.shallowSizeOfInstance(TimePartitionProgressIndex.class)
+ + RamUsageEstimator.shallowSizeOfInstance(HashMap.class)
+ + ProgressIndex.LOCK_SIZE;
+ private static final long ENTRY_SIZE =
+ RamUsageEstimator.HASHTABLE_RAM_BYTES_PER_ENTRY
+ + RamUsageEstimator.alignObjectSize(Long.BYTES);
+
+ private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+
+ private final Map<Long, ProgressIndex> timePartitionId2ProgressIndex;
+
+ private TimePartitionProgressIndex() {
+ this(new HashMap<>());
+ }
+
+ public TimePartitionProgressIndex(final Map<Long, ProgressIndex>
timePartitionId2ProgressIndex) {
+ this.timePartitionId2ProgressIndex = new HashMap<>();
+ timePartitionId2ProgressIndex.forEach(
+ (timePartitionId, progressIndex) -> {
+ if (Objects.nonNull(progressIndex) && !(progressIndex instanceof
MinimumProgressIndex)) {
+ this.timePartitionId2ProgressIndex.put(timePartitionId,
progressIndex);
+ }
+ });
+ }
+
+ public TimePartitionProgressIndex(final long timePartitionId, final
ProgressIndex progressIndex) {
+ this(Collections.singletonMap(timePartitionId, progressIndex));
+ }
+
+ public Map<Long, ProgressIndex> getTimePartitionId2ProgressIndex() {
+ lock.readLock().lock();
+ try {
+ return ImmutableMap.copyOf(timePartitionId2ProgressIndex);
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ public boolean isProgressIndexEqualOrAfter(
+ final long timePartitionId, final ProgressIndex progressIndex) {
+ lock.readLock().lock();
+ try {
+ final ProgressIndex timePartitionProgressIndex =
+ timePartitionId2ProgressIndex.get(timePartitionId);
+ return Objects.nonNull(timePartitionProgressIndex)
+ && timePartitionProgressIndex.isEqualOrAfter(progressIndex);
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ @Override
+ public void serialize(final ByteBuffer byteBuffer) {
+ lock.readLock().lock();
+ try {
+ ProgressIndexType.TIME_PARTITION_PROGRESS_INDEX.serialize(byteBuffer);
+
+ ReadWriteIOUtils.write(timePartitionId2ProgressIndex.size(), byteBuffer);
+ for (final Map.Entry<Long, ProgressIndex> entry :
timePartitionId2ProgressIndex.entrySet()) {
+ ReadWriteIOUtils.write(entry.getKey(), byteBuffer);
+ entry.getValue().serialize(byteBuffer);
+ }
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ @Override
+ public void serialize(final OutputStream stream) throws IOException {
+ lock.readLock().lock();
+ try {
+ ProgressIndexType.TIME_PARTITION_PROGRESS_INDEX.serialize(stream);
+
+ ReadWriteIOUtils.write(timePartitionId2ProgressIndex.size(), stream);
+ for (final Map.Entry<Long, ProgressIndex> entry :
timePartitionId2ProgressIndex.entrySet()) {
+ ReadWriteIOUtils.write(entry.getKey(), stream);
+ entry.getValue().serialize(stream);
+ }
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ @Override
+ public boolean isAfter(@Nonnull final ProgressIndex progressIndex) {
+ lock.readLock().lock();
+ try {
+ if (progressIndex instanceof MinimumProgressIndex) {
+ return !timePartitionId2ProgressIndex.isEmpty();
+ }
+
+ if (progressIndex instanceof HybridProgressIndex) {
+ return ((HybridProgressIndex)
progressIndex).isGivenProgressIndexAfterSelf(this);
+ }
+
+ if (!(progressIndex instanceof TimePartitionProgressIndex)) {
+ return false;
+ }
+
+ final TimePartitionProgressIndex thatTimePartitionProgressIndex =
+ (TimePartitionProgressIndex) progressIndex;
+ boolean hasStrictlyAfterTimePartition =
+ timePartitionId2ProgressIndex.size()
+ >
thatTimePartitionProgressIndex.timePartitionId2ProgressIndex.size();
+ for (final Map.Entry<Long, ProgressIndex> entry :
+
thatTimePartitionProgressIndex.timePartitionId2ProgressIndex.entrySet()) {
+ final ProgressIndex thisProgressIndex =
timePartitionId2ProgressIndex.get(entry.getKey());
+ if (Objects.isNull(thisProgressIndex)
+ || !thisProgressIndex.isEqualOrAfter(entry.getValue())) {
+ return false;
+ }
+ if (thisProgressIndex.isAfter(entry.getValue())) {
+ hasStrictlyAfterTimePartition = true;
+ }
+ }
+ return hasStrictlyAfterTimePartition;
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ @Override
+ public boolean equals(final ProgressIndex progressIndex) {
+ lock.readLock().lock();
+ try {
+ if (!(progressIndex instanceof TimePartitionProgressIndex)) {
+ return false;
+ }
+
+ return timePartitionId2ProgressIndex.equals(
+ ((TimePartitionProgressIndex)
progressIndex).timePartitionId2ProgressIndex);
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof TimePartitionProgressIndex)) {
+ return false;
+ }
+ return this.equals((TimePartitionProgressIndex) obj);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(timePartitionId2ProgressIndex);
+ }
+
+ @Override
+ public ProgressIndex updateToMinimumEqualOrIsAfterProgressIndex(
+ final ProgressIndex progressIndex) {
+ lock.writeLock().lock();
+ try {
+ if (progressIndex == null || progressIndex instanceof
MinimumProgressIndex) {
+ return this;
+ }
+
+ if (!(progressIndex instanceof TimePartitionProgressIndex)) {
+ return ProgressIndex.blendProgressIndex(this, progressIndex);
+ }
+
+ final Map<Long, ProgressIndex> updatedTimePartitionId2ProgressIndex =
+ new HashMap<>(timePartitionId2ProgressIndex);
+ ((TimePartitionProgressIndex) progressIndex)
+ .timePartitionId2ProgressIndex.forEach(
+ (thatK, thatV) ->
+ updatedTimePartitionId2ProgressIndex.compute(
+ thatK,
+ (thisK, thisV) ->
+ Objects.isNull(thisV)
+ ? thatV
+ :
thisV.updateToMinimumEqualOrIsAfterProgressIndex(thatV)));
+ return new
TimePartitionProgressIndex(updatedTimePartitionId2ProgressIndex);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ @Override
+ public ProgressIndexType getType() {
+ return ProgressIndexType.TIME_PARTITION_PROGRESS_INDEX;
+ }
+
+ @Override
+ public <T extends ProgressIndex> Optional<T> getProgressIndexByType(
+ final Class<T> progressIndexClass) {
+ return progressIndexClass.isInstance(this)
+ ? Optional.of(progressIndexClass.cast(this))
+ : Optional.empty();
+ }
+
+ @Override
+ public TotalOrderSumTuple getTotalOrderSumTuple() {
+ lock.readLock().lock();
+ try {
+ final ArrayList<TotalOrderSumTuple> tupleList =
+ timePartitionId2ProgressIndex.values().stream()
+ .map(ProgressIndex::getTotalOrderSumTuple)
+ .collect(Collectors.toCollection(ArrayList::new));
+ tupleList.add(new TotalOrderSumTuple((long)
timePartitionId2ProgressIndex.size()));
+ return ProgressIndex.TotalOrderSumTuple.sum(tupleList);
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ public static TimePartitionProgressIndex deserializeFrom(final ByteBuffer
byteBuffer) {
+ final TimePartitionProgressIndex timePartitionProgressIndex = new
TimePartitionProgressIndex();
+
+ final int size = ReadWriteIOUtils.readInt(byteBuffer);
+ for (int i = 0; i < size; ++i) {
+ final long timePartitionId = ReadWriteIOUtils.readLong(byteBuffer);
+ final ProgressIndex progressIndex =
ProgressIndexType.deserializeFrom(byteBuffer);
+
timePartitionProgressIndex.timePartitionId2ProgressIndex.put(timePartitionId,
progressIndex);
+ }
+ return timePartitionProgressIndex;
+ }
+
+ public static TimePartitionProgressIndex deserializeFrom(final InputStream
stream)
+ throws IOException {
+ final TimePartitionProgressIndex timePartitionProgressIndex = new
TimePartitionProgressIndex();
+
+ final int size = ReadWriteIOUtils.readInt(stream);
+ for (int i = 0; i < size; ++i) {
+ final long timePartitionId = ReadWriteIOUtils.readLong(stream);
+ final ProgressIndex progressIndex =
ProgressIndexType.deserializeFrom(stream);
+
timePartitionProgressIndex.timePartitionId2ProgressIndex.put(timePartitionId,
progressIndex);
+ }
+ return timePartitionProgressIndex;
+ }
+
+ @Override
+ public String toString() {
+ return "TimePartitionProgressIndex{"
+ + "timePartitionId2ProgressIndex="
+ + timePartitionId2ProgressIndex
+ + '}';
+ }
+
+ @Override
+ public long ramBytesUsed() {
+ return INSTANCE_SIZE
+ + timePartitionId2ProgressIndex.size() * ENTRY_SIZE
+ + timePartitionId2ProgressIndex.values().stream()
+ .map(ProgressIndex::ramBytesUsed)
+ .reduce(0L, Long::sum);
+ }
+}
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSourceConstant.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSourceConstant.java
index fe53e228350..346a274563b 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSourceConstant.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSourceConstant.java
@@ -89,6 +89,11 @@ public class PipeSourceConstant {
public static final String EXTRACTOR_HISTORY_LOOSE_RANGE_PATH_VALUE = "path";
public static final String EXTRACTOR_HISTORY_LOOSE_RANGE_ALL_VALUE = "all";
public static final String EXTRACTOR_HISTORY_LOOSE_RANGE_DEFAULT_VALUE = "";
+ public static final String
EXTRACTOR_HISTORY_TSFILE_ORDER_BY_QUERY_PRIORITY_KEY =
+ "extractor.history.tsfile.order-by-query-priority";
+ public static final String SOURCE_HISTORY_TSFILE_ORDER_BY_QUERY_PRIORITY_KEY
=
+ "source.history.tsfile.order-by-query-priority";
+ public static final boolean
EXTRACTOR_HISTORY_TSFILE_ORDER_BY_QUERY_PRIORITY_DEFAULT_VALUE = true;
public static final String EXTRACTOR_MODS_ENABLE_KEY =
"extractor.mods.enable";
public static final String SOURCE_MODS_ENABLE_KEY = "source.mods.enable";
public static final boolean EXTRACTOR_MODS_ENABLE_DEFAULT_VALUE = false;
diff --git
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/task/PipeMetaDeSerTest.java
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/task/PipeMetaDeSerTest.java
index 1e2ecc1bfd3..0992939a5e4 100644
---
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/task/PipeMetaDeSerTest.java
+++
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/task/PipeMetaDeSerTest.java
@@ -19,12 +19,14 @@
package org.apache.iotdb.commons.pipe.task;
+import org.apache.iotdb.commons.consensus.index.ProgressIndex;
import org.apache.iotdb.commons.consensus.index.impl.HybridProgressIndex;
import org.apache.iotdb.commons.consensus.index.impl.IoTProgressIndex;
import org.apache.iotdb.commons.consensus.index.impl.MetaProgressIndex;
import org.apache.iotdb.commons.consensus.index.impl.MinimumProgressIndex;
import org.apache.iotdb.commons.consensus.index.impl.RecoverProgressIndex;
import org.apache.iotdb.commons.consensus.index.impl.SimpleProgressIndex;
+import
org.apache.iotdb.commons.consensus.index.impl.TimePartitionProgressIndex;
import
org.apache.iotdb.commons.consensus.index.impl.TimeWindowStateProgressIndex;
import org.apache.iotdb.commons.exception.pipe.PipeRuntimeCriticalException;
import
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkCriticalException;
@@ -118,6 +120,17 @@ public class PipeMetaDeSerTest {
new PipeTaskMeta(
new
TimeWindowStateProgressIndex(timeSeries2TimestampWindowBufferPairMap),
789));
+ put(
+ 789,
+ new PipeTaskMeta(
+ new TimePartitionProgressIndex(
+ new HashMap<Long, ProgressIndex>() {
+ {
+ put(0L, new SimpleProgressIndex(0, 1));
+ put(1L, new SimpleProgressIndex(0, 2));
+ }
+ }),
+ 789));
put(Integer.MIN_VALUE, new PipeTaskMeta(new
MetaProgressIndex(987), 0));
}
});