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 e3c4a4e5178 [Pipe] Support region-level realtime downgrading (#18441)
e3c4a4e5178 is described below
commit e3c4a4e5178d4bb34141a10e7f545e3cb47f8028
Author: Caideyipi <[email protected]>
AuthorDate: Mon Oct 12 10:05:22 2026 +0800
[Pipe] Support region-level realtime downgrading (#18441)
* Pipe: support region-level realtime downgrading
* Pipe: make region-level downgrading pipe-specific
* Pipe: address region-level downgrading review feedback
* [Pipe] Preserve non-deferred TsFile transfer completion
* [Pipe] Remove redundant progress index local
* [Pipe] Preserve query priority during region-level downgrading
* [Pipe] Order realtime TsFile source events and address downgrade review
---
.../event/dml/insertion/TsFileInsertionEvent.java | 5 +
.../agent/task/connection/PipeEventCollector.java | 10 +
.../common/tablet/PipeRawTabletInsertionEvent.java | 31 +-
.../common/tsfile/PipeTsFileInsertionEvent.java | 201 ++-
.../PipeRealtimeDataRegionHybridSource.java | 668 +++++++++-
.../realtime/PipeRealtimeDataRegionSource.java | 35 +
.../PipeRealtimeDataRegionTsFileSource.java | 150 ++-
.../db/pipe/source/PipeRealtimeExtractTest.java | 1277 +++++++++++++++++++-
.../pipe/config/constant/PipeSourceConstant.java | 5 +
9 files changed, 2311 insertions(+), 71 deletions(-)
diff --git
a/iotdb-api/pipe-api/src/main/java/org/apache/iotdb/pipe/api/event/dml/insertion/TsFileInsertionEvent.java
b/iotdb-api/pipe-api/src/main/java/org/apache/iotdb/pipe/api/event/dml/insertion/TsFileInsertionEvent.java
index 4c7fffcfba5..594a3c89a64 100644
---
a/iotdb-api/pipe-api/src/main/java/org/apache/iotdb/pipe/api/event/dml/insertion/TsFileInsertionEvent.java
+++
b/iotdb-api/pipe-api/src/main/java/org/apache/iotdb/pipe/api/event/dml/insertion/TsFileInsertionEvent.java
@@ -33,6 +33,11 @@ public interface TsFileInsertionEvent extends Event,
AutoCloseable {
* The method is used to convert the {@link TsFileInsertionEvent} into
several {@link
* TabletInsertionEvent}s.
*
+ * <p>The returned iterable represents the lifetime of the conversion.
Processors that retain it
+ * for asynchronous work must eventually exhaust the iterable before the
event can be considered
+ * complete. A processor should not retain the iterable and silently abandon
it, because the
+ * source cannot otherwise determine when all generated tablet events have
finished.
+ *
* @return {@code Iterable<TabletInsertionEvent>} the list of {@link
TabletInsertionEvent}
*/
Iterable<TabletInsertionEvent> toTabletInsertionEvents();
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/connection/PipeEventCollector.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/connection/PipeEventCollector.java
index ac0f6c7bf0b..53efadc77ed 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/connection/PipeEventCollector.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/connection/PipeEventCollector.java
@@ -273,8 +273,18 @@ public class PipeEventCollector implements EventCollector {
((PipeHeartbeatEvent) event).recordConnectorQueueSize(pendingQueue);
}
+ if (event instanceof PipeRawTabletInsertionEvent
+ && ((PipeRawTabletInsertionEvent) event).getSourceEvent()
+ instanceof PipeTsFileInsertionEvent) {
+ // Register before publishing to the queue so a concurrent queue cleanup
can report a
+ // generated tablet as discarded instead of racing with registration.
+ ((PipeTsFileInsertionEvent) ((PipeRawTabletInsertionEvent)
event).getSourceEvent())
+ .registerGeneratedTabletInsertionEvent((PipeRawTabletInsertionEvent)
event);
+ }
+
if (pendingQueue.offer(event)) {
collectInvocationCount.incrementAndGet();
+ return;
}
}
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 dc2ab1d381f..02162bbf3d9 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
@@ -64,6 +64,8 @@ public class PipeRawTabletInsertionEvent extends
PipeInsertionEvent
private final boolean isAligned;
private final EnrichedEvent sourceEvent;
+ private final AtomicBoolean generatedEventRegisteredWithSource = new
AtomicBoolean(false);
+ private final AtomicBoolean generatedEventOutcomeReported = new
AtomicBoolean(false);
private boolean needToReport;
private final PipeTabletMemoryBlock allocatedMemoryBlock;
@@ -118,12 +120,19 @@ public class PipeRawTabletInsertionEvent extends
PipeInsertionEvent
this.allocatedMemoryBlock =
PipeDataNodeResourceManager.memory().forceAllocateForTabletWithRetry(0);
- if (needToReport) {
+ if (needToReport || sourceEvent instanceof PipeTsFileInsertionEvent) {
addOnCommittedHook(
() -> {
- if (shouldReportOnCommit) {
+ // The last generated tablet still owns the progress-index
cleanup. Other generated
+ // tablets only contribute to the transfer completion of their
source TsFile.
+ if (shouldReportOnCommit && needToReport) {
eliminateProgressIndex();
}
+ if (sourceEvent instanceof PipeTsFileInsertionEvent
+ && generatedEventOutcomeReported.compareAndSet(false, true)) {
+ ((PipeTsFileInsertionEvent) sourceEvent)
+ .markGeneratedTabletInsertionEventAsTransferred();
+ }
});
}
}
@@ -306,6 +315,22 @@ public class PipeRawTabletInsertionEvent extends
PipeInsertionEvent
return true;
}
+ /** Marks this generated tablet as accepted by the processor output
collector. */
+ public void markAsGeneratedEventRegisteredWithSource() {
+ generatedEventRegisteredWithSource.set(true);
+ }
+
+ @Override
+ public boolean clearReferenceCount(final String holderMessage) {
+ final boolean cleared = super.clearReferenceCount(holderMessage);
+ if (generatedEventRegisteredWithSource.get()
+ && generatedEventOutcomeReported.compareAndSet(false, true)
+ && sourceEvent instanceof PipeTsFileInsertionEvent) {
+ ((PipeTsFileInsertionEvent)
sourceEvent).markGeneratedTabletInsertionEventAsDiscarded();
+ }
+ return cleared;
+ }
+
protected void eliminateProgressIndex() {
if (sourceEvent instanceof PipeTsFileInsertionEvent) {
((PipeTsFileInsertionEvent) sourceEvent).eliminateProgressIndex();
@@ -391,7 +416,7 @@ public class PipeRawTabletInsertionEvent extends
PipeInsertionEvent
}
public void markAsNeedToReport() {
- if (!needToReport) {
+ if (!needToReport && !(sourceEvent instanceof PipeTsFileInsertionEvent)) {
addOnCommittedHook(
() -> {
if (shouldReportOnCommit) {
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 aebdec7d3d6..9a89c1d367c 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
@@ -124,6 +124,18 @@ public class PipeTsFileInsertionEvent extends
PipeInsertionEvent
private final AtomicInteger parsedTabletInsertionEventCount = new
AtomicInteger(0);
private final AtomicBoolean isTsFileParsingCompleted = new
AtomicBoolean(false);
private final AtomicLong parsedPointCountForCount = new AtomicLong(0);
+ private final AtomicInteger generatedTabletInsertionEventCount = new
AtomicInteger(0);
+ private final AtomicInteger transferredGeneratedTabletInsertionEventCount =
new AtomicInteger(0);
+ private final AtomicInteger discardedGeneratedTabletInsertionEventCount =
new AtomicInteger(0);
+ private final AtomicBoolean generatedTabletInsertionEventsParsingStarted =
+ new AtomicBoolean(false);
+ private final AtomicBoolean generatedTabletInsertionEventsParsingCompleted =
+ new AtomicBoolean(false);
+ private final AtomicBoolean isTsFileEventCommitted = new
AtomicBoolean(false);
+ private final AtomicBoolean isTransferred = new AtomicBoolean(false);
+ private final AtomicBoolean isDiscarded = new AtomicBoolean(false);
+ private final List<Runnable> onTransferredHooks = new ArrayList<>();
+ private final List<Runnable> onDiscardedHooks = new ArrayList<>();
// The point count of the TsFile. Used for metrics on IoTConsensusV2'
receiver side.
// May be updated after it is flushed. Should be negative if not set.
@@ -311,6 +323,7 @@ public class PipeTsFileInsertionEvent extends
PipeInsertionEvent
if (shouldReportOnCommit) {
eliminateProgressIndex();
}
+ markAsTransferredOnCommit();
});
}
@@ -542,6 +555,16 @@ public class PipeTsFileInsertionEvent extends
PipeInsertionEvent
return isSuccessful;
}
+ @Override
+ public boolean clearReferenceCount(final String holderMessage) {
+ final boolean cleared = super.clearReferenceCount(holderMessage);
+ // clearReferenceCount intentionally bypasses the commit queue. Notify the
realtime source even
+ // when the event was already released by a previous holder: its commit
hook may still be
+ // waiting behind an earlier commit, while the explicit discard must
release the in-flight slot.
+ markAsDiscarded();
+ return cleared;
+ }
+
@Override
public void bindProgressIndex(final ProgressIndex overridingProgressIndex) {
this.overridingProgressIndex = overridingProgressIndex;
@@ -597,6 +620,133 @@ public class PipeTsFileInsertionEvent extends
PipeInsertionEvent
}
}
+ /**
+ * Records a tablet generated from this TsFile that has been accepted by the
processor output
+ * collector. The transfer hook must wait for all such tablets, rather than
the first one, before
+ * allowing the region-level downgrade to end.
+ */
+ public void registerGeneratedTabletInsertionEvent() {
+ generatedTabletInsertionEventCount.incrementAndGet();
+ }
+
+ public void registerGeneratedTabletInsertionEvent(final
PipeRawTabletInsertionEvent event) {
+ event.markAsGeneratedEventRegisteredWithSource();
+ registerGeneratedTabletInsertionEvent();
+ }
+
+ /** Marks that a processor has obtained the tablet iterable and may consume
it later. */
+ public void markGeneratedTabletInsertionEventsParsingStarted() {
+ generatedTabletInsertionEventsParsingStarted.set(true);
+ }
+
+ /** Records that one tablet generated from this TsFile has been committed
downstream. */
+ public void markGeneratedTabletInsertionEventAsTransferred() {
+ transferredGeneratedTabletInsertionEventCount.incrementAndGet();
+ markAsTransferredIfGeneratedTabletEventsCompleted();
+ }
+
+ /** Records that one generated tablet was discarded before it could be
committed downstream. */
+ public void markGeneratedTabletInsertionEventAsDiscarded() {
+ discardedGeneratedTabletInsertionEventCount.incrementAndGet();
+ markAsTransferredIfGeneratedTabletEventsCompleted();
+ }
+
+ /** Marks the end of tablet generation for this TsFile. */
+ public void markGeneratedTabletInsertionEventsParsingCompleted() {
+ generatedTabletInsertionEventsParsingCompleted.set(true);
+ markAsTransferredIfGeneratedTabletEventsCompleted();
+ }
+
+ private void markAsTransferredIfGeneratedTabletEventsCompleted() {
+ final boolean generationBoundaryReached =
+ !generatedTabletInsertionEventsParsingStarted.get()
+ || generatedTabletInsertionEventsParsingCompleted.get();
+ // The normal processor path does not retain a tablet iterable unless
parsing has explicitly
+ // been marked as started. In that path, registering the generated
tablets and reaching the
+ // generation boundary is sufficient to establish the transfer boundary
(the source TsFile
+ // commit may be reported separately or may be intentionally skipped).
Once an iterable has
+ // been retained, however, the source TsFile commit must also be observed
before the generated
+ // tablet counts can release the TsFile. This distinction keeps deferred
parsing safe without
+ // delaying the established non-deferred path.
+ if ((isTsFileEventCommitted.get() ||
!generatedTabletInsertionEventsParsingStarted.get())
+ && generationBoundaryReached
+ && transferredGeneratedTabletInsertionEventCount.get()
+ + discardedGeneratedTabletInsertionEventCount.get()
+ >= generatedTabletInsertionEventCount.get()) {
+ markAsTransferred();
+ }
+ }
+
+ private void markAsTransferredOnCommit() {
+ // A processor may retain the iterable returned by
toTabletInsertionEvents() and consume it
+ // after process() returns. In that case the iterable wrapper keeps the
generation boundary open
+ // until its iterator is exhausted. If no processor ever requests the
iterable (for example,
+ // DoNothingProcessor), committing the TsFile itself is the completion
boundary.
+ isTsFileEventCommitted.set(true);
+ markAsTransferredIfGeneratedTabletEventsCompleted();
+ }
+
+ private void markAsTransferred() {
+ final List<Runnable> hooksToRun;
+ synchronized (onTransferredHooks) {
+ if (isDiscarded.get() || !isTransferred.compareAndSet(false, true)) {
+ return;
+ }
+ hooksToRun = new ArrayList<>(onTransferredHooks);
+ onTransferredHooks.clear();
+ onDiscardedHooks.clear();
+ }
+ hooksToRun.forEach(Runnable::run);
+ }
+
+ private void markAsDiscarded() {
+ final List<Runnable> hooksToRun;
+ synchronized (onTransferredHooks) {
+ if (isTransferred.get() || !isDiscarded.compareAndSet(false, true)) {
+ return;
+ }
+ hooksToRun = new ArrayList<>(onDiscardedHooks);
+ onDiscardedHooks.clear();
+ onTransferredHooks.clear();
+ }
+ hooksToRun.forEach(Runnable::run);
+ }
+
+ /**
+ * Adds a hook that is invoked after this TsFile, or the last tablet
generated from it, is
+ * committed downstream.
+ */
+ public void addOnTransferredHook(final Runnable hook) {
+ boolean runHook = false;
+ synchronized (onTransferredHooks) {
+ if (!isTransferred.get() && !isDiscarded.get()) {
+ onTransferredHooks.add(hook);
+ return;
+ }
+ runHook = isTransferred.get();
+ }
+ if (runHook) {
+ hook.run();
+ }
+ }
+
+ /**
+ * Adds a hook that runs when this TsFile event is explicitly discarded via
clearReferenceCount.
+ */
+ public void addOnDiscardedHook(final Runnable hook) {
+ boolean runHook = false;
+ synchronized (onTransferredHooks) {
+ if (!isTransferred.get() && !isDiscarded.get()) {
+ onDiscardedHooks.add(hook);
+ return;
+ }
+ runHook = isDiscarded.get();
+ }
+ if (runHook) {
+ hook.run();
+ }
+ }
+
public PipeTsFileInsertionEvent bindTsFileDedupScopeID(final String
tsFileDedupScopeID) {
this.tsFileDedupScopeID = tsFileDedupScopeID;
return this;
@@ -867,6 +1017,7 @@ public class PipeTsFileInsertionEvent extends
PipeInsertionEvent
final String callerName,
final PipeProcessorSubtaskExecutionGuard processorExecutionGuard)
throws Exception {
+ markGeneratedTabletInsertionEventsParsingStarted();
synchronized (tabletConsumptionLock) {
final long parserStateGeneration;
synchronized (eventParser) {
@@ -891,6 +1042,7 @@ public class PipeTsFileInsertionEvent extends
PipeInsertionEvent
if (!isCurrentGeneration) {
return;
}
+ markGeneratedTabletInsertionEventsParsingCompleted();
return;
}
@@ -1129,9 +1281,12 @@ public class PipeTsFileInsertionEvent extends
PipeInsertionEvent
public Iterable<TabletInsertionEvent> toTabletInsertionEvents(final long
timeoutMs)
throws PipeException {
+ markGeneratedTabletInsertionEventsParsingStarted();
+
try {
if (!waitForTsFileClose()) {
LOGGER.warn(DataNodePipeMessages.PIPE_SKIPPING_TEMPORARY_TSFILE_S_PARSING_WHICH,
tsFile);
+ markGeneratedTabletInsertionEventsParsingCompleted();
return Collections.emptyList();
}
final long parserStateGeneration;
@@ -1145,9 +1300,10 @@ public class PipeTsFileInsertionEvent extends
PipeInsertionEvent
if (parserStateGeneration != this.parserStateGeneration) {
return Collections.emptyList();
}
- return initEventParser().toTabletInsertionEvents();
+ return
wrapGeneratedTabletInsertionEvents(initEventParser().toTabletInsertionEvents());
}
} catch (final Exception e) {
+ markGeneratedTabletInsertionEventsParsingCompleted();
close();
// close() should be called before re-interrupting the thread
@@ -1173,6 +1329,49 @@ public class PipeTsFileInsertionEvent extends
PipeInsertionEvent
}
}
+ private Iterable<TabletInsertionEvent> wrapGeneratedTabletInsertionEvents(
+ final Iterable<TabletInsertionEvent> iterable) {
+ return () -> {
+ final Iterator<TabletInsertionEvent> iterator;
+ try {
+ iterator = iterable.iterator();
+ } catch (final RuntimeException e) {
+ markGeneratedTabletInsertionEventsParsingCompleted();
+ throw e;
+ }
+ return new Iterator<TabletInsertionEvent>() {
+ @Override
+ public boolean hasNext() {
+ try {
+ final boolean hasNext = iterator.hasNext();
+ if (!hasNext) {
+ markGeneratedTabletInsertionEventsParsingCompleted();
+ }
+ return hasNext;
+ } catch (final RuntimeException e) {
+ markGeneratedTabletInsertionEventsParsingCompleted();
+ throw e;
+ }
+ }
+
+ @Override
+ public TabletInsertionEvent next() {
+ try {
+ return iterator.next();
+ } catch (final RuntimeException e) {
+ markGeneratedTabletInsertionEventsParsingCompleted();
+ throw e;
+ }
+ }
+
+ @Override
+ public void remove() {
+ iterator.remove();
+ }
+ };
+ };
+ }
+
private boolean reserveResource4Parsing(
final PipeProcessorSubtaskExecutionGuard processorExecutionGuard,
final long expectedParserStateGeneration)
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionHybridSource.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionHybridSource.java
index 65d03919ab1..7d3f6be7805 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionHybridSource.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionHybridSource.java
@@ -21,6 +21,7 @@ package org.apache.iotdb.db.pipe.source.dataregion.realtime;
import org.apache.iotdb.commons.exception.pipe.PipeRuntimeNonCriticalException;
import org.apache.iotdb.commons.pipe.config.PipeConfig;
+import org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant;
import org.apache.iotdb.commons.pipe.event.ProgressReportEvent;
import org.apache.iotdb.db.i18n.DataNodePipeMessages;
import org.apache.iotdb.db.pipe.agent.PipeDataNodeAgent;
@@ -33,6 +34,9 @@ import
org.apache.iotdb.db.pipe.metric.overview.PipeDataNodeSinglePipeMetrics;
import org.apache.iotdb.db.pipe.resource.PipeDataNodeResourceManager;
import
org.apache.iotdb.db.pipe.source.dataregion.realtime.assigner.PipeTsFileEpochProgressIndexKeeper;
import org.apache.iotdb.db.pipe.source.dataregion.realtime.epoch.TsFileEpoch;
+import
org.apache.iotdb.pipe.api.customizer.configuration.PipeExtractorRuntimeConfiguration;
+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 org.apache.iotdb.pipe.api.event.dml.insertion.TabletInsertionEvent;
import org.apache.iotdb.pipe.api.event.dml.insertion.TsFileInsertionEvent;
@@ -40,8 +44,13 @@ import
org.apache.iotdb.pipe.api.event.dml.insertion.TsFileInsertionEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.ArrayDeque;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.Deque;
import java.util.Optional;
+import java.util.PriorityQueue;
+import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -50,13 +59,69 @@ public class PipeRealtimeDataRegionHybridSource extends
PipeRealtimeDataRegionSo
private static final Logger LOGGER =
LoggerFactory.getLogger(PipeRealtimeDataRegionHybridSource.class);
+ private boolean isRegionLevelDowngradingEnabled =
+
PipeSourceConstant.EXTRACTOR_REALTIME_REGION_LEVEL_DOWNGRADING_DEFAULT_VALUE;
+ private final Object regionLevelDowngradingLock = new Object();
+
private final Set<TsFileEpoch> activeTsFileEpochs =
Collections.newSetFromMap(new ConcurrentHashMap<>());
private final Set<TsFileEpoch> degradedTsFileEpochs =
Collections.newSetFromMap(new ConcurrentHashMap<>());
+ private final Queue<TsFileEpoch> regionLevelDegradedTsFileEpochs =
+ new
PriorityQueue<>(PipeRealtimeDataRegionHybridSource::compareTsFileEpochsByQueryPriority);
+ private final Deque<PipeRealtimeEvent> eventsBeforeRegionLevelDowngrading =
new ArrayDeque<>();
+ private final Deque<PipeRealtimeEvent> regionLevelBufferedEvents = new
ArrayDeque<>();
+
+ private volatile boolean isRegionLevelDegraded = false;
+ private TsFileEpoch regionLevelTailTsFileEpoch = null;
+ private boolean canSupplyEventsBeforeRegionLevelDowngrading = false;
+ private int inFlightTsFileCount = 0;
+
+ @Override
+ public void validate(final PipeParameterValidator validator) throws
Exception {
+ super.validate(validator);
+ validator
+ .validateAttributeValueRange(
+ PipeSourceConstant.EXTRACTOR_REALTIME_REGION_LEVEL_DOWNGRADING_KEY,
+ true,
+ Boolean.TRUE.toString(),
+ Boolean.FALSE.toString())
+ .validateAttributeValueRange(
+ PipeSourceConstant.SOURCE_REALTIME_REGION_LEVEL_DOWNGRADING_KEY,
+ true,
+ Boolean.TRUE.toString(),
+ Boolean.FALSE.toString());
+ }
+
+ @Override
+ public void customize(
+ final PipeParameters parameters, final PipeExtractorRuntimeConfiguration
configuration)
+ throws Exception {
+ super.customize(parameters, configuration);
+ isRegionLevelDowngradingEnabled =
+ parameters.getBooleanOrDefault(
+ Arrays.asList(
+
PipeSourceConstant.EXTRACTOR_REALTIME_REGION_LEVEL_DOWNGRADING_KEY,
+
PipeSourceConstant.SOURCE_REALTIME_REGION_LEVEL_DOWNGRADING_KEY),
+
PipeSourceConstant.EXTRACTOR_REALTIME_REGION_LEVEL_DOWNGRADING_DEFAULT_VALUE);
+ }
@Override
protected void doExtract(final PipeRealtimeEvent event) {
+ if (isRegionLevelDowngradingEnabled) {
+ synchronized (regionLevelDowngradingLock) {
+ if (isClosed.get()) {
+
event.decreaseReferenceCount(PipeRealtimeDataRegionHybridSource.class.getName(),
false);
+ return;
+ }
+ doExtractInternal(event);
+ }
+ return;
+ }
+ doExtractInternal(event);
+ }
+
+ private void doExtractInternal(final PipeRealtimeEvent event) {
final Event eventToExtract = event.getEvent();
if (eventToExtract instanceof TabletInsertionEvent) {
@@ -91,9 +156,46 @@ public class PipeRealtimeDataRegionHybridSource extends
PipeRealtimeDataRegionSo
private void extractTabletInsertion(final PipeRealtimeEvent event) {
markTsFileEpochActive(event.getTsFileEpoch());
+ if (isRegionLevelDowngradingEnabled
+ && isRegionLevelDegraded
+ && degradedTsFileEpochs.contains(event.getTsFileEpoch())) {
+ event.getTsFileEpoch().migrateState(this, currentState ->
TsFileEpoch.State.USING_TSFILE);
+ PipeTsFileEpochProgressIndexKeeper.getInstance()
+ .registerProgressIndex(
+ dataRegionId, getTsFileDedupScopeID(),
event.getTsFileEpoch().getResource());
+
event.decreaseReferenceCount(PipeRealtimeDataRegionHybridSource.class.getName(),
false);
+ return;
+ }
+
TsFileEpoch.State state;
- if (canNotUseTabletAnymore(event)) {
+ if (isRegionLevelDowngradingEnabled && isRegionLevelDegraded) {
+ // Retain only the newest epoch as a realtime tail. Once another epoch
arrives, the previous
+ // tail is downgraded to bound the buffered tablet memory to roughly one
TsFile.
+ prepareRegionLevelTailTsFileEpochUnderLock(event.getTsFileEpoch());
+ if (canNotUseTabletAnymore(event)) {
+ if (regionLevelTailTsFileEpoch == event.getTsFileEpoch()) {
+ degradeRegionLevelTailTsFileEpochUnderLock();
+ bufferPendingEventsForRegionLevelExitUnderLock();
+ rebalanceRegionLevelBufferedEventsUnderLock();
+ }
+ event.getTsFileEpoch().migrateState(this, currentState ->
TsFileEpoch.State.USING_TSFILE);
+ PipeTsFileEpochProgressIndexKeeper.getInstance()
+ .registerProgressIndex(
+ dataRegionId, getTsFileDedupScopeID(),
event.getTsFileEpoch().getResource());
+ markTsFileEpochDegradedFromExtraction(event.getTsFileEpoch());
+
event.decreaseReferenceCount(PipeRealtimeDataRegionHybridSource.class.getName(),
false);
+ return;
+ }
+ event
+ .getTsFileEpoch()
+ .migrateState(
+ this,
+ currentState ->
+ currentState == TsFileEpoch.State.EMPTY
+ ? TsFileEpoch.State.USING_TABLET
+ : currentState);
+ } else if (canNotUseTabletAnymore(event)) {
event.getTsFileEpoch().migrateState(this, curState ->
TsFileEpoch.State.USING_TSFILE);
PipeTsFileEpochProgressIndexKeeper.getInstance()
.registerProgressIndex(
@@ -118,21 +220,25 @@ public class PipeRealtimeDataRegionHybridSource extends
PipeRealtimeDataRegionSo
state = event.getTsFileEpoch().getState(this);
if (state == TsFileEpoch.State.USING_TSFILE || state ==
TsFileEpoch.State.USING_BOTH) {
- markTsFileEpochDegraded(event.getTsFileEpoch());
+ markTsFileEpochDegradedFromExtraction(event.getTsFileEpoch());
}
switch (state) {
case USING_TSFILE:
// Ignore the tablet event.
event.decreaseReferenceCount(PipeRealtimeDataRegionHybridSource.class.getName(),
false);
break;
- case EMPTY:
- case USING_TABLET:
case USING_BOTH:
- // USING_BOTH indicates that there are discarded events previously.
- // In this case, we need to delay the progress report to tsFile event,
to avoid losing data.
- if (state == TsFileEpoch.State.USING_BOTH) {
- event.skipReportOnCommit();
+ if (isRegionLevelDowngradingEnabled && isRegionLevelDegraded) {
+
event.decreaseReferenceCount(PipeRealtimeDataRegionHybridSource.class.getName(),
false);
+ break;
}
+ // USING_BOTH indicates that there are discarded events previously. In
this case, we need
+ // to delay the progress report to the TsFile event, to avoid losing
data.
+ event.skipReportOnCommit();
+ pendingQueue.offer(event);
+ break;
+ case EMPTY:
+ case USING_TABLET:
pendingQueue.offer(event);
break;
default:
@@ -173,11 +279,18 @@ public class PipeRealtimeDataRegionHybridSource extends
PipeRealtimeDataRegionSo
});
final TsFileEpoch.State state = event.getTsFileEpoch().getState(this);
- if (state == TsFileEpoch.State.USING_BOTH) {
- markTsFileEpochDegraded(event.getTsFileEpoch());
+ if (state == TsFileEpoch.State.USING_BOTH
+ || (isRegionLevelDowngradingEnabled
+ && isRegionLevelDegraded
+ && state == TsFileEpoch.State.USING_TSFILE)) {
+ markTsFileEpochDegradedFromExtraction(event.getTsFileEpoch());
}
switch (state) {
case USING_TABLET:
+ if (isRegionLevelDowngradingEnabled && isRegionLevelDegraded) {
+ pendingQueue.offer(event);
+ return;
+ }
// If the state is USING_TABLET, discard the event
PipeTsFileEpochProgressIndexKeeper.getInstance()
.eliminateProgressIndex(
@@ -201,43 +314,289 @@ public class PipeRealtimeDataRegionHybridSource extends
PipeRealtimeDataRegionSo
}
private void markTsFileEpochActive(final TsFileEpoch tsFileEpoch) {
- activeTsFileEpochs.add(tsFileEpoch);
- reportTsFileEpochDegradedStatus();
+ synchronized (regionLevelDowngradingLock) {
+ activeTsFileEpochs.add(tsFileEpoch);
+ reportTsFileEpochDegradedStatusUnderLock();
+ }
}
private void markTsFileEpochDegraded(final TsFileEpoch tsFileEpoch) {
+ markTsFileEpochDegraded(tsFileEpoch, false);
+ }
+
+ private void markTsFileEpochDegradedFromExtraction(final TsFileEpoch
tsFileEpoch) {
+ markTsFileEpochDegraded(tsFileEpoch, true);
+ }
+
+ private void markTsFileEpochDegraded(
+ final TsFileEpoch tsFileEpoch, final boolean
shouldPreservePendingEvents) {
+ synchronized (regionLevelDowngradingLock) {
+ final boolean wasRegionLevelDegraded = isRegionLevelDegraded;
+ if (isRegionLevelDowngradingEnabled
+ && shouldPreservePendingEvents
+ && !wasRegionLevelDegraded) {
+ PipeRealtimeEvent pendingEvent;
+ while ((pendingEvent = (PipeRealtimeEvent) pendingQueue.directPoll())
!= null) {
+ eventsBeforeRegionLevelDowngrading.offerLast(pendingEvent);
+ }
+ canSupplyEventsBeforeRegionLevelDowngrading = true;
+ }
+ if (regionLevelTailTsFileEpoch == tsFileEpoch) {
+ regionLevelTailTsFileEpoch = null;
+ }
+ markTsFileEpochDegradedUnderLock(tsFileEpoch);
+ if (isRegionLevelDowngradingEnabled
+ && shouldPreservePendingEvents
+ && !wasRegionLevelDegraded) {
+
eventsBeforeRegionLevelDowngrading.forEach(this::markTsFileEpochDegradedIfNeededUnderLock);
+ }
+ if (isRegionLevelDowngradingEnabled) {
+ // A downgrade discovered while supplying an event happens after all
remaining pending
+ // events. Track those events as the possible realtime tail before
rebalancing the queues.
+ if (!wasRegionLevelDegraded && !shouldPreservePendingEvents) {
+ bufferPendingEventsAndTrackRegionLevelTailUnderLock();
+ }
+ rebalanceRegionLevelBufferedEventsUnderLock();
+ }
+ }
+ }
+
+ private void markTsFileEpochDegradedUnderLock(final TsFileEpoch tsFileEpoch)
{
activeTsFileEpochs.add(tsFileEpoch);
- degradedTsFileEpochs.add(tsFileEpoch);
- reportTsFileEpochDegradedStatus();
+ if (degradedTsFileEpochs.add(tsFileEpoch) &&
isRegionLevelDowngradingEnabled) {
+ regionLevelDegradedTsFileEpochs.offer(tsFileEpoch);
+ }
+ if (isRegionLevelDowngradingEnabled) {
+ isRegionLevelDegraded = true;
+ }
+ reportTsFileEpochDegradedStatusUnderLock();
+ }
+
+ private boolean shouldOrderTsFileEventUnderLock(final PipeRealtimeEvent
event) {
+ if (!(event.getEvent() instanceof TsFileInsertionEvent)) {
+ return false;
+ }
+
+ final TsFileEpoch.State state = event.getTsFileEpoch().getState(this);
+ return degradedTsFileEpochs.contains(event.getTsFileEpoch())
+ || state == TsFileEpoch.State.USING_TSFILE
+ || state == TsFileEpoch.State.USING_BOTH;
+ }
+
+ private void markTsFileEpochDegradedIfNeededUnderLock(final
PipeRealtimeEvent event) {
+ if (!shouldOrderTsFileEventUnderLock(event)) {
+ return;
+ }
+
+ if (regionLevelTailTsFileEpoch == event.getTsFileEpoch()) {
+ regionLevelTailTsFileEpoch = null;
+ }
+ markTsFileEpochDegradedUnderLock(event.getTsFileEpoch());
+ }
+
+ private void prepareRegionLevelTailTsFileEpochUnderLock(final TsFileEpoch
tsFileEpoch) {
+ if (regionLevelTailTsFileEpoch == tsFileEpoch) {
+ return;
+ }
+
+ if (regionLevelTailTsFileEpoch != null) {
+ degradeRegionLevelTailTsFileEpochUnderLock();
+ bufferPendingEventsForRegionLevelExitUnderLock();
+ rebalanceRegionLevelBufferedEventsUnderLock();
+ }
+ regionLevelTailTsFileEpoch = tsFileEpoch;
+ }
+
+ private void degradeRegionLevelTailTsFileEpochUnderLock() {
+ final TsFileEpoch tsFileEpoch = regionLevelTailTsFileEpoch;
+ if (tsFileEpoch == null) {
+ return;
+ }
+
+ degradeTsFileEpochUnderLock(tsFileEpoch);
+ }
+
+ private void degradeTsFileEpochUnderLock(final TsFileEpoch tsFileEpoch) {
+ if (regionLevelTailTsFileEpoch == tsFileEpoch) {
+ regionLevelTailTsFileEpoch = null;
+ }
+ tsFileEpoch.migrateState(this, state -> TsFileEpoch.State.USING_TSFILE);
+ PipeTsFileEpochProgressIndexKeeper.getInstance()
+ .registerProgressIndex(dataRegionId, getTsFileDedupScopeID(),
tsFileEpoch.getResource());
+ markTsFileEpochDegradedUnderLock(tsFileEpoch);
+ }
+
+ private void degradeLowerPriorityActiveTsFileEpochsUnderLock() {
+ final TsFileEpoch nextDegradedTsFileEpoch =
regionLevelDegradedTsFileEpochs.peek();
+ if (nextDegradedTsFileEpoch == null) {
+ return;
+ }
+
+ // An active epoch may be discovered as degraded only when its queued
tablet is supplied. Make
+ // every known lower-priority epoch enter the ordered TsFile queue before
allowing the current
+ // head to pass, including the realtime tail and epochs whose TsFile has
not been flushed yet.
+ for (final TsFileEpoch activeTsFileEpoch : activeTsFileEpochs) {
+ if (!degradedTsFileEpochs.contains(activeTsFileEpoch)
+ && compareTsFileEpochsByQueryPriority(activeTsFileEpoch,
nextDegradedTsFileEpoch) < 0) {
+ degradeTsFileEpochUnderLock(activeTsFileEpoch);
+ }
+ }
+ }
+
+ private void bufferPendingEventsAndTrackRegionLevelTailUnderLock() {
+ PipeRealtimeEvent pendingEvent;
+ while ((pendingEvent = (PipeRealtimeEvent) pendingQueue.directPoll()) !=
null) {
+ if (pendingEvent.getEvent() instanceof TabletInsertionEvent
+ && !degradedTsFileEpochs.contains(pendingEvent.getTsFileEpoch())) {
+ if (regionLevelTailTsFileEpoch != null
+ && regionLevelTailTsFileEpoch != pendingEvent.getTsFileEpoch()) {
+ degradeRegionLevelTailTsFileEpochUnderLock();
+ }
+ regionLevelTailTsFileEpoch = pendingEvent.getTsFileEpoch();
+ }
+ regionLevelBufferedEvents.offerLast(pendingEvent);
+ }
+ }
+
+ private void rebalanceRegionLevelBufferedEventsUnderLock() {
+ bufferPendingEventsForRegionLevelExitUnderLock();
+ degradeLowerPriorityActiveTsFileEpochsUnderLock();
+
+ final TsFileEpoch nextDegradedTsFileEpoch =
regionLevelDegradedTsFileEpochs.peek();
+ final Deque<PipeRealtimeEvent> retainedEvents = new ArrayDeque<>();
+ boolean nextDegradedTsFileEventPromoted = false;
+ PipeRealtimeEvent bufferedEvent;
+ while ((bufferedEvent = regionLevelBufferedEvents.pollFirst()) != null) {
+ if (bufferedEvent.getEvent() instanceof TabletInsertionEvent
+ && degradedTsFileEpochs.contains(bufferedEvent.getTsFileEpoch())) {
+ bufferedEvent.decreaseReferenceCount(
+ PipeRealtimeDataRegionHybridSource.class.getName(), false);
+ } else if (!nextDegradedTsFileEventPromoted
+ && bufferedEvent.getEvent() instanceof TsFileInsertionEvent
+ && bufferedEvent.getTsFileEpoch() == nextDegradedTsFileEpoch) {
+ pendingQueue.offer(bufferedEvent);
+ nextDegradedTsFileEventPromoted = true;
+ } else {
+ retainedEvents.offerLast(bufferedEvent);
+ }
+ }
+ regionLevelBufferedEvents.addAll(retainedEvents);
}
private void clearTsFileEpoch(final TsFileEpoch tsFileEpoch) {
+ synchronized (regionLevelDowngradingLock) {
+ clearTsFileEpochUnderLock(tsFileEpoch);
+ }
+ }
+
+ private void clearTsFileEpochAfterCommit(final TsFileEpoch tsFileEpoch) {
+ synchronized (regionLevelDowngradingLock) {
+ if (isClosed.get()) {
+ return;
+ }
+ if (inFlightTsFileCount > 0) {
+ --inFlightTsFileCount;
+ }
+ clearTsFileEpochUnderLock(tsFileEpoch);
+
+ // The decision whether newer writes can resume the realtime path must
be made at the exact
+ // point when the last currently degraded TsFile is committed.
Otherwise, writes arriving
+ // between the commit and the next supply call would still be
unnecessarily downgraded.
+ if (isRegionLevelDegraded
+ && inFlightTsFileCount == 0
+ && degradedTsFileEpochs.isEmpty()
+ && eventsBeforeRegionLevelDowngrading.isEmpty()) {
+ bufferPendingEventsForRegionLevelExitUnderLock();
+ tryExitRegionLevelDowngrading(false);
+ }
+ }
+ }
+
+ private void bufferPendingEventsForRegionLevelExitUnderLock() {
+ PipeRealtimeEvent event;
+ while ((event = (PipeRealtimeEvent) pendingQueue.directPoll()) != null) {
+ markTsFileEpochDegradedIfNeededUnderLock(event);
+ regionLevelBufferedEvents.offerLast(event);
+ }
+ }
+
+ private void clearTsFileEpochUnderLock(final TsFileEpoch tsFileEpoch) {
activeTsFileEpochs.remove(tsFileEpoch);
degradedTsFileEpochs.remove(tsFileEpoch);
- reportTsFileEpochDegradedStatus();
+ regionLevelDegradedTsFileEpochs.remove(tsFileEpoch);
+ if (regionLevelTailTsFileEpoch == tsFileEpoch) {
+ regionLevelTailTsFileEpoch = null;
+ }
+ if (isRegionLevelDowngradingEnabled && isRegionLevelDegraded &&
inFlightTsFileCount == 0) {
+ rebalanceRegionLevelBufferedEventsUnderLock();
+ }
+ reportTsFileEpochDegradedStatusUnderLock();
}
- private void reportTsFileEpochDegradedStatus() {
- if (activeTsFileEpochs.isEmpty()) {
+ private void clearRegionLevelBufferedEventsUnderLock() {
+ clearBufferedEventsUnderLock(eventsBeforeRegionLevelDowngrading);
+ clearBufferedEventsUnderLock(regionLevelBufferedEvents);
+ }
+
+ private void clearBufferedEventsUnderLock(final Deque<PipeRealtimeEvent>
events) {
+ PipeRealtimeEvent event;
+ while ((event = events.pollFirst()) != null) {
+
event.clearReferenceCount(PipeRealtimeDataRegionHybridSource.class.getName());
+ }
+ }
+
+ private void reportTsFileEpochDegradedStatusUnderLock() {
+ if (isRegionLevelDowngradingEnabled && isRegionLevelDegraded) {
+ PipeDataNodeAgent.task()
+ .setPipeTsFileEpochDegraded(pipeName, creationTime, dataRegionId,
true);
+ } else if (activeTsFileEpochs.isEmpty()) {
PipeDataNodeAgent.task().clearPipeTsFileEpochDegraded(pipeName,
creationTime, dataRegionId);
} else {
PipeDataNodeAgent.task()
.setPipeTsFileEpochDegraded(
- pipeName, creationTime, dataRegionId,
!degradedTsFileEpochs.isEmpty());
+ pipeName,
+ creationTime,
+ dataRegionId,
+ isRegionLevelDowngradingEnabled ? false :
!degradedTsFileEpochs.isEmpty());
}
}
@Override
public void close() throws Exception {
try {
+ // Do not hold regionLevelDowngradingLock while waiting for the assigner
to stop. An event
+ // already being assigned may need the same lock to finish extraction.
super.close();
} finally {
- activeTsFileEpochs.clear();
- degradedTsFileEpochs.clear();
- PipeDataNodeAgent.task().clearPipeTsFileEpochDegraded(pipeName,
creationTime, dataRegionId);
+ synchronized (regionLevelDowngradingLock) {
+ clearRegionLevelBufferedEventsUnderLock();
+ activeTsFileEpochs.clear();
+ degradedTsFileEpochs.clear();
+ regionLevelDegradedTsFileEpochs.clear();
+ isRegionLevelDegraded = false;
+ regionLevelTailTsFileEpoch = null;
+ canSupplyEventsBeforeRegionLevelDowngrading = false;
+ inFlightTsFileCount = 0;
+ PipeDataNodeAgent.task().clearPipeTsFileEpochDegraded(pipeName,
creationTime, dataRegionId);
+ }
}
}
+ @Override
+ protected void extractProgressReportEvent(final PipeRealtimeEvent event) {
+ if (isRegionLevelDowngradingEnabled) {
+ synchronized (regionLevelDowngradingLock) {
+ if (isClosed.get()) {
+
event.decreaseReferenceCount(PipeRealtimeDataRegionHybridSource.class.getName(),
false);
+ return;
+ }
+ super.extractProgressReportEvent(event);
+ }
+ return;
+ }
+ super.extractProgressReportEvent(event);
+ }
+
// If the insertNode's memory has reached the dangerous threshold, we should
not extract any
// tablets.
private boolean canNotUseTabletAnymore(final PipeRealtimeEvent event) {
@@ -278,47 +637,206 @@ public class PipeRealtimeDataRegionHybridSource extends
PipeRealtimeDataRegionSo
@Override
public Event supply() {
+ if (isRegionLevelDowngradingEnabled) {
+ synchronized (regionLevelDowngradingLock) {
+ return isRegionLevelDegraded ? supplyRegionLevelDegradedInternal() :
supplyInternal();
+ }
+ }
+ return supplyInternal();
+ }
+
+ private Event supplyInternal() {
PipeRealtimeEvent realtimeEvent = (PipeRealtimeEvent)
pendingQueue.directPoll();
while (realtimeEvent != null) {
- Event suppliedEvent;
+ final Event suppliedEvent = supplyExtractedEvent(realtimeEvent);
+ if (suppliedEvent != null) {
+ return suppliedEvent;
+ }
- // Used to judge the type of the event, not directly for supplying.
+ if (isRegionLevelDowngradingEnabled && isRegionLevelDegraded) {
+ return supplyRegionLevelDegradedInternal();
+ }
+
+ realtimeEvent = (PipeRealtimeEvent) pendingQueue.directPoll();
+ }
+
+ // Means the pending queue is empty.
+ return null;
+ }
+
+ private Event supplyExtractedEvent(final PipeRealtimeEvent realtimeEvent) {
+ Event suppliedEvent;
+
+ // Used to judge the type of the event, not directly for supplying.
+ final Event eventToSupply = realtimeEvent.getEvent();
+ if (eventToSupply instanceof TabletInsertionEvent) {
+ suppliedEvent = supplyTabletInsertion(realtimeEvent);
+ } else if (eventToSupply instanceof TsFileInsertionEvent) {
+ suppliedEvent = supplyTsFileInsertion(realtimeEvent);
+ } else if (eventToSupply instanceof PipeHeartbeatEvent) {
+ suppliedEvent = supplyHeartbeat(realtimeEvent);
+ } else if (eventToSupply instanceof PipeDeleteDataNodeEvent
+ || eventToSupply instanceof ProgressReportEvent) {
+ suppliedEvent = supplyDirectly(realtimeEvent);
+ } else {
+ throw new UnsupportedOperationException(
+ String.format(
+ DataNodePipeMessages
+
.PIPE_EXCEPTION_UNSUPPORTED_EVENT_TYPE_S_FOR_HYBRID_REALTIME_EXTRACTOR_S_474BAAC2,
+ eventToSupply.getClass(),
+ this));
+ }
+
+
realtimeEvent.decreaseReferenceCount(PipeRealtimeDataRegionHybridSource.class.getName(),
false);
+
+ if (suppliedEvent != null) {
+ suppliedEvent = assignReplicateIndexIfNeeded(realtimeEvent,
suppliedEvent);
+ maySkipIndex4Event(realtimeEvent);
+ }
+ return suppliedEvent;
+ }
+
+ private Event supplyRegionLevelDegradedInternal() {
+ // Once region-level downgrading starts, only one TsFile is allowed to be
in flight. Events of
+ // newer epochs are buffered until all currently degraded TsFiles are
committed downstream.
+ if (inFlightTsFileCount > 0) {
+ return null;
+ }
+
+ final Event eventBeforeDowngrading =
supplyEventsBeforeRegionLevelDowngradingInternal();
+ if (eventBeforeDowngrading != null) {
+ return eventBeforeDowngrading;
+ }
+
+ PipeRealtimeEvent realtimeEvent = (PipeRealtimeEvent)
pendingQueue.directPoll();
+ while (realtimeEvent != null) {
final Event eventToSupply = realtimeEvent.getEvent();
if (eventToSupply instanceof TabletInsertionEvent) {
- suppliedEvent = supplyTabletInsertion(realtimeEvent);
+ if (degradedTsFileEpochs.contains(realtimeEvent.getTsFileEpoch())) {
+ realtimeEvent.decreaseReferenceCount(
+ PipeRealtimeDataRegionHybridSource.class.getName(), false);
+ } else {
+ regionLevelBufferedEvents.offerLast(realtimeEvent);
+ }
} else if (eventToSupply instanceof TsFileInsertionEvent) {
- suppliedEvent = supplyTsFileInsertion(realtimeEvent);
- } else if (eventToSupply instanceof PipeHeartbeatEvent) {
- suppliedEvent = supplyHeartbeat(realtimeEvent);
- } else if (eventToSupply instanceof PipeDeleteDataNodeEvent
- || eventToSupply instanceof ProgressReportEvent) {
- suppliedEvent = supplyDirectly(realtimeEvent);
+ final TsFileEpoch.State state =
realtimeEvent.getTsFileEpoch().getState(this);
+ if (degradedTsFileEpochs.contains(realtimeEvent.getTsFileEpoch())
+ || state == TsFileEpoch.State.USING_TSFILE
+ || state == TsFileEpoch.State.USING_BOTH) {
+ markTsFileEpochDegraded(realtimeEvent.getTsFileEpoch());
+ if (regionLevelDegradedTsFileEpochs.peek() !=
realtimeEvent.getTsFileEpoch()) {
+ regionLevelBufferedEvents.offerLast(realtimeEvent);
+ realtimeEvent = (PipeRealtimeEvent) pendingQueue.directPoll();
+ continue;
+ }
+ final Event suppliedEvent = supplyExtractedEvent(realtimeEvent);
+ if (suppliedEvent != null) {
+ return suppliedEvent;
+ }
+ } else {
+ regionLevelBufferedEvents.offerLast(realtimeEvent);
+ }
} else {
- throw new UnsupportedOperationException(
- String.format(
- DataNodePipeMessages
-
.PIPE_EXCEPTION_UNSUPPORTED_EVENT_TYPE_S_FOR_HYBRID_REALTIME_EXTRACTOR_S_474BAAC2,
- eventToSupply.getClass(),
- this));
+ regionLevelBufferedEvents.offerLast(realtimeEvent);
}
- realtimeEvent.decreaseReferenceCount(
- PipeRealtimeDataRegionHybridSource.class.getName(), false);
+ realtimeEvent = (PipeRealtimeEvent) pendingQueue.directPoll();
+ }
+
+ return tryExitRegionLevelDowngrading(true);
+ }
- if (suppliedEvent != null) {
- suppliedEvent = assignReplicateIndexIfNeeded(realtimeEvent,
suppliedEvent);
- maySkipIndex4Event(realtimeEvent);
- return suppliedEvent;
+ private Event supplyEventsBeforeRegionLevelDowngradingInternal() {
+ PipeRealtimeEvent realtimeEvent;
+ while ((realtimeEvent = eventsBeforeRegionLevelDowngrading.pollFirst()) !=
null) {
+ final Event eventToSupply = realtimeEvent.getEvent();
+
+ if (canSupplyEventsBeforeRegionLevelDowngrading) {
+ if (eventToSupply instanceof TabletInsertionEvent
+ && degradedTsFileEpochs.contains(realtimeEvent.getTsFileEpoch())) {
+ realtimeEvent.decreaseReferenceCount(
+ PipeRealtimeDataRegionHybridSource.class.getName(), false);
+ continue;
+ }
+
+ if (shouldOrderTsFileEventUnderLock(realtimeEvent)) {
+ markTsFileEpochDegradedIfNeededUnderLock(realtimeEvent);
+ regionLevelBufferedEvents.offerLast(realtimeEvent);
+ continue;
+ }
+
+ final Event suppliedEvent = supplyExtractedEvent(realtimeEvent);
+ if (suppliedEvent != null) {
+ return suppliedEvent;
+ }
+ if (eventToSupply instanceof TabletInsertionEvent
+ && degradedTsFileEpochs.contains(realtimeEvent.getTsFileEpoch())) {
+ canSupplyEventsBeforeRegionLevelDowngrading = false;
+ }
+ continue;
}
- realtimeEvent = (PipeRealtimeEvent) pendingQueue.directPoll();
+ if (eventToSupply instanceof TabletInsertionEvent) {
+ if (degradedTsFileEpochs.contains(realtimeEvent.getTsFileEpoch())) {
+ realtimeEvent.decreaseReferenceCount(
+ PipeRealtimeDataRegionHybridSource.class.getName(), false);
+ } else {
+ regionLevelBufferedEvents.offerLast(realtimeEvent);
+ }
+ } else if (eventToSupply instanceof TsFileInsertionEvent) {
+ final TsFileEpoch.State state =
realtimeEvent.getTsFileEpoch().getState(this);
+ if (degradedTsFileEpochs.contains(realtimeEvent.getTsFileEpoch())
+ || state == TsFileEpoch.State.USING_TSFILE
+ || state == TsFileEpoch.State.USING_BOTH) {
+ markTsFileEpochDegraded(realtimeEvent.getTsFileEpoch());
+ if (regionLevelDegradedTsFileEpochs.peek() !=
realtimeEvent.getTsFileEpoch()) {
+ regionLevelBufferedEvents.offerLast(realtimeEvent);
+ continue;
+ }
+ final Event suppliedEvent = supplyExtractedEvent(realtimeEvent);
+ if (suppliedEvent != null) {
+ return suppliedEvent;
+ }
+ } else {
+ regionLevelBufferedEvents.offerLast(realtimeEvent);
+ }
+ } else {
+ regionLevelBufferedEvents.offerLast(realtimeEvent);
+ }
}
- // Means the pending queue is empty.
+ canSupplyEventsBeforeRegionLevelDowngrading = false;
+ rebalanceRegionLevelBufferedEventsUnderLock();
return null;
}
+ private Event tryExitRegionLevelDowngrading(final boolean
shouldSupplyAfterTransition) {
+ if (!degradedTsFileEpochs.isEmpty()) {
+ // Some degraded epochs are still waiting for their TsFile to be flushed.
+ return null;
+ }
+
+ if (regionLevelTailTsFileEpoch != null
+ && regionLevelBufferedEvents.stream()
+ .filter(event -> event.getTsFileEpoch() ==
regionLevelTailTsFileEpoch)
+ .filter(event -> event.getEvent() instanceof TabletInsertionEvent)
+ .anyMatch(event -> event.getEvent().isReleased())) {
+ degradeRegionLevelTailTsFileEpochUnderLock();
+ rebalanceRegionLevelBufferedEventsUnderLock();
+ return shouldSupplyAfterTransition ? supplyRegionLevelDegradedInternal()
: null;
+ }
+
+ isRegionLevelDegraded = false;
+ regionLevelTailTsFileEpoch = null;
+ PipeRealtimeEvent bufferedEvent;
+ while ((bufferedEvent = regionLevelBufferedEvents.pollFirst()) != null) {
+ pendingQueue.offer(bufferedEvent);
+ }
+ reportTsFileEpochDegradedStatusUnderLock();
+ return shouldSupplyAfterTransition ? supplyInternal() : null;
+ }
+
private Event supplyTabletInsertion(final PipeRealtimeEvent event) {
if
(event.increaseReferenceCount(PipeRealtimeDataRegionHybridSource.class.getName()))
{
return event.getEvent();
@@ -326,7 +844,14 @@ public class PipeRealtimeDataRegionHybridSource extends
PipeRealtimeDataRegionSo
// If the event's reference count can not be increased, it means the
data represented by
// this event is not reliable anymore. but the data represented by this
event
// has been carried by the following tsfile event, so we can just
discard this event.
- event.getTsFileEpoch().migrateState(this, s ->
TsFileEpoch.State.USING_BOTH);
+ event
+ .getTsFileEpoch()
+ .migrateState(
+ this,
+ state ->
+ isRegionLevelDowngradingEnabled
+ ? TsFileEpoch.State.USING_TSFILE
+ : TsFileEpoch.State.USING_BOTH);
markTsFileEpochDegraded(event.getTsFileEpoch());
LOGGER.warn(DataNodePipeMessages.DISCARD_TABLET_EVENT_BECAUSE_IT_IS_NOT,
event);
return null;
@@ -334,27 +859,44 @@ public class PipeRealtimeDataRegionHybridSource extends
PipeRealtimeDataRegionSo
}
private Event supplyTsFileInsertion(final PipeRealtimeEvent event) {
- try {
- if
(event.increaseReferenceCount(PipeRealtimeDataRegionHybridSource.class.getName()))
{
- return event.getEvent();
+ if (isRegionLevelDowngradingEnabled
+ && event.getTsFileEpoch().getState(this) ==
TsFileEpoch.State.USING_TABLET) {
+ PipeTsFileEpochProgressIndexKeeper.getInstance()
+ .eliminateProgressIndex(
+ dataRegionId, getTsFileDedupScopeID(),
event.getTsFileEpoch().getFilePath());
+ clearTsFileEpoch(event.getTsFileEpoch());
+ return null;
+ }
+
+ if
(event.increaseReferenceCount(PipeRealtimeDataRegionHybridSource.class.getName()))
{
+ if (isRegionLevelDowngradingEnabled) {
+ ++inFlightTsFileCount;
+ final PipeTsFileInsertionEvent tsFileInsertionEvent =
+ (PipeTsFileInsertionEvent) event.getEvent();
+ final Runnable clearTsFileEpochHook =
+ () -> clearTsFileEpochAfterCommit(event.getTsFileEpoch());
+ tsFileInsertionEvent.addOnTransferredHook(clearTsFileEpochHook);
+ tsFileInsertionEvent.addOnDiscardedHook(clearTsFileEpochHook);
} else {
- // If the event's reference count can not be increased, it means the
data represented by
- // this event is not reliable anymore. the data has been lost. we
simply discard this
- // event and report the exception to PipeRuntimeAgent.
- final String errorMessage =
- String.format(
-
DataNodePipeMessages.EVENT_CAN_NOT_BE_SUPPLIED_BECAUSE_DATA_IS_LOST,
- event.getEvent());
- LOGGER.error(errorMessage);
- PipeDataNodeAgent.runtime()
- .report(pipeTaskMeta, new
PipeRuntimeNonCriticalException(errorMessage));
- PipeTsFileEpochProgressIndexKeeper.getInstance()
- .eliminateProgressIndex(
- dataRegionId, getTsFileDedupScopeID(),
event.getTsFileEpoch().getFilePath());
- return null;
+ clearTsFileEpoch(event.getTsFileEpoch());
}
- } finally {
+ return event.getEvent();
+ } else {
+ // If the event's reference count can not be increased, it means the
data represented by
+ // this event is not reliable anymore. the data has been lost. we simply
discard this
+ // event and report the exception to PipeRuntimeAgent.
+ final String errorMessage =
+ String.format(
+
DataNodePipeMessages.EVENT_CAN_NOT_BE_SUPPLIED_BECAUSE_DATA_IS_LOST,
+ event.getEvent());
+ LOGGER.error(errorMessage);
+ PipeDataNodeAgent.runtime()
+ .report(pipeTaskMeta, new
PipeRuntimeNonCriticalException(errorMessage));
+ PipeTsFileEpochProgressIndexKeeper.getInstance()
+ .eliminateProgressIndex(
+ dataRegionId, getTsFileDedupScopeID(),
event.getTsFileEpoch().getFilePath());
clearTsFileEpoch(event.getTsFileEpoch());
+ return null;
}
}
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionSource.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionSource.java
index 92ad93e09f1..a678fd0514a 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionSource.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionSource.java
@@ -46,10 +46,13 @@ import
org.apache.iotdb.db.pipe.metric.source.PipeDataRegionEventCounter;
import
org.apache.iotdb.db.pipe.processor.iotconsensusv2.IoTConsensusV2Processor;
import org.apache.iotdb.db.pipe.source.dataregion.DataRegionListeningFilter;
import
org.apache.iotdb.db.pipe.source.dataregion.realtime.assigner.PipeTsFileEpochProgressIndexKeeper;
+import org.apache.iotdb.db.pipe.source.dataregion.realtime.epoch.TsFileEpoch;
import
org.apache.iotdb.db.pipe.source.dataregion.realtime.listener.PipeInsertionDataNodeListener;
import
org.apache.iotdb.db.pipe.source.dataregion.realtime.listener.PipeTimePartitionListener;
import org.apache.iotdb.db.storageengine.StorageEngine;
import org.apache.iotdb.db.storageengine.dataregion.DataRegion;
+import
org.apache.iotdb.db.storageengine.dataregion.read.reader.common.MergeReaderPriority;
+import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource;
import org.apache.iotdb.pipe.api.PipeExtractor;
import
org.apache.iotdb.pipe.api.customizer.configuration.PipeExtractorRuntimeConfiguration;
import
org.apache.iotdb.pipe.api.customizer.configuration.PipeRuntimeEnvironment;
@@ -417,6 +420,38 @@ public abstract class PipeRealtimeDataRegionSource
implements PipeExtractor {
protected abstract void doExtract(final PipeRealtimeEvent event);
+ protected static int compareTsFileEpochsByQueryPriority(
+ final TsFileEpoch epoch1, final TsFileEpoch epoch2) {
+ final TsFileResource resource1 = epoch1.getResource();
+ final TsFileResource resource2 = epoch2.getResource();
+
+ final int queryPriorityComparison =
+ new MergeReaderPriority(
+ resource1.getTsFileID().getTimestamp(),
+ resource1.getVersion(),
+ 0,
+ resource1.isSeq())
+ .compareTo(
+ new MergeReaderPriority(
+ resource2.getTsFileID().getTimestamp(),
+ resource2.getVersion(),
+ 0,
+ resource2.isSeq()));
+ if (queryPriorityComparison != 0) {
+ return queryPriorityComparison;
+ }
+
+ // Equal query priority does not imply the same file: this file-level
comparison omits
+ // compaction versions and directories, and fixes the chunk offset at
zero. Use deterministic
+ // tie-breakers for distinct files with the same seq/unseq flag, version
and timestamp.
+ final int compactionVersionComparison =
+ Long.compare(
+ resource1.getTsFileID().compactionVersion,
resource2.getTsFileID().compactionVersion);
+ return compactionVersionComparison != 0
+ ? compactionVersionComparison
+ : resource1.getTsFilePath().compareTo(resource2.getTsFilePath());
+ }
+
protected void extractHeartbeat(final PipeRealtimeEvent event) {
// Record the pending queue size before trying to put heartbeatEvent into
queue
((PipeHeartbeatEvent)
event.getEvent()).recordExtractorQueueSize(pendingQueue);
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionTsFileSource.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionTsFileSource.java
index 73bef31d85b..1e7a000c8c8 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionTsFileSource.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionTsFileSource.java
@@ -20,27 +20,91 @@
package org.apache.iotdb.db.pipe.source.dataregion.realtime;
import org.apache.iotdb.commons.exception.pipe.PipeRuntimeNonCriticalException;
+import org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant;
import org.apache.iotdb.commons.pipe.event.ProgressReportEvent;
import org.apache.iotdb.db.i18n.DataNodePipeMessages;
import org.apache.iotdb.db.pipe.agent.PipeDataNodeAgent;
import org.apache.iotdb.db.pipe.event.common.deletion.PipeDeleteDataNodeEvent;
import org.apache.iotdb.db.pipe.event.common.heartbeat.PipeHeartbeatEvent;
+import org.apache.iotdb.db.pipe.event.common.tsfile.PipeTsFileInsertionEvent;
import org.apache.iotdb.db.pipe.event.realtime.PipeRealtimeEvent;
import
org.apache.iotdb.db.pipe.source.dataregion.realtime.assigner.PipeTsFileEpochProgressIndexKeeper;
import org.apache.iotdb.db.pipe.source.dataregion.realtime.epoch.TsFileEpoch;
+import
org.apache.iotdb.pipe.api.customizer.configuration.PipeExtractorRuntimeConfiguration;
+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 org.apache.iotdb.pipe.api.event.dml.insertion.TsFileInsertionEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.PriorityQueue;
+import java.util.Queue;
+
public class PipeRealtimeDataRegionTsFileSource extends
PipeRealtimeDataRegionSource {
private static final Logger LOGGER =
LoggerFactory.getLogger(PipeRealtimeDataRegionTsFileSource.class);
+ private boolean isRegionLevelDowngradingEnabled =
+
PipeSourceConstant.EXTRACTOR_REALTIME_REGION_LEVEL_DOWNGRADING_DEFAULT_VALUE;
+ private final Object tsFileOrderingLock = new Object();
+ private final Queue<PipeRealtimeEvent> orderedTsFileEvents =
+ new PriorityQueue<>(
+ (event1, event2) ->
+ compareTsFileEpochsByQueryPriority(event1.getTsFileEpoch(),
event2.getTsFileEpoch()));
+ private final Deque<PipeRealtimeEvent> bufferedEvents = new ArrayDeque<>();
+ private TsFileEpoch inFlightTsFileEpoch;
+
+ @Override
+ public void validate(final PipeParameterValidator validator) throws
Exception {
+ super.validate(validator);
+ validator
+ .validateAttributeValueRange(
+ PipeSourceConstant.EXTRACTOR_REALTIME_REGION_LEVEL_DOWNGRADING_KEY,
+ true,
+ Boolean.TRUE.toString(),
+ Boolean.FALSE.toString())
+ .validateAttributeValueRange(
+ PipeSourceConstant.SOURCE_REALTIME_REGION_LEVEL_DOWNGRADING_KEY,
+ true,
+ Boolean.TRUE.toString(),
+ Boolean.FALSE.toString());
+ }
+
@Override
- protected void doExtract(PipeRealtimeEvent event) {
+ public void customize(
+ final PipeParameters parameters, final PipeExtractorRuntimeConfiguration
configuration)
+ throws Exception {
+ super.customize(parameters, configuration);
+ isRegionLevelDowngradingEnabled =
+ parameters.getBooleanOrDefault(
+ Arrays.asList(
+
PipeSourceConstant.EXTRACTOR_REALTIME_REGION_LEVEL_DOWNGRADING_KEY,
+
PipeSourceConstant.SOURCE_REALTIME_REGION_LEVEL_DOWNGRADING_KEY),
+
PipeSourceConstant.EXTRACTOR_REALTIME_REGION_LEVEL_DOWNGRADING_DEFAULT_VALUE);
+ }
+
+ @Override
+ protected void doExtract(final PipeRealtimeEvent event) {
+ if (isRegionLevelDowngradingEnabled) {
+ synchronized (tsFileOrderingLock) {
+ if (isClosed.get()) {
+
event.decreaseReferenceCount(PipeRealtimeDataRegionTsFileSource.class.getName(),
false);
+ return;
+ }
+ doExtractInternal(event);
+ }
+ return;
+ }
+ doExtractInternal(event);
+ }
+
+ private void doExtractInternal(final PipeRealtimeEvent event) {
if (event.getEvent() instanceof PipeHeartbeatEvent) {
extractHeartbeat(event);
return;
@@ -76,9 +140,72 @@ public class PipeRealtimeDataRegionTsFileSource extends
PipeRealtimeDataRegionSo
return false;
}
+ @Override
+ protected void extractProgressReportEvent(final PipeRealtimeEvent event) {
+ if (isRegionLevelDowngradingEnabled) {
+ synchronized (tsFileOrderingLock) {
+ if (isClosed.get()) {
+
event.decreaseReferenceCount(PipeRealtimeDataRegionTsFileSource.class.getName(),
false);
+ return;
+ }
+ super.extractProgressReportEvent(event);
+ }
+ return;
+ }
+ super.extractProgressReportEvent(event);
+ }
+
+ @Override
+ public void close() throws Exception {
+ try {
+ super.close();
+ } finally {
+ synchronized (tsFileOrderingLock) {
+ PipeRealtimeEvent event;
+ while ((event = orderedTsFileEvents.poll()) != null) {
+
event.clearReferenceCount(PipeRealtimeDataRegionTsFileSource.class.getName());
+ }
+ while ((event = bufferedEvents.pollFirst()) != null) {
+
event.clearReferenceCount(PipeRealtimeDataRegionTsFileSource.class.getName());
+ }
+ inFlightTsFileEpoch = null;
+ }
+ }
+ }
+
@Override
public Event supply() {
- PipeRealtimeEvent realtimeEvent = (PipeRealtimeEvent)
pendingQueue.directPoll();
+ if (isRegionLevelDowngradingEnabled) {
+ synchronized (tsFileOrderingLock) {
+ if (isClosed.get() || inFlightTsFileEpoch != null) {
+ return null;
+ }
+
+ // Include every observed, unsent TsFile before selecting the next
one. Non-file events
+ // stay behind the files so they cannot advance progress past an
unfinished file.
+ PipeRealtimeEvent pendingEvent;
+ while ((pendingEvent = (PipeRealtimeEvent) pendingQueue.directPoll())
!= null) {
+ if (pendingEvent.getEvent() instanceof TsFileInsertionEvent) {
+ orderedTsFileEvents.offer(pendingEvent);
+ } else {
+ bufferedEvents.offerLast(pendingEvent);
+ }
+ }
+ return supplyInternal();
+ }
+ }
+ return supplyInternal();
+ }
+
+ private PipeRealtimeEvent pollNextEvent() {
+ if (!isRegionLevelDowngradingEnabled) {
+ return (PipeRealtimeEvent) pendingQueue.directPoll();
+ }
+ return orderedTsFileEvents.isEmpty() ? bufferedEvents.pollFirst() :
orderedTsFileEvents.poll();
+ }
+
+ private Event supplyInternal() {
+ PipeRealtimeEvent realtimeEvent = pollNextEvent();
while (realtimeEvent != null) {
Event suppliedEvent = null;
@@ -90,6 +217,15 @@ public class PipeRealtimeDataRegionTsFileSource extends
PipeRealtimeDataRegionSo
suppliedEvent = supplyDirectly(realtimeEvent);
} else if (realtimeEvent.increaseReferenceCount(
PipeRealtimeDataRegionTsFileSource.class.getName())) {
+ if (isRegionLevelDowngradingEnabled) {
+ inFlightTsFileEpoch = realtimeEvent.getTsFileEpoch();
+ final TsFileEpoch suppliedTsFileEpoch = inFlightTsFileEpoch;
+ final PipeTsFileInsertionEvent tsFileInsertionEvent =
+ (PipeTsFileInsertionEvent) realtimeEvent.getEvent();
+ final Runnable onCompletedHook = () ->
clearInFlightTsFileEpoch(suppliedTsFileEpoch);
+ tsFileInsertionEvent.addOnTransferredHook(onCompletedHook);
+ tsFileInsertionEvent.addOnDiscardedHook(onCompletedHook);
+ }
suppliedEvent = realtimeEvent.getEvent();
} else {
// if the event's reference count can not be increased, it means the
data represented by
@@ -118,10 +254,18 @@ public class PipeRealtimeDataRegionTsFileSource extends
PipeRealtimeDataRegionSo
return suppliedEvent;
}
- realtimeEvent = (PipeRealtimeEvent) pendingQueue.directPoll();
+ realtimeEvent = pollNextEvent();
}
// means the pending queue is empty.
return null;
}
+
+ private void clearInFlightTsFileEpoch(final TsFileEpoch tsFileEpoch) {
+ synchronized (tsFileOrderingLock) {
+ if (inFlightTsFileEpoch == tsFileEpoch) {
+ inFlightTsFileEpoch = null;
+ }
+ }
+ }
}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/source/PipeRealtimeExtractTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/source/PipeRealtimeExtractTest.java
index fc4a15a885b..da19dd72426 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/source/PipeRealtimeExtractTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/source/PipeRealtimeExtractTest.java
@@ -19,6 +19,7 @@
package org.apache.iotdb.db.pipe.source;
+import org.apache.iotdb.commons.conf.CommonDescriptor;
import org.apache.iotdb.commons.conf.IoTDBConstant;
import org.apache.iotdb.commons.consensus.index.impl.MinimumProgressIndex;
import org.apache.iotdb.commons.path.PartialPath;
@@ -29,6 +30,7 @@ import
org.apache.iotdb.commons.pipe.agent.task.meta.PipeRuntimeMeta;
import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStaticMeta;
import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTaskMeta;
import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTemporaryMetaInAgent;
+import
org.apache.iotdb.commons.pipe.agent.task.progress.PipeEventCommitManager;
import org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant;
import
org.apache.iotdb.commons.pipe.config.plugin.configuraion.PipeTaskRuntimeConfiguration;
import
org.apache.iotdb.commons.pipe.config.plugin.env.PipeTaskSourceRuntimeEnvironment;
@@ -38,12 +40,16 @@ import
org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeId;
import org.apache.iotdb.commons.utils.FileUtils;
import org.apache.iotdb.db.conf.IoTDBDescriptor;
import org.apache.iotdb.db.pipe.agent.PipeDataNodeAgent;
+import
org.apache.iotdb.db.pipe.event.common.tablet.PipeRawTabletInsertionEvent;
+import org.apache.iotdb.db.pipe.event.common.tsfile.PipeTsFileInsertionEvent;
import org.apache.iotdb.db.pipe.event.realtime.PipeRealtimeEvent;
import org.apache.iotdb.db.pipe.event.realtime.PipeRealtimeEventFactory;
import
org.apache.iotdb.db.pipe.source.dataregion.realtime.PipeRealtimeDataRegionHybridSource;
import
org.apache.iotdb.db.pipe.source.dataregion.realtime.PipeRealtimeDataRegionLogSource;
import
org.apache.iotdb.db.pipe.source.dataregion.realtime.PipeRealtimeDataRegionSource;
import
org.apache.iotdb.db.pipe.source.dataregion.realtime.PipeRealtimeDataRegionTsFileSource;
+import
org.apache.iotdb.db.pipe.source.dataregion.realtime.assigner.PipeTsFileEpochProgressIndexKeeper;
+import org.apache.iotdb.db.pipe.source.dataregion.realtime.epoch.TsFileEpoch;
import
org.apache.iotdb.db.pipe.source.dataregion.realtime.listener.PipeInsertionDataNodeListener;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowNode;
import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource;
@@ -56,6 +62,8 @@ import
org.apache.iotdb.pipe.api.event.dml.insertion.TsFileInsertionEvent;
import org.apache.tsfile.common.constant.TsFileConstant;
import org.apache.tsfile.enums.TSDataType;
import org.apache.tsfile.file.metadata.IDeviceID;
+import org.apache.tsfile.write.record.Tablet;
+import org.apache.tsfile.write.schema.MeasurementSchema;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -68,8 +76,10 @@ import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -99,11 +109,14 @@ public class PipeRealtimeExtractTest {
private ExecutorService writeService;
private ExecutorService listenerService;
private int dataNodeId;
+ private double pipeTotalFloatingMemoryProportion;
@Before
public void setUp() throws Exception {
dataNodeId = IoTDBDescriptor.getInstance().getConfig().getDataNodeId();
IoTDBDescriptor.getInstance().getConfig().setDataNodeId(0);
+ pipeTotalFloatingMemoryProportion =
+
CommonDescriptor.getInstance().getConfig().getPipeTotalFloatingMemoryProportion();
removeTestPipeMeta();
writeService = Executors.newFixedThreadPool(2);
listenerService = Executors.newFixedThreadPool(4);
@@ -120,6 +133,9 @@ public class PipeRealtimeExtractTest {
@After
public void tearDown() throws Exception {
IoTDBDescriptor.getInstance().getConfig().setDataNodeId(dataNodeId);
+ CommonDescriptor.getInstance()
+ .getConfig()
+
.setPipeTotalFloatingMemoryProportion(pipeTotalFloatingMemoryProportion);
writeService.shutdownNow();
listenerService.shutdownNow();
FileUtils.deleteFileOrDirectory(tmpDir);
@@ -379,6 +395,1163 @@ public class PipeRealtimeExtractTest {
Assert.assertNull(getGlobalTsFileEpochDegraded());
}
+ @Test
+ public void testHybridSourceRegionLevelDowngradingIsPipeSpecific() throws
Exception {
+ try (final PipeRealtimeDataRegionHybridSource disabledExtractor =
+ new PipeRealtimeDataRegionHybridSource();
+ final PipeRealtimeDataRegionHybridSource enabledExtractor =
+ new PipeRealtimeDataRegionHybridSource()) {
+ final PipeParameters disabledParameters =
+ new PipeParameters(
+ new HashMap<String, String>() {
+ {
+ put(PipeSourceConstant.EXTRACTOR_PATTERN_KEY, pattern1);
+ }
+ });
+ final PipeParameters enabledParameters =
+ new PipeParameters(
+ new HashMap<String, String>() {
+ {
+ put(PipeSourceConstant.EXTRACTOR_PATTERN_KEY, pattern1);
+ put(
+
PipeSourceConstant.EXTRACTOR_REALTIME_REGION_LEVEL_DOWNGRADING_KEY,
+ Boolean.TRUE.toString());
+ }
+ });
+
+ final PipeTaskRuntimeConfiguration disabledConfiguration =
+ new PipeTaskRuntimeConfiguration(
+ new PipeTaskSourceRuntimeEnvironment(
+ "region-level-downgrading-disabled",
+ TEST_PIPE_CREATION_TIME,
+ dataRegion1,
+ new PipeTaskMeta(MinimumProgressIndex.INSTANCE, 1)));
+ final PipeTaskRuntimeConfiguration enabledConfiguration =
+ new PipeTaskRuntimeConfiguration(
+ new PipeTaskSourceRuntimeEnvironment(
+ "region-level-downgrading-enabled",
+ TEST_PIPE_CREATION_TIME,
+ dataRegion1,
+ new PipeTaskMeta(MinimumProgressIndex.INSTANCE, 1)));
+
+ disabledExtractor.validate(new
PipeParameterValidator(disabledParameters));
+ disabledExtractor.customize(disabledParameters, disabledConfiguration);
+ enabledExtractor.validate(new PipeParameterValidator(enabledParameters));
+ enabledExtractor.customize(enabledParameters, enabledConfiguration);
+
+ Assert.assertFalse(isRegionLevelDowngradingEnabled(disabledExtractor));
+ Assert.assertTrue(isRegionLevelDowngradingEnabled(enabledExtractor));
+ }
+ }
+
+ @Test
+ public void testHybridSourceRegionLevelDowngradingWaitsForTsFileCommit()
throws Exception {
+ registerTestPipeMeta();
+
+ final PipeEventCommitManager commitManager =
PipeEventCommitManager.getInstance();
+ commitManager.register(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1, "test");
+ try (final PipeRealtimeDataRegionHybridSource extractor =
+ new PipeRealtimeDataRegionHybridSource()) {
+ final PipeParameters parameters =
+ new PipeParameters(
+ new HashMap<String, String>() {
+ {
+ put(PipeSourceConstant.EXTRACTOR_PATTERN_KEY, pattern1);
+ put(
+
PipeSourceConstant.SOURCE_REALTIME_REGION_LEVEL_DOWNGRADING_KEY,
+ Boolean.TRUE.toString());
+ }
+ });
+ final PipeTaskMeta pipeTaskMeta = new
PipeTaskMeta(MinimumProgressIndex.INSTANCE, 1);
+ final PipeTaskRuntimeConfiguration configuration =
+ new PipeTaskRuntimeConfiguration(
+ new PipeTaskSourceRuntimeEnvironment(
+ TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME, dataRegion1,
pipeTaskMeta));
+
+ extractor.validate(new PipeParameterValidator(parameters));
+ extractor.customize(parameters, configuration);
+
+ final TsFileResource firstResource = createTsFileResource(dataRegion1,
"101-101-0-0.tsfile");
+ final PipeRealtimeEvent firstTabletEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(
+ false,
+ "root.sg",
+ createInsertRowNode("first-degraded-tablet", "a"),
+ firstResource),
+ extractor,
+ pipeTaskMeta);
+
+
Assert.assertTrue(firstTabletEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(firstTabletEvent);
+ Assert.assertEquals(Boolean.FALSE, getGlobalTsFileEpochDegraded());
+
+ firstTabletEvent.clearReferenceCount(TEST_REFERENCE_HOLDER);
+
+ // Queue a tablet from another epoch before the first epoch triggers
region-level
+ // downgrading. It should be buffered while the degraded TsFile is being
sent.
+ final TsFileResource secondResource = createTsFileResource(dataRegion1,
"102-102-0-0.tsfile");
+ final PipeRealtimeEvent secondTabletEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(
+ false,
+ "root.sg",
+ createInsertRowNode("second-degraded-tablet", "a"),
+ secondResource),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(secondTabletEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(secondTabletEvent);
+ Assert.assertEquals(
+ TsFileEpoch.State.USING_TABLET,
secondTabletEvent.getTsFileEpoch().getState(extractor));
+
+ Assert.assertNull(extractor.supply());
+ Assert.assertEquals(Boolean.TRUE, getGlobalTsFileEpochDegraded());
+ Assert.assertEquals(
+ TsFileEpoch.State.USING_TABLET,
secondTabletEvent.getTsFileEpoch().getState(extractor));
+ Assert.assertFalse(secondTabletEvent.getEvent().isReleased());
+
+ // Simulate that the buffered tablet is evicted before the previous
degraded TsFile is
+ // committed. The second epoch should then continue region-level
downgrading with its TsFile.
+ secondTabletEvent.clearReferenceCount(TEST_REFERENCE_HOLDER);
+
+ final PipeRealtimeEvent firstTsFileEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(false, "root.sg",
firstResource, false),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(firstTsFileEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(firstTsFileEvent);
+ Assert.assertEquals(
+ TsFileEpoch.State.USING_TSFILE,
firstTsFileEvent.getTsFileEpoch().getState(extractor));
+
+ final Event firstSuppliedTsFile = extractor.supply();
+ Assert.assertTrue(firstSuppliedTsFile instanceof TsFileInsertionEvent);
+ Assert.assertEquals(Boolean.TRUE, getGlobalTsFileEpochDegraded());
+
+ final PipeRealtimeEvent secondTsFileEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(false, "root.sg",
secondResource, false),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(secondTsFileEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(secondTsFileEvent);
+
+ // The second TsFile stays in the source until the first TsFile is
committed downstream.
+ Assert.assertNull(extractor.supply());
+ final PipeTsFileInsertionEvent suppliedFirstTsFile =
+ (PipeTsFileInsertionEvent) firstSuppliedTsFile;
+ suppliedFirstTsFile.registerGeneratedTabletInsertionEvent();
+ suppliedFirstTsFile.registerGeneratedTabletInsertionEvent();
+ suppliedFirstTsFile.markGeneratedTabletInsertionEventsParsingCompleted();
+ final PipeRawTabletInsertionEvent firstGeneratedTabletEvent =
+ createGeneratedTabletEvent(suppliedFirstTsFile, pipeTaskMeta,
"first-generated");
+ final PipeRawTabletInsertionEvent secondGeneratedTabletEvent =
+ createGeneratedTabletEvent(suppliedFirstTsFile, pipeTaskMeta,
"second-generated");
+
Assert.assertTrue(firstGeneratedTabletEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+
Assert.assertTrue(secondGeneratedTabletEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+
Assert.assertTrue(suppliedFirstTsFile.decreaseReferenceCount(TEST_REFERENCE_HOLDER,
false));
+ commitSuppliedEvent(firstGeneratedTabletEvent, commitManager);
+ Assert.assertEquals(Boolean.TRUE, getGlobalTsFileEpochDegraded());
+ Assert.assertEquals(2, getActiveTsFileEpochCount(extractor));
+ Assert.assertEquals(1, getInFlightTsFileCount(extractor));
+ commitSuppliedEvent(secondGeneratedTabletEvent, commitManager);
+ Assert.assertEquals(1, getActiveTsFileEpochCount(extractor));
+ Assert.assertEquals(0, getInFlightTsFileCount(extractor));
+ Assert.assertEquals(Boolean.TRUE, getGlobalTsFileEpochDegraded());
+
+ final Event secondSuppliedTsFile = extractor.supply();
+ Assert.assertTrue(secondSuppliedTsFile instanceof TsFileInsertionEvent);
+ Assert.assertEquals(Boolean.TRUE, getGlobalTsFileEpochDegraded());
+
+ commitSuppliedEvent(secondSuppliedTsFile, commitManager);
+ Assert.assertEquals(0, getActiveTsFileEpochCount(extractor));
+ Assert.assertEquals(0, getInFlightTsFileCount(extractor));
+ Assert.assertNull(getGlobalTsFileEpochDegraded());
+ Assert.assertNull(extractor.supply());
+ } finally {
+ commitManager.deregister(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1);
+ }
+ }
+
+ @Test
+ public void testGeneratedTabletTransferWaitsForAllTabletCommits() throws
Exception {
+ registerTestPipeMeta();
+
+ final PipeEventCommitManager commitManager =
PipeEventCommitManager.getInstance();
+ final String dedupScopeId = "generated-tablet-transfer-test";
+ commitManager.register(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1, "test");
+ try {
+ final PipeTaskMeta pipeTaskMeta = new
PipeTaskMeta(MinimumProgressIndex.INSTANCE, 1);
+ final TsFileResource resource = createTsFileResource(dataRegion1,
"110-110-0-0.tsfile");
+ final PipeTsFileInsertionEvent tsFileEvent =
+ new PipeTsFileInsertionEvent(false, "root.sg", resource, false)
+ .shallowCopySelfAndBindPipeTaskMetaForProgressReport(
+ TEST_PIPE_NAME,
+ TEST_PIPE_CREATION_TIME,
+ pipeTaskMeta,
+ null,
+ null,
+ null,
+ null,
+ null,
+ true,
+ Long.MIN_VALUE,
+ Long.MAX_VALUE);
+ tsFileEvent.bindTsFileDedupScopeID(dedupScopeId);
+ PipeTsFileEpochProgressIndexKeeper.getInstance()
+ .registerProgressIndex(dataRegion1, dedupScopeId, resource);
+
+ final AtomicBoolean transferred = new AtomicBoolean(false);
+ tsFileEvent.addOnTransferredHook(() -> transferred.set(true));
+ tsFileEvent.registerGeneratedTabletInsertionEvent();
+ tsFileEvent.registerGeneratedTabletInsertionEvent();
+
+ // The default PipeProcessor path iterates toTabletInsertionEvents()
directly and does not
+ // report parser completion. The source TsFile commit is still the
boundary before generated
+ // tablet commits.
+ tsFileEvent.skipReportOnCommit();
+ tsFileEvent.getOnCommittedHooks().forEach(Runnable::run);
+
+ final PipeRawTabletInsertionEvent firstGeneratedTabletEvent =
+ createGeneratedTabletEvent(tsFileEvent, pipeTaskMeta, "first",
false);
+ final PipeRawTabletInsertionEvent secondGeneratedTabletEvent =
+ createGeneratedTabletEvent(tsFileEvent, pipeTaskMeta, "second",
true);
+
Assert.assertTrue(firstGeneratedTabletEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+
Assert.assertTrue(secondGeneratedTabletEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+
+ commitSuppliedEvent(firstGeneratedTabletEvent, commitManager);
+ Assert.assertFalse(transferred.get());
+ Assert.assertTrue(
+ PipeTsFileEpochProgressIndexKeeper.getInstance()
+ .containsTsFile(dataRegion1, dedupScopeId,
resource.getTsFilePath()));
+
+ commitSuppliedEvent(secondGeneratedTabletEvent, commitManager);
+ Assert.assertTrue(transferred.get());
+ Assert.assertFalse(
+ PipeTsFileEpochProgressIndexKeeper.getInstance()
+ .containsTsFile(dataRegion1, dedupScopeId,
resource.getTsFilePath()));
+ } finally {
+ commitManager.deregister(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1);
+ PipeTsFileEpochProgressIndexKeeper.getInstance()
+ .clearProgressIndex(dataRegion1, dedupScopeId);
+ }
+ }
+
+ @Test
+ public void testGeneratedTabletTransferWaitsForDeferredGeneration() throws
Exception {
+ registerTestPipeMeta();
+
+ final PipeEventCommitManager commitManager =
PipeEventCommitManager.getInstance();
+ commitManager.register(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1, "test");
+ final String dedupScopeId = "deferred-generated-tablet-transfer-test";
+ try {
+ final PipeTaskMeta pipeTaskMeta = new
PipeTaskMeta(MinimumProgressIndex.INSTANCE, 1);
+ final TsFileResource resource = createTsFileResource(dataRegion1,
"111-111-0-0.tsfile");
+ final PipeTsFileInsertionEvent tsFileEvent =
+ new PipeTsFileInsertionEvent(false, "root.sg", resource, false)
+ .shallowCopySelfAndBindPipeTaskMetaForProgressReport(
+ TEST_PIPE_NAME,
+ TEST_PIPE_CREATION_TIME,
+ pipeTaskMeta,
+ null,
+ null,
+ null,
+ null,
+ null,
+ true,
+ Long.MIN_VALUE,
+ Long.MAX_VALUE);
+ tsFileEvent.bindTsFileDedupScopeID(dedupScopeId);
+
+ final AtomicBoolean transferred = new AtomicBoolean(false);
+ tsFileEvent.addOnTransferredHook(() -> transferred.set(true));
+ tsFileEvent.markGeneratedTabletInsertionEventsParsingStarted();
+ tsFileEvent.skipReportOnCommit();
+ tsFileEvent.getOnCommittedHooks().forEach(Runnable::run);
+ Assert.assertFalse(transferred.get());
+
+ tsFileEvent.registerGeneratedTabletInsertionEvent();
+ tsFileEvent.markGeneratedTabletInsertionEventsParsingCompleted();
+ final PipeRawTabletInsertionEvent generatedTabletEvent =
+ createGeneratedTabletEvent(tsFileEvent, pipeTaskMeta, "deferred");
+
Assert.assertTrue(generatedTabletEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ commitSuppliedEvent(generatedTabletEvent, commitManager);
+ Assert.assertTrue(transferred.get());
+ } finally {
+ commitManager.deregister(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1);
+ PipeTsFileEpochProgressIndexKeeper.getInstance()
+ .clearProgressIndex(dataRegion1, dedupScopeId);
+ }
+ }
+
+ @Test
+ public void
testHybridSourceClearsInFlightTsFileWhenSuppliedEventIsDiscarded() throws
Exception {
+ registerTestPipeMeta();
+
+ final PipeEventCommitManager commitManager =
PipeEventCommitManager.getInstance();
+ commitManager.register(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1, "test");
+ try (final PipeRealtimeDataRegionHybridSource extractor =
+ new PipeRealtimeDataRegionHybridSource()) {
+ final PipeTaskMeta pipeTaskMeta = new
PipeTaskMeta(MinimumProgressIndex.INSTANCE, 1);
+ final PipeTaskRuntimeConfiguration configuration =
+ new PipeTaskRuntimeConfiguration(
+ new PipeTaskSourceRuntimeEnvironment(
+ TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME, dataRegion1,
pipeTaskMeta));
+ final PipeParameters parameters =
+ new PipeParameters(
+ new HashMap<String, String>() {
+ {
+ put(PipeSourceConstant.EXTRACTOR_PATTERN_KEY, pattern1);
+ put(
+
PipeSourceConstant.SOURCE_REALTIME_REGION_LEVEL_DOWNGRADING_KEY,
+ Boolean.TRUE.toString());
+ }
+ });
+ extractor.validate(new PipeParameterValidator(parameters));
+ extractor.customize(parameters, configuration);
+
+ final TsFileResource resource = createTsFileResource(dataRegion1,
"112-112-0-0.tsfile");
+ final PipeRealtimeEvent tabletEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(
+ false, "root.sg",
createInsertRowNode("discarded-tsfile-tablet", "a"), resource),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(tabletEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(tabletEvent);
+ tabletEvent.clearReferenceCount(TEST_REFERENCE_HOLDER);
+ Assert.assertNull(extractor.supply());
+ Assert.assertEquals(Boolean.TRUE, getGlobalTsFileEpochDegraded());
+
+ final PipeRealtimeEvent tsFileEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(false, "root.sg",
resource, false),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(tsFileEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(tsFileEvent);
+
+ final Event suppliedTsFile = extractor.supply();
+ Assert.assertTrue(suppliedTsFile instanceof TsFileInsertionEvent);
+ Assert.assertEquals(1, getInFlightTsFileCount(extractor));
+
+ ((EnrichedEvent)
suppliedTsFile).clearReferenceCount(TEST_REFERENCE_HOLDER);
+ Assert.assertEquals(0, getInFlightTsFileCount(extractor));
+ Assert.assertNull(getGlobalTsFileEpochDegraded());
+ } finally {
+ commitManager.deregister(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1);
+ }
+ }
+
+ @Test
+ public void testHybridSourceCompensatesForDiscardedGeneratedTabletEvents()
throws Exception {
+ registerTestPipeMeta();
+
+ final PipeEventCommitManager commitManager =
PipeEventCommitManager.getInstance();
+ commitManager.register(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1, "test");
+ try (final PipeRealtimeDataRegionHybridSource extractor =
+ new PipeRealtimeDataRegionHybridSource()) {
+ final PipeTaskMeta pipeTaskMeta = new
PipeTaskMeta(MinimumProgressIndex.INSTANCE, 1);
+ final PipeTaskRuntimeConfiguration configuration =
+ new PipeTaskRuntimeConfiguration(
+ new PipeTaskSourceRuntimeEnvironment(
+ TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME, dataRegion1,
pipeTaskMeta));
+ final PipeParameters parameters =
+ new PipeParameters(
+ new HashMap<String, String>() {
+ {
+ put(PipeSourceConstant.EXTRACTOR_PATTERN_KEY, pattern1);
+ put(
+
PipeSourceConstant.SOURCE_REALTIME_REGION_LEVEL_DOWNGRADING_KEY,
+ Boolean.TRUE.toString());
+ }
+ });
+ extractor.validate(new PipeParameterValidator(parameters));
+ extractor.customize(parameters, configuration);
+
+ final TsFileResource resource = createTsFileResource(dataRegion1,
"113-113-0-0.tsfile");
+ final PipeRealtimeEvent tabletEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(
+ false,
+ "root.sg",
+ createInsertRowNode("discarded-generated-tablet", "a"),
+ resource),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(tabletEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(tabletEvent);
+ tabletEvent.clearReferenceCount(TEST_REFERENCE_HOLDER);
+ Assert.assertNull(extractor.supply());
+
+ final PipeRealtimeEvent tsFileRealtimeEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(false, "root.sg",
resource, false),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(tsFileRealtimeEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(tsFileRealtimeEvent);
+ final PipeTsFileInsertionEvent suppliedTsFile =
(PipeTsFileInsertionEvent) extractor.supply();
+ Assert.assertNotNull(suppliedTsFile);
+ Assert.assertEquals(1, getInFlightTsFileCount(extractor));
+
+ suppliedTsFile.registerGeneratedTabletInsertionEvent();
+ suppliedTsFile.registerGeneratedTabletInsertionEvent();
+ suppliedTsFile.markGeneratedTabletInsertionEventsParsingCompleted();
+ final PipeRawTabletInsertionEvent firstGeneratedTablet =
+ createGeneratedTabletEvent(suppliedTsFile, pipeTaskMeta,
"discarded-first");
+ final PipeRawTabletInsertionEvent secondGeneratedTablet =
+ createGeneratedTabletEvent(suppliedTsFile, pipeTaskMeta,
"discarded-second");
+ firstGeneratedTablet.markAsGeneratedEventRegisteredWithSource();
+ secondGeneratedTablet.markAsGeneratedEventRegisteredWithSource();
+
Assert.assertTrue(firstGeneratedTablet.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+
Assert.assertTrue(secondGeneratedTablet.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+
+ commitManager.enrichWithCommitterKeyAndCommitId(
+ suppliedTsFile, TEST_PIPE_CREATION_TIME, dataRegion1);
+
Assert.assertTrue(suppliedTsFile.decreaseReferenceCount(TEST_REFERENCE_HOLDER,
false));
+ firstGeneratedTablet.clearReferenceCount(TEST_REFERENCE_HOLDER);
+ Assert.assertEquals(1, getInFlightTsFileCount(extractor));
+ secondGeneratedTablet.clearReferenceCount(TEST_REFERENCE_HOLDER);
+
+ Assert.assertEquals(0, getInFlightTsFileCount(extractor));
+ Assert.assertNull(getGlobalTsFileEpochDegraded());
+ } finally {
+ commitManager.deregister(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1);
+ }
+ }
+
+ @Test
+ public void
testHybridSourceRegionLevelDowngradingResumesCompleteBufferedTablets()
+ throws Exception {
+ registerTestPipeMeta();
+
+ final PipeEventCommitManager commitManager =
PipeEventCommitManager.getInstance();
+ commitManager.register(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1, "test");
+ try (final PipeRealtimeDataRegionHybridSource extractor =
+ new PipeRealtimeDataRegionHybridSource()) {
+ final PipeParameters parameters =
+ new PipeParameters(
+ new HashMap<String, String>() {
+ {
+ put(PipeSourceConstant.EXTRACTOR_PATTERN_KEY, pattern1);
+ put(
+
PipeSourceConstant.SOURCE_REALTIME_REGION_LEVEL_DOWNGRADING_KEY,
+ Boolean.TRUE.toString());
+ }
+ });
+ final PipeTaskMeta pipeTaskMeta = new
PipeTaskMeta(MinimumProgressIndex.INSTANCE, 1);
+ final PipeTaskRuntimeConfiguration configuration =
+ new PipeTaskRuntimeConfiguration(
+ new PipeTaskSourceRuntimeEnvironment(
+ TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME, dataRegion1,
pipeTaskMeta));
+
+ extractor.validate(new PipeParameterValidator(parameters));
+ extractor.customize(parameters, configuration);
+
+ final TsFileResource firstResource = createTsFileResource(dataRegion1,
"103-103-0-0.tsfile");
+ final PipeRealtimeEvent firstTabletEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(
+ false,
+ "root.sg",
+ createInsertRowNode("first-degraded-tablet", "a"),
+ firstResource),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(firstTabletEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(firstTabletEvent);
+ firstTabletEvent.clearReferenceCount(TEST_REFERENCE_HOLDER);
+
+ final TsFileResource secondResource = createTsFileResource(dataRegion1,
"104-104-0-0.tsfile");
+ final PipeRealtimeEvent secondTabletEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(
+ false,
+ "root.sg",
+ createInsertRowNode("fully-buffered-tablet", "a"),
+ secondResource),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(secondTabletEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(secondTabletEvent);
+
+ Assert.assertNull(extractor.supply());
+ Assert.assertEquals(Boolean.TRUE, getGlobalTsFileEpochDegraded());
+ Assert.assertEquals(
+ TsFileEpoch.State.USING_TABLET,
secondTabletEvent.getTsFileEpoch().getState(extractor));
+ Assert.assertFalse(secondTabletEvent.getEvent().isReleased());
+
+ final PipeRealtimeEvent firstTsFileEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(false, "root.sg",
firstResource, false),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(firstTsFileEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(firstTsFileEvent);
+ final Event firstSuppliedTsFile = extractor.supply();
+ Assert.assertTrue(firstSuppliedTsFile instanceof TsFileInsertionEvent);
+
+ commitLastGeneratedTabletEvent(
+ (PipeTsFileInsertionEvent) firstSuppliedTsFile, commitManager,
pipeTaskMeta);
+ Assert.assertEquals(Boolean.FALSE, getGlobalTsFileEpochDegraded());
+
+ // The latest TsFile is still open. Since all of its requests survived
in memory at the
+ // commit boundary above, later writes of the same TsFile should
immediately continue on the
+ // realtime path instead of waiting for another flush.
+ final PipeRealtimeEvent newRealtimeTabletEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(
+ false,
+ "root.sg",
+ createInsertRowNode("new-realtime-tablet", "a"),
+ secondResource),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(newRealtimeTabletEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(newRealtimeTabletEvent);
+
+ final Event resumedTabletEvent = extractor.supply();
+ Assert.assertTrue(resumedTabletEvent instanceof TabletInsertionEvent);
+ Assert.assertSame(secondTabletEvent.getEvent(), resumedTabletEvent);
+ Assert.assertEquals(Boolean.FALSE, getGlobalTsFileEpochDegraded());
+ commitSuppliedEvent(resumedTabletEvent, commitManager);
+
+ final Event newSuppliedTabletEvent = extractor.supply();
+ Assert.assertTrue(newSuppliedTabletEvent instanceof
TabletInsertionEvent);
+ Assert.assertSame(newRealtimeTabletEvent.getEvent(),
newSuppliedTabletEvent);
+ Assert.assertEquals(Boolean.FALSE, getGlobalTsFileEpochDegraded());
+ commitSuppliedEvent(newSuppliedTabletEvent, commitManager);
+
+ final PipeRealtimeEvent secondTsFileEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(false, "root.sg",
secondResource, false),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(secondTsFileEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(secondTsFileEvent);
+
+ // The second TsFile is no longer needed because all of its tablets
survived buffering.
+ Assert.assertNull(extractor.supply());
+ Assert.assertNull(getGlobalTsFileEpochDegraded());
+ } finally {
+ commitManager.deregister(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1);
+ }
+ }
+
+ @Test
+ public void testHybridSourceRegionLevelDowngradingOnlyCachesLatestTsFile()
throws Exception {
+ registerTestPipeMeta();
+
+ final PipeEventCommitManager commitManager =
PipeEventCommitManager.getInstance();
+ commitManager.register(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1, "test");
+ try (final PipeRealtimeDataRegionHybridSource extractor =
+ new PipeRealtimeDataRegionHybridSource()) {
+ final PipeParameters parameters =
+ new PipeParameters(
+ new HashMap<String, String>() {
+ {
+ put(PipeSourceConstant.EXTRACTOR_PATTERN_KEY, pattern1);
+ put(
+
PipeSourceConstant.SOURCE_REALTIME_REGION_LEVEL_DOWNGRADING_KEY,
+ Boolean.TRUE.toString());
+ }
+ });
+ final PipeTaskMeta pipeTaskMeta = new
PipeTaskMeta(MinimumProgressIndex.INSTANCE, 1);
+ final PipeTaskRuntimeConfiguration configuration =
+ new PipeTaskRuntimeConfiguration(
+ new PipeTaskSourceRuntimeEnvironment(
+ TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME, dataRegion1,
pipeTaskMeta));
+
+ extractor.validate(new PipeParameterValidator(parameters));
+ extractor.customize(parameters, configuration);
+
+ final TsFileResource firstResource = createTsFileResource(dataRegion1,
"107-107-0-0.tsfile");
+ final PipeRealtimeEvent firstTabletEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(
+ false,
+ "root.sg",
+ createInsertRowNode("first-degraded-tablet", "a"),
+ firstResource),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(firstTabletEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(firstTabletEvent);
+ firstTabletEvent.clearReferenceCount(TEST_REFERENCE_HOLDER);
+
+ Assert.assertNull(extractor.supply());
+ Assert.assertEquals(Boolean.TRUE, getGlobalTsFileEpochDegraded());
+
+ final TsFileResource secondResource = createTsFileResource(dataRegion1,
"108-108-0-0.tsfile");
+ final PipeRealtimeEvent secondTabletEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(
+ false,
+ "root.sg",
+ createInsertRowNode("second-buffered-tablet", "a"),
+ secondResource),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(secondTabletEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(secondTabletEvent);
+
+ final PipeRealtimeEvent secondTsFileEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(false, "root.sg",
secondResource, false),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(secondTsFileEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(secondTsFileEvent);
+
+ final TsFileResource thirdResource = createTsFileResource(dataRegion1,
"109-109-0-0.tsfile");
+ final PipeRealtimeEvent thirdTabletEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(
+ false,
+ "root.sg",
+ createInsertRowNode("latest-buffered-tablet", "a"),
+ thirdResource),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(thirdTabletEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(thirdTabletEvent);
+
+ // Once a newer epoch appears, the former tail is downgraded even if all
its tablets are
+ // still available. This bounds the region-level cache to the latest
TsFile.
+ Assert.assertEquals(
+ TsFileEpoch.State.USING_TSFILE,
secondTabletEvent.getTsFileEpoch().getState(extractor));
+ Assert.assertTrue(secondTabletEvent.getEvent().isReleased());
+ Assert.assertEquals(
+ TsFileEpoch.State.USING_TABLET,
thirdTabletEvent.getTsFileEpoch().getState(extractor));
+ Assert.assertFalse(thirdTabletEvent.getEvent().isReleased());
+
+ // Extract the first TsFile after the second one to verify that query
priority, rather than
+ // flush completion order, decides which file can pass downstream.
+ final PipeRealtimeEvent firstTsFileEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(false, "root.sg",
firstResource, false),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(firstTsFileEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(firstTsFileEvent);
+
+ final Event firstSuppliedTsFile = extractor.supply();
+ Assert.assertTrue(firstSuppliedTsFile instanceof TsFileInsertionEvent);
+ Assert.assertSame(firstTsFileEvent.getEvent(), firstSuppliedTsFile);
+ commitSuppliedEvent(firstSuppliedTsFile, commitManager);
+
+ final Event secondSuppliedTsFile = extractor.supply();
+ Assert.assertTrue(secondSuppliedTsFile instanceof TsFileInsertionEvent);
+ Assert.assertSame(secondTsFileEvent.getEvent(), secondSuppliedTsFile);
+ commitSuppliedEvent(secondSuppliedTsFile, commitManager);
+
+ Assert.assertEquals(Boolean.FALSE, getGlobalTsFileEpochDegraded());
+ final Event resumedLatestTablet = extractor.supply();
+ Assert.assertTrue(resumedLatestTablet instanceof TabletInsertionEvent);
+ Assert.assertSame(thirdTabletEvent.getEvent(), resumedLatestTablet);
+ commitSuppliedEvent(resumedLatestTablet, commitManager);
+
+ final PipeRealtimeEvent thirdTsFileEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(false, "root.sg",
thirdResource, false),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(thirdTsFileEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(thirdTsFileEvent);
+
+ Assert.assertNull(extractor.supply());
+ Assert.assertNull(getGlobalTsFileEpochDegraded());
+ } finally {
+ commitManager.deregister(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1);
+ }
+ }
+
+ @Test
+ public void testHybridSourceRegionLevelDowngradingFollowsQueryPriority()
throws Exception {
+ registerTestPipeMeta();
+
+ final PipeEventCommitManager commitManager =
PipeEventCommitManager.getInstance();
+ commitManager.register(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1, "test");
+ try (final PipeRealtimeDataRegionHybridSource extractor =
+ new PipeRealtimeDataRegionHybridSource()) {
+ final PipeParameters parameters =
+ new PipeParameters(
+ new HashMap<String, String>() {
+ {
+ put(PipeSourceConstant.EXTRACTOR_PATTERN_KEY, pattern1);
+ put(
+
PipeSourceConstant.SOURCE_REALTIME_REGION_LEVEL_DOWNGRADING_KEY,
+ Boolean.TRUE.toString());
+ }
+ });
+ final PipeTaskMeta pipeTaskMeta = new
PipeTaskMeta(MinimumProgressIndex.INSTANCE, 1);
+ final PipeTaskRuntimeConfiguration configuration =
+ new PipeTaskRuntimeConfiguration(
+ new PipeTaskSourceRuntimeEnvironment(
+ TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME, dataRegion1,
pipeTaskMeta));
+
+ extractor.validate(new PipeParameterValidator(parameters));
+ extractor.customize(parameters, configuration);
+
+ final TsFileResource loadedUnseqResource =
+ createTsFileResource(dataRegion1, "100-3-0-0.tsfile");
+ loadedUnseqResource.setSeq(false);
+ final PipeRealtimeEvent loadedUnseqTsFileEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(
+ false, "root.sg", loadedUnseqResource, true),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(loadedUnseqTsFileEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(loadedUnseqTsFileEvent);
+
+ final TsFileResource firstUnseqResource =
+ createTsFileResource(dataRegion1, "200-2-0-0.tsfile");
+ firstUnseqResource.setSeq(false);
+ final PipeRealtimeEvent firstUnseqTabletEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(
+ false,
+ "root.sg",
+ createInsertRowNode("first-unseq-degraded-tablet", "a"),
+ firstUnseqResource),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(firstUnseqTabletEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+
+
CommonDescriptor.getInstance().getConfig().setPipeTotalFloatingMemoryProportion(0);
+ try {
+ extractor.extract(firstUnseqTabletEvent);
+ } finally {
+ CommonDescriptor.getInstance()
+ .getConfig()
+
.setPipeTotalFloatingMemoryProportion(pipeTotalFloatingMemoryProportion);
+ }
+ Assert.assertEquals(
+ TsFileEpoch.State.USING_TSFILE,
+ firstUnseqTabletEvent.getTsFileEpoch().getState(extractor));
+ firstUnseqTabletEvent.clearReferenceCount(TEST_REFERENCE_HOLDER);
+
+ final TsFileResource seqResource = createTsFileResource(dataRegion1,
"300-10-0-0.tsfile");
+ seqResource.setSeq(true);
+ final PipeRealtimeEvent seqTabletEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(
+ false,
+ "root.sg",
+ createInsertRowNode("lower-priority-seq-tail", "a"),
+ seqResource),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(seqTabletEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(seqTabletEvent);
+
+ final PipeRealtimeEvent seqTsFileEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(false, "root.sg",
seqResource, false),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(seqTsFileEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(seqTsFileEvent);
+
+ final PipeRealtimeEvent firstUnseqTsFileEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(
+ false, "root.sg", firstUnseqResource, false),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(firstUnseqTsFileEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(firstUnseqTsFileEvent);
+
+ // Seq has lower query priority than every unseq file, so the previously
undegraded realtime
+ // tail must be promoted and sent before either unseq file. Within
unseq, version 2 precedes
+ // version 3 even though the loaded version-3 event was queued before
downgrading started.
+ final Event firstSuppliedTsFile = extractor.supply();
+ Assert.assertTrue(firstSuppliedTsFile instanceof TsFileInsertionEvent);
+ Assert.assertSame(seqTsFileEvent.getEvent(), firstSuppliedTsFile);
+ seqTabletEvent.clearReferenceCount(TEST_REFERENCE_HOLDER);
+ commitSuppliedEvent(firstSuppliedTsFile, commitManager);
+
+ final Event secondSuppliedTsFile = extractor.supply();
+ Assert.assertTrue(secondSuppliedTsFile instanceof TsFileInsertionEvent);
+ Assert.assertSame(firstUnseqTsFileEvent.getEvent(),
secondSuppliedTsFile);
+ commitSuppliedEvent(secondSuppliedTsFile, commitManager);
+
+ final Event thirdSuppliedTsFile = extractor.supply();
+ Assert.assertTrue(thirdSuppliedTsFile instanceof TsFileInsertionEvent);
+ Assert.assertSame(loadedUnseqTsFileEvent.getEvent(),
thirdSuppliedTsFile);
+ commitSuppliedEvent(thirdSuppliedTsFile, commitManager);
+
+ Assert.assertNull(getGlobalTsFileEpochDegraded());
+ Assert.assertNull(extractor.supply());
+ } finally {
+ commitManager.deregister(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1);
+ }
+ }
+
+ @Test
+ public void
testHybridSourceRegionLevelDowngradingPreservesPreviouslyQueuedEvents()
+ throws Exception {
+ registerTestPipeMeta();
+
+ final PipeEventCommitManager commitManager =
PipeEventCommitManager.getInstance();
+ commitManager.register(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1, "test");
+ try (final PipeRealtimeDataRegionHybridSource extractor =
+ new PipeRealtimeDataRegionHybridSource()) {
+ final PipeParameters parameters =
+ new PipeParameters(
+ new HashMap<String, String>() {
+ {
+ put(PipeSourceConstant.EXTRACTOR_PATTERN_KEY, pattern1);
+ put(
+
PipeSourceConstant.SOURCE_REALTIME_REGION_LEVEL_DOWNGRADING_KEY,
+ Boolean.TRUE.toString());
+ }
+ });
+ final PipeTaskMeta pipeTaskMeta = new
PipeTaskMeta(MinimumProgressIndex.INSTANCE, 1);
+ final PipeTaskRuntimeConfiguration configuration =
+ new PipeTaskRuntimeConfiguration(
+ new PipeTaskSourceRuntimeEnvironment(
+ TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME, dataRegion1,
pipeTaskMeta));
+
+ extractor.validate(new PipeParameterValidator(parameters));
+ extractor.customize(parameters, configuration);
+
+ final TsFileResource olderResource = createTsFileResource(dataRegion1,
"105-105-0-0.tsfile");
+ final PipeRealtimeEvent olderTabletEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(
+ false,
+ "root.sg",
+ createInsertRowNode("queued-before-downgrading", "a"),
+ olderResource),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(olderTabletEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(olderTabletEvent);
+
+ // Seal the older epoch while leaving its tablet queued in the source.
+ final PipeRealtimeEvent olderTsFileEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(false, "root.sg",
olderResource, false),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(olderTsFileEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(olderTsFileEvent);
+
+ final TsFileResource degradedResource =
+ createTsFileResource(dataRegion1, "106-106-0-0.tsfile");
+ final PipeRealtimeEvent degradedTabletEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(
+ false,
+ "root.sg",
+ createInsertRowNode("trigger-region-downgrading", "a"),
+ degradedResource),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(degradedTabletEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+
+
CommonDescriptor.getInstance().getConfig().setPipeTotalFloatingMemoryProportion(0);
+ try {
+ extractor.extract(degradedTabletEvent);
+ } finally {
+ CommonDescriptor.getInstance()
+ .getConfig()
+
.setPipeTotalFloatingMemoryProportion(pipeTotalFloatingMemoryProportion);
+ }
+ Assert.assertEquals(
+ TsFileEpoch.State.USING_TSFILE,
degradedTabletEvent.getTsFileEpoch().getState(extractor));
+ Assert.assertEquals(Boolean.TRUE, getGlobalTsFileEpochDegraded());
+
+ final PipeRealtimeEvent degradedTsFileEvent =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(
+ false, "root.sg", degradedResource, false),
+ extractor,
+ pipeTaskMeta);
+
Assert.assertTrue(degradedTsFileEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(degradedTsFileEvent);
+
+ // The tablet that was already queued before downgrading must not be
overtaken by the later
+ // degraded TsFile.
+ final Event firstSuppliedEvent = extractor.supply();
+ Assert.assertTrue(firstSuppliedEvent instanceof TabletInsertionEvent);
+ Assert.assertSame(olderTabletEvent.getEvent(), firstSuppliedEvent);
+ commitSuppliedEvent(firstSuppliedEvent, commitManager);
+
+ final Event secondSuppliedEvent = extractor.supply();
+ Assert.assertTrue(secondSuppliedEvent instanceof TsFileInsertionEvent);
+ Assert.assertSame(degradedTsFileEvent.getEvent(), secondSuppliedEvent);
+ commitSuppliedEvent(secondSuppliedEvent, commitManager);
+
+ Assert.assertNull(getGlobalTsFileEpochDegraded());
+ Assert.assertNull(extractor.supply());
+ } finally {
+ commitManager.deregister(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1);
+ }
+ }
+
+ @Test
+ public void testTsFileSourceRegionLevelDowngradingUsesPipeParameters()
throws Exception {
+ for (final String key :
+ Arrays.asList(
+ null,
+ PipeSourceConstant.SOURCE_REALTIME_REGION_LEVEL_DOWNGRADING_KEY,
+
PipeSourceConstant.EXTRACTOR_REALTIME_REGION_LEVEL_DOWNGRADING_KEY)) {
+ final PipeTaskMeta pipeTaskMeta = new
PipeTaskMeta(MinimumProgressIndex.INSTANCE, 1);
+ try (final PipeRealtimeDataRegionTsFileSource extractor =
+ createTsFileSourceForTest(key, pipeTaskMeta)) {
+ final String prefix = key == null ? "120" : key.startsWith("source") ?
"121" : "122";
+ final PipeRealtimeEvent firstEvent =
+ extractTsFileForTest(
+ extractor,
+ pipeTaskMeta,
+ createTsFileResource(dataRegion1, prefix + "-1-0-0.tsfile"),
+ false);
+ final PipeRealtimeEvent secondEvent =
+ extractTsFileForTest(
+ extractor,
+ pipeTaskMeta,
+ createTsFileResource(dataRegion1, prefix + "-2-0-0.tsfile"),
+ false);
+ Assert.assertSame(firstEvent.getEvent(), extractor.supply());
+ if (key == null) {
+ Assert.assertSame(secondEvent.getEvent(), extractor.supply());
+ } else {
+ Assert.assertNull(extractor.supply());
+ }
+ firstEvent.clearReferenceCount(TEST_REFERENCE_HOLDER);
+ if (key != null) {
+ Assert.assertSame(secondEvent.getEvent(), extractor.supply());
+ }
+ secondEvent.clearReferenceCount(TEST_REFERENCE_HOLDER);
+ }
+ }
+ }
+
+ @Test
+ public void testTsFileSourceRegionLevelDowngradingFollowsQueryPriority()
throws Exception {
+ final PipeEventCommitManager commitManager =
PipeEventCommitManager.getInstance();
+ commitManager.register(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1, "test");
+ final PipeTaskMeta pipeTaskMeta = new
PipeTaskMeta(MinimumProgressIndex.INSTANCE, 1);
+ try (final PipeRealtimeDataRegionTsFileSource extractor =
+ createTsFileSourceForTest(
+ PipeSourceConstant.SOURCE_REALTIME_REGION_LEVEL_DOWNGRADING_KEY,
pipeTaskMeta)) {
+ final TsFileResource[] resources = {
+ createTsFileResource(dataRegion1, "300-10-0-0.tsfile"),
+ createTsFileResource(dataRegion1, "150-2-0-0.tsfile"),
+ createTsFileResource(dataRegion1, "200-2-0-0.tsfile"),
+ createTsFileResource(dataRegion1, "1", "200-2-0-0.tsfile"),
+ createTsFileResource(dataRegion1, "200-2-1-0.tsfile"),
+ createTsFileResource(dataRegion1, "100-3-0-0.tsfile")
+ };
+ final PipeRealtimeEvent[] events = new
PipeRealtimeEvent[resources.length];
+ // Reverse arrival order: seq/unseq, version, timestamp, compaction
version and directory
+ // must all use the same ordering as Hybrid source, including loaded
files.
+ for (int i = resources.length - 1; i >= 0; --i) {
+ resources[i].setSeq(i == 0);
+ events[i] = extractTsFileForTest(extractor, pipeTaskMeta,
resources[i], i == 5);
+ }
+ for (final PipeRealtimeEvent event : events) {
+ final Event suppliedEvent = extractor.supply();
+ Assert.assertSame(event.getEvent(), suppliedEvent);
+ Assert.assertNull(extractor.supply());
+ commitSuppliedEvent(suppliedEvent, commitManager);
+ }
+ Assert.assertNull(extractor.supply());
+ } finally {
+ commitManager.deregister(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1);
+ }
+ }
+
+ @Test
+ public void testTsFileSourceRegionLevelDowngradingWaitsForGeneratedTablets()
throws Exception {
+ final PipeEventCommitManager commitManager =
PipeEventCommitManager.getInstance();
+ commitManager.register(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1, "test");
+ final PipeTaskMeta pipeTaskMeta = new
PipeTaskMeta(MinimumProgressIndex.INSTANCE, 1);
+ try (final PipeRealtimeDataRegionTsFileSource extractor =
+ createTsFileSourceForTest(
+ PipeSourceConstant.SOURCE_REALTIME_REGION_LEVEL_DOWNGRADING_KEY,
pipeTaskMeta)) {
+ extractTsFileForTest(
+ extractor, pipeTaskMeta, createTsFileResource(dataRegion1,
"123-1-0-0.tsfile"), false);
+ final PipeRealtimeEvent progressEvent =
createProgressReportRealtimeEvent();
+
Assert.assertTrue(progressEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(progressEvent);
+ final PipeRealtimeEvent secondEvent =
+ extractTsFileForTest(
+ extractor,
+ pipeTaskMeta,
+ createTsFileResource(dataRegion1, "123-2-0-0.tsfile"),
+ false);
+
+ final PipeTsFileInsertionEvent firstTsFile = (PipeTsFileInsertionEvent)
extractor.supply();
+ firstTsFile.markGeneratedTabletInsertionEventsParsingStarted();
+ commitSuppliedEvent(firstTsFile, commitManager);
+ // Committing a TsFile with a retained iterable and no tablets yet must
not release the gate.
+ Assert.assertNull(extractor.supply());
+ final PipeRawTabletInsertionEvent firstTablet =
+ createGeneratedTabletEvent(firstTsFile, pipeTaskMeta, "first",
false);
+ final PipeRawTabletInsertionEvent secondTablet =
+ createGeneratedTabletEvent(firstTsFile, pipeTaskMeta, "second");
+ firstTsFile.registerGeneratedTabletInsertionEvent(firstTablet);
+ firstTsFile.registerGeneratedTabletInsertionEvent(secondTablet);
+
Assert.assertTrue(firstTablet.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+
Assert.assertTrue(secondTablet.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ commitSuppliedEvent(firstTablet, commitManager);
+ Assert.assertNull(extractor.supply());
+ commitSuppliedEvent(secondTablet, commitManager);
+ Assert.assertNull(extractor.supply());
+ firstTsFile.markGeneratedTabletInsertionEventsParsingCompleted();
+
+ final Event secondTsFile = extractor.supply();
+ Assert.assertSame(secondEvent.getEvent(), secondTsFile);
+ Assert.assertNull(extractor.supply());
+ commitSuppliedEvent(secondTsFile, commitManager);
+ Assert.assertSame(progressEvent.getEvent(), extractor.supply());
+ releaseSuppliedEvent(progressEvent.getEvent());
+ Assert.assertNull(extractor.supply());
+ } finally {
+ commitManager.deregister(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1);
+ }
+ }
+
+ @Test
+ public void
testTsFileSourceRegionLevelDowngradingCompensatesForDiscardedEvents()
+ throws Exception {
+ final PipeEventCommitManager commitManager =
PipeEventCommitManager.getInstance();
+ commitManager.register(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1, "test");
+ final PipeTaskMeta pipeTaskMeta = new
PipeTaskMeta(MinimumProgressIndex.INSTANCE, 1);
+ try (final PipeRealtimeDataRegionTsFileSource extractor =
+ createTsFileSourceForTest(
+ PipeSourceConstant.SOURCE_REALTIME_REGION_LEVEL_DOWNGRADING_KEY,
pipeTaskMeta)) {
+ extractTsFileForTest(
+ extractor, pipeTaskMeta, createTsFileResource(dataRegion1,
"124-1-0-0.tsfile"), false);
+ final PipeRealtimeEvent secondEvent =
+ extractTsFileForTest(
+ extractor,
+ pipeTaskMeta,
+ createTsFileResource(dataRegion1, "124-2-0-0.tsfile"),
+ false);
+ final PipeRealtimeEvent thirdEvent =
+ extractTsFileForTest(
+ extractor,
+ pipeTaskMeta,
+ createTsFileResource(dataRegion1, "124-3-0-0.tsfile"),
+ false);
+
+ final PipeTsFileInsertionEvent firstTsFile = (PipeTsFileInsertionEvent)
extractor.supply();
+ final PipeRawTabletInsertionEvent firstTablet =
+ createGeneratedTabletEvent(firstTsFile, pipeTaskMeta,
"discarded-first", false);
+ final PipeRawTabletInsertionEvent secondTablet =
+ createGeneratedTabletEvent(firstTsFile, pipeTaskMeta,
"discarded-second");
+ firstTsFile.markGeneratedTabletInsertionEventsParsingStarted();
+ firstTsFile.registerGeneratedTabletInsertionEvent(firstTablet);
+ firstTsFile.registerGeneratedTabletInsertionEvent(secondTablet);
+ firstTsFile.markGeneratedTabletInsertionEventsParsingCompleted();
+
Assert.assertTrue(firstTablet.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+
Assert.assertTrue(secondTablet.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ commitSuppliedEvent(firstTsFile, commitManager);
+ firstTablet.clearReferenceCount(TEST_REFERENCE_HOLDER);
+ firstTablet.clearReferenceCount(TEST_REFERENCE_HOLDER);
+ Assert.assertNull(extractor.supply());
+ secondTablet.clearReferenceCount(TEST_REFERENCE_HOLDER);
+
+ final Event secondTsFile = extractor.supply();
+ Assert.assertSame(secondEvent.getEvent(), secondTsFile);
+ secondTablet.clearReferenceCount(TEST_REFERENCE_HOLDER);
+ Assert.assertNull(extractor.supply());
+ // Direct TsFile discard releases the gate once, without closing the
source.
+ releaseSuppliedEvent(secondTsFile);
+ Assert.assertSame(thirdEvent.getEvent(), extractor.supply());
+ releaseSuppliedEvent(thirdEvent.getEvent());
+ Assert.assertNull(extractor.supply());
+ } finally {
+ commitManager.deregister(TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME,
dataRegion1);
+ }
+ }
+
+ @Test
+ public void
testTsFileSourceRegionLevelDowngradingReleasesBufferedEventsOnClose()
+ throws Exception {
+ final PipeTaskMeta pipeTaskMeta = new
PipeTaskMeta(MinimumProgressIndex.INSTANCE, 1);
+ try (final PipeRealtimeDataRegionTsFileSource extractor =
+ createTsFileSourceForTest(
+ PipeSourceConstant.SOURCE_REALTIME_REGION_LEVEL_DOWNGRADING_KEY,
pipeTaskMeta)) {
+ final PipeRealtimeEvent firstEvent =
+ extractTsFileForTest(
+ extractor,
+ pipeTaskMeta,
+ createTsFileResource(dataRegion1, "125-1-0-0.tsfile"),
+ false);
+ final PipeRealtimeEvent bufferedFile =
+ extractTsFileForTest(
+ extractor,
+ pipeTaskMeta,
+ createTsFileResource(dataRegion1, "125-2-0-0.tsfile"),
+ false);
+ final PipeRealtimeEvent heartbeat =
+ PipeRealtimeEventFactory.createRealtimeEvent(dataRegion1, false);
+
Assert.assertTrue(heartbeat.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(heartbeat);
+ Assert.assertSame(firstEvent.getEvent(), extractor.supply());
+ final PipeRealtimeEvent pendingFile =
+ extractTsFileForTest(
+ extractor,
+ pipeTaskMeta,
+ createTsFileResource(dataRegion1, "125-3-0-0.tsfile"),
+ false);
+ extractor.close();
+ Assert.assertEquals(0, bufferedFile.getEvent().getReferenceCount());
+ Assert.assertEquals(0, pendingFile.getEvent().getReferenceCount());
+ Assert.assertEquals(0, heartbeat.getEvent().getReferenceCount());
+ releaseSuppliedEvent(firstEvent.getEvent());
+ final PipeRealtimeEvent lateFile =
+ extractTsFileForTest(
+ extractor,
+ pipeTaskMeta,
+ createTsFileResource(dataRegion1, "125-4-0-0.tsfile"),
+ false);
+ Assert.assertEquals(0, lateFile.getEvent().getReferenceCount());
+ Assert.assertNull(extractor.supply());
+ }
+ }
+
+ private PipeRealtimeDataRegionTsFileSource createTsFileSourceForTest(
+ final String regionLevelDowngradingKey, final PipeTaskMeta pipeTaskMeta)
throws Exception {
+ final HashMap<String, String> attributes = new HashMap<>();
+ attributes.put(PipeSourceConstant.EXTRACTOR_PATTERN_KEY, pattern1);
+ if (regionLevelDowngradingKey != null) {
+ attributes.put(regionLevelDowngradingKey, Boolean.TRUE.toString());
+ }
+ final PipeParameters parameters = new PipeParameters(attributes);
+ final PipeRealtimeDataRegionTsFileSource extractor = new
PipeRealtimeDataRegionTsFileSource();
+ extractor.validate(new PipeParameterValidator(parameters));
+ extractor.customize(
+ parameters,
+ new PipeTaskRuntimeConfiguration(
+ new PipeTaskSourceRuntimeEnvironment(
+ TEST_PIPE_NAME, TEST_PIPE_CREATION_TIME, dataRegion1,
pipeTaskMeta)));
+ return extractor;
+ }
+
+ private PipeRealtimeEvent extractTsFileForTest(
+ final PipeRealtimeDataRegionSource extractor,
+ final PipeTaskMeta pipeTaskMeta,
+ final TsFileResource resource,
+ final boolean isLoaded) {
+ final PipeRealtimeEvent event =
+ bindToTestPipe(
+ PipeRealtimeEventFactory.createRealtimeEvent(false, "root.sg",
resource, isLoaded),
+ extractor,
+ pipeTaskMeta);
+ Assert.assertTrue(event.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+ extractor.extract(event);
+ return event;
+ }
+
private Future<?> write2DataRegion(
final int writeNum, final int dataRegionId, final int startNum) {
final File dataRegionDir =
@@ -469,8 +1642,15 @@ public class PipeRealtimeExtractTest {
private TsFileResource createTsFileResource(final int dataRegionId, final
String fileName)
throws IOException {
+ return createTsFileResource(dataRegionId, "0", fileName);
+ }
+
+ private TsFileResource createTsFileResource(
+ final int dataRegionId, final String timePartition, final String
fileName)
+ throws IOException {
final File dataRegionDir =
- new File(tsFileDir.getPath() + File.separator + dataRegionId +
File.separator + "0");
+ new File(
+ tsFileDir.getPath() + File.separator + dataRegionId +
File.separator + timePartition);
Assert.assertTrue(dataRegionDir.mkdirs() || dataRegionDir.isDirectory());
final File tsFile = new File(dataRegionDir, fileName);
@@ -577,6 +1757,101 @@ public class PipeRealtimeExtractTest {
}
}
+ private PipeRealtimeEvent bindToTestPipe(
+ final PipeRealtimeEvent event,
+ final PipeRealtimeDataRegionSource extractor,
+ final PipeTaskMeta pipeTaskMeta) {
+ return event.shallowCopySelfAndBindPipeTaskMetaForProgressReport(
+ TEST_PIPE_NAME,
+ TEST_PIPE_CREATION_TIME,
+ pipeTaskMeta,
+ extractor.getTreePattern(),
+ extractor.getTablePattern(),
+ String.valueOf(extractor.getUserId()),
+ extractor.getUserName(),
+ extractor.getCliHostname(),
+ extractor.isSkipIfNoPrivileges(),
+ extractor.getRealtimeDataExtractionStartTime(),
+ extractor.getRealtimeDataExtractionEndTime());
+ }
+
+ private void commitSuppliedEvent(final Event event, final
PipeEventCommitManager commitManager) {
+ final EnrichedEvent enrichedEvent = (EnrichedEvent) event;
+ commitManager.enrichWithCommitterKeyAndCommitId(
+ enrichedEvent, TEST_PIPE_CREATION_TIME, dataRegion1);
+
Assert.assertTrue(enrichedEvent.decreaseReferenceCount(TEST_REFERENCE_HOLDER,
true));
+ }
+
+ private void commitLastGeneratedTabletEvent(
+ final PipeTsFileInsertionEvent tsFileEvent,
+ final PipeEventCommitManager commitManager,
+ final PipeTaskMeta pipeTaskMeta) {
+ tsFileEvent.registerGeneratedTabletInsertionEvent();
+ tsFileEvent.markGeneratedTabletInsertionEventsParsingCompleted();
+ final PipeRawTabletInsertionEvent generatedTabletEvent =
+ createGeneratedTabletEvent(tsFileEvent, pipeTaskMeta, "generated");
+
+
Assert.assertTrue(generatedTabletEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER));
+
Assert.assertTrue(tsFileEvent.decreaseReferenceCount(TEST_REFERENCE_HOLDER,
false));
+ commitSuppliedEvent(generatedTabletEvent, commitManager);
+ }
+
+ private PipeRawTabletInsertionEvent createGeneratedTabletEvent(
+ final PipeTsFileInsertionEvent tsFileEvent,
+ final PipeTaskMeta pipeTaskMeta,
+ final String deviceId) {
+ return createGeneratedTabletEvent(tsFileEvent, pipeTaskMeta, deviceId,
true);
+ }
+
+ private PipeRawTabletInsertionEvent createGeneratedTabletEvent(
+ final PipeTsFileInsertionEvent tsFileEvent,
+ final PipeTaskMeta pipeTaskMeta,
+ final String deviceId,
+ final boolean needToReport) {
+ final Tablet tablet =
+ new Tablet(
+ "root.sg.d." + deviceId,
+ Collections.singletonList(new MeasurementSchema("s",
TSDataType.INT32)),
+ 1);
+ return new PipeRawTabletInsertionEvent(
+ false,
+ "root.sg",
+ null,
+ null,
+ tablet,
+ false,
+ TEST_PIPE_NAME,
+ TEST_PIPE_CREATION_TIME,
+ pipeTaskMeta,
+ tsFileEvent,
+ needToReport);
+ }
+
+ private int getActiveTsFileEpochCount(final
PipeRealtimeDataRegionHybridSource extractor)
+ throws Exception {
+ final Field activeTsFileEpochsField =
+
PipeRealtimeDataRegionHybridSource.class.getDeclaredField("activeTsFileEpochs");
+ activeTsFileEpochsField.setAccessible(true);
+ return ((Set<?>) activeTsFileEpochsField.get(extractor)).size();
+ }
+
+ private int getInFlightTsFileCount(final PipeRealtimeDataRegionHybridSource
extractor)
+ throws Exception {
+ final Field inFlightTsFileCountField =
+
PipeRealtimeDataRegionHybridSource.class.getDeclaredField("inFlightTsFileCount");
+ inFlightTsFileCountField.setAccessible(true);
+ return inFlightTsFileCountField.getInt(extractor);
+ }
+
+ private boolean isRegionLevelDowngradingEnabled(
+ final PipeRealtimeDataRegionHybridSource extractor) throws Exception {
+ final Field isRegionLevelDowngradingEnabledField =
+ PipeRealtimeDataRegionHybridSource.class.getDeclaredField(
+ "isRegionLevelDowngradingEnabled");
+ isRegionLevelDowngradingEnabledField.setAccessible(true);
+ return isRegionLevelDowngradingEnabledField.getBoolean(extractor);
+ }
+
private PipeRealtimeEvent createProgressReportRealtimeEvent() {
final ProgressReportEvent progressReportEvent = new
ProgressReportEvent(null, 0, null);
progressReportEvent.bindProgressIndex(MinimumProgressIndex.INSTANCE);
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 055553c7e1a..70d452ae36a 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
@@ -142,6 +142,11 @@ public class PipeSourceConstant {
public static final String EXTRACTOR_REALTIME_LOOSE_RANGE_PATH_VALUE =
"path";
public static final String EXTRACTOR_REALTIME_LOOSE_RANGE_ALL_VALUE = "all";
public static final String EXTRACTOR_REALTIME_LOOSE_RANGE_DEFAULT_VALUE = "";
+ public static final String EXTRACTOR_REALTIME_REGION_LEVEL_DOWNGRADING_KEY =
+ "extractor.realtime.region-level-downgrading";
+ public static final String SOURCE_REALTIME_REGION_LEVEL_DOWNGRADING_KEY =
+ "source.realtime.region-level-downgrading";
+ public static final boolean
EXTRACTOR_REALTIME_REGION_LEVEL_DOWNGRADING_DEFAULT_VALUE = false;
public static final String EXTRACTOR_MODE_STREAMING_KEY =
"extractor.mode.streaming";
public static final String SOURCE_MODE_STREAMING_KEY =
"source.mode.streaming";