This is an automated email from the ASF dual-hosted git repository.

xiangfu0 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new 4ff9dbe8389 Fix Kafka metadata for sparse partition recovery (#18687)
4ff9dbe8389 is described below

commit 4ff9dbe8389c1559a0c04c0db0049917bfeedfdd
Author: Xiang Fu <[email protected]>
AuthorDate: Mon Jun 8 20:01:25 2026 -0700

    Fix Kafka metadata for sparse partition recovery (#18687)
---
 .../realtime/PinotLLCRealtimeSegmentManager.java   |  10 +-
 .../fakestream/FakeStreamMetadataProvider.java     |  27 +++
 .../kafka30/KafkaStreamMetadataProvider.java       |  32 ++--
 .../kafka30/KafkaPartitionLevelConsumerTest.java   |  36 ++++
 .../kafka30/KafkaStreamMetadataProviderTest.java   | 188 +++++++++++++++++++++
 .../kafka40/KafkaStreamMetadataProvider.java       |  32 ++--
 .../kafka40/KafkaPartitionLevelConsumerTest.java   |  36 ++++
 .../kafka40/KafkaStreamMetadataProviderTest.java   | 188 +++++++++++++++++++++
 .../pinot/spi/stream/StreamMetadataProvider.java   |  64 ++-----
 .../stream/PartitionGroupMetadataFetcherTest.java  |  99 -----------
 10 files changed, 531 insertions(+), 181 deletions(-)

diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManager.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManager.java
index 46982aac0fb..0e27d156529 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManager.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManager.java
@@ -2022,13 +2022,9 @@ public class PinotLLCRealtimeSegmentManager implements 
PinotClusterConfigChangeL
       streamConfig.setOffsetCriteria(OffsetCriteria.SMALLEST_OFFSET_CRITERIA);
 
       // Kinesis shard-split flow requires us to pass 
currentPartitionGroupConsumptionStatusList so that
-      // we can check if its completely consumed
-      // However the kafka implementation of computePartitionGroupMetadata() 
breaks if we pass the current status
-      // This leads to streamSmallestOffset set to null in selectStartOffset() 
method
-      // The overall dependency isn't clean and is causing the issue and 
requires refactor
-      // Temporarily, we are passing a boolean flag to indicate if we want to 
use the current status
-      // The kafka implementation of computePartitionGroupMetadata() will 
ignore the current status
-      // while the kinesis implementation will use it.
+      // we can check if it is completely consumed. Kafka needs stream offsets 
for every partition in this path, so
+      // forceGetOffsetFromStream lets its metadata provider fetch offsets 
from the stream instead of reusing
+      // current status offsets.
       List<StreamMetadata> streamMetadataList = getNewStreamMetadataList(
           streamConfigs, currentPartitionGroupConsumptionStatusList, 
idealState, true);
       streamConfig.setOffsetCriteria(originalOffsetCriteria);
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamMetadataProvider.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamMetadataProvider.java
index 99e3d90749d..be662b53805 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamMetadataProvider.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamMetadataProvider.java
@@ -19,13 +19,17 @@
 package org.apache.pinot.core.realtime.impl.fakestream;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import org.apache.pinot.spi.stream.LongMsgOffset;
 import org.apache.pinot.spi.stream.OffsetCriteria;
+import org.apache.pinot.spi.stream.PartitionGroupConsumptionStatus;
+import org.apache.pinot.spi.stream.PartitionGroupMetadata;
 import org.apache.pinot.spi.stream.StreamConfig;
 import org.apache.pinot.spi.stream.StreamMetadataProvider;
 import org.apache.pinot.spi.stream.StreamPartitionMsgOffset;
@@ -60,6 +64,29 @@ public class FakeStreamMetadataProvider implements 
StreamMetadataProvider {
     }
   }
 
+  @Override
+  public List<PartitionGroupMetadata> computePartitionGroupMetadata(String 
clientId, StreamConfig streamConfig,
+      List<PartitionGroupConsumptionStatus> partitionGroupConsumptionStatuses, 
int timeoutMillis) {
+    Map<Integer, StreamPartitionMsgOffset> partitionIdToEndOffset =
+        new HashMap<>(partitionGroupConsumptionStatuses.size());
+    for (PartitionGroupConsumptionStatus partitionGroupConsumptionStatus : 
partitionGroupConsumptionStatuses) {
+      
partitionIdToEndOffset.put(partitionGroupConsumptionStatus.getStreamPartitionGroupId(),
+          partitionGroupConsumptionStatus.getEndOffset());
+    }
+
+    List<PartitionGroupMetadata> partitionGroupMetadataList = new 
ArrayList<>(_numPartitions);
+    for (int partitionId = 0; partitionId < _numPartitions; partitionId++) {
+      if (partitionIdToEndOffset.containsKey(partitionId)) {
+        partitionGroupMetadataList.add(
+            new PartitionGroupMetadata(partitionId, 
partitionIdToEndOffset.get(partitionId)));
+      } else {
+        partitionGroupMetadataList.add(new PartitionGroupMetadata(partitionId,
+            fetchStreamPartitionOffset(streamConfig.getOffsetCriteria(), 
timeoutMillis)));
+      }
+    }
+    return partitionGroupMetadataList;
+  }
+
   @Override
   public boolean supportsOffsetLag() {
     return true;
diff --git 
a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-3.0/src/main/java/org/apache/pinot/plugin/stream/kafka30/KafkaStreamMetadataProvider.java
 
b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-3.0/src/main/java/org/apache/pinot/plugin/stream/kafka30/KafkaStreamMetadataProvider.java
index ebc0a044ccd..77d76c1a434 100644
--- 
a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-3.0/src/main/java/org/apache/pinot/plugin/stream/kafka30/KafkaStreamMetadataProvider.java
+++ 
b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-3.0/src/main/java/org/apache/pinot/plugin/stream/kafka30/KafkaStreamMetadataProvider.java
@@ -123,31 +123,37 @@ public class KafkaStreamMetadataProvider extends 
KafkaPartitionLevelConnectionHa
   public List<PartitionGroupMetadata> computePartitionGroupMetadata(String 
clientId, StreamConfig streamConfig,
       List<PartitionGroupConsumptionStatus> partitionGroupConsumptionStatuses, 
int timeoutMillis)
       throws IOException, java.util.concurrent.TimeoutException {
-    if (_partitionIdSubset.isEmpty()) {
-      return 
StreamMetadataProvider.super.computePartitionGroupMetadata(clientId, 
streamConfig,
-          partitionGroupConsumptionStatuses, timeoutMillis);
-    }
     Map<Integer, StreamPartitionMsgOffset> partitionIdToEndOffset =
         new HashMap<>(partitionGroupConsumptionStatuses.size());
     for (PartitionGroupConsumptionStatus s : 
partitionGroupConsumptionStatuses) {
       partitionIdToEndOffset.put(s.getStreamPartitionGroupId(), 
s.getEndOffset());
     }
+
+    List<Integer> partitionIds;
+    if (_partitionIdSubset.isEmpty()) {
+      int partitionCount = fetchPartitionCount(timeoutMillis);
+      partitionIds = new ArrayList<>(partitionCount);
+      for (int partitionId = 0; partitionId < partitionCount; partitionId++) {
+        partitionIds.add(partitionId);
+      }
+    } else {
+      partitionIds = _partitionIdSubset;
+    }
+
     StreamConsumerFactory streamConsumerFactory = 
StreamConsumerFactoryProvider.create(streamConfig);
-    List<PartitionGroupMetadata> result = new 
ArrayList<>(_partitionIdSubset.size());
-    for (Integer partitionId : _partitionIdSubset) {
-      StreamPartitionMsgOffset endOffset = 
partitionIdToEndOffset.get(partitionId);
-      StreamPartitionMsgOffset startOffset;
-      if (endOffset == null) {
+    List<PartitionGroupMetadata> result = new ArrayList<>(partitionIds.size());
+    for (Integer partitionId : partitionIds) {
+      if (partitionIdToEndOffset.containsKey(partitionId)) {
+        result.add(new PartitionGroupMetadata(partitionId, 
partitionIdToEndOffset.get(partitionId)));
+      } else {
         try (StreamMetadataProvider partitionMetadataProvider =
             streamConsumerFactory.createPartitionMetadataProvider(
                 StreamConsumerFactory.getUniqueClientId(clientId), 
partitionId)) {
-          startOffset = partitionMetadataProvider.fetchStreamPartitionOffset(
+          StreamPartitionMsgOffset startOffset = 
partitionMetadataProvider.fetchStreamPartitionOffset(
               streamConfig.getOffsetCriteria(), timeoutMillis);
+          result.add(new PartitionGroupMetadata(partitionId, startOffset));
         }
-      } else {
-        startOffset = endOffset;
       }
-      result.add(new PartitionGroupMetadata(partitionId, startOffset));
     }
     return result;
   }
diff --git 
a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-3.0/src/test/java/org/apache/pinot/plugin/stream/kafka30/KafkaPartitionLevelConsumerTest.java
 
b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-3.0/src/test/java/org/apache/pinot/plugin/stream/kafka30/KafkaPartitionLevelConsumerTest.java
index 96afe08e070..db5ee9bcfd5 100644
--- 
a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-3.0/src/test/java/org/apache/pinot/plugin/stream/kafka30/KafkaPartitionLevelConsumerTest.java
+++ 
b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-3.0/src/test/java/org/apache/pinot/plugin/stream/kafka30/KafkaPartitionLevelConsumerTest.java
@@ -49,6 +49,7 @@ import org.apache.pinot.spi.stream.MessageBatch;
 import org.apache.pinot.spi.stream.OffsetCriteria;
 import org.apache.pinot.spi.stream.PartitionGroupConsumer;
 import org.apache.pinot.spi.stream.PartitionGroupConsumptionStatus;
+import org.apache.pinot.spi.stream.PartitionGroupMetadata;
 import org.apache.pinot.spi.stream.StreamConfig;
 import org.apache.pinot.spi.stream.StreamConsumerFactory;
 import org.apache.pinot.spi.stream.StreamConsumerFactoryProvider;
@@ -282,6 +283,41 @@ public class KafkaPartitionLevelConsumerTest {
     }
   }
 
+  @Test
+  public void testComputePartitionGroupMetadataUsesKafkaPartitionIds()
+      throws Exception {
+    String streamType = "kafka";
+    String streamKafkaBrokerList = _kafkaBrokerAddress;
+    String clientId = "partition-id-client";
+    String tableNameWithType = "tableName_REALTIME";
+
+    Map<String, String> streamConfigMap = new HashMap<>();
+    streamConfigMap.put("streamType", streamType);
+    streamConfigMap.put("stream.kafka.topic.name", 
TEST_TOPIC_SUBSET_PARTITION);
+    streamConfigMap.put("stream.kafka.broker.list", streamKafkaBrokerList);
+    streamConfigMap.put("stream.kafka.consumer.factory.class.name", 
getKafkaConsumerFactoryName());
+    streamConfigMap.put("stream.kafka.decoder.class.name", "decoderClass");
+    StreamConfig streamConfig = new StreamConfig(tableNameWithType, 
streamConfigMap);
+
+    List<PartitionGroupConsumptionStatus> currentStatuses = List.of(
+        new PartitionGroupConsumptionStatus(0, 0, new LongMsgOffset(0), new 
LongMsgOffset(10), "DONE"),
+        new PartitionGroupConsumptionStatus(1, 1, new LongMsgOffset(0), new 
LongMsgOffset(10), "DONE"),
+        new PartitionGroupConsumptionStatus(3, 3, new LongMsgOffset(0), new 
LongMsgOffset(10), "DONE"),
+        new PartitionGroupConsumptionStatus(4, 4, new LongMsgOffset(0), new 
LongMsgOffset(10), "DONE"),
+        new PartitionGroupConsumptionStatus(5, 5, new LongMsgOffset(0), new 
LongMsgOffset(10), "DONE"),
+        new PartitionGroupConsumptionStatus(6, 6, new LongMsgOffset(0), new 
LongMsgOffset(10), "DONE"),
+        new PartitionGroupConsumptionStatus(7, 7, new LongMsgOffset(0), new 
LongMsgOffset(10), "DONE"));
+
+    try (KafkaStreamMetadataProvider streamMetadataProvider =
+        new KafkaStreamMetadataProvider(clientId, streamConfig)) {
+      List<PartitionGroupMetadata> partitionGroupMetadataList =
+          streamMetadataProvider.computePartitionGroupMetadata(clientId, 
streamConfig, currentStatuses, 10000);
+
+      
assertEquals(partitionGroupMetadataList.stream().map(PartitionGroupMetadata::getPartitionGroupId)
+          .collect(Collectors.toList()), List.of(0, 1, 2, 3, 4, 5, 6, 7));
+    }
+  }
+
   @Test
   public void testFetchMessages() {
     String streamType = "kafka";
diff --git 
a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-3.0/src/test/java/org/apache/pinot/plugin/stream/kafka30/KafkaStreamMetadataProviderTest.java
 
b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-3.0/src/test/java/org/apache/pinot/plugin/stream/kafka30/KafkaStreamMetadataProviderTest.java
new file mode 100644
index 00000000000..973f338bbb7
--- /dev/null
+++ 
b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-3.0/src/test/java/org/apache/pinot/plugin/stream/kafka30/KafkaStreamMetadataProviderTest.java
@@ -0,0 +1,188 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pinot.plugin.stream.kafka30;
+
+import java.time.Duration;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.apache.kafka.clients.consumer.Consumer;
+import org.apache.kafka.common.PartitionInfo;
+import org.apache.kafka.common.TopicPartition;
+import org.apache.kafka.common.utils.Bytes;
+import org.apache.pinot.spi.stream.LongMsgOffset;
+import org.apache.pinot.spi.stream.PartitionGroupConsumptionStatus;
+import org.apache.pinot.spi.stream.PartitionGroupMetadata;
+import org.apache.pinot.spi.stream.StreamConfig;
+import org.apache.pinot.spi.stream.StreamConfigProperties;
+import org.apache.pinot.spi.stream.StreamMetadataProvider;
+import org.testng.annotations.Test;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+
+
+public class KafkaStreamMetadataProviderTest {
+  private static final ThreadLocal<Consumer<Bytes, Bytes>> MOCK_CONSUMER = new 
ThreadLocal<>();
+
+  @Test
+  public void testComputePartitionGroupMetadataRecoversMissingLowPartitionId()
+      throws Exception {
+    String topicName = "asset";
+    Consumer<Bytes, Bytes> consumer = mockConsumer(topicName, 8);
+    MOCK_CONSUMER.set(consumer);
+    try {
+      StreamConfig streamConfig = getStreamConfig(topicName);
+      List<PartitionGroupConsumptionStatus> currentStatuses = List.of(
+          new PartitionGroupConsumptionStatus(0, 0, new LongMsgOffset(0), new 
LongMsgOffset(10), "DONE"),
+          new PartitionGroupConsumptionStatus(1, 1, new LongMsgOffset(0), new 
LongMsgOffset(11), "DONE"),
+          new PartitionGroupConsumptionStatus(3, 3, new LongMsgOffset(0), new 
LongMsgOffset(13), "DONE"),
+          new PartitionGroupConsumptionStatus(4, 4, new LongMsgOffset(0), new 
LongMsgOffset(14), "DONE"),
+          new PartitionGroupConsumptionStatus(5, 5, new LongMsgOffset(0), new 
LongMsgOffset(15), "DONE"),
+          new PartitionGroupConsumptionStatus(6, 6, new LongMsgOffset(0), new 
LongMsgOffset(16), "DONE"),
+          new PartitionGroupConsumptionStatus(7, 7, new LongMsgOffset(0), new 
LongMsgOffset(17), "DONE"));
+
+      assertEquals(computePartitionIdsWithSizeBasedAlgorithm(currentStatuses, 
8), List.of(0, 1, 3, 4, 5, 6, 7, 7));
+
+      try (KafkaStreamMetadataProvider streamMetadataProvider =
+          new MockKafkaStreamMetadataProvider("client", streamConfig)) {
+        List<PartitionGroupMetadata> partitionGroupMetadataList =
+            streamMetadataProvider.computePartitionGroupMetadata("client", 
streamConfig, currentStatuses, 10000);
+
+        
assertEquals(partitionGroupMetadataList.stream().map(PartitionGroupMetadata::getPartitionGroupId)
+            .collect(Collectors.toList()), List.of(0, 1, 2, 3, 4, 5, 6, 7));
+        
assertEquals(partitionGroupMetadataList.get(2).getStartOffset().toString(), 
"1002");
+        
assertEquals(partitionGroupMetadataList.get(3).getStartOffset().toString(), 
"13");
+      }
+    } finally {
+      MOCK_CONSUMER.remove();
+    }
+  }
+
+  @Test
+  public void testComputePartitionGroupMetadataHandlesTopicExpansion()
+      throws Exception {
+    String topicName = "asset";
+    Consumer<Bytes, Bytes> consumer = mockConsumer(topicName, 8);
+    MOCK_CONSUMER.set(consumer);
+    try {
+      StreamConfig streamConfig = getStreamConfig(topicName);
+      List<PartitionGroupConsumptionStatus> currentStatuses = List.of(
+          new PartitionGroupConsumptionStatus(0, 0, new LongMsgOffset(0), new 
LongMsgOffset(10), "DONE"),
+          new PartitionGroupConsumptionStatus(1, 1, new LongMsgOffset(0), new 
LongMsgOffset(11), "DONE"),
+          new PartitionGroupConsumptionStatus(2, 2, new LongMsgOffset(0), new 
LongMsgOffset(12), "DONE"),
+          new PartitionGroupConsumptionStatus(3, 3, new LongMsgOffset(0), new 
LongMsgOffset(13), "DONE"));
+
+      try (KafkaStreamMetadataProvider streamMetadataProvider =
+          new MockKafkaStreamMetadataProvider("client", streamConfig)) {
+        List<PartitionGroupMetadata> partitionGroupMetadataList =
+            streamMetadataProvider.computePartitionGroupMetadata("client", 
streamConfig, currentStatuses, 10000);
+
+        
assertEquals(partitionGroupMetadataList.stream().map(PartitionGroupMetadata::getPartitionGroupId)
+            .collect(Collectors.toList()), List.of(0, 1, 2, 3, 4, 5, 6, 7));
+        assertEquals(partitionGroupMetadataList.stream().map(metadata -> 
metadata.getStartOffset().toString())
+            .collect(Collectors.toList()), List.of("10", "11", "12", "13", 
"1004", "1005", "1006", "1007"));
+      }
+    } finally {
+      MOCK_CONSUMER.remove();
+    }
+  }
+
+  private static StreamConfig getStreamConfig(String topicName) {
+    Map<String, String> streamConfigMap = new HashMap<>();
+    streamConfigMap.put("streamType", "kafka");
+    streamConfigMap.put("stream.kafka.topic.name", topicName);
+    streamConfigMap.put("stream.kafka.broker.list", "unused:9092");
+    streamConfigMap.put("stream.kafka.consumer.factory.class.name", 
MockKafkaConsumerFactory.class.getName());
+    streamConfigMap.put("stream.kafka." + 
StreamConfigProperties.STREAM_CONSUMER_OFFSET_CRITERIA, "smallest");
+    streamConfigMap.put("stream.kafka.decoder.class.name", "decoderClass");
+    return new StreamConfig("tableName_REALTIME", streamConfigMap);
+  }
+
+  @SuppressWarnings("unchecked")
+  private static Consumer<Bytes, Bytes> mockConsumer(String topicName, int 
partitionCount) {
+    Consumer<Bytes, Bytes> consumer = mock(Consumer.class);
+    List<PartitionInfo> partitionInfos = IntStream.range(0, partitionCount)
+        .mapToObj(partitionId -> new PartitionInfo(topicName, partitionId, 
null, null, null))
+        .collect(Collectors.toList());
+    when(consumer.partitionsFor(eq(topicName), 
any(Duration.class))).thenReturn(partitionInfos);
+    when(consumer.beginningOffsets(any(Collection.class), 
any(Duration.class))).thenAnswer(invocation -> {
+      Collection<TopicPartition> topicPartitions = invocation.getArgument(0);
+      Map<TopicPartition, Long> offsets = new HashMap<>();
+      for (TopicPartition topicPartition : topicPartitions) {
+        offsets.put(topicPartition, 1000L + topicPartition.partition());
+      }
+      return offsets;
+    });
+    when(consumer.endOffsets(any(Collection.class), 
any(Duration.class))).thenAnswer(invocation -> {
+      Collection<TopicPartition> topicPartitions = invocation.getArgument(0);
+      Map<TopicPartition, Long> offsets = new HashMap<>();
+      for (TopicPartition topicPartition : topicPartitions) {
+        offsets.put(topicPartition, 2000L + topicPartition.partition());
+      }
+      return offsets;
+    });
+    return consumer;
+  }
+
+  private static List<Integer> computePartitionIdsWithSizeBasedAlgorithm(
+      List<PartitionGroupConsumptionStatus> currentStatuses, int 
partitionCount) {
+    List<Integer> partitionIds = currentStatuses.stream()
+        .map(PartitionGroupConsumptionStatus::getStreamPartitionGroupId)
+        .collect(Collectors.toList());
+    for (int partitionId = currentStatuses.size(); partitionId < 
partitionCount; partitionId++) {
+      partitionIds.add(partitionId);
+    }
+    return partitionIds;
+  }
+
+  public static class MockKafkaConsumerFactory extends KafkaConsumerFactory {
+    @Override
+    public StreamMetadataProvider createPartitionMetadataProvider(String 
clientId, int partition) {
+      return new MockKafkaStreamMetadataProvider(clientId, _streamConfig, 
partition);
+    }
+
+    @Override
+    public StreamMetadataProvider createStreamMetadataProvider(String 
clientId) {
+      return new MockKafkaStreamMetadataProvider(clientId, _streamConfig);
+    }
+  }
+
+  private static class MockKafkaStreamMetadataProvider extends 
KafkaStreamMetadataProvider {
+    MockKafkaStreamMetadataProvider(String clientId, StreamConfig 
streamConfig) {
+      super(clientId, streamConfig);
+    }
+
+    MockKafkaStreamMetadataProvider(String clientId, StreamConfig 
streamConfig, int partition) {
+      super(clientId, streamConfig, partition);
+    }
+
+    @Override
+    protected Consumer<Bytes, Bytes> createConsumer(Properties consumerProp) {
+      return MOCK_CONSUMER.get();
+    }
+  }
+}
diff --git 
a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-4.0/src/main/java/org/apache/pinot/plugin/stream/kafka40/KafkaStreamMetadataProvider.java
 
b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-4.0/src/main/java/org/apache/pinot/plugin/stream/kafka40/KafkaStreamMetadataProvider.java
index 4a9ff545b67..5605a58c363 100644
--- 
a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-4.0/src/main/java/org/apache/pinot/plugin/stream/kafka40/KafkaStreamMetadataProvider.java
+++ 
b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-4.0/src/main/java/org/apache/pinot/plugin/stream/kafka40/KafkaStreamMetadataProvider.java
@@ -123,31 +123,37 @@ public class KafkaStreamMetadataProvider extends 
KafkaPartitionLevelConnectionHa
   public List<PartitionGroupMetadata> computePartitionGroupMetadata(String 
clientId, StreamConfig streamConfig,
       List<PartitionGroupConsumptionStatus> partitionGroupConsumptionStatuses, 
int timeoutMillis)
       throws IOException, java.util.concurrent.TimeoutException {
-    if (_partitionIdSubset.isEmpty()) {
-      return 
StreamMetadataProvider.super.computePartitionGroupMetadata(clientId, 
streamConfig,
-          partitionGroupConsumptionStatuses, timeoutMillis);
-    }
     Map<Integer, StreamPartitionMsgOffset> partitionIdToEndOffset =
         new HashMap<>(partitionGroupConsumptionStatuses.size());
     for (PartitionGroupConsumptionStatus s : 
partitionGroupConsumptionStatuses) {
       partitionIdToEndOffset.put(s.getStreamPartitionGroupId(), 
s.getEndOffset());
     }
+
+    List<Integer> partitionIds;
+    if (_partitionIdSubset.isEmpty()) {
+      int partitionCount = fetchPartitionCount(timeoutMillis);
+      partitionIds = new ArrayList<>(partitionCount);
+      for (int partitionId = 0; partitionId < partitionCount; partitionId++) {
+        partitionIds.add(partitionId);
+      }
+    } else {
+      partitionIds = _partitionIdSubset;
+    }
+
     StreamConsumerFactory streamConsumerFactory = 
StreamConsumerFactoryProvider.create(streamConfig);
-    List<PartitionGroupMetadata> result = new 
ArrayList<>(_partitionIdSubset.size());
-    for (Integer partitionId : _partitionIdSubset) {
-      StreamPartitionMsgOffset endOffset = 
partitionIdToEndOffset.get(partitionId);
-      StreamPartitionMsgOffset startOffset;
-      if (endOffset == null) {
+    List<PartitionGroupMetadata> result = new ArrayList<>(partitionIds.size());
+    for (Integer partitionId : partitionIds) {
+      if (partitionIdToEndOffset.containsKey(partitionId)) {
+        result.add(new PartitionGroupMetadata(partitionId, 
partitionIdToEndOffset.get(partitionId)));
+      } else {
         try (StreamMetadataProvider partitionMetadataProvider =
             streamConsumerFactory.createPartitionMetadataProvider(
                 StreamConsumerFactory.getUniqueClientId(clientId), 
partitionId)) {
-          startOffset = partitionMetadataProvider.fetchStreamPartitionOffset(
+          StreamPartitionMsgOffset startOffset = 
partitionMetadataProvider.fetchStreamPartitionOffset(
               streamConfig.getOffsetCriteria(), timeoutMillis);
+          result.add(new PartitionGroupMetadata(partitionId, startOffset));
         }
-      } else {
-        startOffset = endOffset;
       }
-      result.add(new PartitionGroupMetadata(partitionId, startOffset));
     }
     return result;
   }
diff --git 
a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-4.0/src/test/java/org/apache/pinot/plugin/stream/kafka40/KafkaPartitionLevelConsumerTest.java
 
b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-4.0/src/test/java/org/apache/pinot/plugin/stream/kafka40/KafkaPartitionLevelConsumerTest.java
index 3584595f870..d43650cddb6 100644
--- 
a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-4.0/src/test/java/org/apache/pinot/plugin/stream/kafka40/KafkaPartitionLevelConsumerTest.java
+++ 
b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-4.0/src/test/java/org/apache/pinot/plugin/stream/kafka40/KafkaPartitionLevelConsumerTest.java
@@ -50,6 +50,7 @@ import org.apache.pinot.spi.stream.MessageBatch;
 import org.apache.pinot.spi.stream.OffsetCriteria;
 import org.apache.pinot.spi.stream.PartitionGroupConsumer;
 import org.apache.pinot.spi.stream.PartitionGroupConsumptionStatus;
+import org.apache.pinot.spi.stream.PartitionGroupMetadata;
 import org.apache.pinot.spi.stream.StreamConfig;
 import org.apache.pinot.spi.stream.StreamConsumerFactory;
 import org.apache.pinot.spi.stream.StreamConsumerFactoryProvider;
@@ -286,6 +287,41 @@ public class KafkaPartitionLevelConsumerTest {
     }
   }
 
+  @Test
+  public void testComputePartitionGroupMetadataUsesKafkaPartitionIds()
+      throws Exception {
+    String streamType = "kafka";
+    String streamKafkaBrokerList = _kafkaBrokerAddress;
+    String clientId = "partition-id-client";
+    String tableNameWithType = "tableName_REALTIME";
+
+    Map<String, String> streamConfigMap = new HashMap<>();
+    streamConfigMap.put("streamType", streamType);
+    streamConfigMap.put("stream.kafka.topic.name", 
TEST_TOPIC_SUBSET_PARTITION);
+    streamConfigMap.put("stream.kafka.broker.list", streamKafkaBrokerList);
+    streamConfigMap.put("stream.kafka.consumer.factory.class.name", 
getKafkaConsumerFactoryName());
+    streamConfigMap.put("stream.kafka.decoder.class.name", "decoderClass");
+    StreamConfig streamConfig = new StreamConfig(tableNameWithType, 
streamConfigMap);
+
+    List<PartitionGroupConsumptionStatus> currentStatuses = List.of(
+        new PartitionGroupConsumptionStatus(0, 0, new LongMsgOffset(0), new 
LongMsgOffset(10), "DONE"),
+        new PartitionGroupConsumptionStatus(1, 1, new LongMsgOffset(0), new 
LongMsgOffset(10), "DONE"),
+        new PartitionGroupConsumptionStatus(3, 3, new LongMsgOffset(0), new 
LongMsgOffset(10), "DONE"),
+        new PartitionGroupConsumptionStatus(4, 4, new LongMsgOffset(0), new 
LongMsgOffset(10), "DONE"),
+        new PartitionGroupConsumptionStatus(5, 5, new LongMsgOffset(0), new 
LongMsgOffset(10), "DONE"),
+        new PartitionGroupConsumptionStatus(6, 6, new LongMsgOffset(0), new 
LongMsgOffset(10), "DONE"),
+        new PartitionGroupConsumptionStatus(7, 7, new LongMsgOffset(0), new 
LongMsgOffset(10), "DONE"));
+
+    try (KafkaStreamMetadataProvider streamMetadataProvider =
+        new KafkaStreamMetadataProvider(clientId, streamConfig)) {
+      List<PartitionGroupMetadata> partitionGroupMetadataList =
+          streamMetadataProvider.computePartitionGroupMetadata(clientId, 
streamConfig, currentStatuses, 10000);
+
+      
assertEquals(partitionGroupMetadataList.stream().map(PartitionGroupMetadata::getPartitionGroupId)
+          .collect(Collectors.toList()), List.of(0, 1, 2, 3, 4, 5, 6, 7));
+    }
+  }
+
   @Test
   public void testFetchMessages() {
     String streamType = "kafka";
diff --git 
a/pinot-plugins/pinot-stream-ingestion/pinot-kafka-4.0/src/test/java/org/apache/pinot/plugin/stream/kafka40/KafkaStreamMetadataProviderTest.java
 
b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-4.0/src/test/java/org/apache/pinot/plugin/stream/kafka40/KafkaStreamMetadataProviderTest.java
new file mode 100644
index 00000000000..f3a34b57fe7
--- /dev/null
+++ 
b/pinot-plugins/pinot-stream-ingestion/pinot-kafka-4.0/src/test/java/org/apache/pinot/plugin/stream/kafka40/KafkaStreamMetadataProviderTest.java
@@ -0,0 +1,188 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pinot.plugin.stream.kafka40;
+
+import java.time.Duration;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.apache.kafka.clients.consumer.Consumer;
+import org.apache.kafka.common.PartitionInfo;
+import org.apache.kafka.common.TopicPartition;
+import org.apache.kafka.common.utils.Bytes;
+import org.apache.pinot.spi.stream.LongMsgOffset;
+import org.apache.pinot.spi.stream.PartitionGroupConsumptionStatus;
+import org.apache.pinot.spi.stream.PartitionGroupMetadata;
+import org.apache.pinot.spi.stream.StreamConfig;
+import org.apache.pinot.spi.stream.StreamConfigProperties;
+import org.apache.pinot.spi.stream.StreamMetadataProvider;
+import org.testng.annotations.Test;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+
+
+public class KafkaStreamMetadataProviderTest {
+  private static final ThreadLocal<Consumer<Bytes, Bytes>> MOCK_CONSUMER = new 
ThreadLocal<>();
+
+  @Test
+  public void testComputePartitionGroupMetadataRecoversMissingLowPartitionId()
+      throws Exception {
+    String topicName = "asset";
+    Consumer<Bytes, Bytes> consumer = mockConsumer(topicName, 8);
+    MOCK_CONSUMER.set(consumer);
+    try {
+      StreamConfig streamConfig = getStreamConfig(topicName);
+      List<PartitionGroupConsumptionStatus> currentStatuses = List.of(
+          new PartitionGroupConsumptionStatus(0, 0, new LongMsgOffset(0), new 
LongMsgOffset(10), "DONE"),
+          new PartitionGroupConsumptionStatus(1, 1, new LongMsgOffset(0), new 
LongMsgOffset(11), "DONE"),
+          new PartitionGroupConsumptionStatus(3, 3, new LongMsgOffset(0), new 
LongMsgOffset(13), "DONE"),
+          new PartitionGroupConsumptionStatus(4, 4, new LongMsgOffset(0), new 
LongMsgOffset(14), "DONE"),
+          new PartitionGroupConsumptionStatus(5, 5, new LongMsgOffset(0), new 
LongMsgOffset(15), "DONE"),
+          new PartitionGroupConsumptionStatus(6, 6, new LongMsgOffset(0), new 
LongMsgOffset(16), "DONE"),
+          new PartitionGroupConsumptionStatus(7, 7, new LongMsgOffset(0), new 
LongMsgOffset(17), "DONE"));
+
+      assertEquals(computePartitionIdsWithSizeBasedAlgorithm(currentStatuses, 
8), List.of(0, 1, 3, 4, 5, 6, 7, 7));
+
+      try (KafkaStreamMetadataProvider streamMetadataProvider =
+          new MockKafkaStreamMetadataProvider("client", streamConfig)) {
+        List<PartitionGroupMetadata> partitionGroupMetadataList =
+            streamMetadataProvider.computePartitionGroupMetadata("client", 
streamConfig, currentStatuses, 10000);
+
+        
assertEquals(partitionGroupMetadataList.stream().map(PartitionGroupMetadata::getPartitionGroupId)
+            .collect(Collectors.toList()), List.of(0, 1, 2, 3, 4, 5, 6, 7));
+        
assertEquals(partitionGroupMetadataList.get(2).getStartOffset().toString(), 
"1002");
+        
assertEquals(partitionGroupMetadataList.get(3).getStartOffset().toString(), 
"13");
+      }
+    } finally {
+      MOCK_CONSUMER.remove();
+    }
+  }
+
+  @Test
+  public void testComputePartitionGroupMetadataHandlesTopicExpansion()
+      throws Exception {
+    String topicName = "asset";
+    Consumer<Bytes, Bytes> consumer = mockConsumer(topicName, 8);
+    MOCK_CONSUMER.set(consumer);
+    try {
+      StreamConfig streamConfig = getStreamConfig(topicName);
+      List<PartitionGroupConsumptionStatus> currentStatuses = List.of(
+          new PartitionGroupConsumptionStatus(0, 0, new LongMsgOffset(0), new 
LongMsgOffset(10), "DONE"),
+          new PartitionGroupConsumptionStatus(1, 1, new LongMsgOffset(0), new 
LongMsgOffset(11), "DONE"),
+          new PartitionGroupConsumptionStatus(2, 2, new LongMsgOffset(0), new 
LongMsgOffset(12), "DONE"),
+          new PartitionGroupConsumptionStatus(3, 3, new LongMsgOffset(0), new 
LongMsgOffset(13), "DONE"));
+
+      try (KafkaStreamMetadataProvider streamMetadataProvider =
+          new MockKafkaStreamMetadataProvider("client", streamConfig)) {
+        List<PartitionGroupMetadata> partitionGroupMetadataList =
+            streamMetadataProvider.computePartitionGroupMetadata("client", 
streamConfig, currentStatuses, 10000);
+
+        
assertEquals(partitionGroupMetadataList.stream().map(PartitionGroupMetadata::getPartitionGroupId)
+            .collect(Collectors.toList()), List.of(0, 1, 2, 3, 4, 5, 6, 7));
+        assertEquals(partitionGroupMetadataList.stream().map(metadata -> 
metadata.getStartOffset().toString())
+            .collect(Collectors.toList()), List.of("10", "11", "12", "13", 
"1004", "1005", "1006", "1007"));
+      }
+    } finally {
+      MOCK_CONSUMER.remove();
+    }
+  }
+
+  private static StreamConfig getStreamConfig(String topicName) {
+    Map<String, String> streamConfigMap = new HashMap<>();
+    streamConfigMap.put("streamType", "kafka");
+    streamConfigMap.put("stream.kafka.topic.name", topicName);
+    streamConfigMap.put("stream.kafka.broker.list", "unused:9092");
+    streamConfigMap.put("stream.kafka.consumer.factory.class.name", 
MockKafkaConsumerFactory.class.getName());
+    streamConfigMap.put("stream.kafka." + 
StreamConfigProperties.STREAM_CONSUMER_OFFSET_CRITERIA, "smallest");
+    streamConfigMap.put("stream.kafka.decoder.class.name", "decoderClass");
+    return new StreamConfig("tableName_REALTIME", streamConfigMap);
+  }
+
+  @SuppressWarnings("unchecked")
+  private static Consumer<Bytes, Bytes> mockConsumer(String topicName, int 
partitionCount) {
+    Consumer<Bytes, Bytes> consumer = mock(Consumer.class);
+    List<PartitionInfo> partitionInfos = IntStream.range(0, partitionCount)
+        .mapToObj(partitionId -> new PartitionInfo(topicName, partitionId, 
null, null, null))
+        .collect(Collectors.toList());
+    when(consumer.partitionsFor(eq(topicName), 
any(Duration.class))).thenReturn(partitionInfos);
+    when(consumer.beginningOffsets(any(Collection.class), 
any(Duration.class))).thenAnswer(invocation -> {
+      Collection<TopicPartition> topicPartitions = invocation.getArgument(0);
+      Map<TopicPartition, Long> offsets = new HashMap<>();
+      for (TopicPartition topicPartition : topicPartitions) {
+        offsets.put(topicPartition, 1000L + topicPartition.partition());
+      }
+      return offsets;
+    });
+    when(consumer.endOffsets(any(Collection.class), 
any(Duration.class))).thenAnswer(invocation -> {
+      Collection<TopicPartition> topicPartitions = invocation.getArgument(0);
+      Map<TopicPartition, Long> offsets = new HashMap<>();
+      for (TopicPartition topicPartition : topicPartitions) {
+        offsets.put(topicPartition, 2000L + topicPartition.partition());
+      }
+      return offsets;
+    });
+    return consumer;
+  }
+
+  private static List<Integer> computePartitionIdsWithSizeBasedAlgorithm(
+      List<PartitionGroupConsumptionStatus> currentStatuses, int 
partitionCount) {
+    List<Integer> partitionIds = currentStatuses.stream()
+        .map(PartitionGroupConsumptionStatus::getStreamPartitionGroupId)
+        .collect(Collectors.toList());
+    for (int partitionId = currentStatuses.size(); partitionId < 
partitionCount; partitionId++) {
+      partitionIds.add(partitionId);
+    }
+    return partitionIds;
+  }
+
+  public static class MockKafkaConsumerFactory extends KafkaConsumerFactory {
+    @Override
+    public StreamMetadataProvider createPartitionMetadataProvider(String 
clientId, int partition) {
+      return new MockKafkaStreamMetadataProvider(clientId, _streamConfig, 
partition);
+    }
+
+    @Override
+    public StreamMetadataProvider createStreamMetadataProvider(String 
clientId) {
+      return new MockKafkaStreamMetadataProvider(clientId, _streamConfig);
+    }
+  }
+
+  private static class MockKafkaStreamMetadataProvider extends 
KafkaStreamMetadataProvider {
+    MockKafkaStreamMetadataProvider(String clientId, StreamConfig 
streamConfig) {
+      super(clientId, streamConfig);
+    }
+
+    MockKafkaStreamMetadataProvider(String clientId, StreamConfig 
streamConfig, int partition) {
+      super(clientId, streamConfig, partition);
+    }
+
+    @Override
+    protected Consumer<Bytes, Bytes> createConsumer(Properties consumerProp) {
+      return MOCK_CONSUMER.get();
+    }
+  }
+}
diff --git 
a/pinot-spi/src/main/java/org/apache/pinot/spi/stream/StreamMetadataProvider.java
 
b/pinot-spi/src/main/java/org/apache/pinot/spi/stream/StreamMetadataProvider.java
index 4b5666a7a35..6a742771a21 100644
--- 
a/pinot-spi/src/main/java/org/apache/pinot/spi/stream/StreamMetadataProvider.java
+++ 
b/pinot-spi/src/main/java/org/apache/pinot/spi/stream/StreamMetadataProvider.java
@@ -20,7 +20,6 @@ package org.apache.pinot.spi.stream;
 
 import java.io.Closeable;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -78,60 +77,27 @@ public interface StreamMetadataProvider extends Closeable {
   StreamPartitionMsgOffset fetchStreamPartitionOffset(OffsetCriteria 
offsetCriteria, long timeoutMillis)
       throws TimeoutException;
 
-  /**
-   * Computes the list of {@link PartitionGroupMetadata} for the latest state 
of the stream, using the current
-   * {@link PartitionGroupConsumptionStatus}
-   *
-   * Default behavior is the one for the Kafka stream, where each partition 
group contains only one partition
-   * @param partitionGroupConsumptionStatuses list of {@link 
PartitionGroupConsumptionStatus} for current partition
-   *                                          groups
-   */
-  default List<PartitionGroupMetadata> computePartitionGroupMetadata(String 
clientId, StreamConfig streamConfig,
+  /// Computes the list of [PartitionGroupMetadata] for the latest state of 
the stream, using the current
+  /// [PartitionGroupConsumptionStatus].
+  ///
+  /// Implementations must define how stream partitions map to Pinot partition 
groups.
+  ///
+  /// @param partitionGroupConsumptionStatuses list of 
[PartitionGroupConsumptionStatus] for current partition groups
+  List<PartitionGroupMetadata> computePartitionGroupMetadata(String clientId, 
StreamConfig streamConfig,
       List<PartitionGroupConsumptionStatus> partitionGroupConsumptionStatuses, 
int timeoutMillis)
-      throws IOException, TimeoutException {
-    int partitionCount = fetchPartitionCount(timeoutMillis);
-    List<PartitionGroupMetadata> newPartitionGroupMetadataList = new 
ArrayList<>(partitionCount);
-
-    // Add a PartitionGroupMetadata into the list, foreach partition already 
present in current.
-    // Setting endOffset (exclusive) as the startOffset for new partition 
group.
-    // If partition group is still in progress, this value will be null
-    for (PartitionGroupConsumptionStatus 
currentPartitionGroupConsumptionStatus : partitionGroupConsumptionStatuses) {
-      newPartitionGroupMetadataList.add(
-          new 
PartitionGroupMetadata(currentPartitionGroupConsumptionStatus.getStreamPartitionGroupId(),
-              currentPartitionGroupConsumptionStatus.getEndOffset()));
-    }
-    // Add PartitionGroupMetadata for new partitions
-    // Use offset criteria from stream config
-    StreamConsumerFactory streamConsumerFactory = 
StreamConsumerFactoryProvider.create(streamConfig);
-    for (int i = partitionGroupConsumptionStatuses.size(); i < partitionCount; 
i++) {
-      try (StreamMetadataProvider partitionMetadataProvider = 
streamConsumerFactory.createPartitionMetadataProvider(
-          StreamConsumerFactory.getUniqueClientId(clientId), i)) {
-        StreamPartitionMsgOffset streamPartitionMsgOffset =
-            
partitionMetadataProvider.fetchStreamPartitionOffset(streamConfig.getOffsetCriteria(),
 timeoutMillis);
-        newPartitionGroupMetadataList.add(new PartitionGroupMetadata(i, 
streamPartitionMsgOffset));
-      }
-    }
-    return newPartitionGroupMetadataList;
-  }
+      throws IOException, TimeoutException;
 
-  /**
-   * @param forceGetOffsetFromStream - the flag is a workaround to not use 
partitionGroupConsumptionStatuses.
-   *                                  This is required because 
PinotLLCRealtimeSegmentManager.selectStartOffset()
-   *                                  actually requires the offsets from the 
stream, but was originally relying on
-   *                                  passing an empty 
partitionGroupConsumptionStatuses to the method.
-   *                                  The change for <a 
href="https://github.com/apache/pinot/issues/15608";>...</a>
-   *                                  required to pass the actual 
partitionGroupConsumptionStatuses
-   *                                  TODO - Remove the flag and fix the 
clients calling computePartitionGroupMetadata()
-   */
+  /// Computes partition group metadata with the option to force offsets to be 
fetched from the stream instead of
+  /// derived from [PartitionGroupConsumptionStatus].
+  ///
+  /// @param forceGetOffsetFromStream whether the implementation should ignore 
current consumption statuses when
+  /// selecting start offsets
   default List<PartitionGroupMetadata> computePartitionGroupMetadata(String 
clientId, StreamConfig streamConfig,
       List<PartitionGroupConsumptionStatus> partitionGroupConsumptionStatuses, 
int timeoutMillis,
       boolean forceGetOffsetFromStream)
       throws IOException, TimeoutException {
-    if (forceGetOffsetFromStream) {
-      return computePartitionGroupMetadata(clientId, streamConfig, 
Collections.emptyList(), timeoutMillis);
-    } else {
-      return computePartitionGroupMetadata(clientId, streamConfig, 
partitionGroupConsumptionStatuses, timeoutMillis);
-    }
+    return computePartitionGroupMetadata(clientId, streamConfig,
+        forceGetOffsetFromStream ? Collections.emptyList() : 
partitionGroupConsumptionStatuses, timeoutMillis);
   }
 
   default Map<String, PartitionLagState> getCurrentPartitionLagState(
diff --git 
a/pinot-spi/src/test/java/org/apache/pinot/spi/stream/PartitionGroupMetadataFetcherTest.java
 
b/pinot-spi/src/test/java/org/apache/pinot/spi/stream/PartitionGroupMetadataFetcherTest.java
index 3a175ef7fcd..4cab96d169c 100644
--- 
a/pinot-spi/src/test/java/org/apache/pinot/spi/stream/PartitionGroupMetadataFetcherTest.java
+++ 
b/pinot-spi/src/test/java/org/apache/pinot/spi/stream/PartitionGroupMetadataFetcherTest.java
@@ -500,103 +500,4 @@ public class PartitionGroupMetadataFetcherTest {
     when(streamConfig.getTableNameWithType()).thenReturn(tableName);
     return streamConfig;
   }
-
-  private static final class DefaultComputeOnlyMetadataProvider implements 
StreamMetadataProvider {
-    private int _fetchPartitionCountCalls;
-
-    @Override
-    public int fetchPartitionCount(long timeoutMillis) {
-      _fetchPartitionCountCalls++;
-      return 1;
-    }
-
-    @Override
-    public StreamPartitionMsgOffset fetchStreamPartitionOffset(OffsetCriteria 
offsetCriteria, long timeoutMillis) {
-      throw new UnsupportedOperationException("Should not be called");
-    }
-
-    @Override
-    public boolean supportsOffsetLag() {
-      return false;
-    }
-
-    @Override
-    public void close() {
-    }
-
-    int getFetchPartitionCountCalls() {
-      return _fetchPartitionCountCalls;
-    }
-  }
-
-  private static final class OverriddenComputeMetadataProvider implements 
StreamMetadataProvider {
-    private int _fetchPartitionCountCalls;
-    private final StreamPartitionMsgOffset _offset = 
mock(StreamPartitionMsgOffset.class);
-
-    @Override
-    public int fetchPartitionCount(long timeoutMillis) {
-      _fetchPartitionCountCalls++;
-      return 3;
-    }
-
-    @Override
-    public StreamPartitionMsgOffset fetchStreamPartitionOffset(OffsetCriteria 
offsetCriteria, long timeoutMillis) {
-      throw new UnsupportedOperationException("Should not be called");
-    }
-
-    @Override
-    public List<PartitionGroupMetadata> computePartitionGroupMetadata(String 
clientId, StreamConfig streamConfig,
-        List<PartitionGroupConsumptionStatus> 
partitionGroupConsumptionStatuses, int timeoutMillis,
-        boolean forceGetOffsetFromStream) {
-      return Collections.singletonList(new PartitionGroupMetadata(0, _offset));
-    }
-
-    @Override
-    public boolean supportsOffsetLag() {
-      return false;
-    }
-
-    @Override
-    public void close() {
-    }
-
-    int getFetchPartitionCountCalls() {
-      return _fetchPartitionCountCalls;
-    }
-  }
-
-  private static final class OverriddenFourArgComputeMetadataProvider 
implements StreamMetadataProvider {
-    private int _fetchPartitionCountCalls;
-    private final StreamPartitionMsgOffset _offset = 
mock(StreamPartitionMsgOffset.class);
-
-    @Override
-    public int fetchPartitionCount(long timeoutMillis) {
-      _fetchPartitionCountCalls++;
-      return 3;
-    }
-
-    @Override
-    public StreamPartitionMsgOffset fetchStreamPartitionOffset(OffsetCriteria 
offsetCriteria, long timeoutMillis) {
-      throw new UnsupportedOperationException("Should not be called");
-    }
-
-    @Override
-    public List<PartitionGroupMetadata> computePartitionGroupMetadata(String 
clientId, StreamConfig streamConfig,
-        List<PartitionGroupConsumptionStatus> 
partitionGroupConsumptionStatuses, int timeoutMillis) {
-      return Collections.singletonList(new PartitionGroupMetadata(0, _offset));
-    }
-
-    @Override
-    public boolean supportsOffsetLag() {
-      return false;
-    }
-
-    @Override
-    public void close() {
-    }
-
-    int getFetchPartitionCountCalls() {
-      return _fetchPartitionCountCalls;
-    }
-  }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to