diff --git a/docs/reference-metrics.md b/docs/reference-metrics.md
index 8a83a0b93e..b19cfd6188 100644
--- a/docs/reference-metrics.md
+++ b/docs/reference-metrics.md
@@ -72,6 +72,7 @@ The KoP metrics are exposed under "/metrics" at port `8000` along with Pulsar me
| kop_server_MESSAGE_OUT | Counter | The consumer message out stats. Available labels: *topic*, *partition*, *group*.
*topic*: the topic name to consume.
*partition*: the partition id for the topic to consume
*group*: the group id for consumer to consumer message from topic-partition
|
| kop_server_ENTRIES_OUT | Counter | The consumer entries out stats. Available labels: *topic*, *partition*, *group*.
*topic*: the topic name to consume.
*partition*: the partition id for the topic to consume
*group*: the group id for consumer to consumer message from topic-partition
|
| kop_server_CONSUME_MESSAGE_CONVERSIONS | Counter | The consumer message conversions in stats. Available labels: *topic*, *partition*.
*topic*: the topic name to consume.
*partition*: the partition id for the topic to consume
|
+| kop_server_WAITING_FETCHES_TRIGGERED | Counter | Number of fetches that have been delayed due to not enough data, and that have been unblocked because some message has been produced|
### Kop event metrics
diff --git a/kafka-impl/src/main/java/io/streamnative/pulsar/handlers/kop/DelayedFetch.java b/kafka-impl/src/main/java/io/streamnative/pulsar/handlers/kop/DelayedFetch.java
index 105c2644a0..811b36de2b 100644
--- a/kafka-impl/src/main/java/io/streamnative/pulsar/handlers/kop/DelayedFetch.java
+++ b/kafka-impl/src/main/java/io/streamnative/pulsar/handlers/kop/DelayedFetch.java
@@ -15,36 +15,67 @@
import io.streamnative.pulsar.handlers.kop.utils.delayed.DelayedOperation;
import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
+import lombok.extern.slf4j.Slf4j;
+@Slf4j
public class DelayedFetch extends DelayedOperation {
private final Runnable callback;
private final AtomicLong bytesReadable;
private final int minBytes;
+ private final MessageFetchContext messageFetchContext;
+ private final AtomicBoolean restarted = new AtomicBoolean();
+ private final AtomicBoolean someMessageProduced = new AtomicBoolean();
- protected DelayedFetch(long delayMs, AtomicLong bytesReadable, int minBytes, Runnable callback) {
+ protected DelayedFetch(long delayMs, AtomicLong bytesReadable, int minBytes,
+ MessageFetchContext messageFetchContext) {
super(delayMs, Optional.empty());
- this.callback = callback;
this.bytesReadable = bytesReadable;
this.minBytes = minBytes;
+ this.messageFetchContext = messageFetchContext;
+ this.callback = messageFetchContext::complete;
}
@Override
public void onExpiration() {
+ if (restarted.get()) {
+ return;
+ }
callback.run();
}
@Override
public void onComplete() {
+ if (restarted.get()) {
+ return;
+ }
callback.run();
}
@Override
public boolean tryComplete() {
+ if (someMessageProduced.get()) {
+ // if we are here then we were waiting for the condition
+ // someone wrote some messages to one of the topics
+ // trigger the Fetch from scratch
+ restarted.set(true);
+ messageFetchContext.onDataWrittenToSomePartition();
+ return true;
+ }
if (bytesReadable.get() < minBytes){
return false;
}
callback.run();
return true;
}
+
+ @Override
+ public boolean wakeup() {
+ // In the future we could notify the MessageFetchContext that the
+ // new data is only on this partition and not
+ // on other partitions
+ someMessageProduced.set(true);
+ return true;
+ }
}
diff --git a/kafka-impl/src/main/java/io/streamnative/pulsar/handlers/kop/KafkaRequestHandler.java b/kafka-impl/src/main/java/io/streamnative/pulsar/handlers/kop/KafkaRequestHandler.java
index ed51012167..4b46613d50 100644
--- a/kafka-impl/src/main/java/io/streamnative/pulsar/handlers/kop/KafkaRequestHandler.java
+++ b/kafka-impl/src/main/java/io/streamnative/pulsar/handlers/kop/KafkaRequestHandler.java
@@ -56,6 +56,7 @@
import io.streamnative.pulsar.handlers.kop.utils.OffsetFinder;
import io.streamnative.pulsar.handlers.kop.utils.TopicNameUtils;
import io.streamnative.pulsar.handlers.kop.utils.delayed.DelayedOperation;
+import io.streamnative.pulsar.handlers.kop.utils.delayed.DelayedOperationKey;
import io.streamnative.pulsar.handlers.kop.utils.delayed.DelayedOperationPurgatory;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
@@ -941,6 +942,11 @@ protected void handleProduceRequest(KafkaHeaderAndRequest produceHar,
mergedResponse.putAll(unauthorizedTopicResponsesMap);
mergedResponse.putAll(invalidRequestResponses);
resultFuture.complete(new ProduceResponse(mergedResponse));
+ mergedResponse.forEach((_topicPartition, _response) -> {
+ if (_response.error == Errors.NONE) {
+ notifyPendingFetches(_topicPartition);
+ }
+ });
});
}
};
@@ -979,6 +985,20 @@ protected void handleProduceRequest(KafkaHeaderAndRequest produceHar,
}
+ private void notifyPendingFetches(TopicPartition topicPartition) {
+ ctx.executor().execute(() -> {
+ DelayedOperationKey.TopicPartitionOperationKey key =
+ new DelayedOperationKey.TopicPartitionOperationKey(topicPartition);
+ int matches = fetchPurgatory.checkAndComplete(key);
+ if (matches > 0) {
+ requestStats.getWaitingFetchesTriggered().add(matches);
+ if (log.isDebugEnabled()) {
+ log.debug("{} DelayedFetch woke up for {}", matches, topicPartition);
+ }
+ }
+ });
+ }
+
private void validateRecords(short version, MemoryRecords records) {
if (version >= 3) {
Iterator iterator = records.batches().iterator();
diff --git a/kafka-impl/src/main/java/io/streamnative/pulsar/handlers/kop/KopServerStats.java b/kafka-impl/src/main/java/io/streamnative/pulsar/handlers/kop/KopServerStats.java
index 65c3cea99f..8e8324d1d6 100644
--- a/kafka-impl/src/main/java/io/streamnative/pulsar/handlers/kop/KopServerStats.java
+++ b/kafka-impl/src/main/java/io/streamnative/pulsar/handlers/kop/KopServerStats.java
@@ -75,6 +75,7 @@ public interface KopServerStats {
String PREPARE_METADATA = "PREPARE_METADATA";
String MESSAGE_READ = "MESSAGE_READ";
String FETCH_DECODE = "FETCH_DECODE";
+ String WAITING_FETCHES_TRIGGERED = "WAITING_FETCHES_TRIGGERED";
/**
* Consumer stats.
diff --git a/kafka-impl/src/main/java/io/streamnative/pulsar/handlers/kop/MessageFetchContext.java b/kafka-impl/src/main/java/io/streamnative/pulsar/handlers/kop/MessageFetchContext.java
index a79b836d42..d1f974c6a3 100644
--- a/kafka-impl/src/main/java/io/streamnative/pulsar/handlers/kop/MessageFetchContext.java
+++ b/kafka-impl/src/main/java/io/streamnative/pulsar/handlers/kop/MessageFetchContext.java
@@ -68,6 +68,7 @@
import org.apache.kafka.common.requests.IsolationLevel;
import org.apache.kafka.common.requests.RequestHeader;
import org.apache.kafka.common.requests.ResponseCallbackWrapper;
+import org.apache.kafka.common.utils.SystemTime;
import org.apache.pulsar.metadata.api.GetResult;
/**
@@ -83,6 +84,7 @@ protected MessageFetchContext newObject(Handle handle) {
};
private final Handle recyclerHandle;
+ private long startTime;
private Map> responseData;
private ConcurrentLinkedQueue decodeResults;
private KafkaRequestHandler requestHandler;
@@ -95,7 +97,7 @@ protected MessageFetchContext newObject(Handle handle) {
private RequestHeader header;
private volatile CompletableFuture resultFuture;
private AtomicBoolean hasComplete;
- private AtomicLong bytesReadable;
+ private AtomicLong bytesRead;
private DelayedOperationPurgatory fetchPurgatory;
private String namespacePrefix;
@@ -119,8 +121,9 @@ public static MessageFetchContext get(KafkaRequestHandler requestHandler,
context.header = kafkaHeaderAndRequest.getHeader();
context.resultFuture = resultFuture;
context.hasComplete = new AtomicBoolean(false);
- context.bytesReadable = new AtomicLong(0);
+ context.bytesRead = new AtomicLong(0);
context.fetchPurgatory = fetchPurgatory;
+ context.startTime = SystemTime.SYSTEM.hiResClockMs();
return context;
}
@@ -142,6 +145,7 @@ public static MessageFetchContext getForTest(FetchRequest fetchRequest,
context.header = null;
context.resultFuture = resultFuture;
context.hasComplete = new AtomicBoolean(false);
+ context.startTime = SystemTime.SYSTEM.hiResClockMs();
return context;
}
@@ -163,7 +167,7 @@ private void recycle() {
header = null;
resultFuture = null;
hasComplete = null;
- bytesReadable = null;
+ bytesRead = null;
fetchPurgatory = null;
namespacePrefix = null;
recyclerHandle.recycle(this);
@@ -195,15 +199,47 @@ private void addErrorPartitionResponse(TopicPartition topicPartition, Errors err
private void tryComplete() {
if (resultFuture != null && responseData.size() >= fetchRequest.fetchData().size()
&& hasComplete.compareAndSet(false, true)) {
- DelayedFetch delayedFetch = new DelayedFetch(fetchRequest.maxWait(), bytesReadable,
- fetchRequest.minBytes(), this::complete);
- List