raghavyadav01 commented on code in PR #20020:
URL: https://github.com/apache/pinot/pull/20020#discussion_r4448541516


##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/writer/StatelessRealtimeSegmentWriter.java:
##########
@@ -272,6 +281,12 @@ public void run() {
 
           // Fetch messages
           MessageBatch messageBatch = _consumer.fetchMessages(_currentOffset, 
_fetchTimeoutMs);
+          Preconditions.checkState(!messageBatch.hasDataLoss(), "Stream data 
loss at offset %s while replaying to %s",

Review Comment:
   This changes behavior for re-ingestion. A segment whose start offset has 
already aged out of retention used to finish as a partial segment, and now the 
replay fails outright with no way to opt out. That may be the right call, but 
the description says there's no behavior change. Can we call it out in the 
description and release notes?



##########
pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeSegmentDataManager.java:
##########
@@ -1164,16 +1174,49 @@ public Map<String, ConsumerPartitionState> 
getConsumerPartitionState(
 
   /// Checks and reports if the consumer is going through data loss.
   ///
+  /// The [ServerGauge#STREAM_DATA_LOSS] gauge is set for every batch that 
reports loss. The ERROR log line and the
+  /// segment error entry are emitted at most once per 
[#DATA_LOSS_REPORT_INTERVAL_MS] per segment, carrying the
+  /// number of affected batches since the previous report, so a stream that 
keeps reporting loss on every poll
+  /// does not flood the log or churn the segment error cache. A batch with 
loss whose report was suppressed is
+  /// reported by the first batch processed after the interval has elapsed, 
even when that batch and every one before
+  /// it is healthy, so a loss event that the stream recovers from within the 
interval still reaches the log and the
+  /// segment error cache. This method runs for every fetched batch, including 
empty ones, so the deferred report
+  /// follows the end of the interval within one fetch.
+  ///
   /// @param messageBatch Message batch to validate
-  private void reportDataLoss(MessageBatch messageBatch) {
-    if (messageBatch.hasDataLoss()) {
-      _serverMetrics.setValueOfTableGauge(_clientId, 
ServerGauge.STREAM_DATA_LOSS, 1L);
-      String message = "Message loss detected in stream partition: " + 
_partitionGroupId + " for table: "
-          + _tableNameWithType + " startOffset: " + _currentOffset + " 
batchFirstOffset: "
-          + messageBatch.getFirstMessageOffset();
-      _segmentLogger.error(message);
-      _realtimeTableDataManager.addSegmentError(_segmentNameStr, new 
SegmentErrorInfo(now(), message, null));
+  @VisibleForTesting
+  void reportDataLoss(MessageBatch messageBatch) {
+    if (!messageBatch.hasDataLoss()) {
+      if (_unreportedDataLossLocation != null) {
+        long now = now();
+        if (now - _lastDataLossReportTimeMs >= DATA_LOSS_REPORT_INTERVAL_MS) {
+          logDataLoss(now, _unreportedDataLossLocation);
+        }
+      }
+      return;
     }
+    _serverMetrics.setValueOfTableGauge(_clientId, 
ServerGauge.STREAM_DATA_LOSS, 1L);
+    _dataLossBatchesSinceLastReport++;
+    String location = "startOffset: " + _currentOffset + " batchFirstOffset: " 
+ messageBatch.getFirstMessageOffset();
+    long now = now();
+    if (_lastDataLossReportTimeMs >= 0 && now - _lastDataLossReportTimeMs < 
DATA_LOSS_REPORT_INTERVAL_MS) {

Review Comment:
   A suppressed loss only gets logged when `reportDataLoss` runs again after 
the window closes, and nothing flushes it when the consume loop exits. Say a 
loss is logged at t=0, a different one is found at t=30s, and the segment 
commits at t=40s. The second loss never reaches the log or the segment error 
cache, and the gauge is removed when the segment closes. Could we emit any 
pending `_unreportedDataLossLocation` when consumption stops?



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/writer/StatelessRealtimeSegmentWriter.java:
##########
@@ -272,6 +281,12 @@ public void run() {
 
           // Fetch messages
           MessageBatch messageBatch = _consumer.fetchMessages(_currentOffset, 
_fetchTimeoutMs);
+          Preconditions.checkState(!messageBatch.hasDataLoss(), "Stream data 
loss at offset %s while replaying to %s",
+              _currentOffset, _endOffset);
+          if (messageBatch.getUnfilteredMessageCount() == 0) {

Review Comment:
   With this, the writer only finishes once a record at or after `endOffset` 
comes in. #20024 commits segments that end one past the last marker and 
switches the writer to adopting the empty batch's offset for that reason. 
During a rollout, a server with only this change could re-ingest one of those 
segments on an idle transactional partition and spin until the re-ingestion 
timeout. Could this writer change land together with #20024, or at least ship 
in the same release?



##########
pinot-plugins/pinot-stream-ingestion/pinot-kafka-3.0/src/main/java/org/apache/pinot/plugin/stream/kafka30/KafkaPartitionLevelConsumer.java:
##########
@@ -139,18 +151,15 @@ public synchronized KafkaMessageBatch 
fetchMessages(StreamPartitionMsgOffset sta
         }
         lastMessageMetadata = messageMetadata;
       }
-    } else if (_isReadCommitted) {
-      // No records returned. With read_committed isolation, the underlying 
KafkaConsumer's
-      // internal position may still have advanced past offsets filtered out 
as aborted
-      // transactional records, so snap _nextReadOffset to the consumer's 
actual position.
-      // RealtimeSegmentDataManager doesn't advance _currentOffset on empty 
batches and will
-      // call us again with the same startOffset; without this update the 
seek-check would
-      // re-seek to startOffset and undo the consumer's progress through the 
aborted region.
-      //
-      // For read_uncommitted (the default), empty polls can never advance the 
internal
-      // position past records that didn't exist, so we skip the position() 
call in the
-      // hot path. The call is bounded by the same timeout as poll() so a sick 
broker
-      // can't stall this loop unbounded.
+    } else {

Review Comment:
   In this PR nothing reads an empty batch's next offset: the data manager only 
adopts it when there are unfiltered messages, and the writer now ignores it. So 
this change doesn't do anything yet, and the offset it reports isn't validated 
against a reset. #20024 adds that validation. Would you consider keeping this 
PR to the two rate limits plus the writer's loss check, and moving the 
empty-poll position change and the keep-offset logic into #20024?



-- 
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