This is an automated email from the ASF dual-hosted git repository.
Jackie-Jiang 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 3117dbb262a Avoid repeated force commits during rebalance (#20097)
3117dbb262a is described below
commit 3117dbb262ae798f175d9e3a453b6eabd5732b6d
Author: Xiaotian (Jackie) Jiang <[email protected]>
AuthorDate: Fri Oct 30 15:13:54 2026 -0700
Avoid repeated force commits during rebalance (#20097)
---
.../helix/core/rebalance/TableRebalancer.java | 15 +-
.../rebalance/TableRebalancerForceCommitTest.java | 352 +++++++++++++++++++++
.../tests/BaseClusterIntegrationTestSet.java | 46 ++-
3 files changed, 383 insertions(+), 30 deletions(-)
diff --git
a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancer.java
b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancer.java
index c658e41e2d2..f43e7b0d14c 100644
---
a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancer.java
+++
b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancer.java
@@ -22,6 +22,8 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Suppliers;
import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import java.util.ArrayList;
import java.util.Collection;
@@ -74,6 +76,7 @@ import
org.apache.pinot.common.restlet.resources.RebalanceSummaryResult;
import org.apache.pinot.common.tier.PinotServerTierStorage;
import org.apache.pinot.common.tier.Tier;
import org.apache.pinot.common.tier.TierFactory;
+import org.apache.pinot.common.utils.LLCSegmentName;
import org.apache.pinot.common.utils.PauselessConsumptionUtils;
import org.apache.pinot.common.utils.SegmentUtils;
import org.apache.pinot.common.utils.config.TierConfigUtils;
@@ -554,6 +557,9 @@ public class TableRebalancer {
// NOTE: Monitor the segments to be moved from both the previous round and
this round to ensure the moved segments
// in the previous round are also converged.
List<String> oldSegmentsToMove = segmentsToMove;
+ // Force commit each partition's consuming segments at most once during
this rebalance. A force commit creates
+ // a consuming successor in the same partition, which must not be
committed again on retries or later steps.
+ IntSet forceCommittedPartitions = new IntOpenHashSet();
while (true) {
boolean segmentsToMoveChanged = oldSegmentsToMove != segmentsToMove;
if (segmentsToMoveChanged) {
@@ -589,7 +595,6 @@ public class TableRebalancer {
Map<String, Map<String, String>> nextAssignment;
Map<Integer, List<String>> nextPartitionAssignment;
boolean needsRecalculation;
- boolean shouldForceCommit = forceCommit;
do {
needsRecalculation = false;
@@ -677,7 +682,7 @@ public class TableRebalancer {
}
// Step 2: Handle force commit if flag is set, then recalculate if
force commit occurred
- if (shouldForceCommit) {
+ if (forceCommit) {
try {
nextAssignment = getNextAssignment(currentAssignment,
stepPartitionAssignment ? currentPartitionAssignment :
Map.of(), targetTableAssignment,
@@ -691,6 +696,8 @@ public class TableRebalancer {
tierToInstancePartitionsMap, targetAssignment,
preChecksResult, summaryResult);
}
Set<String> consumingSegmentsToMoveNext =
getMovingConsumingSegments(currentAssignment, nextAssignment);
+ consumingSegmentsToMoveNext.removeIf(
+ segment -> forceCommittedPartitions.contains(new
LLCSegmentName(segment).getPartitionGroupId()));
if (!consumingSegmentsToMoveNext.isEmpty()) {
needsRecalculation = true;
@@ -709,11 +716,13 @@ public class TableRebalancer {
return new RebalanceResult(rebalanceJobId,
RebalanceResult.Status.FAILED, errorMsg, instancePartitionsMap,
tierToInstancePartitionsMap, targetAssignment,
preChecksResult, summaryResult);
}
+ for (String segment : consumingSegmentsToMoveNext) {
+ forceCommittedPartitions.add(new
LLCSegmentName(segment).getPartitionGroupId());
+ }
idealStateRecord = idealState.getRecord();
_tableRebalanceObserver.onTrigger(TableRebalanceObserver.Trigger.FORCE_COMMIT_END_TRIGGER,
null, null,
new
TableRebalanceObserver.RebalanceContext(consumingSegmentsToMoveNext.size()));
}
- shouldForceCommit = false; // Only attempt force commit once
}
} while (needsRecalculation);
diff --git
a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancerForceCommitTest.java
b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancerForceCommitTest.java
new file mode 100644
index 00000000000..232d28e5863
--- /dev/null
+++
b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancerForceCommitTest.java
@@ -0,0 +1,352 @@
+/**
+ * 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.controller.helix.core.rebalance;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.Consumer;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.helix.AccessOption;
+import org.apache.helix.BaseDataAccessor;
+import org.apache.helix.HelixDataAccessor;
+import org.apache.helix.HelixManager;
+import org.apache.helix.PropertyKey;
+import org.apache.helix.model.ExternalView;
+import org.apache.helix.model.IdealState;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.zkclient.exception.ZkBadVersionException;
+import org.apache.pinot.common.assignment.InstancePartitions;
+import org.apache.pinot.common.assignment.PartitionAssignmentUtils;
+import org.apache.pinot.common.restlet.resources.BatchConfig;
+import org.apache.pinot.common.restlet.resources.RebalanceConfig;
+import org.apache.pinot.common.restlet.resources.RebalanceResult;
+import org.apache.pinot.common.utils.LLCSegmentName;
+import
org.apache.pinot.controller.helix.core.assignment.segment.SegmentAssignment;
+import
org.apache.pinot.controller.helix.core.assignment.segment.SegmentAssignmentFactory;
+import
org.apache.pinot.controller.helix.core.assignment.segment.TableAssignment;
+import
org.apache.pinot.controller.helix.core.realtime.PinotLLCRealtimeSegmentManager;
+import org.apache.pinot.spi.config.table.RoutingConfig;
+import org.apache.pinot.spi.config.table.TableConfig;
+import org.apache.pinot.spi.config.table.TableType;
+import org.apache.pinot.spi.config.table.assignment.InstancePartitionsType;
+import org.apache.pinot.spi.utils.Enablement;
+import org.apache.pinot.spi.utils.builder.TableConfigBuilder;
+import org.mockito.MockedStatic;
+import org.slf4j.Logger;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static
org.apache.pinot.controller.helix.core.assignment.segment.SegmentAssignmentUtils.getInstanceStateMap;
+import static
org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel.CONSUMING;
+import static
org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel.ONLINE;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+
+/// Exercises force commit through the rebalance loop with deterministic
IdealState write conflicts.
+/// Each test owns a synchronous fixture; no mutable rebalance state is shared
between invocations.
+public class TableRebalancerForceCommitTest {
+ private static final String TABLE_NAME = "forceCommit_REALTIME";
+ private static final List<String> SOURCE_INSTANCES = List.of("oldServer0",
"oldServer1");
+ private static final List<String> TARGET_INSTANCES = List.of("newServer0",
"newServer1");
+
+ @DataProvider
+ public Object[][] rebalanceSteps() {
+ return new Object[][]{{0, 0, 0}, {0, 3, 0}, {1, 0, 0}, {1, 1, 1}};
+ }
+
+ @Test(dataProvider = "rebalanceSteps")
+ public void testForceCommitOnceAcrossRetriesAndMovementSteps(int
minAvailableReplicas, int conflicts,
+ int successfulWritesBeforeConflict) throws Exception {
+ RebalanceFixture fixture = new RebalanceFixture(0);
+ fixture._config.setMinAvailableReplicas(minAvailableReplicas);
+ fixture._conflictsRemaining = conflicts;
+ fixture._successfulWritesBeforeConflict = successfulWritesBeforeConflict;
+ fixture.assertSuccess(0);
+ assertEquals(fixture._commitsAtWrites.size(), minAvailableReplicas == 1 ?
2 : 1);
+ }
+
+ @Test
+ public void testForceCommitNewPartitionSelectedAfterConflict() throws
Exception {
+ RebalanceFixture fixture = new RebalanceFixture(0, 1);
+ fixture._config.setBatchSizePerServer(1);
+ fixture._beforeWrite = () -> fixture.movePartition(0, TARGET_INSTANCES);
+ fixture.assertSuccess(0, 1);
+ assertEquals(fixture._writeAttempts, 2);
+ }
+
+ @Test
+ public void testRememberDeferredPartitionAfterConflict() throws Exception {
+ RebalanceFixture fixture = new RebalanceFixture(1);
+ fixture._config.setBatchSizePerServer(1);
+ PartitionAssignmentUtils.setInstances(fixture._state, 0, SOURCE_INSTANCES);
+ // After partition 1 commits, the competing write adds partition 0, which
the next batch moves first.
+ fixture._beforeWrite = () -> {
+ fixture.addPartition(0);
+ fixture.advanceVersion();
+ };
+ fixture.assertSuccess(1, 0);
+ assertEquals(fixture._commitsAtWrites, List.of(2, 2));
+ }
+
+ @Test
+ public void testCommitPublicationRacesWithAssignmentWrite() throws Exception
{
+ RebalanceFixture fixture = new RebalanceFixture(0);
+ // The wait observes DONE metadata; publication of ONLINE + successor in
IdealState is still pending.
+ fixture._onCommit = segments -> {
+ fixture._beforeWrite = () -> fixture.publishCommit(segments, null);
+ };
+ fixture.assertSuccess(0);
+ assertEquals(fixture._writeAttempts, 2);
+ }
+
+ @Test
+ public void testForceCommitNewPartitionSelectedDuringInnerReplan() throws
Exception {
+ RebalanceFixture fixture = new RebalanceFixture(1);
+ fixture._config.setBatchSizePerServer(1);
+ PartitionAssignmentUtils.setInstances(fixture._state, 0, SOURCE_INSTANCES);
+ fixture._onCommit = segments -> {
+ fixture.publishCommit(segments, null);
+ if (fixture._commits.size() == 1) {
+ fixture.addPartition(0);
+ }
+ };
+ fixture.assertSuccess(1, 0);
+ // Both partitions commit before the first assignment write, and partition
1 stays remembered afterward.
+ assertEquals(fixture._commitsAtWrites, List.of(2, 2));
+ }
+
+ @Test
+ public void testForceCommitFailureAbortsBeforeMovingSegments() throws
Exception {
+ RebalanceFixture fixture = new RebalanceFixture(0);
+ IdealState initialState = copy(fixture._state);
+ fixture._onCommit = segments -> {
+ throw new IllegalStateException("Injected force commit failure");
+ };
+ RebalanceResult result = fixture.run();
+ assertEquals(result.getStatus(), RebalanceResult.Status.FAILED);
+ assertTrue(result.getDescription().contains("Injected force commit
failure"));
+ assertEquals(fixture._commits, List.of(Set.of(segmentName(0, 11))));
+ assertEquals(fixture._writeAttempts, 0);
+ assertEquals(fixture._state.getRecord().getMapFields(),
initialState.getRecord().getMapFields());
+ }
+
+ @Test
+ public void testForceCommitDisabled() throws Exception {
+ RebalanceFixture fixture = new RebalanceFixture(0);
+ fixture._config.setForceCommit(false);
+ fixture.assertSuccess();
+ }
+
+ @Test
+ public void testRememberForceCommitAfterConcurrentMovement() throws
Exception {
+ RebalanceFixture fixture = new RebalanceFixture(0);
+ fixture._config.setMinAvailableReplicas(1);
+ fixture._beforeWrite = () -> fixture.movePartition(0,
+ List.of(SOURCE_INSTANCES.getFirst(), TARGET_INSTANCES.getFirst()));
+ fixture.assertSuccess(0);
+ assertEquals(fixture._writeAttempts, 2);
+ }
+
+ @Test
+ public void testRememberForceCommitAfterNonStrictSuccessorRelocation()
throws Exception {
+ RebalanceFixture fixture = new RebalanceFixture(0);
+ fixture._tableConfig.setRoutingConfig(null);
+ fixture._state.getRecord().getListFields().clear();
+ fixture._onCommit = segments -> {
+ // The original stays ONLINE on A; its non-pinned successor consumes on
B. Recalculation then targets C.
+ fixture.publishCommit(segments, TARGET_INSTANCES);
+ fixture._targetInstances = List.of("reassignedServer0",
"reassignedServer1");
+ };
+ fixture.assertSuccess(0);
+ assertEquals(fixture._commitsAtWrites, List.of(1));
+ }
+
+ /// In-memory IdealState store with hooks for commit completion and one
competing write.
+ /// Each fixture is confined to one test thread.
+ private static class RebalanceFixture {
+ private final RebalanceConfig _config = new RebalanceConfig();
+ private final TableConfig _tableConfig = new
TableConfigBuilder(TableType.REALTIME).setTableName(TABLE_NAME)
+ .setNumReplicas(2).setRoutingConfig(new RoutingConfig(null, null,
+ RoutingConfig.STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE,
false)).build();
+ private IdealState _state = new IdealState(TABLE_NAME);
+ private List<String> _targetInstances = TARGET_INSTANCES;
+ private final Set<Integer> _partitions = new HashSet<>();
+ private final List<Set<String>> _commits = new ArrayList<>();
+ private final List<Integer> _commitsAtWrites = new ArrayList<>();
+ private int _writeAttempts;
+ private int _conflictsRemaining;
+ private int _successfulWritesBeforeConflict;
+ private Runnable _beforeWrite;
+ private Consumer<Set<String>> _onCommit = segments ->
publishCommit(segments, null);
+
+ RebalanceFixture(int... partitions) {
+ _state.setReplicas("2");
+ Arrays.stream(partitions).forEach(this::addPartition);
+ _config.setDryRun(false);
+ _config.setDisableSummary(true);
+ _config.setIncludeConsuming(true);
+ _config.setForceCommit(true);
+ _config.setMinAvailableReplicas(0);
+ }
+
+ void addPartition(int partition) {
+ _partitions.add(partition);
+ _state.getRecord().setMapField(segmentName(partition, 11),
getInstanceStateMap(SOURCE_INSTANCES, CONSUMING));
+ PartitionAssignmentUtils.setInstances(_state, partition,
SOURCE_INSTANCES);
+ }
+
+ void advanceVersion() {
+ _state.getRecord().setVersion(_state.getRecord().getVersion() + 1);
+ }
+
+ void movePartition(int partition, List<String> instances) {
+ _state.getRecord().getMapFields().replaceAll((segment, states) ->
+ new LLCSegmentName(segment).getPartitionGroupId() == partition
+ ? getInstanceStateMap(instances,
states.values().iterator().next()) : states);
+ PartitionAssignmentUtils.setInstances(_state, partition, instances);
+ advanceVersion();
+ }
+
+ void publishCommit(Set<String> segments, List<String> successorInstances) {
+ for (String segment : segments) {
+ LLCSegmentName name = new LLCSegmentName(segment);
+ List<String> instances = new
ArrayList<>(_state.getRecord().getMapField(segment).keySet());
+ _state.getRecord().setMapField(segment, getInstanceStateMap(instances,
ONLINE));
+ _state.getRecord().setMapField(segmentName(name.getPartitionGroupId(),
name.getSequenceNumber() + 1),
+ getInstanceStateMap(successorInstances != null ?
successorInstances : instances, CONSUMING));
+ }
+ advanceVersion();
+ }
+
+ boolean writeAssignment(ZNRecord record, int expectedVersion) {
+ _writeAttempts++;
+ assertTrue(_writeAttempts <= 20, "Rebalance did not make progress after
20 writes");
+ assertEquals(expectedVersion, _state.getRecord().getVersion(),
"Assignment used an outdated IdealState");
+ if (_beforeWrite != null) {
+ Runnable beforeWrite = _beforeWrite;
+ _beforeWrite = null;
+ beforeWrite.run();
+ }
+ if (_conflictsRemaining > 0 && _commitsAtWrites.size() >=
_successfulWritesBeforeConflict) {
+ assertEquals(_commitsAtWrites.size(), _successfulWritesBeforeConflict);
+ _conflictsRemaining--;
+ advanceVersion();
+ }
+ if (expectedVersion != _state.getRecord().getVersion()) {
+ throw new ZkBadVersionException("Injected concurrent IdealState
write");
+ }
+ _state = copy(new IdealState(record));
+ advanceVersion();
+ _commitsAtWrites.add(_commits.size());
+ return true;
+ }
+
+ void assertSuccess(int... committedPartitions) throws Exception {
+ RebalanceResult result = run();
+ assertEquals(result.getStatus(), RebalanceResult.Status.DONE,
result.getDescription());
+ assertEquals(_conflictsRemaining, 0);
+ assertEquals(_commits, Arrays.stream(committedPartitions).mapToObj(id ->
Set.of(segmentName(id, 11))).toList());
+ for (int partition : _partitions) {
+ assertEquals(_state.getRecord().getMapField(segmentName(partition,
_config.isForceCommit() ? 12 : 11)),
+ getInstanceStateMap(_targetInstances, CONSUMING));
+ }
+ }
+
+ RebalanceResult run() throws Exception {
+ HelixManager manager = mock(HelixManager.class);
+ HelixDataAccessor accessor = mock(HelixDataAccessor.class);
+ @SuppressWarnings("unchecked")
+ BaseDataAccessor<ZNRecord> baseAccessor = mock(BaseDataAccessor.class);
+ PropertyKey.Builder keys = new PropertyKey.Builder("testCluster");
+ when(manager.getHelixDataAccessor()).thenReturn(accessor);
+ when(accessor.keyBuilder()).thenReturn(keys);
+ when(accessor.getBaseDataAccessor()).thenReturn(baseAccessor);
+
when(accessor.getProperty(keys.idealStates(TABLE_NAME))).thenAnswer(invocation
-> copy(_state));
+ when(accessor.getProperty(keys.externalView(TABLE_NAME)))
+ .thenAnswer(invocation -> new
ExternalView(copy(_state).getRecord()));
+ when(baseAccessor.set(eq(keys.idealStates(TABLE_NAME).getPath()),
any(ZNRecord.class), anyInt(),
+ eq(AccessOption.PERSISTENT))).thenAnswer(invocation ->
+ writeAssignment(invocation.getArgument(1),
invocation.getArgument(2)));
+ PinotLLCRealtimeSegmentManager segmentManager =
mock(PinotLLCRealtimeSegmentManager.class);
+ when(segmentManager.forceCommit(eq(TABLE_NAME), isNull(),
any(String.class), any(BatchConfig.class)))
+ .thenAnswer(invocation -> {
+ Set<String> segments = new
HashSet<>(Arrays.asList(invocation.<String>getArgument(2).split(",")));
+ // Match the real API's filtering of names that are no longer
CONSUMING in IdealState.
+ segments.removeIf(segment ->
!_state.getRecord().getMapFields().containsKey(segment)
+ ||
!_state.getRecord().getMapField(segment).containsValue(CONSUMING));
+ _commits.add(segments);
+ return segments;
+ });
+ doAnswer(invocation -> {
+ _onCommit.accept(invocation.getArgument(1));
+ return null;
+ }).when(segmentManager).waitUntilSegmentsForceCommitted(eq(TABLE_NAME),
any(), any());
+ SegmentAssignment assignment = mock(SegmentAssignment.class);
+ when(assignment.rebalanceTable(any(), any(), any(), any(),
any())).thenAnswer(invocation -> {
+ IdealState state = invocation.getArgument(0);
+ Map<String, Map<String, String>> target = new TreeMap<>();
+ state.getRecord().getMapFields().forEach((segment, states) ->
+ target.put(segment, getInstanceStateMap(_targetInstances,
states.values().iterator().next())));
+ Map<Integer, List<String>> targetPartitions = new TreeMap<>();
+ PartitionAssignmentUtils.getPartitionAssignment(state).keySet()
+ .forEach(partition -> targetPartitions.put(partition,
_targetInstances));
+ return new TableAssignment(target, targetPartitions);
+ });
+ TableRebalancer rebalancer = new TableRebalancer(manager, new
NoOpTableRebalanceObserver(), null, null, null,
+ segmentManager) {
+ @Override
+ public Pair<Map<InstancePartitionsType, InstancePartitions>, Boolean>
getInstancePartitionsMap(
+ TableConfig config, boolean reassignInstances, boolean bootstrap,
boolean dryRun,
+ Enablement minimizeDataMovement, Logger logger) {
+ return Pair.of(Map.of(), true);
+ }
+ };
+ try (MockedStatic<SegmentAssignmentFactory> factory =
mockStatic(SegmentAssignmentFactory.class)) {
+ factory.when(() ->
SegmentAssignmentFactory.getSegmentAssignment(manager,
_tableConfig)).thenReturn(assignment);
+ return rebalancer.rebalance(_tableConfig, _config, null);
+ }
+ }
+ }
+
+ private static String segmentName(int partition, int sequence) {
+ return new LLCSegmentName("forceCommit", partition, sequence,
0).getSegmentName();
+ }
+
+ private static IdealState copy(IdealState state) {
+ ZNRecord record = new ZNRecord(state.getRecord());
+ // ZNRecord's copy constructor shares nested collections; persisted
reads/writes must not share them.
+ state.getRecord().getMapFields().forEach((segment, states) ->
record.setMapField(segment, new TreeMap<>(states)));
+ state.getRecord().getListFields().forEach((key, values) ->
record.setListField(key, new ArrayList<>(values)));
+ return new IdealState(record);
+ }
+}
diff --git
a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/BaseClusterIntegrationTestSet.java
b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/BaseClusterIntegrationTestSet.java
index 9f4c0e4b31d..00f34387f0c 100644
---
a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/BaseClusterIntegrationTestSet.java
+++
b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/BaseClusterIntegrationTestSet.java
@@ -29,6 +29,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.helix.PropertyKey;
import org.apache.helix.model.ExternalView;
@@ -40,6 +41,7 @@ import org.apache.pinot.client.ResultSet;
import org.apache.pinot.client.ResultSetGroup;
import org.apache.pinot.client.admin.PinotAdminException;
import org.apache.pinot.client.admin.PinotAdminNotFoundException;
+import org.apache.pinot.common.metadata.segment.SegmentZKMetadata;
import
org.apache.pinot.common.restlet.resources.PinotTableReloadStatusResponse;
import org.apache.pinot.common.restlet.resources.RebalanceConfig;
import org.apache.pinot.common.restlet.resources.RebalanceResult;
@@ -48,7 +50,6 @@ import
org.apache.pinot.common.restlet.resources.TableSegmentsReloadCheckRespons
import org.apache.pinot.common.restlet.resources.TableView;
import org.apache.pinot.common.utils.LLCSegmentName;
import org.apache.pinot.common.utils.config.InstanceUtils;
-import org.apache.pinot.controller.util.ConsumingSegmentInfoReader;
import org.apache.pinot.core.query.utils.idset.IdSet;
import org.apache.pinot.core.query.utils.idset.IdSets;
import org.apache.pinot.spi.config.instance.Instance;
@@ -60,6 +61,7 @@ import org.apache.pinot.spi.data.FieldSpec;
import org.apache.pinot.spi.data.MetricFieldSpec;
import org.apache.pinot.spi.data.Schema;
import org.apache.pinot.spi.utils.CommonConstants;
+import org.apache.pinot.spi.utils.CommonConstants.Segment.Realtime.Status;
import org.apache.pinot.spi.utils.InstanceTypeUtils;
import org.apache.pinot.spi.utils.JsonUtils;
import org.apache.pinot.spi.utils.StringUtil;
@@ -1129,20 +1131,17 @@ public abstract class BaseClusterIntegrationTestSet
extends BaseClusterIntegrati
// Set force commit
rebalanceConfig.setForceCommit(true);
+ // Snapshot all consuming segments, including when includeConsuming is
false and the rebalance summary has none.
+ String tableNameWithType = tableConfig.getTableName();
+ Set<String> originalConsumingSegments =
_helixResourceManager.getConsumingSegments(tableNameWithType);
+ assertFalse(originalConsumingSegments.isEmpty(), "Expected consuming
segments before rebalance");
+ Map<Integer, Integer> expectedSequences =
originalConsumingSegments.stream().map(LLCSegmentName::new)
+ .collect(Collectors.toMap(LLCSegmentName::getPartitionGroupId,
+ segment -> segment.getSequenceNumber() + (shouldCommit ? 1 : 0)));
+
// Execute rebalance
RebalanceResult rebalanceResult = triggerTableRebalance(rebalanceConfig,
TableType.REALTIME);
- // Get original consuming segments (if present)
- Set<String> originalConsumingSegmentsToMove = null;
- if (rebalanceResult.getRebalanceSummaryResult() != null
- && rebalanceResult.getRebalanceSummaryResult().getSegmentInfo() != null
- &&
rebalanceResult.getRebalanceSummaryResult().getSegmentInfo().getConsumingSegmentToBeMovedSummary()
!= null) {
- originalConsumingSegmentsToMove =
rebalanceResult.getRebalanceSummaryResult().getSegmentInfo()
- .getConsumingSegmentToBeMovedSummary()
- .getConsumingSegmentsToBeMovedWithMostOffsetsToCatchUp()
- .keySet();
- }
-
// Wait for completion based on the flag
if (waitForEVISConverge) {
waitForTableEVISConverge(getTableName(), timeoutMs);
@@ -1150,21 +1149,14 @@ public abstract class BaseClusterIntegrationTestSet
extends BaseClusterIntegrati
waitForRebalanceToComplete(rebalanceResult.getJobId(), timeoutMs);
}
- // Check if segments were committed (only if there were consuming segments
to move)
- if (originalConsumingSegmentsToMove != null &&
!originalConsumingSegmentsToMove.isEmpty()) {
- ConsumingSegmentInfoReader.ConsumingSegmentsInfoMap
consumingSegmentInfoResponse =
-
getOrCreateAdminClient().getTableClient().getConsumingSegmentsInfo(getTableName(),
- ConsumingSegmentInfoReader.ConsumingSegmentsInfoMap.class);
- LLCSegmentName consumingSegmentNow = new LLCSegmentName(
-
consumingSegmentInfoResponse._segmentToConsumingInfoMap.keySet().stream().sorted().iterator().next());
- LLCSegmentName consumingSegmentOriginal =
- new
LLCSegmentName(originalConsumingSegmentsToMove.stream().sorted().iterator().next());
-
- if (shouldCommit) {
- assertEquals(consumingSegmentNow.getSequenceNumber(),
consumingSegmentOriginal.getSequenceNumber() + 1);
- } else {
- assertEquals(consumingSegmentNow.getSequenceNumber(),
consumingSegmentOriginal.getSequenceNumber());
- }
+ for (String originalSegment : originalConsumingSegments) {
+ SegmentZKMetadata metadata =
_helixResourceManager.getSegmentZKMetadata(tableNameWithType, originalSegment);
+ assertNotNull(metadata, "Missing metadata for " + originalSegment);
+ assertEquals(metadata.getStatus(), shouldCommit ? Status.DONE :
Status.IN_PROGRESS, originalSegment);
}
+ Map<Integer, Integer> currentSequences =
_helixResourceManager.getConsumingSegments(tableNameWithType).stream()
+ .map(LLCSegmentName::new)
+ .collect(Collectors.toMap(LLCSegmentName::getPartitionGroupId,
LLCSegmentName::getSequenceNumber));
+ assertEquals(currentSequences, expectedSequences);
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]