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 8f1bed8246c [Subscription] Consolidate poll queue fixes (#18771)
8f1bed8246c is described below
commit 8f1bed8246cc03426435839b2c829a9759deed74
Author: Caideyipi <[email protected]>
AuthorDate: Wed Sep 30 10:37:24 2026 +0800
[Subscription] Consolidate poll queue fixes (#18771)
---
.../db/subscription/broker/SubscriptionBroker.java | 7 +-
.../broker/SubscriptionPrefetchingQueue.java | 92 +++++++++++++++++-----
.../broker/SubscriptionPrefetchingQueueStates.java | 28 +++++--
3 files changed, 98 insertions(+), 29 deletions(-)
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/SubscriptionBroker.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/SubscriptionBroker.java
index 3b1dec89dd1..3cfb34014d7 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/SubscriptionBroker.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/SubscriptionBroker.java
@@ -129,10 +129,13 @@ public class SubscriptionBroker implements
ISubscriptionBroker {
// Iterate over each sorted topic name and poll the corresponding events
int remainingTopicSize = sortedTopicNames.size();
for (final String topicName : sortedTopicNames) {
- remainingTopicSize -= 1;
// Check pipe-based queue
final SubscriptionPrefetchingQueue prefetchingQueue =
topicNameToPrefetchingQueue.get(topicName);
+ // Include the current topic when splitting the remaining receiver
deadline. Otherwise, the
+ // first topic receives too large a share and later topics are polled
after it has mostly
+ // elapsed.
+ final int currentTopicSize = remainingTopicSize--;
// Recheck
if (Objects.isNull(prefetchingQueue) || prefetchingQueue.isClosed()) {
@@ -146,7 +149,7 @@ public class SubscriptionBroker implements
ISubscriptionBroker {
final PollTimer timer =
new PollTimer(
System.currentTimeMillis(),
- SubscriptionAgent.receiver().remainingMs() / Math.max(1,
remainingTopicSize));
+ SubscriptionAgent.receiver().remainingMs() / Math.max(1,
currentTopicSize));
event = prefetchingQueue.pollV2(consumerId, timer);
} else {
// TODO: migrate poll to pollV2
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/SubscriptionPrefetchingQueue.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/SubscriptionPrefetchingQueue.java
index b760ee62f6f..716e7d90f15 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/SubscriptionPrefetchingQueue.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/SubscriptionPrefetchingQueue.java
@@ -265,31 +265,35 @@ public abstract class SubscriptionPrefetchingQueue {
}
public SubscriptionEvent pollV2(final String consumerId, final PollTimer
timer) {
- acquireReadLock();
- try {
- return isClosed() ? null : pollInternalV2(consumerId, timer);
- } finally {
- releaseReadLock();
- }
- }
-
- private SubscriptionEvent pollInternalV2(final String consumerId, final
PollTimer timer) {
states.markPollRequest();
- // do-while ensures at least one poll
- do {
- SubscriptionEvent event;
+ boolean firstPoll = true;
+ while (firstPoll || timer.notExpired()) {
+ firstPoll = false;
+
+ // Keep the read lock only for the queue and in-flight event transition.
Waiting for more
+ // data happens below, outside the lock, so cleanup can acquire the
write lock promptly.
+ acquireReadLock();
try {
- if (prefetchingQueue.isEmpty()) {
- // TODO: concurrent polling of multiple prefetching queues
- Thread.sleep(100);
- onEvent();
+ if (isClosed()) {
+ return null;
}
- event = pollPrefetchedEvent(consumerId);
+ final SubscriptionEvent event = pollInternalV2(consumerId);
if (Objects.nonNull(event)) {
return event;
}
+ } finally {
+ releaseReadLock();
+ }
+
+ timer.update();
+ if (timer.isExpired()) {
+ break;
+ }
+
+ try {
+ Thread.sleep(Math.min(100L, timer.remainingMs()));
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
LOGGER.warn(
@@ -297,9 +301,52 @@ public abstract class SubscriptionPrefetchingQueue {
.PIPE_LOG_SUBSCRIPTION_SUBSCRIPTIONPREFETCHINGQUEUE_INTERRUPTED_WHILE_F8923826,
this,
e);
+ break;
}
timer.update();
- } while (!timer.isExpired());
+ }
+
+ return null;
+ }
+
+ private SubscriptionEvent pollInternalV2(final String consumerId) {
+ if (prefetchingQueue.isEmpty()) {
+ onEvent();
+ }
+
+ final long size = prefetchingQueue.size();
+ long count = 0;
+
+ SubscriptionEvent event;
+ while (count++ < size && Objects.nonNull(event = prefetchingQueue.poll()))
{
+ if (event.isCommitted()) {
+ LOGGER.warn(
+ DataNodePipeMessages
+
.PIPE_LOG_SUBSCRIPTION_SUBSCRIPTIONPREFETCHINGQUEUE_POLL_COMMITTED_8684FF17,
+ this,
+ event);
+ // no need to update inFlightEvents
+ continue;
+ }
+
+ if (!event.pollable()) {
+ LOGGER.warn(
+ DataNodePipeMessages
+
.PIPE_LOG_SUBSCRIPTION_SUBSCRIPTIONPREFETCHINGQUEUE_POLL_NON_POLLABLE_644D5D6B,
+ this,
+ event);
+ nackAndRequeue(event);
+ continue;
+ }
+
+ // This operation should be performed before updating inFlightEvents to
prevent multiple
+ // consumers from consuming the same event.
+ event.recordLastPolledTimestamp(); // now non-pollable
+
+ inFlightEvents.put(new Pair<>(consumerId, event.getCommitContext()),
event);
+ event.recordLastPolledConsumerId(consumerId);
+ return event;
+ }
return null;
}
@@ -332,8 +379,7 @@ public abstract class SubscriptionPrefetchingQueue {
.PIPE_LOG_SUBSCRIPTION_SUBSCRIPTIONPREFETCHINGQUEUE_POLL_NON_POLLABLE_644D5D6B,
this,
event);
- event.nack(); // now pollable
- // no need to update inFlightEvents and prefetchingQueue
+ nackAndRequeue(event);
continue;
}
@@ -349,6 +395,12 @@ public abstract class SubscriptionPrefetchingQueue {
return null;
}
+ private void nackAndRequeue(final SubscriptionEvent event) {
+ event.nack(); // now pollable
+ // The event has already been removed from this queue, so put it back
after resetting its state.
+ prefetchingQueue.offer(event);
+ }
+
/////////////////////////////// prefetch ///////////////////////////////
public boolean executePrefetch() {
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/SubscriptionPrefetchingQueueStates.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/SubscriptionPrefetchingQueueStates.java
index 70eacd2eaaa..a9167b7b475 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/SubscriptionPrefetchingQueueStates.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/SubscriptionPrefetchingQueueStates.java
@@ -25,11 +25,12 @@ import
org.apache.iotdb.db.subscription.agent.SubscriptionAgent;
import org.apache.iotdb.metrics.core.utils.IoTDBMovingAverage;
import com.codahale.metrics.Clock;
-import com.codahale.metrics.Counter;
import com.codahale.metrics.Meter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.concurrent.atomic.AtomicLong;
+
import static com.google.common.base.MoreObjects.toStringHelper;
/**
@@ -58,7 +59,7 @@ public class SubscriptionPrefetchingQueueStates {
private volatile long lastPollRequestTimestamp;
private final Meter pollRequestMeter;
private final Meter missingPrefechMeter;
- private final Counter disorderCauseCounter; // TODO: use meter
+ private final AtomicLong disorderCauseCounter;
public SubscriptionPrefetchingQueueStates(final SubscriptionPrefetchingQueue
prefetchingQueue) {
this.prefetchingQueue = prefetchingQueue;
@@ -66,7 +67,7 @@ public class SubscriptionPrefetchingQueueStates {
this.lastPollRequestTimestamp = -1;
this.pollRequestMeter = new Meter(new IoTDBMovingAverage(),
Clock.defaultClock());
this.missingPrefechMeter = new Meter(new IoTDBMovingAverage(),
Clock.defaultClock());
- this.disorderCauseCounter = new Counter();
+ this.disorderCauseCounter = new AtomicLong();
}
public void markPollRequest() {
@@ -79,7 +80,9 @@ public class SubscriptionPrefetchingQueueStates {
}
public void markDisorderCause() {
- disorderCauseCounter.inc();
+ // Disable prefetching for the current sequence. The guard is cleared
after the prefetched and
+ // in-flight events have drained so a single disorder does not permanently
disable prefetching.
+ disorderCauseCounter.incrementAndGet();
}
private double pollRate() {
@@ -117,7 +120,14 @@ public class SubscriptionPrefetchingQueueStates {
// 1.5. disorder history
if (hasDisorderCause()) {
- return false;
+ // Keep prefetching disabled while events from the disordered sequence
are still visible to
+ // consumers. Once both queues are drained, the next sequence starts
from a clean ordering
+ // boundary and prefetching can recover.
+ if (prefetchingQueue.getPrefetchedEventCount() > 0
+ || prefetchingQueue.getSubscriptionUncommittedEventCount() > 0) {
+ return false;
+ }
+ clearDisorderCause();
}
// 2. permissive conditions
@@ -155,7 +165,11 @@ public class SubscriptionPrefetchingQueueStates {
}
private boolean hasDisorderCause() {
- return disorderCauseCounter.getCount() > 0;
+ return disorderCauseCounter.get() > 0;
+ }
+
+ private void clearDisorderCause() {
+ disorderCauseCounter.set(0);
}
private static boolean isApproximatelyZero(final double value) {
@@ -168,7 +182,7 @@ public class SubscriptionPrefetchingQueueStates {
.add("lastPollRequestTimestamp", lastPollRequestTimestamp)
.add("pollRate", pollRate())
.add("missingRate", missingRate())
- .add("disorderCause", disorderCauseCounter.getCount())
+ .add("disorderCause", disorderCauseCounter.get())
.toString();
}
}