Copilot commented on code in PR #19170:
URL: https://github.com/apache/pinot/pull/19170#discussion_r3762580946
##########
pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManager.java:
##########
@@ -1448,28 +1448,34 @@ public void ensureAllPartitionsConsuming(TableConfig
tableConfig, List<StreamCon
Preconditions.checkState(!_isStopping, "Segment manager is stopping");
String realtimeTableName = tableConfig.getTableName();
+
+ // Fetch all stream offsets BEFORE acquiring the ideal-state update lock.
On tables with many partitions these
+ // stream round-trips can take minutes; doing them here (against a
snapshot of the ideal state) keeps the
+ // per-table ideal-state lock hold-time proportional to the in-memory
ideal-state mutation, not to the offset
+ // I/O. The updater lambda below performs no stream I/O, so it is also
cheap to re-run on ZK CAS retries.
+ IdealState snapshotIdealState =
HelixHelper.getTableIdealState(_helixManager, realtimeTableName);
+ if (snapshotIdealState == null) {
+ LOGGER.warn("Cannot find ideal state for table: {}, skipping
ensureAllPartitionsConsuming", realtimeTableName);
+ return;
+ }
+ if (!snapshotIdealState.isEnabled() || isTablePaused(snapshotIdealState)) {
+ LOGGER.info("Skipping LLC segments validation for table: {},
isTableEnabled: {}, isTablePaused: {}",
+ realtimeTableName, snapshotIdealState.isEnabled(),
isTablePaused(snapshotIdealState));
+ return;
+ }
+
try {
+ PreFetchedOffsets preFetchedOffsets =
+ preFetchOffsets(streamConfigs, realtimeTableName,
snapshotIdealState, offsetCriteria);
+
HelixHelper.updateIdealState(_helixManager, realtimeTableName,
idealState -> {
assert idealState != null;
boolean isTableEnabled = idealState.isEnabled();
boolean isTablePaused = isTablePaused(idealState);
- boolean offsetsHaveToChange = offsetCriteria != null;
if (isTableEnabled && !isTablePaused) {
- List<PartitionGroupConsumptionStatus>
currentPartitionGroupConsumptionStatusList =
- offsetsHaveToChange ? List.of()
- // offsets from metadata are not valid anymore; fetch for
all partitions
- : getPartitionGroupConsumptionStatusList(idealState,
streamConfigs);
- // FIXME: Right now, we assume topics are sharing same offset
criteria
- OffsetCriteria originalOffsetCriteria =
streamConfigs.get(0).getOffsetCriteria();
- // Read the smallest offset when a new partition is detected
- streamConfigs.stream()
- .forEach(streamConfig -> streamConfig.setOffsetCriteria(
- offsetsHaveToChange ? offsetCriteria :
OffsetCriteria.SMALLEST_OFFSET_CRITERIA));
- List<StreamMetadata> streamMetadataList =
- getNewStreamMetadataList(streamConfigs,
currentPartitionGroupConsumptionStatusList, idealState);
- streamConfigs.stream().forEach(streamConfig ->
streamConfig.setOffsetCriteria(originalOffsetCriteria));
- return ensureAllPartitionsConsuming(tableConfig, streamConfigs,
idealState, streamMetadataList,
- offsetCriteria);
+ return ensureAllPartitionsConsuming(tableConfig, streamConfigs,
idealState,
+ preFetchedOffsets._streamMetadataList, offsetCriteria,
Review Comment:
The fresh-state recheck only covers a whole-table pause. A partial topic
pause updates `PauseState.indexOfInactiveTopics` while `isTablePaused()`
remains false, so if that update lands after the snapshot, this lambda still
applies metadata fetched for the now-paused topic. It can repair that topic or
create a newly discovered partition after the pause, reintroducing CONSUMING
segments that the pause operation intentionally stopped. Revalidate per-topic
pause state against this fresh `idealState` before both repair and
new-partition creation (and add the pause-between-prefetch-and-update
interleaving test).
##########
pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManager.java:
##########
@@ -1481,6 +1487,78 @@ public void ensureAllPartitionsConsuming(TableConfig
tableConfig, List<StreamCon
}
}
+ /// Fetches, from a read-only snapshot of the ideal state and outside the
ideal-state update lock, the stream
+ /// state needed by [#ensureAllPartitionsConsuming]: the latest
partition-group metadata (with start offsets) and,
+ /// when required, the smallest stream offset per partition.
+ ///
+ /// The smallest-offset fetch is a real stream round-trip, so it is only
performed when it can actually be used:
+ /// on a reset (`offsetCriteria != null`) or when at least one partition
currently lacks a CONSUMING segment and
+ /// may need a new one created. On a healthy table nothing is fetched and
`null` is returned for it, signalling
+ /// the repair pass to reuse the start offsets. When the criteria is
SMALLEST the start offsets already are the
+ /// smallest offsets, so it is likewise left `null`.
+ ///
+ /// Any temporary mutation of the shared `streamConfigs` offset criteria is
always restored, even on error.
+ @VisibleForTesting
+ PreFetchedOffsets preFetchOffsets(List<StreamConfig> streamConfigs, String
realtimeTableName,
+ IdealState snapshotIdealState, OffsetCriteria offsetCriteria) {
+ boolean offsetsHaveToChange = offsetCriteria != null;
+ List<PartitionGroupConsumptionStatus>
currentPartitionGroupConsumptionStatusList =
+ offsetsHaveToChange ? List.of()
+ // offsets from metadata are not valid anymore; fetch for all
partitions
+ : getPartitionGroupConsumptionStatusList(snapshotIdealState,
streamConfigs);
+ // FIXME: Right now, we assume topics are sharing same offset criteria
+ OffsetCriteria originalOffsetCriteria =
streamConfigs.get(0).getOffsetCriteria();
+ // For the periodic run, compute start offsets with SMALLEST so a newly
detected partition starts from the
+ // beginning; for a reset, use the requested criteria. Restored in the
finally below.
+ streamConfigs.forEach(streamConfig -> streamConfig.setOffsetCriteria(
+ offsetsHaveToChange ? offsetCriteria :
OffsetCriteria.SMALLEST_OFFSET_CRITERIA));
+ try {
+ List<StreamMetadata> streamMetadataList =
+ getNewStreamMetadataList(streamConfigs,
currentPartitionGroupConsumptionStatusList, snapshotIdealState);
+ Map<Integer, StreamPartitionMsgOffset> partitionIdToSmallestOffset =
null;
+ if (offsetCriteria == null ||
!offsetCriteria.equals(OffsetCriteria.SMALLEST_OFFSET_CRITERIA)) {
+ Map<Integer, SegmentZKMetadata> latestSegmentZKMetadataMap =
getLatestSegmentZKMetadataMap(realtimeTableName);
+ if (offsetsHaveToChange ||
anyPartitionNeedsSmallestOffset(snapshotIdealState,
latestSegmentZKMetadataMap)) {
+ partitionIdToSmallestOffset =
+ fetchPartitionGroupIdToSmallestOffset(streamConfigs,
snapshotIdealState, latestSegmentZKMetadataMap);
Review Comment:
This eagerly performs another full latest-metadata scan merely to decide
whether smallest offsets are needed. `getPartitionGroupConsumptionStatusList()`
above already reads the latest ZK metadata once per partition, and the updater
reads it again at line 1817; this call adds a third O(partitions) set of
PropertyStore reads on every healthy validation cycle (about 1,024 extra reads
for the motivating table). Determine the gate from the snapshot's latest
IdealState segment names/current statuses, and only build the ZK metadata map
when the smallest-offset fetch is actually required.
##########
pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManagerTest.java:
##########
@@ -2494,8 +2625,11 @@ public void setUpNewTable() {
}
public void ensureAllPartitionsConsuming() {
- ensureAllPartitionsConsuming(_tableConfig, _streamConfigs, _idealState,
- getNewStreamMetadataList(_streamConfigs, List.of(),
mock(IdealState.class)), null);
+ // Mirror the production flow: pre-fetch offsets (gated) outside the
ideal-state update, then pass them into the
+ // package-private repair method.
+ PreFetchedOffsets preFetchedOffsets = preFetchOffsets(_streamConfigs,
REALTIME_TABLE_NAME, _idealState, null);
+ ensureAllPartitionsConsuming(_tableConfig, _streamConfigs, _idealState,
preFetchedOffsets._streamMetadataList,
+ null, preFetchedOffsets._partitionIdToSmallestOffset);
Review Comment:
This test helper bypasses the changed public method and calls prefetch and
repair sequentially itself, so none of the tests verifies the core regression:
stream metadata calls must occur before `HelixHelper.updateIdealState`, and CAS
retries must not repeat them. Add a test around the public
`ensureAllPartitionsConsuming` path with a controlled Helix accessor/updater
that records call order and retries the updater; assert each stream fetch
occurs once and outside the updater.
--
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]