Skip to content

Skip missing consuming segment check for paused realtime tables - #19310

Open
rohityadav1993 wants to merge 1 commit into
apache:masterfrom
rohityadav1993:fix-missing-consuming-segment-paused-tables
Open

Skip missing consuming segment check for paused realtime tables#19310
rohityadav1993 wants to merge 1 commit into
apache:masterfrom
rohityadav1993:fix-missing-consuming-segment-paused-tables

Conversation

@rohityadav1993

@rohityadav1993 rohityadav1993 commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

bug metric

Summary

missingConsumingSegmentTotalCount and its two sibling gauges report a false positive for REALTIME tables whose ingestion is intentionally paused. This gates the check on the table's pause state and resets the gauges while paused.

Problem

SegmentStatusChecker.updateSegmentMetrics invokes MissingConsumingSegmentFinder for every enabled REALTIME table, with no check for whether the table is paused via the pause/resume ingestion API.

When a table is paused, PinotLLCRealtimeSegmentManager deliberately does not create a replacement CONSUMING segment after the current one commits, leaving the partition with only a completed segment. MissingConsumingSegmentFinder treats exactly that shape — no consuming segment, a completed segment, and a stream offset that has advanced past it — as a missing consumer. Since the stream keeps advancing while paused, the condition never clears, so MISSING_CONSUMING_SEGMENT_MAX_DURATION_MINUTES grows for the duration of the pause. The result is indistinguishable from a genuine ingestion failure.

Disabled tables are unaffected: updateSegmentMetrics returns early on !idealState.isEnabled() and calls removeMetricsForTable. Paused tables had no equivalent guard.

The existing per-topic PauseState.getIndexOfInactiveTopics() exclusion inside the finder does not cover this. updatePauseStateInIdealState only carries forward a pre-existing inactive-topic list; a whole-table pause never populates it, so isPaused() == true leaves the finder's per-topic exclusion empty.

Fix

Reuse the pause state already computed in updateSegmentMetrics for the TABLE_CONSUMPTION_PAUSED gauge, and skip the finder when the table is paused.

These gauges are last-write-wins with no "unset" value, so simply skipping the call would leave whatever value was written before the pause in place, alerting indefinitely. The paused branch therefore actively resets them through a new MissingConsumingSegmentFinder.resetMetrics, which keeps the set of gauge names in the class that emits them — otherwise a future gauge added to findAndEmitMetrics would silently go stale on the paused path, reintroducing this same bug.

No config, REST, metric-name, or wire-format changes. The only new surface is MissingConsumingSegmentFinder.resetMetrics, an internal helper on an existing controller-side class.

Known gap / possible follow-up

This gates on whole-table pause (isTablePaused) only. Individually paused topics in a multi-topic stream config (PauseState.getIndexOfInactiveTopics(), added in #16692) are already handled for the ordinary case: PartitionGroupMetadataFetcher.fetchMultipleStreams skips paused topic indices, so those partitions never enter _partitionGroupIdToLargestStreamOffsetMap and the main detection loop never examines them.

There is a narrower gap, which I have left alone here. When that map ends up empty, findMissingSegments falls back to iterating partitionGroupIdToLatestCompletedSegmentMap and counts every partition without a consuming segment as missing, consulting neither pause state nor stream offsets. Pausing every topic of a multi-topic table individually reaches that fallback. The same fallback is also taken when the stream-metadata fetch throws, which is existing behaviour and a separate question.

That case seemed better handled on its own, since it is about the fallback path's semantics rather than the pause gate this PR adds. Happy to follow up separately, or to fold it in here if reviewers prefer.

Test Plan

New SegmentStatusCheckerTest.realtimePausedTableHasNoMissingConsumingSegmentAlert, built on the existing realtimeBasicTest fixture and run in two phases against the same metrics instance:

  1. unpaused — asserts MISSING_CONSUMING_SEGMENT_TOTAL_COUNT == 2, establishing a non-zero reading
  2. paused — asserts all three MISSING_CONSUMING_SEGMENT_* gauges are 0

The two-phase shape is deliberate: it covers the actual production scenario, where the gauge is already reporting non-zero from an earlier run and the pause must actively clear it, rather than merely never setting it.

Verified the test fails without the fix. With SegmentStatusChecker.java reverted to master, the run fails on the second phase with expected [0] but found [2], and the surefire log contains no Caught exception while updating segment status — confirming the failure is a real computed value rather than a swallowed exception leaving the gauge at its default.

  • SegmentStatusCheckerTest: 42/42 pass
  • MissingConsumingSegmentFinderTest: 6/6 pass, no regression
  • spotless:apply, checkstyle:check, license:check on pinot-controller: clean

SegmentStatusChecker invoked MissingConsumingSegmentFinder for every
enabled REALTIME table without checking whether ingestion was paused.
Pausing deliberately leaves a partition with no CONSUMING segment once
the current one commits, so the finder read that as a missing consumer
and the missingConsumingSegment* gauges reported a growing false
positive for as long as the pause lasted.

Gate the finder on the table pause state already computed in
updateSegmentMetrics. Because these gauges are last-write-wins with no
"unset" value, the paused branch actively resets them via a new
MissingConsumingSegmentFinder.resetMetrics, keeping ownership of the
gauge names with the class that emits them.
@codecov-commenter

codecov-commenter commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 67.18%. Comparing base (e4888b0) to head (3f19e70).
⚠️ Report is 2 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19310      +/-   ##
============================================
- Coverage     67.20%   67.18%   -0.03%     
  Complexity     1424     1424              
============================================
  Files          3462     3462              
  Lines        220361   220368       +7     
  Branches      35147    35148       +1     
============================================
- Hits         148087   148046      -41     
- Misses        60467    60518      +51     
+ Partials      11807    11804       -3     
Flag Coverage Δ
integration 100.00% <ø> (ø)
integration1 100.00% <ø> (ø)
integration2 0.00% <ø> (ø)
java-25 67.18% <100.00%> (-0.03%) ⬇️
lane-a 100.00% <ø> (ø)
lane-b 0.00% <ø> (ø)
temurin 67.18% <100.00%> (-0.03%) ⬇️
unittests 67.17% <100.00%> (-0.03%) ⬇️
unittests1 57.83% <ø> (-0.02%) ⬇️
unittests2 39.20% <100.00%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@rohityadav1993
rohityadav1993 marked this pull request as ready for review August 19, 2026 06:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants