xiangfu0 commented on code in PR #20106:
URL: https://github.com/apache/pinot/pull/20106#discussion_r4429247412


##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/tablestate/TableStateUtils.java:
##########
@@ -82,12 +84,56 @@ public static List<String> 
getSegmentsInGivenStateForThisInstance(HelixManager h
   public static boolean isAllSegmentsLoaded(HelixManager helixManager, String 
tableNameWithType) {
     List<String> onlineSegments =
         getSegmentsInGivenStateForThisInstance(helixManager, 
tableNameWithType, SegmentStateModel.ONLINE);
+    return areSegmentsLoaded(helixManager, tableNameWithType, onlineSegments);
+  }
+
+  /// Checks the current assignment before a consumer starts. Earlier ONLINE 
segments of the same partition must be
+  /// loaded. Non-LLC ONLINE segments are conservatively included because they 
may hold base rows for the partition.
+  /// The consumer itself and its successors are excluded so that they do not 
wait for their own completion.
+  ///
+  /// @param isLocallyReady identifies segments whose metadata is already 
ready locally despite a pending ONLINE
+  ///                      transition, such as a predecessor that finished 
consumption and released its permit
+  public static boolean isReadyToConsume(HelixManager helixManager, String 
tableNameWithType, LLCSegmentName consumer,
+      Predicate<String> isLocallyReady) {
+    HelixDataAccessor dataAccessor = helixManager.getHelixDataAccessor();
+    IdealState idealState = 
dataAccessor.getProperty(dataAccessor.keyBuilder().idealStates(tableNameWithType));
+    if (idealState == null) {
+      LOGGER.warn("Failed to find ideal state for table: {}", 
tableNameWithType);
+      return false;
+    }
+    String instanceName = helixManager.getInstanceName();
+    Map<String, String> consumerAssignment = 
idealState.getInstanceStateMap(consumer.getSegmentName());
+    String consumerState = consumerAssignment != null ? 
consumerAssignment.get(instanceName) : null;
+    if (!SegmentStateModel.CONSUMING.equals(consumerState) && 
!SegmentStateModel.ONLINE.equals(consumerState)) {
+      return false;
+    }
+
+    List<String> requiredSegments = new ArrayList<>();
+    for (Map.Entry<String, Map<String, String>> entry : 
idealState.getRecord().getMapFields().entrySet()) {
+      if 
(!SegmentStateModel.ONLINE.equals(entry.getValue().get(instanceName))) {
+        continue;
+      }
+      String segmentName = entry.getKey();
+      LLCSegmentName llcSegmentName = LLCSegmentName.of(segmentName);
+      if (llcSegmentName != null && (llcSegmentName.getPartitionGroupId() != 
consumer.getPartitionGroupId()
+          || llcSegmentName.getSequenceNumber() >= 
consumer.getSequenceNumber())) {
+        continue;
+      }
+      if (!isLocallyReady.test(segmentName)) {
+        requiredSegments.add(segmentName);

Review Comment:
   **MAJOR (needs owner decision): an ERROR segment can halt ingestion 
indefinitely.** `areSegmentsLoaded` returns `false` whenever a required segment 
is in ERROR. This check now runs at every consumer start, not just once at 
startup. So one old segment of the partition in ERROR stops that replica's 
consumption until someone resets it. Because non-LLC segments are required for 
every partition, an uploaded segment in ERROR stops all partitions. The only 
signal is a log line every 5s. Stopping may be the right call for partial 
upsert, but please confirm the intent and add a gauge/meter so the stall can be 
alerted on.



##########
pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeTableDataManager.java:
##########
@@ -235,43 +235,38 @@ protected void doInit() {
     _enforceConsumptionInOrder = isEnforceConsumptionInOrder();
     _streamMetadataProviderCache = getStreamMetadataProviderCache();
 
-    // For dedup and partial-upsert, need to wait for all segments loaded 
before starting consuming data
-    BooleanSupplier readyForDedupOrPartialUpsert;
-    if (isDedupEnabled() || isPartialUpsertEnabled()) {
-      readyForDedupOrPartialUpsert = new BooleanSupplier() {
-        volatile boolean _allSegmentsLoaded;
-        long _lastCheckTimeMs;
-
-        @Override
-        public boolean getAsBoolean() {
-          if (_allSegmentsLoaded) {
-            return true;
-          } else {
-            synchronized (this) {
-              if (_allSegmentsLoaded) {
-                return true;
-              }
-              long currentTimeMs = System.currentTimeMillis();
-              if (currentTimeMs - _lastCheckTimeMs <= 
READY_TO_CONSUME_DATA_CHECK_INTERVAL_MS) {
-                return false;
-              }
-              _lastCheckTimeMs = currentTimeMs;
-              _allSegmentsLoaded = 
TableStateUtils.isAllSegmentsLoaded(_helixManager, _tableNameWithType);
-              return _allSegmentsLoaded;
-            }
-          }
-        }
-      };
-    } else {
-      readyForDedupOrPartialUpsert = () -> true;
-    }
-    _isTableReadyToConsumeData = () -> 
readyForDedupOrPartialUpsert.getAsBoolean()
-        && _isServerReadyToConsumeData.getAsBoolean();
     _serverIngestionOomProtectionManager = new 
ServerIngestionOomProtectionManager(
         () -> getCachedTableConfigAndSchema().getLeft(), () -> 
isUpsertEnabled() || isDedupEnabled(),
         _serverIngestionOomProtectionThrottleState);
   }
 
+  /// Checks the assigned history again for each consumer, including 
partitions added after table initialization.
+  /// The consumer thread polls this supplier before acquiring its permit, so 
segment loading is never blocked by it.
+  @VisibleForTesting
+  BooleanSupplier createReadyToConsumeDataSupplier(LLCSegmentName 
llcSegmentName) {
+    if (!isDedupEnabled() && !isPartialUpsertEnabled()) {
+      return _isServerReadyToConsumeData;
+    }
+    return () -> {

Review Comment:
   **MAJOR: up to 5s stall on every rollover.** This check now runs before 
`acquire()` for every consumer. Under `DISALLOW_ALWAYS` (the default for 
non-pauseless partial upsert and dedup), `n-1` never sets 
`hasReleasedConsumerSemaphore`, so `n` waits until `n-1`'s CURRENTSTATE is 
ONLINE. It then only notices on the next fixed 5s `Thread.sleep` tick. Before 
this change, it blocked in `acquire()` and woke as soon as the permit was 
released. Pauseless has the same problem whenever the first check runs before 
`n-1` reaches its build. My probe confirmed this: with `n-1` already replaced 
locally (immutable segment, metadata loaded) but CURRENTSTATE still CONSUMING, 
the supplier returns `false`. Could the local coordinator handoff count for the 
immediate predecessor, or could the wait wake on release instead of the 5s 
timer?



##########
pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeTableDataManager.java:
##########
@@ -235,43 +235,38 @@ protected void doInit() {
     _enforceConsumptionInOrder = isEnforceConsumptionInOrder();
     _streamMetadataProviderCache = getStreamMetadataProviderCache();
 
-    // For dedup and partial-upsert, need to wait for all segments loaded 
before starting consuming data
-    BooleanSupplier readyForDedupOrPartialUpsert;
-    if (isDedupEnabled() || isPartialUpsertEnabled()) {
-      readyForDedupOrPartialUpsert = new BooleanSupplier() {
-        volatile boolean _allSegmentsLoaded;
-        long _lastCheckTimeMs;
-
-        @Override
-        public boolean getAsBoolean() {
-          if (_allSegmentsLoaded) {
-            return true;
-          } else {
-            synchronized (this) {
-              if (_allSegmentsLoaded) {
-                return true;
-              }
-              long currentTimeMs = System.currentTimeMillis();
-              if (currentTimeMs - _lastCheckTimeMs <= 
READY_TO_CONSUME_DATA_CHECK_INTERVAL_MS) {
-                return false;
-              }
-              _lastCheckTimeMs = currentTimeMs;
-              _allSegmentsLoaded = 
TableStateUtils.isAllSegmentsLoaded(_helixManager, _tableNameWithType);
-              return _allSegmentsLoaded;
-            }
-          }
-        }
-      };
-    } else {
-      readyForDedupOrPartialUpsert = () -> true;
-    }
-    _isTableReadyToConsumeData = () -> 
readyForDedupOrPartialUpsert.getAsBoolean()
-        && _isServerReadyToConsumeData.getAsBoolean();
     _serverIngestionOomProtectionManager = new 
ServerIngestionOomProtectionManager(
         () -> getCachedTableConfigAndSchema().getLeft(), () -> 
isUpsertEnabled() || isDedupEnabled(),
         _serverIngestionOomProtectionThrottleState);
   }
 
+  /// Checks the assigned history again for each consumer, including 
partitions added after table initialization.
+  /// The consumer thread polls this supplier before acquiring its permit, so 
segment loading is never blocked by it.
+  @VisibleForTesting
+  BooleanSupplier createReadyToConsumeDataSupplier(LLCSegmentName 
llcSegmentName) {
+    if (!isDedupEnabled() && !isPartialUpsertEnabled()) {
+      return _isServerReadyToConsumeData;
+    }
+    return () -> {
+      if (!_isServerReadyToConsumeData.getAsBoolean()) {
+        return false;
+      }
+      try {
+        return TableStateUtils.isReadyToConsume(_helixManager, 
_tableNameWithType, llcSegmentName, segmentName -> {

Review Comment:
   **MAJOR: ZK reads now multiply by partition count.** The old supplier was 
shared, `synchronized`, and throttled by `_lastCheckTimeMs`, so a table did at 
most one check per 5s. Now each waiting consumer reads the full IdealState plus 
LiveInstance and CurrentState on every poll. My probe with 64 waiting 
partitions measured 64 IdealState reads and 192 ZK reads per polling round. For 
large upsert tables, a server restart or rebalance multiplies ZK traffic by the 
number of partitions. Could consumers share a table-level IS/CS snapshot, 
cached for the interval, and each evaluate their own partition against it?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to