You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pinot.apache.org by xi...@apache.org on 2019/08/21 07:49:24 UTC
[incubator-pinot] branch winedepot-0.10 created (now d81112f)
This is an automated email from the ASF dual-hosted git repository.
xiangfu pushed a change to branch winedepot-0.10
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git.
at d81112f Update selection columns logic
This branch includes the following new commits:
new 09ce670 Handle NPE from getting live instance (#4528)
new d6de05d brag about Star-Tree Index support (#4530)
new 179377c [Controller Separation] Add logic for lead controller resource (#4323)
new 5fcb412 [Instance Assignment] Plug in instance assignment (#4516)
new 1c1d497 [TE] frontend - harleyjj/create-alert - add link for onboarding mysql and presto metrics (#4483)
new f2baddd [Instance Assignment] De-couple assignment strategy from SegmentAssignment (#4533)
new e80b53a Fixing the issue of selection order by won't give correct ordering
new d81112f Update selection columns logic
The 8 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org
[incubator-pinot] 05/08: [TE] frontend - harleyjj/create-alert -
add link for onboarding mysql and presto metrics (#4483)
Posted by xi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
xiangfu pushed a commit to branch winedepot-0.10
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git
commit 1c1d497c568e781185b95e689ee9f2782e5d59d5
Author: Harley Jackson <hj...@linkedin.com>
AuthorDate: Mon Aug 19 08:51:08 2019 -0700
[TE] frontend - harleyjj/create-alert - add link for onboarding mysql and presto metrics (#4483)
---
.../app/pods/components/detection-yaml/template.hbs | 5 +++--
.../app/pods/self-serve/import-sql-metric/controller.js | 4 ++--
.../tests/acceptance/self-serve-import-metric-test.js | 3 +--
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/detection-yaml/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/detection-yaml/template.hbs
index 108bb5c..0cdee45 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/detection-yaml/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/detection-yaml/template.hbs
@@ -3,9 +3,10 @@
</div>
<div class="col-xs-12 {{if isEditMode "bottom-margin"}}">
{{#unless isEditMode}}
- <label for="select-metric" class="control-label te-label te-label--taller required">Can't find your metric?
+ <label for="select-metric" class="control-label te-label te-label--taller required">Can't find your metric? Import a Metric From
{{#link-to "self-serve.import-metric" class="thirdeye-link-secondary thirdeye-link-secondary--inside"}}
- Import a Metric From InGraphs
+ InGraphs{{/link-to}} or {{#link-to "self-serve.import-sql-metric" class="thirdeye-link-secondary thirdeye-link-secondary--inside"}}
+ MySQL/Presto
{{/link-to}}
</label>
{{/unless}}
diff --git a/thirdeye/thirdeye-frontend/app/pods/self-serve/import-sql-metric/controller.js b/thirdeye/thirdeye-frontend/app/pods/self-serve/import-sql-metric/controller.js
index f2798f5..d4135cf 100644
--- a/thirdeye/thirdeye-frontend/app/pods/self-serve/import-sql-metric/controller.js
+++ b/thirdeye/thirdeye-frontend/app/pods/self-serve/import-sql-metric/controller.js
@@ -23,7 +23,7 @@ export default Controller.extend({
this.timeFormatOptions = ['EPOCH', 'yyyyMMdd', 'yyyy-MM-dd', 'yyyy-MM-dd-HH', 'yyyy-MM-dd HH:mm:ss', 'yyyy-MM-dd HH:mm:ss.S', 'yyyyMMddHHmmss'];
this.timeGranularityOptions = ['1MILLISECONDS', '1SECONDS', '1MINUTES', '1HOURS', '1DAYS', '1WEEKs', '1MONTHS', '1YEARS'];
this.timezoneOptions = ["UTC", "Pacific/Midway", "US/Hawaii", "US/Alaska", "US/Pacific", "US/Arizona", "US/Mountain", "US/Central",
- "US/Eastern", "America/Caracas", "America/Manaus", "America/Santiago", "Canada/Newfoundland", "Brazil/East", "America/Buenos_Aires",
+ "US/Eastern", "America/Caracas", "America/Manaus", "America/Santiago", "Canada/Newfoundland", "Brazil/East", "America/Buenos_Aires",
"America/Godthab", "America/Montevideo", "Atlantic/South_Georgia", "Atlantic/Azores", "Atlantic/Cape_Verde", "Africa/Casablanca",
"Europe/London", "Europe/Berlin", "Europe/Belgrade", "Europe/Brussels", "Europe/Warsaw", "Africa/Algiers", "Asia/Amman", "Europe/Athens",
"Asia/Beirut", "Africa/Cairo", "Africa/Harare", "Europe/Helsinki", "Asia/Jerusalem", "Europe/Minsk", "Africa/Windhoek",
@@ -47,7 +47,7 @@ export default Controller.extend({
}
}
});
- });
+ }).catch(err => {});
},
/**
diff --git a/thirdeye/thirdeye-frontend/tests/acceptance/self-serve-import-metric-test.js b/thirdeye/thirdeye-frontend/tests/acceptance/self-serve-import-metric-test.js
index 331c475..d073a4b 100644
--- a/thirdeye/thirdeye-frontend/tests/acceptance/self-serve-import-metric-test.js
+++ b/thirdeye/thirdeye-frontend/tests/acceptance/self-serve-import-metric-test.js
@@ -7,7 +7,7 @@ import { visit, fillIn, click, currentURL, triggerKeyEvent } from '@ember/test-h
module('Acceptance | import metric', function(hooks) {
setupApplicationTest(hooks);
- const importLinkText = 'Import a Metric From InGraphs';
+ const importLinkText = 'InGraphs';
const returnLinkText = 'Back to Create';
const inGraphsLinkText = 'Go to InGraphs';
const dashboardToImport = 'thirdeye-all';
@@ -17,7 +17,6 @@ module('Acceptance | import metric', function(hooks) {
test(`visiting alert creation page to navigate to import page`, async (assert) => {
await visit(`/self-serve/create-alert`);
-
assert.equal(
$(selfServeConst.SECONDARY_LINK).get(1).innerText.trim(),
importLinkText,
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org
[incubator-pinot] 04/08: [Instance Assignment] Plug in instance
assignment (#4516)
Posted by xi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
xiangfu pushed a commit to branch winedepot-0.10
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git
commit 5fcb4124379d4cfe7f3dc817270abe0abff0bbbb
Author: Xiaotian (Jackie) Jiang <17...@users.noreply.github.com>
AuthorDate: Thu Aug 15 17:29:41 2019 -0700
[Instance Assignment] Plug in instance assignment (#4516)
- Add instance assignment APIs including:
- GET/PUT/DELETE instance partitions
- Assign instances for a table
- Replace instance in instance partitions
- Assign instances if necessary while creating/updating table config
- Backward-compatible, no change to the cluster if instance assignment is not configured
---
.../instance/InstanceAssignmentConfigUtils.java | 84 ++---
.../pinot/common/metadata/ZKMetadataProvider.java | 8 -
.../PinotInstanceAssignmentRestletResource.java | 301 ++++++++++++++++++
.../helix/ControllerRequestURLBuilder.java | 38 ++-
.../helix/core/PinotHelixResourceManager.java | 183 +++++------
.../helix/core/assignment/InstancePartitions.java | 8 +-
.../core/assignment/InstancePartitionsUtils.java | 56 +++-
.../instance/InstanceAssignmentDriver.java | 6 +-
.../instance/InstanceReplicaPartitionSelector.java | 7 +-
...PinotInstanceAssignmentRestletResourceTest.java | 339 +++++++++++++++++++++
.../instance/InstanceAssignmentTest.java | 93 +-----
.../ReplicaGroupRebalanceStrategyTest.java | 4 +-
.../sharding/SegmentAssignmentStrategyTest.java | 72 -----
.../apache/pinot/core/util/ReplicationUtils.java | 35 ---
14 files changed, 902 insertions(+), 332 deletions(-)
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/config/instance/InstanceAssignmentConfigUtils.java b/pinot-common/src/main/java/org/apache/pinot/common/config/instance/InstanceAssignmentConfigUtils.java
index f094527..446f107 100644
--- a/pinot-common/src/main/java/org/apache/pinot/common/config/instance/InstanceAssignmentConfigUtils.java
+++ b/pinot-common/src/main/java/org/apache/pinot/common/config/instance/InstanceAssignmentConfigUtils.java
@@ -33,8 +33,40 @@ public class InstanceAssignmentConfigUtils {
private InstanceAssignmentConfigUtils() {
}
+ /**
+ * Returns whether the instance assignment is allowed for the given table config.
+ */
+ public static boolean allowInstanceAssignment(TableConfig tableConfig,
+ InstancePartitionsType instancePartitionsType) {
+ TableType tableType = tableConfig.getTableType();
+ Map<InstancePartitionsType, InstanceAssignmentConfig> instanceAssignmentConfigMap =
+ tableConfig.getInstanceAssignmentConfigMap();
+ switch (instancePartitionsType) {
+ // Allow OFFLINE instance assignment if the OFFLINE table has it configured or (for backward-compatibility) is
+ // using replica-group segment assignment
+ case OFFLINE:
+ return tableType == TableType.OFFLINE && ((instanceAssignmentConfigMap != null && instanceAssignmentConfigMap
+ .containsKey(InstancePartitionsType.OFFLINE))
+ || AssignmentStrategy.REPLICA_GROUP_SEGMENT_ASSIGNMENT_STRATEGY
+ .equalsIgnoreCase(tableConfig.getValidationConfig().getSegmentAssignmentStrategy()));
+ // Allow CONSUMING/COMPLETED instance assignment if the REALTIME table has it configured
+ case CONSUMING:
+ case COMPLETED:
+ return tableType == TableType.REALTIME && (instanceAssignmentConfigMap != null && instanceAssignmentConfigMap
+ .containsKey(instancePartitionsType));
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ /**
+ * Extracts or generates default instance assignment config from the given table config.
+ */
public static InstanceAssignmentConfig getInstanceAssignmentConfig(TableConfig tableConfig,
InstancePartitionsType instancePartitionsType) {
+ Preconditions.checkState(allowInstanceAssignment(tableConfig, instancePartitionsType),
+ "Instance assignment is not allowed for the given table config");
+
// Use the instance assignment config from the table config if it exists
Map<InstancePartitionsType, InstanceAssignmentConfig> instanceAssignmentConfigMap =
tableConfig.getInstanceAssignmentConfigMap();
@@ -42,49 +74,33 @@ public class InstanceAssignmentConfigUtils {
if (instanceAssignmentConfigMap.containsKey(instancePartitionsType)) {
return instanceAssignmentConfigMap.get(instancePartitionsType);
}
- // Use the CONSUMING config for COMPLETED segments if COMPLETED config does not exist
- if (instancePartitionsType == InstancePartitionsType.COMPLETED && instanceAssignmentConfigMap
- .containsKey(InstancePartitionsType.CONSUMING)) {
- return instanceAssignmentConfigMap.get(InstancePartitionsType.CONSUMING);
- }
}
// Generate default instance assignment config if it does not exist
+ // Only allow default config for OFFLINE table with replica-group segment assignment for backward-compatibility
InstanceAssignmentConfig instanceAssignmentConfig = new InstanceAssignmentConfig();
InstanceTagPoolConfig tagPoolConfig = new InstanceTagPoolConfig();
- String serverTag =
- TagNameUtils.getServerTagFromTableConfigAndInstancePartitionsType(tableConfig, instancePartitionsType);
- tagPoolConfig.setTag(serverTag);
+ tagPoolConfig.setTag(TagNameUtils.getOfflineTagForTenant(tableConfig.getTenantConfig().getServer()));
instanceAssignmentConfig.setTagPoolConfig(tagPoolConfig);
InstanceReplicaPartitionConfig replicaPartitionConfig = new InstanceReplicaPartitionConfig();
- SegmentsValidationAndRetentionConfig validationConfig = tableConfig.getValidationConfig();
- if (AssignmentStrategy.REPLICA_GROUP_SEGMENT_ASSIGNMENT_STRATEGY
- .equalsIgnoreCase(validationConfig.getSegmentAssignmentStrategy())) {
- replicaPartitionConfig.setReplicaGroupBased(true);
- if (tableConfig.getTableType() == TableType.OFFLINE) {
- replicaPartitionConfig.setNumReplicas(validationConfig.getReplicationNumber());
- ReplicaGroupStrategyConfig replicaGroupStrategyConfig =
- tableConfig.getValidationConfig().getReplicaGroupStrategyConfig();
- Preconditions
- .checkState(replicaGroupStrategyConfig != null, "Failed to find the replica-group strategy config");
- String partitionColumn = replicaGroupStrategyConfig.getPartitionColumn();
- if (partitionColumn != null) {
- int numPartitions =
- tableConfig.getIndexingConfig().getSegmentPartitionConfig().getNumPartitions(partitionColumn);
- Preconditions.checkState(numPartitions > 0, "Number of partitions for column: %s is not properly configured",
- partitionColumn);
- replicaPartitionConfig.setNumPartitions(numPartitions);
- replicaPartitionConfig.setNumInstancesPerPartition(replicaGroupStrategyConfig.getNumInstancesPerPartition());
- } else {
- // If partition column is not configured, use replicaGroupStrategyConfig.getNumInstancesPerPartition() as
- // number of instances per replica for backward-compatibility
- replicaPartitionConfig.setNumInstancesPerReplica(replicaGroupStrategyConfig.getNumInstancesPerPartition());
- }
- } else {
- replicaPartitionConfig.setNumReplicas(validationConfig.getReplicasPerPartitionNumber());
- }
+ replicaPartitionConfig.setReplicaGroupBased(true);
+ SegmentsValidationAndRetentionConfig segmentConfig = tableConfig.getValidationConfig();
+ replicaPartitionConfig.setNumReplicas(segmentConfig.getReplicationNumber());
+ ReplicaGroupStrategyConfig replicaGroupStrategyConfig = segmentConfig.getReplicaGroupStrategyConfig();
+ Preconditions.checkState(replicaGroupStrategyConfig != null, "Failed to find the replica-group strategy config");
+ String partitionColumn = replicaGroupStrategyConfig.getPartitionColumn();
+ if (partitionColumn != null) {
+ int numPartitions = tableConfig.getIndexingConfig().getSegmentPartitionConfig().getNumPartitions(partitionColumn);
+ Preconditions.checkState(numPartitions > 0, "Number of partitions for column: %s is not properly configured",
+ partitionColumn);
+ replicaPartitionConfig.setNumPartitions(numPartitions);
+ replicaPartitionConfig.setNumInstancesPerPartition(replicaGroupStrategyConfig.getNumInstancesPerPartition());
+ } else {
+ // If partition column is not configured, use replicaGroupStrategyConfig.getNumInstancesPerPartition() as
+ // number of instances per replica for backward-compatibility
+ replicaPartitionConfig.setNumInstancesPerReplica(replicaGroupStrategyConfig.getNumInstancesPerPartition());
}
instanceAssignmentConfig.setReplicaPartitionConfig(replicaPartitionConfig);
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/metadata/ZKMetadataProvider.java b/pinot-common/src/main/java/org/apache/pinot/common/metadata/ZKMetadataProvider.java
index 2a3ad60..f8baf67 100644
--- a/pinot-common/src/main/java/org/apache/pinot/common/metadata/ZKMetadataProvider.java
+++ b/pinot-common/src/main/java/org/apache/pinot/common/metadata/ZKMetadataProvider.java
@@ -136,14 +136,6 @@ public class ZKMetadataProvider {
}
}
- public static void removeInstancePartitionAssignmentFromPropertyStore(ZkHelixPropertyStore<ZNRecord> propertyStore,
- String tableNameWithType) {
- String propertyStorePath = constructPropertyStorePathForInstancePartitions(tableNameWithType);
- if (propertyStore.exists(propertyStorePath, AccessOption.PERSISTENT)) {
- propertyStore.remove(propertyStorePath, AccessOption.PERSISTENT);
- }
- }
-
public static boolean setOfflineSegmentZKMetadata(ZkHelixPropertyStore<ZNRecord> propertyStore,
String offlineTableName, OfflineSegmentZKMetadata offlineSegmentZKMetadata, int expectedVersion) {
// NOTE: Helix will throw ZkBadVersionException if version does not match
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotInstanceAssignmentRestletResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotInstanceAssignmentRestletResource.java
new file mode 100644
index 0000000..9f8d48f
--- /dev/null
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotInstanceAssignmentRestletResource.java
@@ -0,0 +1,301 @@
+/**
+ * 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.api.resources;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.apache.helix.model.InstanceConfig;
+import org.apache.pinot.common.config.TableConfig;
+import org.apache.pinot.common.config.TableNameBuilder;
+import org.apache.pinot.common.config.instance.InstanceAssignmentConfigUtils;
+import org.apache.pinot.common.utils.CommonConstants.Helix.TableType;
+import org.apache.pinot.common.utils.InstancePartitionsType;
+import org.apache.pinot.common.utils.JsonUtils;
+import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
+import org.apache.pinot.controller.helix.core.assignment.InstancePartitions;
+import org.apache.pinot.controller.helix.core.assignment.InstancePartitionsUtils;
+import org.apache.pinot.controller.helix.core.assignment.instance.InstanceAssignmentDriver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+@Api(tags = Constants.TABLE_TAG)
+@Path("/")
+public class PinotInstanceAssignmentRestletResource {
+ private static final Logger LOGGER = LoggerFactory.getLogger(PinotInstanceAssignmentRestletResource.class);
+
+ @Inject
+ PinotHelixResourceManager _resourceManager;
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/tables/{tableName}/instancePartitions")
+ @ApiOperation(value = "Get the instance partitions")
+ public Map<InstancePartitionsType, InstancePartitions> getInstancePartitions(
+ @ApiParam(value = "Name of the table") @PathParam("tableName") String tableName,
+ @ApiParam(value = "OFFLINE|CONSUMING|COMPLETED") @QueryParam("type") @Nullable InstancePartitionsType instancePartitionsType) {
+ Map<InstancePartitionsType, InstancePartitions> instancePartitionsMap = new TreeMap<>();
+
+ String rawTableName = TableNameBuilder.extractRawTableName(tableName);
+ TableType tableType = TableNameBuilder.getTableTypeFromTableName(tableName);
+ if (tableType != TableType.REALTIME) {
+ if (instancePartitionsType == InstancePartitionsType.OFFLINE || instancePartitionsType == null) {
+ InstancePartitions offlineInstancePartitions = InstancePartitionsUtils
+ .fetchInstancePartitions(_resourceManager.getPropertyStore(),
+ InstancePartitionsType.OFFLINE.getInstancePartitionsName(rawTableName));
+ if (offlineInstancePartitions != null) {
+ instancePartitionsMap.put(InstancePartitionsType.OFFLINE, offlineInstancePartitions);
+ }
+ }
+ }
+ if (tableType != TableType.OFFLINE) {
+ if (instancePartitionsType == InstancePartitionsType.CONSUMING || instancePartitionsType == null) {
+ InstancePartitions consumingInstancePartitions = InstancePartitionsUtils
+ .fetchInstancePartitions(_resourceManager.getPropertyStore(),
+ InstancePartitionsType.CONSUMING.getInstancePartitionsName(rawTableName));
+ if (consumingInstancePartitions != null) {
+ instancePartitionsMap.put(InstancePartitionsType.CONSUMING, consumingInstancePartitions);
+ }
+ }
+ if (instancePartitionsType == InstancePartitionsType.COMPLETED || instancePartitionsType == null) {
+ InstancePartitions completedInstancePartitions = InstancePartitionsUtils
+ .fetchInstancePartitions(_resourceManager.getPropertyStore(),
+ InstancePartitionsType.COMPLETED.getInstancePartitionsName(rawTableName));
+ if (completedInstancePartitions != null) {
+ instancePartitionsMap.put(InstancePartitionsType.COMPLETED, completedInstancePartitions);
+ }
+ }
+ }
+
+ if (instancePartitionsMap.isEmpty()) {
+ throw new ControllerApplicationException(LOGGER, "Failed to find the instance partitions",
+ Response.Status.NOT_FOUND);
+ } else {
+ return instancePartitionsMap;
+ }
+ }
+
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/tables/{tableName}/assignInstances")
+ @ApiOperation(value = "Assign server instances to a table")
+ public Map<InstancePartitionsType, InstancePartitions> assignInstances(
+ @ApiParam(value = "Name of the table") @PathParam("tableName") String tableName,
+ @ApiParam(value = "OFFLINE|CONSUMING|COMPLETED") @QueryParam("type") @Nullable InstancePartitionsType instancePartitionsType,
+ @ApiParam(value = "Whether to do dry-run") @DefaultValue("false") @QueryParam("dryRun") boolean dryRun) {
+ Map<InstancePartitionsType, InstancePartitions> instancePartitionsMap = new TreeMap<>();
+ List<InstanceConfig> instanceConfigs = _resourceManager.getAllHelixInstanceConfigs();
+
+ TableType tableType = TableNameBuilder.getTableTypeFromTableName(tableName);
+ if (tableType != TableType.REALTIME && (instancePartitionsType == InstancePartitionsType.OFFLINE
+ || instancePartitionsType == null)) {
+ TableConfig offlineTableConfig = _resourceManager.getOfflineTableConfig(tableName);
+ if (offlineTableConfig != null) {
+ try {
+ if (InstanceAssignmentConfigUtils
+ .allowInstanceAssignment(offlineTableConfig, InstancePartitionsType.OFFLINE)) {
+ instancePartitionsMap.put(InstancePartitionsType.OFFLINE, new InstanceAssignmentDriver(offlineTableConfig)
+ .assignInstances(InstancePartitionsType.OFFLINE, instanceConfigs));
+ }
+ } catch (IllegalStateException e) {
+ throw new ControllerApplicationException(LOGGER, "Caught IllegalStateException", Response.Status.BAD_REQUEST,
+ e);
+ } catch (Exception e) {
+ throw new ControllerApplicationException(LOGGER, "Caught exception while calculating the instance partitions",
+ Response.Status.INTERNAL_SERVER_ERROR, e);
+ }
+ }
+ }
+ if (tableType != TableType.OFFLINE && instancePartitionsType != InstancePartitionsType.OFFLINE) {
+ TableConfig realtimeTableConfig = _resourceManager.getRealtimeTableConfig(tableName);
+ if (realtimeTableConfig != null) {
+ try {
+ InstanceAssignmentDriver instanceAssignmentDriver = new InstanceAssignmentDriver(realtimeTableConfig);
+ if (instancePartitionsType == InstancePartitionsType.CONSUMING || instancePartitionsType == null) {
+ if (InstanceAssignmentConfigUtils
+ .allowInstanceAssignment(realtimeTableConfig, InstancePartitionsType.CONSUMING)) {
+ instancePartitionsMap.put(InstancePartitionsType.CONSUMING,
+ instanceAssignmentDriver.assignInstances(InstancePartitionsType.CONSUMING, instanceConfigs));
+ }
+ }
+ if (instancePartitionsType == InstancePartitionsType.COMPLETED || instancePartitionsType == null) {
+ if (InstanceAssignmentConfigUtils
+ .allowInstanceAssignment(realtimeTableConfig, InstancePartitionsType.COMPLETED)) {
+ instancePartitionsMap.put(InstancePartitionsType.COMPLETED,
+ instanceAssignmentDriver.assignInstances(InstancePartitionsType.COMPLETED, instanceConfigs));
+ }
+ }
+ } catch (IllegalStateException e) {
+ throw new ControllerApplicationException(LOGGER, "Caught IllegalStateException", Response.Status.BAD_REQUEST,
+ e);
+ } catch (Exception e) {
+ throw new ControllerApplicationException(LOGGER, "Caught exception while calculating the instance partitions",
+ Response.Status.INTERNAL_SERVER_ERROR, e);
+ }
+ }
+ }
+
+ if (instancePartitionsMap.isEmpty()) {
+ throw new ControllerApplicationException(LOGGER, "Failed to find the instance assignment config",
+ Response.Status.NOT_FOUND);
+ }
+
+ if (!dryRun) {
+ for (InstancePartitions instancePartitions : instancePartitionsMap.values()) {
+ persistInstancePartitionsHelper(instancePartitions);
+ }
+ }
+
+ return instancePartitionsMap;
+ }
+
+ private void persistInstancePartitionsHelper(InstancePartitions instancePartitions) {
+ try {
+ LOGGER.info("Persisting instance partitions: {} to the property store", instancePartitions.getName());
+ InstancePartitionsUtils.persistInstancePartitions(_resourceManager.getPropertyStore(), instancePartitions);
+ } catch (Exception e) {
+ throw new ControllerApplicationException(LOGGER, "Caught Exception while persisting the instance partitions",
+ Response.Status.INTERNAL_SERVER_ERROR, e);
+ }
+ }
+
+ @PUT
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/tables/{tableName}/instancePartitions")
+ @ApiOperation(value = "Create/update the instance partitions")
+ public Map<InstancePartitionsType, InstancePartitions> setInstancePartitions(
+ @ApiParam(value = "Name of the table") @PathParam("tableName") String tableName, String instancePartitionsStr) {
+ InstancePartitions instancePartitions;
+ try {
+ instancePartitions = JsonUtils.stringToObject(instancePartitionsStr, InstancePartitions.class);
+ } catch (IOException e) {
+ throw new ControllerApplicationException(LOGGER, "Failed to deserialize the instance partitions",
+ Response.Status.BAD_REQUEST);
+ }
+
+ String instancePartitionsName = instancePartitions.getName();
+ String rawTableName = TableNameBuilder.extractRawTableName(tableName);
+ TableType tableType = TableNameBuilder.getTableTypeFromTableName(tableName);
+ if (tableType != TableType.REALTIME) {
+ if (InstancePartitionsType.OFFLINE.getInstancePartitionsName(rawTableName).equals(instancePartitionsName)) {
+ persistInstancePartitionsHelper(instancePartitions);
+ return Collections.singletonMap(InstancePartitionsType.OFFLINE, instancePartitions);
+ }
+ }
+ if (tableType != TableType.OFFLINE) {
+ if (InstancePartitionsType.CONSUMING.getInstancePartitionsName(rawTableName).equals(instancePartitionsName)) {
+ persistInstancePartitionsHelper(instancePartitions);
+ return Collections.singletonMap(InstancePartitionsType.CONSUMING, instancePartitions);
+ }
+ if (InstancePartitionsType.COMPLETED.getInstancePartitionsName(rawTableName).equals(instancePartitionsName)) {
+ persistInstancePartitionsHelper(instancePartitions);
+ return Collections.singletonMap(InstancePartitionsType.COMPLETED, instancePartitions);
+ }
+ }
+
+ throw new ControllerApplicationException(LOGGER, "Instance partitions cannot be applied to the table",
+ Response.Status.BAD_REQUEST);
+ }
+
+ @DELETE
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/tables/{tableName}/instancePartitions")
+ @ApiOperation(value = "Remove the instance partitions")
+ public SuccessResponse removeInstancePartitions(
+ @ApiParam(value = "Name of the table") @PathParam("tableName") String tableName,
+ @ApiParam(value = "OFFLINE|CONSUMING|COMPLETED") @QueryParam("type") @Nullable InstancePartitionsType instancePartitionsType) {
+ String rawTableName = TableNameBuilder.extractRawTableName(tableName);
+ TableType tableType = TableNameBuilder.getTableTypeFromTableName(tableName);
+ if (tableType != TableType.REALTIME && (instancePartitionsType == InstancePartitionsType.OFFLINE
+ || instancePartitionsType == null)) {
+ removeInstancePartitionsHelper(InstancePartitionsType.OFFLINE.getInstancePartitionsName(rawTableName));
+ }
+ if (tableType != TableType.OFFLINE) {
+ if (instancePartitionsType == InstancePartitionsType.CONSUMING || instancePartitionsType == null) {
+ removeInstancePartitionsHelper(InstancePartitionsType.CONSUMING.getInstancePartitionsName(rawTableName));
+ }
+ if (instancePartitionsType == InstancePartitionsType.COMPLETED || instancePartitionsType == null) {
+ removeInstancePartitionsHelper(InstancePartitionsType.COMPLETED.getInstancePartitionsName(rawTableName));
+ }
+ }
+ return new SuccessResponse("Instance partitions removed");
+ }
+
+ private void removeInstancePartitionsHelper(String instancePartitionsName) {
+ try {
+ LOGGER.info("Removing instance partitions: {} from the property store", instancePartitionsName);
+ InstancePartitionsUtils.removeInstancePartitions(_resourceManager.getPropertyStore(), instancePartitionsName);
+ } catch (Exception e) {
+ throw new ControllerApplicationException(LOGGER, "Caught Exception while removing the instance partitions",
+ Response.Status.INTERNAL_SERVER_ERROR, e);
+ }
+ }
+
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/tables/{tableName}/replaceInstance")
+ @ApiOperation(value = "Replace an instance in the instance partitions")
+ public Map<InstancePartitionsType, InstancePartitions> replaceInstance(
+ @ApiParam(value = "Name of the table") @PathParam("tableName") String tableName,
+ @ApiParam(value = "OFFLINE|CONSUMING|COMPLETED") @QueryParam("type") @Nullable InstancePartitionsType instancePartitionsType,
+ @ApiParam(value = "Old instance to be replaced", required = true) @QueryParam("oldInstanceId") String oldInstanceId,
+ @ApiParam(value = "New instance to replace with", required = true) @QueryParam("newInstanceId") String newInstanceId) {
+ Map<InstancePartitionsType, InstancePartitions> instancePartitionsMap =
+ getInstancePartitions(tableName, instancePartitionsType);
+ Iterator<InstancePartitions> iterator = instancePartitionsMap.values().iterator();
+ while (iterator.hasNext()) {
+ InstancePartitions instancePartitions = iterator.next();
+ boolean oldInstanceFound = false;
+ Map<String, List<String>> partitionToInstancesMap = instancePartitions.getPartitionToInstancesMap();
+ for (List<String> instances : partitionToInstancesMap.values()) {
+ oldInstanceFound |= Collections.replaceAll(instances, oldInstanceId, newInstanceId);
+ }
+ if (oldInstanceFound) {
+ persistInstancePartitionsHelper(instancePartitions);
+ } else {
+ iterator.remove();
+ }
+ }
+ if (instancePartitionsMap.isEmpty()) {
+ throw new ControllerApplicationException(LOGGER, "Failed to find the old instance", Response.Status.NOT_FOUND);
+ } else {
+ return instancePartitionsMap;
+ }
+ }
+}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/ControllerRequestURLBuilder.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/ControllerRequestURLBuilder.java
index 6dc7b40..d638392 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/ControllerRequestURLBuilder.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/ControllerRequestURLBuilder.java
@@ -18,8 +18,9 @@
*/
package org.apache.pinot.controller.helix;
-import org.apache.avro.reflect.Nullable;
+import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
+import org.apache.pinot.common.utils.InstancePartitionsType;
import org.apache.pinot.common.utils.StringUtil;
import org.apache.pinot.common.utils.URIUtils;
@@ -202,4 +203,39 @@ public class ControllerRequestURLBuilder {
public String forSegmentListAPI(String tableName) {
return StringUtil.join("/", _baseUrl, "segments", tableName);
}
+
+ public String forInstancePartitions(String tableName, @Nullable InstancePartitionsType instancePartitionsType) {
+ String url = StringUtil.join("/", _baseUrl, "tables", tableName, "instancePartitions");
+ if (instancePartitionsType != null) {
+ url += "?type=" + instancePartitionsType;
+ }
+ return url;
+ }
+
+ public String forInstanceAssign(String tableName, @Nullable InstancePartitionsType instancePartitionsType,
+ boolean dryRun) {
+ String url = StringUtil.join("/", _baseUrl, "tables", tableName, "assignInstances");
+ if (instancePartitionsType != null) {
+ url += "?type=" + instancePartitionsType;
+ if (dryRun) {
+ url += "&dryRun=true";
+ }
+ } else {
+ if (dryRun) {
+ url += "?dryRun=true";
+ }
+ }
+ return url;
+ }
+
+ public String forInstanceReplace(String tableName, @Nullable InstancePartitionsType instancePartitionsType,
+ String oldInstanceId, String newInstanceId) {
+ String url =
+ StringUtil.join("/", _baseUrl, "tables", tableName, "replaceInstance") + "?oldInstanceId=" + oldInstanceId
+ + "&newInstanceId=" + newInstanceId;
+ if (instancePartitionsType != null) {
+ url += "&type=" + instancePartitionsType;
+ }
+ return url;
+ }
}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java
index d4579de..c7fb073 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java
@@ -72,6 +72,7 @@ import org.apache.pinot.common.config.TableNameBuilder;
import org.apache.pinot.common.config.TagNameUtils;
import org.apache.pinot.common.config.Tenant;
import org.apache.pinot.common.config.TenantConfig;
+import org.apache.pinot.common.config.instance.InstanceAssignmentConfigUtils;
import org.apache.pinot.common.data.Schema;
import org.apache.pinot.common.exception.InvalidConfigException;
import org.apache.pinot.common.exception.TableNotFoundException;
@@ -83,14 +84,14 @@ import org.apache.pinot.common.metadata.instance.InstanceZKMetadata;
import org.apache.pinot.common.metadata.segment.OfflineSegmentZKMetadata;
import org.apache.pinot.common.metadata.segment.RealtimeSegmentZKMetadata;
import org.apache.pinot.common.metadata.segment.SegmentZKMetadata;
-import org.apache.pinot.common.partition.ReplicaGroupPartitionAssignment;
-import org.apache.pinot.common.partition.ReplicaGroupPartitionAssignmentGenerator;
import org.apache.pinot.common.restlet.resources.RebalanceResult;
import org.apache.pinot.common.segment.SegmentMetadata;
import org.apache.pinot.common.utils.CommonConstants.Helix;
import org.apache.pinot.common.utils.CommonConstants.Helix.StateModel.BrokerOnlineOfflineStateModel;
import org.apache.pinot.common.utils.CommonConstants.Helix.StateModel.SegmentOnlineOfflineStateModel;
import org.apache.pinot.common.utils.CommonConstants.Helix.TableType;
+import org.apache.pinot.common.utils.InstancePartitionsType;
+import org.apache.pinot.common.utils.JsonUtils;
import org.apache.pinot.common.utils.SchemaUtils;
import org.apache.pinot.common.utils.helix.HelixHelper;
import org.apache.pinot.common.utils.helix.PinotHelixPropertyStoreZnRecordProvider;
@@ -98,16 +99,17 @@ import org.apache.pinot.common.utils.retry.RetryPolicies;
import org.apache.pinot.common.utils.retry.RetryPolicy;
import org.apache.pinot.controller.ControllerConf;
import org.apache.pinot.controller.api.resources.StateType;
+import org.apache.pinot.controller.helix.core.assignment.InstancePartitions;
+import org.apache.pinot.controller.helix.core.assignment.InstancePartitionsUtils;
+import org.apache.pinot.controller.helix.core.assignment.instance.InstanceAssignmentDriver;
import org.apache.pinot.controller.helix.core.realtime.PinotLLCRealtimeSegmentManager;
import org.apache.pinot.controller.helix.core.rebalance.RebalanceSegmentStrategy;
import org.apache.pinot.controller.helix.core.rebalance.RebalanceSegmentStrategyFactory;
import org.apache.pinot.controller.helix.core.sharding.SegmentAssignmentStrategy;
-import org.apache.pinot.controller.helix.core.sharding.SegmentAssignmentStrategyEnum;
import org.apache.pinot.controller.helix.core.sharding.SegmentAssignmentStrategyFactory;
import org.apache.pinot.controller.helix.core.util.ZKMetadataUtils;
import org.apache.pinot.controller.helix.starter.HelixConfig;
import org.apache.pinot.core.realtime.stream.StreamConfig;
-import org.apache.pinot.core.util.ReplicationUtils;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -1024,8 +1026,9 @@ public class PinotHelixResourceManager {
validateTableTenantConfig(tableConfig);
String tableNameWithType = tableConfig.getTableName();
- TableType tableType = tableConfig.getTableType();
SegmentsValidationAndRetentionConfig segmentsConfig = tableConfig.getValidationConfig();
+
+ TableType tableType = tableConfig.getTableType();
switch (tableType) {
case OFFLINE:
// existing tooling relies on this check not existing for realtime table (to migrate to LLC)
@@ -1040,14 +1043,16 @@ public class PinotHelixResourceManager {
_enableBatchMessageMode);
LOGGER.info("adding table via the admin");
_helixAdmin.addResource(_helixClusterName, tableNameWithType, offlineIdealState);
- LOGGER.info("successfully added the table : " + tableNameWithType + " to the cluster");
// lets add table configs
ZKMetadataProvider.setOfflineTableConfig(_propertyStore, tableNameWithType, tableConfig.toZNRecord());
- // Update replica group partition assignment to the property store if applicable
- updateReplicaGroupPartitionAssignment(tableConfig);
+ // Assign instances
+ assignInstances(tableConfig, true);
+
+ LOGGER.info("Successfully added table: {}", tableNameWithType);
break;
+
case REALTIME:
IndexingConfig indexingConfig = tableConfig.getIndexingConfig();
verifyIndexingConfig(tableNameWithType, indexingConfig);
@@ -1067,11 +1072,6 @@ public class PinotHelixResourceManager {
// lets add table configs
ZKMetadataProvider.setRealtimeTableConfig(_propertyStore, tableNameWithType, tableConfig.toZNRecord());
- // Update replica group partition assignment to the property store if applicable
- if (ReplicationUtils.setupRealtimeReplicaGroups(tableConfig)) {
- updateReplicaGroupPartitionAssignment(tableConfig);
- }
-
/*
* PinotRealtimeSegmentManager sets up watches on table and segment path. When a table gets created,
* it expects the INSTANCE path in propertystore to be set up so that it can get the group ID and
@@ -1086,10 +1086,14 @@ public class PinotHelixResourceManager {
*/
ensureRealtimeClusterIsSetUp(tableConfig, tableNameWithType, indexingConfig);
+ // Assign instances
+ assignInstances(tableConfig, true);
+
LOGGER.info("Successfully added or updated the table {} ", tableNameWithType);
break;
+
default:
- throw new InvalidTableConfigException("UnSupported table type: " + tableType);
+ throw new InvalidTableConfigException("Unsupported table type: " + tableType);
}
String brokerTenantName = TagNameUtils.getBrokerTagForTenant(tableConfig.getTenantConfig().getBroker());
@@ -1132,43 +1136,6 @@ public class PinotHelixResourceManager {
}
}
- /**
- * Update replica group partition assignment in the property store
- *
- * @param tableConfig a table config
- */
- private void updateReplicaGroupPartitionAssignment(TableConfig tableConfig) {
- String tableNameWithType = tableConfig.getTableName();
- String assignmentStrategy = tableConfig.getValidationConfig().getSegmentAssignmentStrategy();
- // We create replica group partition assignment and write to property store if new table config
- // has the replica group config.
- if (assignmentStrategy != null && SegmentAssignmentStrategyEnum.valueOf(assignmentStrategy)
- == SegmentAssignmentStrategyEnum.ReplicaGroupSegmentAssignmentStrategy) {
- ReplicaGroupPartitionAssignmentGenerator partitionAssignmentGenerator =
- new ReplicaGroupPartitionAssignmentGenerator(_propertyStore);
-
- // Create the new replica group partition assignment if there is none in the property store.
- // This will create the replica group partition assignment and write to the property store in 2 cases:
- // 1. when we create the table with replica group segment assignment
- // 2. when we update the table config with replica group segment assignment from another assignment strategy
- if (partitionAssignmentGenerator.getReplicaGroupPartitionAssignment(tableNameWithType) == null) {
- TableType tableType = tableConfig.getTableType();
- List<String> servers;
- if (tableType.equals(TableType.OFFLINE)) {
- OfflineTagConfig offlineTagConfig = new OfflineTagConfig(tableConfig);
- servers = getInstancesWithTag(offlineTagConfig.getOfflineServerTag());
- } else {
- RealtimeTagConfig realtimeTagConfig = new RealtimeTagConfig(tableConfig);
- servers = getInstancesWithTag(realtimeTagConfig.getConsumingServerTag());
- }
- int numReplicas = ReplicationUtils.getReplication(tableConfig);
- ReplicaGroupPartitionAssignment partitionAssignment = partitionAssignmentGenerator
- .buildReplicaGroupPartitionAssignment(tableNameWithType, tableConfig, numReplicas, servers);
- partitionAssignmentGenerator.writeReplicaGroupPartitionAssignment(partitionAssignment);
- }
- }
- }
-
public static class InvalidTableConfigException extends RuntimeException {
public InvalidTableConfigException(String message) {
super(message);
@@ -1268,6 +1235,35 @@ public class PinotHelixResourceManager {
}
}
+ private void assignInstances(TableConfig tableConfig, boolean override)
+ throws IOException {
+ String tableNameWithType = tableConfig.getTableName();
+ String rawTableName = TableNameBuilder.extractRawTableName(tableNameWithType);
+
+ List<InstancePartitionsType> instancePartitionsTypesToAssign = new ArrayList<>();
+ for (InstancePartitionsType instancePartitionsType : InstancePartitionsType.values()) {
+ if (InstanceAssignmentConfigUtils.allowInstanceAssignment(tableConfig, instancePartitionsType)) {
+ if (override || InstancePartitionsUtils
+ .fetchInstancePartitions(_propertyStore, instancePartitionsType.getInstancePartitionsName(rawTableName))
+ == null) {
+ instancePartitionsTypesToAssign.add(instancePartitionsType);
+ }
+ }
+ }
+
+ if (!instancePartitionsTypesToAssign.isEmpty()) {
+ LOGGER.info("Assigning {} instances to table: {}", instancePartitionsTypesToAssign, tableNameWithType);
+ InstanceAssignmentDriver instanceAssignmentDriver = new InstanceAssignmentDriver(tableConfig);
+ List<InstanceConfig> instanceConfigs = getAllHelixInstanceConfigs();
+ for (InstancePartitionsType instancePartitionsType : instancePartitionsTypesToAssign) {
+ InstancePartitions instancePartitions =
+ instanceAssignmentDriver.assignInstances(instancePartitionsType, instanceConfigs);
+ InstancePartitionsUtils.persistInstancePartitions(_propertyStore, instancePartitions);
+ LOGGER.info("Persisted instance partitions: {}", JsonUtils.objectToString(instancePartitions));
+ }
+ }
+ }
+
/**
* Validate the table config and update it
* @throws IOException
@@ -1284,34 +1280,42 @@ public class PinotHelixResourceManager {
public void setExistingTableConfig(TableConfig tableConfig)
throws IOException {
String tableNameWithType = tableConfig.getTableName();
+ SegmentsValidationAndRetentionConfig segmentsConfig = tableConfig.getValidationConfig();
+
TableType tableType = tableConfig.getTableType();
- if (tableType == TableType.REALTIME) {
- IndexingConfig indexingConfig = tableConfig.getIndexingConfig();
- verifyIndexingConfig(tableNameWithType, indexingConfig);
- // Update replica group partition assignment to the property store if applicable
- if (ReplicationUtils.setupRealtimeReplicaGroups(tableConfig)) {
- updateReplicaGroupPartitionAssignment(tableConfig);
- }
- ZKMetadataProvider.setRealtimeTableConfig(_propertyStore, tableNameWithType, tableConfig.toZNRecord());
- ensureRealtimeClusterIsSetUp(tableConfig, tableNameWithType, indexingConfig);
- } else if (tableType == TableType.OFFLINE) {
- // Update replica group partition assignment to the property store if applicable
- updateReplicaGroupPartitionAssignment(tableConfig);
-
- ZKMetadataProvider.setOfflineTableConfig(_propertyStore, tableNameWithType, tableConfig.toZNRecord());
- IdealState idealState = _helixAdmin.getResourceIdealState(_helixClusterName, tableNameWithType);
- final String configReplication = tableConfig.getValidationConfig().getReplication();
- if (configReplication != null && !tableConfig.getValidationConfig().getReplication()
- .equals(idealState.getReplicas())) {
- HelixHelper.updateIdealState(_helixZkManager, tableNameWithType, new Function<IdealState, IdealState>() {
- @Nullable
- @Override
- public IdealState apply(@Nullable IdealState idealState) {
- idealState.setReplicas(configReplication);
- return idealState;
- }
- }, RetryPolicies.exponentialBackoffRetryPolicy(5, 1000L, 1.2f));
- }
+ switch (tableType) {
+ case OFFLINE:
+ ZKMetadataProvider.setOfflineTableConfig(_propertyStore, tableNameWithType, tableConfig.toZNRecord());
+
+ // Update IdealState replication
+ IdealState idealState = _helixAdmin.getResourceIdealState(_helixClusterName, tableNameWithType);
+ String replicationConfigured = segmentsConfig.getReplication();
+ if (!idealState.getReplicas().equals(replicationConfigured)) {
+ HelixHelper.updateIdealState(_helixZkManager, tableNameWithType, is -> {
+ assert is != null;
+ is.setReplicas(replicationConfigured);
+ return is;
+ }, RetryPolicies.exponentialBackoffRetryPolicy(5, 1000L, 1.2f));
+ }
+
+ // Assign instances
+ assignInstances(tableConfig, false);
+
+ break;
+
+ case REALTIME:
+ IndexingConfig indexingConfig = tableConfig.getIndexingConfig();
+ verifyIndexingConfig(tableNameWithType, indexingConfig);
+ ZKMetadataProvider.setRealtimeTableConfig(_propertyStore, tableNameWithType, tableConfig.toZNRecord());
+ ensureRealtimeClusterIsSetUp(tableConfig, tableNameWithType, indexingConfig);
+
+ // Assign instances
+ assignInstances(tableConfig, false);
+
+ break;
+
+ default:
+ throw new InvalidTableConfigException("Unsupported table type: " + tableType);
}
}
@@ -1396,9 +1400,11 @@ public class PinotHelixResourceManager {
ZKMetadataProvider.removeResourceConfigFromPropertyStore(_propertyStore, offlineTableName);
LOGGER.info("Deleting table {}: Removed table config", offlineTableName);
- // Remove replica group partition assignment
- ZKMetadataProvider.removeInstancePartitionAssignmentFromPropertyStore(_propertyStore, offlineTableName);
- LOGGER.info("Deleting table {}: Removed replica group partition assignment", offlineTableName);
+ // Remove instance partitions
+ InstancePartitionsUtils.removeInstancePartitions(_propertyStore,
+ InstancePartitionsType.OFFLINE.getInstancePartitionsName(TableNameBuilder.extractRawTableName(tableName)));
+ LOGGER.info("Deleting table {}: Removed instance partitions", offlineTableName);
+
LOGGER.info("Deleting table {}: Finish", offlineTableName);
}
@@ -1430,11 +1436,15 @@ public class PinotHelixResourceManager {
ZKMetadataProvider.removeResourceConfigFromPropertyStore(_propertyStore, realtimeTableName);
LOGGER.info("Deleting table {}: Removed table config", realtimeTableName);
- // Remove replica group partition assignment
- ZKMetadataProvider.removeInstancePartitionAssignmentFromPropertyStore(_propertyStore, realtimeTableName);
- LOGGER.info("Deleting table {}: Removed replica group partition assignment", realtimeTableName);
+ // Remove instance partitions
+ String rawTableName = TableNameBuilder.extractRawTableName(tableName);
+ InstancePartitionsUtils.removeInstancePartitions(_propertyStore,
+ InstancePartitionsType.CONSUMING.getInstancePartitionsName(rawTableName));
+ InstancePartitionsUtils.removeInstancePartitions(_propertyStore,
+ InstancePartitionsType.COMPLETED.getInstancePartitionsName(rawTableName));
+ LOGGER.info("Deleting table {}: Removed instance partitions", realtimeTableName);
- // Remove groupId/PartitionId mapping for HLC table
+ // Remove groupId/partitionId mapping for HLC table
if (instancesForTable != null) {
for (String instance : instancesForTable) {
InstanceZKMetadata instanceZKMetadata = ZKMetadataProvider.getInstanceZKMetadata(_propertyStore, instance);
@@ -1444,7 +1454,8 @@ public class PinotHelixResourceManager {
}
}
}
- LOGGER.info("Deleting table {}: Removed replica group partition assignment", realtimeTableName);
+ LOGGER.info("Deleting table {}: Removed groupId/partitionId mapping for HLC table", realtimeTableName);
+
LOGGER.info("Deleting table {}: Finish", realtimeTableName);
}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/InstancePartitions.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/InstancePartitions.java
index 6d9a35f..4ed659e 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/InstancePartitions.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/InstancePartitions.java
@@ -18,7 +18,10 @@
*/
package org.apache.pinot.controller.helix.core.assignment;
+import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@@ -32,6 +35,7 @@ import org.apache.helix.ZNRecord;
* list of server instances, and is persisted under the ZK path: {@code <cluster>/PROPERTYSTORE/INSTANCE_PARTITIONS}.
* <p>The segment assignment will be based on the instance partitions of the table.
*/
+@JsonIgnoreProperties(ignoreUnknown = true)
public class InstancePartitions {
private static final char PARTITION_REPLICA_SEPARATOR = '_';
@@ -46,7 +50,9 @@ public class InstancePartitions {
_partitionToInstancesMap = new TreeMap<>();
}
- private InstancePartitions(String name, Map<String, List<String>> partitionToInstancesMap) {
+ @JsonCreator
+ private InstancePartitions(@JsonProperty(value = "name", required = true) String name,
+ @JsonProperty(value = "partitionToInstancesMap", required = true) Map<String, List<String>> partitionToInstancesMap) {
_name = name;
_partitionToInstancesMap = partitionToInstancesMap;
for (String key : partitionToInstancesMap.keySet()) {
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/InstancePartitionsUtils.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/InstancePartitionsUtils.java
index be2c99d..8e74340 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/InstancePartitionsUtils.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/InstancePartitionsUtils.java
@@ -20,10 +20,13 @@ package org.apache.pinot.controller.helix.core.assignment;
import java.util.Collections;
import java.util.List;
+import javax.annotation.Nullable;
+import org.I0Itec.zkclient.exception.ZkException;
import org.apache.helix.AccessOption;
import org.apache.helix.HelixManager;
import org.apache.helix.ZNRecord;
import org.apache.helix.store.HelixPropertyStore;
+import org.apache.helix.store.zk.ZkHelixPropertyStore;
import org.apache.pinot.common.config.TableConfig;
import org.apache.pinot.common.config.TableNameBuilder;
import org.apache.pinot.common.config.TagNameUtils;
@@ -45,14 +48,23 @@ public class InstancePartitionsUtils {
public static InstancePartitions fetchOrComputeInstancePartitions(HelixManager helixManager, TableConfig tableConfig,
InstancePartitionsType instancePartitionsType) {
String tableNameWithType = tableConfig.getTableName();
- String instancePartitionsName =
- instancePartitionsType.getInstancePartitionsName(TableNameBuilder.extractRawTableName(tableNameWithType));
+ String rawTableName = TableNameBuilder.extractRawTableName(tableNameWithType);
+ String instancePartitionsName = instancePartitionsType.getInstancePartitionsName(rawTableName);
// Fetch the instance partitions from property store if exists
- String path = ZKMetadataProvider.constructPropertyStorePathForInstancePartitions(instancePartitionsName);
- ZNRecord znRecord = helixManager.getHelixPropertyStore().get(path, null, AccessOption.PERSISTENT);
- if (znRecord != null) {
- return InstancePartitions.fromZNRecord(znRecord);
+ ZkHelixPropertyStore<ZNRecord> propertyStore = helixManager.getHelixPropertyStore();
+ InstancePartitions instancePartitions = fetchInstancePartitions(propertyStore, instancePartitionsName);
+ if (instancePartitions != null) {
+ return instancePartitions;
+ }
+
+ // Use the CONSUMING instance partitions for COMPLETED segments if exists
+ if (instancePartitionsType == InstancePartitionsType.COMPLETED) {
+ InstancePartitions consumingInstancePartitions = fetchInstancePartitions(propertyStore,
+ InstancePartitionsType.CONSUMING.getInstancePartitionsName(rawTableName));
+ if (consumingInstancePartitions != null) {
+ return consumingInstancePartitions;
+ }
}
// Compute the instance partitions (for backward compatible)
@@ -64,19 +76,41 @@ public class InstancePartitionsUtils {
instances.sort(null);
int numInstances = instances.size();
Collections.rotate(instances, -(Math.abs(tableNameWithType.hashCode()) % numInstances));
- InstancePartitions instancePartitions = new InstancePartitions(instancePartitionsName);
+ instancePartitions = new InstancePartitions(instancePartitionsName);
instancePartitions.setInstances(0, 0, instances);
return instancePartitions;
}
/**
+ * Fetches the instance partitions from Helix property store.
+ */
+ @Nullable
+ public static InstancePartitions fetchInstancePartitions(HelixPropertyStore<ZNRecord> propertyStore,
+ String instancePartitionsName) {
+ String path = ZKMetadataProvider.constructPropertyStorePathForInstancePartitions(instancePartitionsName);
+ ZNRecord znRecord = propertyStore.get(path, null, AccessOption.PERSISTENT);
+ return znRecord != null ? InstancePartitions.fromZNRecord(znRecord) : null;
+ }
+
+ /**
* Persists the instance partitions to Helix property store.
- *
- * @return true if the instance partitions was successfully persisted, false otherwise
*/
- public static boolean persistInstancePartitions(HelixPropertyStore<ZNRecord> propertyStore,
+ public static void persistInstancePartitions(HelixPropertyStore<ZNRecord> propertyStore,
InstancePartitions instancePartitions) {
String path = ZKMetadataProvider.constructPropertyStorePathForInstancePartitions(instancePartitions.getName());
- return propertyStore.set(path, instancePartitions.toZNRecord(), AccessOption.PERSISTENT);
+ if (!propertyStore.set(path, instancePartitions.toZNRecord(), AccessOption.PERSISTENT)) {
+ throw new ZkException("Failed to persist instance partitions: " + instancePartitions.getName());
+ }
+ }
+
+ /**
+ * Removes the instance partitions from Helix property store.
+ */
+ public static void removeInstancePartitions(HelixPropertyStore<ZNRecord> propertyStore,
+ String instancePartitionsName) {
+ String path = ZKMetadataProvider.constructPropertyStorePathForInstancePartitions(instancePartitionsName);
+ if (!propertyStore.remove(path, AccessOption.PERSISTENT)) {
+ throw new ZkException("Failed to remove instance partitions: " + instancePartitionsName);
+ }
}
}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceAssignmentDriver.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceAssignmentDriver.java
index a1fbc40..a715834 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceAssignmentDriver.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceAssignmentDriver.java
@@ -24,6 +24,7 @@ import java.util.List;
import java.util.Map;
import org.apache.helix.model.InstanceConfig;
import org.apache.pinot.common.config.TableConfig;
+import org.apache.pinot.common.config.TableNameBuilder;
import org.apache.pinot.common.config.instance.InstanceAssignmentConfig;
import org.apache.pinot.common.config.instance.InstanceAssignmentConfigUtils;
import org.apache.pinot.common.config.instance.InstanceConstraintConfig;
@@ -80,6 +81,9 @@ public class InstanceAssignmentDriver {
Preconditions.checkState(replicaPartitionConfig != null, "Instance replica/partition config is missing");
InstanceReplicaPartitionSelector replicaPartitionSelector =
new InstanceReplicaPartitionSelector(replicaPartitionConfig, tableNameWithType);
- return replicaPartitionSelector.selectInstances(poolToInstanceConfigsMap);
+ InstancePartitions instancePartitions = new InstancePartitions(
+ instancePartitionsType.getInstancePartitionsName(TableNameBuilder.extractRawTableName(tableNameWithType)));
+ replicaPartitionSelector.selectInstances(poolToInstanceConfigsMap, instancePartitions);
+ return instancePartitions;
}
}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceReplicaPartitionSelector.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceReplicaPartitionSelector.java
index 8f6976c..90c35fb 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceReplicaPartitionSelector.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceReplicaPartitionSelector.java
@@ -51,7 +51,8 @@ public class InstanceReplicaPartitionSelector {
* Selects instances based on the replica and partition config, and stores the result into the given instance
* partitions.
*/
- public InstancePartitions selectInstances(Map<Integer, List<InstanceConfig>> poolToInstanceConfigsMap) {
+ public void selectInstances(Map<Integer, List<InstanceConfig>> poolToInstanceConfigsMap,
+ InstancePartitions instancePartitions) {
int numPools = poolToInstanceConfigsMap.size();
Preconditions.checkState(numPools != 0, "No pool qualified for selection");
@@ -61,8 +62,6 @@ public class InstanceReplicaPartitionSelector {
LOGGER.info("Starting instance replica/partition selection for table: {} with hash: {} from pools: {}",
_tableNameWithType, tableNameHash, pools);
- InstancePartitions instancePartitions = new InstancePartitions(_tableNameWithType);
-
if (_replicaPartitionConfig.isReplicaGroupBased()) {
// Replica-group based selection
@@ -183,7 +182,5 @@ public class InstanceReplicaPartitionSelector {
// Set the instances as partition 0 replica 0
instancePartitions.setInstances(0, 0, instancesToSelect);
}
-
- return instancePartitions;
}
}
diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceAssignmentRestletResourceTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceAssignmentRestletResourceTest.java
new file mode 100644
index 0000000..9d6e7d7
--- /dev/null
+++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceAssignmentRestletResourceTest.java
@@ -0,0 +1,339 @@
+/**
+ * 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.api;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import java.io.FileNotFoundException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+import org.apache.pinot.common.config.TableConfig;
+import org.apache.pinot.common.config.TableNameBuilder;
+import org.apache.pinot.common.config.TagNameUtils;
+import org.apache.pinot.common.config.Tenant;
+import org.apache.pinot.common.config.instance.InstanceAssignmentConfig;
+import org.apache.pinot.common.config.instance.InstanceReplicaPartitionConfig;
+import org.apache.pinot.common.config.instance.InstanceTagPoolConfig;
+import org.apache.pinot.common.data.FieldSpec.DataType;
+import org.apache.pinot.common.data.Schema;
+import org.apache.pinot.common.utils.CommonConstants.Helix.TableType;
+import org.apache.pinot.common.utils.InstancePartitionsType;
+import org.apache.pinot.common.utils.JsonUtils;
+import org.apache.pinot.common.utils.TenantRole;
+import org.apache.pinot.controller.ControllerConf;
+import org.apache.pinot.controller.helix.ControllerTest;
+import org.apache.pinot.controller.helix.core.assignment.InstancePartitions;
+import org.apache.pinot.core.realtime.impl.fakestream.FakeStreamConfigUtils;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+
+public class PinotInstanceAssignmentRestletResourceTest extends ControllerTest {
+ private static final int NUM_SERVER_INSTANCES = 3;
+ private static final String TENANT_NAME = "testTenant";
+ private static final String RAW_TABLE_NAME = "testTable";
+ private static final String TIME_COLUMN_NAME = "daysSinceEpoch";
+
+ @BeforeClass
+ public void setUp()
+ throws Exception {
+ startZk();
+ ControllerConf config = getDefaultControllerConfiguration();
+ config.setTenantIsolationEnabled(false);
+ startController(config);
+ addFakeBrokerInstancesToAutoJoinHelixCluster(1, false);
+ addFakeServerInstancesToAutoJoinHelixCluster(NUM_SERVER_INSTANCES, false);
+
+ // Create broker and server tenant
+ Tenant brokerTenant = new Tenant.TenantBuilder(TENANT_NAME).setRole(TenantRole.BROKER).setTotalInstances(1).build();
+ _helixResourceManager.createBrokerTenant(brokerTenant);
+ Tenant serverTenant =
+ new Tenant.TenantBuilder(TENANT_NAME).setRole(TenantRole.SERVER).setOfflineInstances(1).setRealtimeInstances(1)
+ .build();
+ _helixResourceManager.createServerTenant(serverTenant);
+ }
+
+ @Test
+ public void testInstanceAssignment()
+ throws Exception {
+ Schema schema =
+ new Schema.SchemaBuilder().setSchemaName(RAW_TABLE_NAME).addTime(TIME_COLUMN_NAME, TimeUnit.DAYS, DataType.INT)
+ .build();
+ _helixResourceManager.addOrUpdateSchema(schema);
+ TableConfig offlineTableConfig =
+ new TableConfig.Builder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setBrokerTenant(TENANT_NAME)
+ .setServerTenant(TENANT_NAME).build();
+ _helixResourceManager.addTable(offlineTableConfig);
+ TableConfig realtimeTableConfig =
+ new TableConfig.Builder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setBrokerTenant(TENANT_NAME)
+ .setServerTenant(TENANT_NAME).setLLC(true)
+ .setStreamConfigs(FakeStreamConfigUtils.getDefaultLowLevelStreamConfigs().getStreamConfigsMap()).build();
+ _helixResourceManager.addTable(realtimeTableConfig);
+
+ // There should be no instance partitions
+ try {
+ getInstancePartitionsMap();
+ fail();
+ } catch (FileNotFoundException e) {
+ // Expected
+ }
+
+ // Assign instances should fail
+ try {
+ sendPostRequest(_controllerRequestURLBuilder.forInstanceAssign(RAW_TABLE_NAME, null, true), null);
+ fail();
+ } catch (FileNotFoundException e) {
+ // Expected
+ }
+
+ // Add OFFLINE instance assignment config to the OFFLINE table config
+ InstanceAssignmentConfig offlineInstanceAssignmentConfig = new InstanceAssignmentConfig();
+ InstanceTagPoolConfig offlineInstanceTagPoolConfig = new InstanceTagPoolConfig();
+ offlineInstanceTagPoolConfig.setTag(TagNameUtils.getOfflineTagForTenant(TENANT_NAME));
+ offlineInstanceAssignmentConfig.setTagPoolConfig(offlineInstanceTagPoolConfig);
+ offlineInstanceAssignmentConfig.setReplicaPartitionConfig(new InstanceReplicaPartitionConfig());
+ offlineTableConfig.setInstanceAssignmentConfigMap(
+ Collections.singletonMap(InstancePartitionsType.OFFLINE, offlineInstanceAssignmentConfig));
+ _helixResourceManager.setExistingTableConfig(offlineTableConfig);
+
+ // OFFLINE instance partitions should be generated
+ Map<InstancePartitionsType, InstancePartitions> instancePartitionsMap = getInstancePartitionsMap();
+ assertEquals(instancePartitionsMap.size(), 1);
+ InstancePartitions offlineInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE);
+ assertNotNull(offlineInstancePartitions);
+ assertEquals(offlineInstancePartitions.getNumReplicas(), 1);
+ assertEquals(offlineInstancePartitions.getNumPartitions(), 1);
+ assertEquals(offlineInstancePartitions.getInstances(0, 0).size(), 1);
+ String offlineInstanceId = offlineInstancePartitions.getInstances(0, 0).get(0);
+
+ // Add CONSUMING instance assignment config to the REALTIME table config
+ InstanceAssignmentConfig consumingInstanceAssignmentConfig = new InstanceAssignmentConfig();
+ InstanceTagPoolConfig realtimeInstanceTagPoolConfig = new InstanceTagPoolConfig();
+ realtimeInstanceTagPoolConfig.setTag(TagNameUtils.getRealtimeTagForTenant(TENANT_NAME));
+ consumingInstanceAssignmentConfig.setTagPoolConfig(realtimeInstanceTagPoolConfig);
+ consumingInstanceAssignmentConfig.setReplicaPartitionConfig(new InstanceReplicaPartitionConfig());
+ realtimeTableConfig.setInstanceAssignmentConfigMap(
+ Collections.singletonMap(InstancePartitionsType.CONSUMING, consumingInstanceAssignmentConfig));
+ _helixResourceManager.setExistingTableConfig(realtimeTableConfig);
+
+ // CONSUMING instance partitions should be generated
+ instancePartitionsMap = getInstancePartitionsMap();
+ assertEquals(instancePartitionsMap.size(), 2);
+ offlineInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE);
+ assertNotNull(offlineInstancePartitions);
+ assertEquals(offlineInstancePartitions.getNumReplicas(), 1);
+ assertEquals(offlineInstancePartitions.getNumPartitions(), 1);
+ assertEquals(offlineInstancePartitions.getInstances(0, 0), Collections.singletonList(offlineInstanceId));
+ InstancePartitions consumingInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.CONSUMING);
+ assertNotNull(consumingInstancePartitions);
+ assertEquals(consumingInstancePartitions.getNumReplicas(), 1);
+ assertEquals(consumingInstancePartitions.getNumPartitions(), 1);
+ assertEquals(consumingInstancePartitions.getInstances(0, 0).size(), 1);
+ String realtimeInstanceId = consumingInstancePartitions.getInstances(0, 0).get(0);
+
+ // Use OFFLINE instance assignment config as the COMPLETED instance assignment config
+ realtimeTableConfig
+ .setInstanceAssignmentConfigMap(new TreeMap<InstancePartitionsType, InstanceAssignmentConfig>() {{
+ put(InstancePartitionsType.CONSUMING, consumingInstanceAssignmentConfig);
+ put(InstancePartitionsType.COMPLETED, offlineInstanceAssignmentConfig);
+ }});
+ _helixResourceManager.setExistingTableConfig(realtimeTableConfig);
+
+ // COMPLETED instance partitions should be generated
+ instancePartitionsMap = getInstancePartitionsMap();
+ assertEquals(instancePartitionsMap.size(), 3);
+ offlineInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE);
+ assertNotNull(offlineInstancePartitions);
+ assertEquals(offlineInstancePartitions.getNumReplicas(), 1);
+ assertEquals(offlineInstancePartitions.getNumPartitions(), 1);
+ assertEquals(offlineInstancePartitions.getInstances(0, 0), Collections.singletonList(offlineInstanceId));
+ consumingInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.CONSUMING);
+ assertNotNull(consumingInstancePartitions);
+ assertEquals(consumingInstancePartitions.getNumReplicas(), 1);
+ assertEquals(consumingInstancePartitions.getNumPartitions(), 1);
+ assertEquals(consumingInstancePartitions.getInstances(0, 0), Collections.singletonList(realtimeInstanceId));
+ InstancePartitions completedInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.COMPLETED);
+ assertEquals(completedInstancePartitions.getNumReplicas(), 1);
+ assertEquals(completedInstancePartitions.getNumPartitions(), 1);
+ assertEquals(completedInstancePartitions.getInstances(0, 0), Collections.singletonList(offlineInstanceId));
+
+ // Test fetching instance partitions by table name with type suffix
+ instancePartitionsMap = deserializeInstancePartitionsMap(sendGetRequest(_controllerRequestURLBuilder
+ .forInstancePartitions(TableNameBuilder.OFFLINE.tableNameWithType(RAW_TABLE_NAME), null)));
+ assertEquals(instancePartitionsMap.size(), 1);
+ assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.OFFLINE));
+ instancePartitionsMap = deserializeInstancePartitionsMap(sendGetRequest(_controllerRequestURLBuilder
+ .forInstancePartitions(TableNameBuilder.REALTIME.tableNameWithType(RAW_TABLE_NAME), null)));
+ assertEquals(instancePartitionsMap.size(), 2);
+ assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.CONSUMING));
+ assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.COMPLETED));
+
+ // Test fetching instance partitions by table name and instance partitions type
+ for (InstancePartitionsType instancePartitionsType : InstancePartitionsType.values()) {
+ instancePartitionsMap = deserializeInstancePartitionsMap(
+ sendGetRequest(_controllerRequestURLBuilder.forInstancePartitions(RAW_TABLE_NAME, instancePartitionsType)));
+ assertEquals(instancePartitionsMap.size(), 1);
+ assertEquals(instancePartitionsMap.get(instancePartitionsType).getName(),
+ instancePartitionsType.getInstancePartitionsName(RAW_TABLE_NAME));
+ }
+
+ // Remove the instance partitions for both OFFLINE and REALTIME table
+ sendDeleteRequest(_controllerRequestURLBuilder.forInstancePartitions(RAW_TABLE_NAME, null));
+ try {
+ getInstancePartitionsMap();
+ fail();
+ } catch (FileNotFoundException e) {
+ // Expected
+ }
+
+ // Assign instances without instance partitions type (dry run)
+ instancePartitionsMap = deserializeInstancePartitionsMap(
+ sendPostRequest(_controllerRequestURLBuilder.forInstanceAssign(RAW_TABLE_NAME, null, true), null));
+ assertEquals(instancePartitionsMap.size(), 3);
+ offlineInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE);
+ assertNotNull(offlineInstancePartitions);
+ assertEquals(offlineInstancePartitions.getNumReplicas(), 1);
+ assertEquals(offlineInstancePartitions.getNumPartitions(), 1);
+ assertEquals(offlineInstancePartitions.getInstances(0, 0), Collections.singletonList(offlineInstanceId));
+ consumingInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.CONSUMING);
+ assertNotNull(consumingInstancePartitions);
+ assertEquals(consumingInstancePartitions.getNumReplicas(), 1);
+ assertEquals(consumingInstancePartitions.getNumPartitions(), 1);
+ assertEquals(consumingInstancePartitions.getInstances(0, 0), Collections.singletonList(realtimeInstanceId));
+ completedInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.COMPLETED);
+ assertEquals(completedInstancePartitions.getNumReplicas(), 1);
+ assertEquals(completedInstancePartitions.getNumPartitions(), 1);
+ assertEquals(completedInstancePartitions.getInstances(0, 0), Collections.singletonList(offlineInstanceId));
+
+ // Instance partitions should not be persisted
+ try {
+ getInstancePartitionsMap();
+ fail();
+ } catch (FileNotFoundException e) {
+ // Expected
+ }
+
+ // Assign instances for both OFFLINE and REALTIME table
+ sendPostRequest(_controllerRequestURLBuilder.forInstanceAssign(RAW_TABLE_NAME, null, false), null);
+
+ // Instance partitions should be persisted
+ instancePartitionsMap = getInstancePartitionsMap();
+ assertEquals(instancePartitionsMap.size(), 3);
+
+ // Remove the instance partitions for REALTIME table
+ sendDeleteRequest(_controllerRequestURLBuilder
+ .forInstancePartitions(TableNameBuilder.REALTIME.tableNameWithType(RAW_TABLE_NAME), null));
+ instancePartitionsMap = getInstancePartitionsMap();
+ assertEquals(instancePartitionsMap.size(), 1);
+ assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.OFFLINE));
+
+ // Assign instances for COMPLETED segments
+ instancePartitionsMap = deserializeInstancePartitionsMap(sendPostRequest(
+ _controllerRequestURLBuilder.forInstanceAssign(RAW_TABLE_NAME, InstancePartitionsType.COMPLETED, false), null));
+ assertEquals(instancePartitionsMap.size(), 1);
+ assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.COMPLETED));
+
+ // There should be OFFLINE and COMPLETED instance partitions persisted
+ instancePartitionsMap = getInstancePartitionsMap();
+ assertEquals(instancePartitionsMap.size(), 2);
+ assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.OFFLINE));
+ assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.COMPLETED));
+
+ // Replace OFFLINE instance with REALTIME instance for COMPLETED instance partitions
+ instancePartitionsMap = deserializeInstancePartitionsMap(sendPostRequest(_controllerRequestURLBuilder
+ .forInstanceReplace(RAW_TABLE_NAME, InstancePartitionsType.COMPLETED, offlineInstanceId, realtimeInstanceId),
+ null));
+ assertEquals(instancePartitionsMap.size(), 1);
+ assertEquals(instancePartitionsMap.get(InstancePartitionsType.COMPLETED).getInstances(0, 0),
+ Collections.singletonList(realtimeInstanceId));
+
+ // Replace the instance again using REALTIME table name (old instance does not exist)
+ try {
+ sendPostRequest(_controllerRequestURLBuilder
+ .forInstanceReplace(TableNameBuilder.REALTIME.tableNameWithType(RAW_TABLE_NAME), null, offlineInstanceId,
+ realtimeInstanceId), null);
+ fail();
+ } catch (FileNotFoundException e) {
+ // Expected
+ }
+
+ // Post the CONSUMING instance partitions
+ instancePartitionsMap = deserializeInstancePartitionsMap(
+ sendPutRequest(_controllerRequestURLBuilder.forInstancePartitions(RAW_TABLE_NAME, null),
+ JsonUtils.objectToString(consumingInstancePartitions)));
+ assertEquals(instancePartitionsMap.size(), 1);
+ assertEquals(instancePartitionsMap.get(InstancePartitionsType.CONSUMING).getInstances(0, 0),
+ Collections.singletonList(realtimeInstanceId));
+
+ // OFFLINE instance partitions should have OFFLINE instance, CONSUMING and COMPLETED instance partitions should have
+ // REALTIME instance
+ instancePartitionsMap = getInstancePartitionsMap();
+ assertEquals(instancePartitionsMap.size(), 3);
+ assertEquals(instancePartitionsMap.get(InstancePartitionsType.OFFLINE).getInstances(0, 0),
+ Collections.singletonList(offlineInstanceId));
+ assertEquals(instancePartitionsMap.get(InstancePartitionsType.CONSUMING).getInstances(0, 0),
+ Collections.singletonList(realtimeInstanceId));
+ assertEquals(instancePartitionsMap.get(InstancePartitionsType.COMPLETED).getInstances(0, 0),
+ Collections.singletonList(realtimeInstanceId));
+
+ // Delete the OFFLINE table
+ _helixResourceManager.deleteOfflineTable(RAW_TABLE_NAME);
+ instancePartitionsMap = getInstancePartitionsMap();
+ assertEquals(instancePartitionsMap.size(), 2);
+ assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.CONSUMING));
+ assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.COMPLETED));
+
+ // Delete the REALTIME table
+ _helixResourceManager.deleteRealtimeTable(RAW_TABLE_NAME);
+ try {
+ getInstancePartitionsMap();
+ fail();
+ } catch (FileNotFoundException e) {
+ // Expected
+ }
+ }
+
+ private Map<InstancePartitionsType, InstancePartitions> getInstancePartitionsMap()
+ throws Exception {
+ return deserializeInstancePartitionsMap(
+ sendGetRequest(_controllerRequestURLBuilder.forInstancePartitions(RAW_TABLE_NAME, null)));
+ }
+
+ private Map<InstancePartitionsType, InstancePartitions> deserializeInstancePartitionsMap(
+ String instancePartitionsMapString)
+ throws Exception {
+ return JsonUtils.stringToObject(instancePartitionsMapString,
+ new TypeReference<Map<InstancePartitionsType, InstancePartitions>>() {
+ });
+ }
+
+ @AfterClass
+ public void tearDown() {
+ stopFakeInstances();
+ stopController();
+ stopZk();
+ }
+}
diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceAssignmentTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceAssignmentTest.java
index 8639b00..fec7a73 100644
--- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceAssignmentTest.java
+++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceAssignmentTest.java
@@ -29,8 +29,8 @@ import org.apache.pinot.common.config.ReplicaGroupStrategyConfig;
import org.apache.pinot.common.config.SegmentPartitionConfig;
import org.apache.pinot.common.config.TableConfig;
import org.apache.pinot.common.config.TagNameUtils;
-import org.apache.pinot.common.config.TagOverrideConfig;
import org.apache.pinot.common.config.instance.InstanceAssignmentConfig;
+import org.apache.pinot.common.config.instance.InstanceAssignmentConfigUtils;
import org.apache.pinot.common.config.instance.InstanceReplicaPartitionConfig;
import org.apache.pinot.common.config.instance.InstanceTagPoolConfig;
import org.apache.pinot.common.utils.CommonConstants.Helix.TableType;
@@ -40,6 +40,7 @@ import org.apache.pinot.controller.helix.core.assignment.InstancePartitions;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
import static org.testng.Assert.fail;
@@ -47,34 +48,9 @@ public class InstanceAssignmentTest {
private static final String RAW_TABLE_NAME = "myTable";
private static final String TENANT_NAME = "tenant";
private static final String OFFLINE_TAG = TagNameUtils.getOfflineTagForTenant(TENANT_NAME);
- private static final String REALTIME_TAG = TagNameUtils.getRealtimeTagForTenant(TENANT_NAME);
private static final String SERVER_INSTANCE_ID_PREFIX = "Server_localhost_";
@Test
- public void testDefaultOfflineNonReplicaGroup() {
- TableConfig tableConfig =
- new TableConfig.Builder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setServerTenant(TENANT_NAME).build();
- InstanceAssignmentDriver driver = new InstanceAssignmentDriver(tableConfig);
- int numInstances = 10;
- List<InstanceConfig> instanceConfigs = new ArrayList<>(numInstances);
- for (int i = 0; i < numInstances; i++) {
- InstanceConfig instanceConfig = new InstanceConfig(SERVER_INSTANCE_ID_PREFIX + i);
- instanceConfig.addTag(OFFLINE_TAG);
- instanceConfigs.add(instanceConfig);
- }
-
- // All instances should be assigned as replica 0 partition 0
- InstancePartitions instancePartitions = driver.assignInstances(InstancePartitionsType.OFFLINE, instanceConfigs);
- assertEquals(instancePartitions.getNumReplicas(), 1);
- assertEquals(instancePartitions.getNumPartitions(), 1);
- List<String> expectedInstances = new ArrayList<>(numInstances);
- for (int i = 0; i < numInstances; i++) {
- expectedInstances.add(SERVER_INSTANCE_ID_PREFIX + i);
- }
- assertEquals(instancePartitions.getInstances(0, 0), expectedInstances);
- }
-
- @Test
public void testDefaultOfflineReplicaGroup() {
int numReplicas = 3;
TableConfig tableConfig =
@@ -149,53 +125,6 @@ public class InstanceAssignmentTest {
}
@Test
- public void testRealtimeTagOverride() {
- // Override COMPLETED tag to use OFFLINE tag
- TagOverrideConfig tagOverrideConfig = new TagOverrideConfig();
- tagOverrideConfig.setRealtimeConsuming(REALTIME_TAG);
- tagOverrideConfig.setRealtimeCompleted(OFFLINE_TAG);
- TableConfig tableConfig =
- new TableConfig.Builder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setTagOverrideConfig(tagOverrideConfig)
- .build();
- InstanceAssignmentDriver driver = new InstanceAssignmentDriver(tableConfig);
- int numInstances = 10;
- List<InstanceConfig> instanceConfigs = new ArrayList<>(numInstances);
- for (int i = 0; i < numInstances; i++) {
- InstanceConfig instanceConfig = new InstanceConfig(SERVER_INSTANCE_ID_PREFIX + i);
- if (i % 2 == 0) {
- instanceConfig.addTag(REALTIME_TAG);
- } else {
- instanceConfig.addTag(OFFLINE_TAG);
- }
- instanceConfigs.add(instanceConfig);
- }
-
- // All instances with REALTIME tag should be assigned as replica 0 partition 0 for CONSUMING instance partitions
- InstancePartitions instancePartitions = driver.assignInstances(InstancePartitionsType.CONSUMING, instanceConfigs);
- assertEquals(instancePartitions.getNumReplicas(), 1);
- assertEquals(instancePartitions.getNumPartitions(), 1);
- List<String> expectedInstances = new ArrayList<>(numInstances);
- for (int i = 0; i < numInstances; i++) {
- if (i % 2 == 0) {
- expectedInstances.add(SERVER_INSTANCE_ID_PREFIX + i);
- }
- }
- assertEquals(instancePartitions.getInstances(0, 0), expectedInstances);
-
- // All instances with OFFLINE tag should be assigned as replica 0 partition 0 for COMPLETED instance partitions
- instancePartitions = driver.assignInstances(InstancePartitionsType.COMPLETED, instanceConfigs);
- assertEquals(instancePartitions.getNumReplicas(), 1);
- assertEquals(instancePartitions.getNumPartitions(), 1);
- expectedInstances = new ArrayList<>(numInstances);
- for (int i = 0; i < numInstances; i++) {
- if (i % 2 != 0) {
- expectedInstances.add(SERVER_INSTANCE_ID_PREFIX + i);
- }
- }
- assertEquals(instancePartitions.getInstances(0, 0), expectedInstances);
- }
-
- @Test
public void testPoolBased() {
// 10 instances in 2 pools, each with 5 instances
int numInstances = 10;
@@ -329,10 +258,7 @@ public class InstanceAssignmentTest {
@Test
public void testIllegalConfig() {
- InstanceAssignmentConfig assignmentConfig = new InstanceAssignmentConfig();
- TableConfig tableConfig = new TableConfig.Builder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME)
- .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, assignmentConfig))
- .build();
+ TableConfig tableConfig = new TableConfig.Builder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build();
InstanceAssignmentDriver driver = new InstanceAssignmentDriver(tableConfig);
int numInstances = 10;
@@ -342,6 +268,19 @@ public class InstanceAssignmentTest {
instanceConfigs.add(instanceConfig);
}
+ // No instance assignment config
+ assertFalse(InstanceAssignmentConfigUtils.allowInstanceAssignment(tableConfig, InstancePartitionsType.OFFLINE));
+ try {
+ driver.assignInstances(InstancePartitionsType.OFFLINE, instanceConfigs);
+ fail();
+ } catch (IllegalStateException e) {
+ assertEquals(e.getMessage(), "Instance assignment is not allowed for the given table config");
+ }
+
+ InstanceAssignmentConfig assignmentConfig = new InstanceAssignmentConfig();
+ tableConfig
+ .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, assignmentConfig));
+
// No instance tag/pool config
try {
driver.assignInstances(InstancePartitionsType.OFFLINE, instanceConfigs);
diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/ReplicaGroupRebalanceStrategyTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/ReplicaGroupRebalanceStrategyTest.java
index 1a5d017..41bdc8d 100644
--- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/ReplicaGroupRebalanceStrategyTest.java
+++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/ReplicaGroupRebalanceStrategyTest.java
@@ -217,9 +217,11 @@ public class ReplicaGroupRebalanceStrategyTest extends ControllerTest {
}
// Test removing a replica group
+ // NOTE: ReplicaGroupRebalanceSegmentStrategy is able to remove a replica-group only when all the instances in the
+ // replica are removed
_helixAdmin.removeInstanceTag(getHelixClusterName(), "Server_localhost_0", OFFLINE_TENENT_NAME);
- _helixAdmin.removeInstanceTag(getHelixClusterName(), "Server_localhost_1", OFFLINE_TENENT_NAME);
_helixAdmin.removeInstanceTag(getHelixClusterName(), "Server_localhost_2", OFFLINE_TENENT_NAME);
+ _helixAdmin.removeInstanceTag(getHelixClusterName(), "Server_localhost_4", OFFLINE_TENENT_NAME);
targetNumInstancePerPartition = 3;
targetNumReplicaGroup = 2;
diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/sharding/SegmentAssignmentStrategyTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/sharding/SegmentAssignmentStrategyTest.java
index 92ea2af..9f35b9b 100644
--- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/sharding/SegmentAssignmentStrategyTest.java
+++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/sharding/SegmentAssignmentStrategyTest.java
@@ -206,78 +206,6 @@ public class SegmentAssignmentStrategyTest extends ControllerTest {
Assert.assertTrue(partitionAssignment == null);
}
- /**
- * Tests to check creation/deletion of replica group znode when table is created/updated/deleted
- */
- @Test
- public void testRealtimeReplicaGroupPartitionAssignment() {
- String rawTableName = TABLE_NAME_REPLICA_GROUP_PARTITION_ASSIGNMENT;
- String tableNameWithType = TableNameBuilder.REALTIME.tableNameWithType(rawTableName);
-
- Schema schema = new Schema.SchemaBuilder().setSchemaName(rawTableName).build();
- _helixResourceManager.addOrUpdateSchema(schema);
-
- Map<String, String> streamConfigMap = FakeStreamConfigUtils.getDefaultLowLevelStreamConfigs().getStreamConfigsMap();
-
- // Adding a table without replica group
- TableConfig tableConfig = new TableConfig.Builder(CommonConstants.Helix.TableType.REALTIME)
- .setTableName(TABLE_NAME_REPLICA_GROUP_PARTITION_ASSIGNMENT)
- .setSegmentAssignmentStrategy("RandomAssignmentStrategy").setNumReplicas(NUM_REPLICAS)
- .setStreamConfigs(streamConfigMap).setLLC(true).build();
- try {
- _helixResourceManager.addTable(tableConfig);
- } catch (Exception e) {
- // ignore
- }
-
- // Check that partition assignment does not exist
- ReplicaGroupPartitionAssignment partitionAssignment =
- _partitionAssignmentGenerator.getReplicaGroupPartitionAssignment(tableNameWithType);
-
- Assert.assertNull(partitionAssignment);
-
- // Update table config with replica group config
- int numInstancesPerPartition = 5;
- ReplicaGroupStrategyConfig replicaGroupStrategyConfig = new ReplicaGroupStrategyConfig();
- replicaGroupStrategyConfig.setNumInstancesPerPartition(numInstancesPerPartition);
- replicaGroupStrategyConfig.setMirrorAssignmentAcrossReplicaGroups(true);
-
- TableConfig replicaGroupTableConfig = new TableConfig.Builder(CommonConstants.Helix.TableType.REALTIME)
- .setTableName(TABLE_NAME_REPLICA_GROUP_PARTITION_ASSIGNMENT).setNumReplicas(NUM_REPLICAS)
- .setSegmentAssignmentStrategy("ReplicaGroupSegmentAssignmentStrategy").setStreamConfigs(streamConfigMap)
- .setLLC(true).build();
-
- replicaGroupTableConfig.getValidationConfig().setReplicaGroupStrategyConfig(replicaGroupStrategyConfig);
-
- // Check that the replica group partition assignment is created
- try {
- _helixResourceManager.setExistingTableConfig(replicaGroupTableConfig);
- } catch (Exception e) {
- // ignore
- }
- partitionAssignment = _partitionAssignmentGenerator.getReplicaGroupPartitionAssignment(tableNameWithType);
- Assert.assertNotNull(partitionAssignment);
-
- // After table deletion, check that the replica group partition assignment is deleted
- _helixResourceManager.deleteRealtimeTable(rawTableName);
- partitionAssignment = _partitionAssignmentGenerator.getReplicaGroupPartitionAssignment(tableNameWithType);
- Assert.assertNull(partitionAssignment);
-
- // Create a table with replica group
- try {
- _helixResourceManager.addTable(replicaGroupTableConfig);
- } catch (Exception e) {
- // ignore
- }
- partitionAssignment = _partitionAssignmentGenerator.getReplicaGroupPartitionAssignment(tableNameWithType);
- Assert.assertNotNull(partitionAssignment);
-
- // Check that the replica group partition assignment is deleted
- _helixResourceManager.deleteRealtimeTable(rawTableName);
- partitionAssignment = _partitionAssignmentGenerator.getReplicaGroupPartitionAssignment(tableNameWithType);
- Assert.assertNull(partitionAssignment);
- }
-
@Test
public void testTableLevelAndMirroringReplicaGroupSegmentAssignmentStrategy()
throws Exception {
diff --git a/pinot-core/src/main/java/org/apache/pinot/core/util/ReplicationUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/util/ReplicationUtils.java
index 2c37800..5b0bd28 100644
--- a/pinot-core/src/main/java/org/apache/pinot/core/util/ReplicationUtils.java
+++ b/pinot-core/src/main/java/org/apache/pinot/core/util/ReplicationUtils.java
@@ -54,39 +54,4 @@ public class ReplicationUtils {
}
return false;
}
-
- /**
- * Returns the {@link SegmentsValidationAndRetentionConfig::getReplicasPerPartitionNumber} if it is eligible for use,
- * else returns the {@link SegmentsValidationAndRetentionConfig::getReplicationNumber}
- *
- * The reason we have replication as well as replicasPerPartition is, "replication" is used in HLC to support 'split' kafka topics
- * For example, if replication is 3, and we have 6 realtime servers, each server will half of the events in the topic
- * (see @link PinotTableIdealStateBuilder::setupInstanceConfigForHighLevelConsumer}
- * ReplicasPerPartition is used in LLC as the replication.
- * We need to keep both, as we could be operating in dual mode during migrations from HLC to LLC
- */
- public static int getReplication(TableConfig tableConfig) {
-
- SegmentsValidationAndRetentionConfig validationConfig = tableConfig.getValidationConfig();
- return useReplicasPerPartition(tableConfig) ? validationConfig.getReplicasPerPartitionNumber() : validationConfig
- .getReplicationNumber();
- }
-
- /**
- * Check if replica groups setup is necessary for realtime
- *
- * Replica groups is supported when only LLC is present.
- * We do not want znode being created or failures in some validations, when attempting to setup replica groups in HLC.
- * As a result, Replica groups cannot be used during migrations from HLC to LLC.
- * In such a scenario, migrate to LLC completely, and then enable replica groups
- *
- */
- public static boolean setupRealtimeReplicaGroups(TableConfig tableConfig) {
- TableType tableType = tableConfig.getTableType();
- if (tableType.equals(TableType.REALTIME)) {
- StreamConfig streamConfig = new StreamConfig(tableConfig.getIndexingConfig().getStreamConfigs());
- return streamConfig.hasLowLevelConsumerType() && !streamConfig.hasHighLevelConsumerType();
- }
- return false;
- }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org
[incubator-pinot] 07/08: Fixing the issue of selection order by
won't give correct ordering
Posted by xi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
xiangfu pushed a commit to branch winedepot-0.10
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git
commit e80b53a474a925a6c762e7b73893923aa1aefd9f
Author: Xiang Fu <fx...@gmail.com>
AuthorDate: Mon Aug 19 18:44:28 2019 -0700
Fixing the issue of selection order by won't give correct ordering
---
.../core/query/selection/SelectionOperatorService.java | 17 +++++++++++++++--
.../tests/OfflineClusterIntegrationTest.java | 16 ++++++++++++++++
2 files changed, 31 insertions(+), 2 deletions(-)
diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/selection/SelectionOperatorService.java b/pinot-core/src/main/java/org/apache/pinot/core/query/selection/SelectionOperatorService.java
index 16d4d8a..f504507 100644
--- a/pinot-core/src/main/java/org/apache/pinot/core/query/selection/SelectionOperatorService.java
+++ b/pinot-core/src/main/java/org/apache/pinot/core/query/selection/SelectionOperatorService.java
@@ -20,6 +20,7 @@ package org.apache.pinot.core.query.selection;
import java.io.Serializable;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
@@ -70,6 +71,7 @@ import org.apache.pinot.core.query.selection.comparator.CompositeDocIdValCompara
public class SelectionOperatorService {
private final List<String> _selectionColumns;
private final List<SelectionSort> _sortSequence;
+ private final List<Integer> _sortColumnIdx;
private final DataSchema _dataSchema;
private final int _selectionOffset;
private final int _maxNumRows;
@@ -86,6 +88,7 @@ public class SelectionOperatorService {
public SelectionOperatorService(@Nonnull Selection selection, @Nonnull IndexSegment indexSegment) {
_selectionColumns = SelectionOperatorUtils.getSelectionColumns(selection.getSelectionColumns(), indexSegment);
_sortSequence = getSortSequence(selection.getSelectionSortSequence());
+ _sortColumnIdx = getSortColumnIdx(_sortSequence, _selectionColumns);
_dataSchema = SelectionOperatorUtils.extractDataSchema(_sortSequence, _selectionColumns, indexSegment);
// Select rows from offset to offset + size.
_selectionOffset = selection.getOffset();
@@ -93,6 +96,14 @@ public class SelectionOperatorService {
_rows = new PriorityQueue<>(_maxNumRows, getStrictComparator());
}
+ private List<Integer> getSortColumnIdx(List<SelectionSort> sortSequence, List<String> selectionColumns) {
+ List<Integer> sortColumnIdx = new ArrayList<>();
+ for (SelectionSort seq: sortSequence) {
+ sortColumnIdx.add(selectionColumns.indexOf(seq.column));
+ }
+ return sortColumnIdx;
+ }
+
/**
* Constructor for <code>SelectionOperatorService</code> with {@link DataSchema}. (Inter segment)
*
@@ -102,6 +113,7 @@ public class SelectionOperatorService {
public SelectionOperatorService(@Nonnull Selection selection, @Nonnull DataSchema dataSchema) {
_selectionColumns = SelectionOperatorUtils.getSelectionColumns(selection.getSelectionColumns(), dataSchema);
_sortSequence = getSortSequence(selection.getSelectionSortSequence());
+ _sortColumnIdx = getSortColumnIdx(_sortSequence, _selectionColumns);
_dataSchema = dataSchema;
// Select rows from offset to offset + size.
_selectionOffset = selection.getOffset();
@@ -211,8 +223,9 @@ public class SelectionOperatorService {
for (int i = 0; i < numSortColumns; i++) {
int ret = 0;
SelectionSort selectionSort = _sortSequence.get(i);
- Serializable v1 = o1[i];
- Serializable v2 = o2[i];
+ int colIdx = _sortColumnIdx.get(i);
+ Serializable v1 = o1[colIdx];
+ Serializable v2 = o2[colIdx];
// Only compare single-value columns.
if (v1 instanceof Number) {
diff --git a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/OfflineClusterIntegrationTest.java b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/OfflineClusterIntegrationTest.java
index 456efb4..c9ab5b7 100644
--- a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/OfflineClusterIntegrationTest.java
+++ b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/OfflineClusterIntegrationTest.java
@@ -681,6 +681,22 @@ public class OfflineClusterIntegrationTest extends BaseClusterIntegrationTestSet
"SELECT ArrTime, ArrTime, count(*) FROM mytable WHERE DaysSinceEpoch <= 16312 AND Carrier = 'DL' group by ArrTime, ArrTime"));
}
+ @Test
+ public void testQueryWithOrderby()
+ throws Exception {
+ //test repeated columns in selection query
+ String query = "SELECT ArrTime, Carrier, DaysSinceEpoch FROM mytable ORDER BY DaysSinceEpoch DESC";
+ testQuery(query, Collections.singletonList(query));
+
+ //test repeated columns in selection query
+ query = "SELECT ArrTime, DaysSinceEpoch, Carrier FROM mytable ORDER BY Carrier DESC";
+ testQuery(query, Collections.singletonList(query));
+
+ //test repeated columns in selection query
+ query = "SELECT ArrTime, DaysSinceEpoch, Carrier FROM mytable ORDER BY Carrier DESC, ArrTime DESC";
+ testQuery(query, Collections.singletonList(query));
+ }
+
@AfterClass
public void tearDown()
throws Exception {
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org
[incubator-pinot] 02/08: brag about Star-Tree Index support (#4530)
Posted by xi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
xiangfu pushed a commit to branch winedepot-0.10
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git
commit d6de05d0bb9a13f0f5906715965968cb3e5d275a
Author: The Alchemist <ka...@gmail.com>
AuthorDate: Wed Aug 14 18:45:59 2019 -0400
brag about Star-Tree Index support (#4530)
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index ef119fa..6b285a6 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,7 @@ Looking for the ThirdEye anomaly detection and root-cause analysis platform? Che
## Key Features
- A column-oriented database with various compression schemes such as Run Length, Fixed Bit Length
-- Pluggable indexing technologies - Sorted Index, Bitmap Index, Inverted Index
+- Pluggable indexing technologies - Sorted Index, Bitmap Index, Inverted Index, Star-Tree Index
- Ability to optimize query/execution plan based on query and segment metadata
- Near real time ingestion from Kafka and batch ingestion from Hadoop
- SQL like language that supports _selection, aggregation, filtering, group by, order by, distinct_ queries on fact data
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org
[incubator-pinot] 08/08: Update selection columns logic
Posted by xi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
xiangfu pushed a commit to branch winedepot-0.10
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git
commit d81112f84fed8fbaff75d5073df051b6c4d6cba1
Author: Xiang Fu <fx...@gmail.com>
AuthorDate: Tue Aug 20 12:34:33 2019 -0700
Update selection columns logic
---
.../core/query/selection/SelectionOperatorService.java | 16 ++++------------
.../core/query/selection/SelectionOperatorUtils.java | 18 +++++++++++-------
2 files changed, 15 insertions(+), 19 deletions(-)
diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/selection/SelectionOperatorService.java b/pinot-core/src/main/java/org/apache/pinot/core/query/selection/SelectionOperatorService.java
index f504507..e2f87f4 100644
--- a/pinot-core/src/main/java/org/apache/pinot/core/query/selection/SelectionOperatorService.java
+++ b/pinot-core/src/main/java/org/apache/pinot/core/query/selection/SelectionOperatorService.java
@@ -71,7 +71,7 @@ import org.apache.pinot.core.query.selection.comparator.CompositeDocIdValCompara
public class SelectionOperatorService {
private final List<String> _selectionColumns;
private final List<SelectionSort> _sortSequence;
- private final List<Integer> _sortColumnIdx;
+ private final int[] _sortColumnIdx;
private final DataSchema _dataSchema;
private final int _selectionOffset;
private final int _maxNumRows;
@@ -88,22 +88,14 @@ public class SelectionOperatorService {
public SelectionOperatorService(@Nonnull Selection selection, @Nonnull IndexSegment indexSegment) {
_selectionColumns = SelectionOperatorUtils.getSelectionColumns(selection.getSelectionColumns(), indexSegment);
_sortSequence = getSortSequence(selection.getSelectionSortSequence());
- _sortColumnIdx = getSortColumnIdx(_sortSequence, _selectionColumns);
_dataSchema = SelectionOperatorUtils.extractDataSchema(_sortSequence, _selectionColumns, indexSegment);
+ _sortColumnIdx = SelectionOperatorUtils.getColumnIndices(SelectionOperatorUtils.extractSortColumns(_sortSequence), _dataSchema);
// Select rows from offset to offset + size.
_selectionOffset = selection.getOffset();
_maxNumRows = _selectionOffset + selection.getSize();
_rows = new PriorityQueue<>(_maxNumRows, getStrictComparator());
}
- private List<Integer> getSortColumnIdx(List<SelectionSort> sortSequence, List<String> selectionColumns) {
- List<Integer> sortColumnIdx = new ArrayList<>();
- for (SelectionSort seq: sortSequence) {
- sortColumnIdx.add(selectionColumns.indexOf(seq.column));
- }
- return sortColumnIdx;
- }
-
/**
* Constructor for <code>SelectionOperatorService</code> with {@link DataSchema}. (Inter segment)
*
@@ -113,8 +105,8 @@ public class SelectionOperatorService {
public SelectionOperatorService(@Nonnull Selection selection, @Nonnull DataSchema dataSchema) {
_selectionColumns = SelectionOperatorUtils.getSelectionColumns(selection.getSelectionColumns(), dataSchema);
_sortSequence = getSortSequence(selection.getSelectionSortSequence());
- _sortColumnIdx = getSortColumnIdx(_sortSequence, _selectionColumns);
_dataSchema = dataSchema;
+ _sortColumnIdx = SelectionOperatorUtils.getColumnIndices(SelectionOperatorUtils.extractSortColumns(_sortSequence), _dataSchema);
// Select rows from offset to offset + size.
_selectionOffset = selection.getOffset();
_maxNumRows = _selectionOffset + selection.getSize();
@@ -223,7 +215,7 @@ public class SelectionOperatorService {
for (int i = 0; i < numSortColumns; i++) {
int ret = 0;
SelectionSort selectionSort = _sortSequence.get(i);
- int colIdx = _sortColumnIdx.get(i);
+ int colIdx = _sortColumnIdx[i];
Serializable v1 = o1[colIdx];
Serializable v2 = o2[colIdx];
diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/selection/SelectionOperatorUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/query/selection/SelectionOperatorUtils.java
index abdcd38..27585fe 100644
--- a/pinot-core/src/main/java/org/apache/pinot/core/query/selection/SelectionOperatorUtils.java
+++ b/pinot-core/src/main/java/org/apache/pinot/core/query/selection/SelectionOperatorUtils.java
@@ -67,14 +67,13 @@ import org.apache.pinot.core.indexsegment.IndexSegment;
* </ul>
*/
public class SelectionOperatorUtils {
- private SelectionOperatorUtils() {
- }
-
private static final String INT_PATTERN = "##########";
private static final String LONG_PATTERN = "####################";
private static final String FLOAT_PATTERN = "#########0.0####";
private static final String DOUBLE_PATTERN = "###################0.0#########";
private static final DecimalFormatSymbols DECIMAL_FORMAT_SYMBOLS = DecimalFormatSymbols.getInstance(Locale.US);
+ private SelectionOperatorUtils() {
+ }
/**
* Expand <code>'SELECT *'</code> to select all columns with {@link IndexSegment}, order all columns alphabetically.
@@ -437,8 +436,7 @@ public class SelectionOperatorUtils {
* @param dataSchema data schema.
* @return column indices
*/
- public static int[] getColumnIndices(@Nonnull List<String> selectionColumns,
- @Nonnull DataSchema dataSchema) {
+ public static int[] getColumnIndices(@Nonnull List<String> selectionColumns, @Nonnull DataSchema dataSchema) {
int numSelectionColumns = selectionColumns.size();
int[] columnIndices = new int[numSelectionColumns];
int numColumnsInDataSchema = dataSchema.size();
@@ -452,8 +450,6 @@ public class SelectionOperatorUtils {
return columnIndices;
}
-
-
/**
* Extract columns from the row based on the given column indices.
* <p>The extracted row is used to build the {@link SelectionResults}.
@@ -679,4 +675,12 @@ public class SelectionOperatorUtils {
queue.offer(value);
}
}
+
+ public static List<String> extractSortColumns(List<SelectionSort> sortSequence) {
+ List<String> columns = new ArrayList<>();
+ for (SelectionSort s : sortSequence) {
+ columns.add(s.column);
+ }
+ return columns;
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org
[incubator-pinot] 06/08: [Instance Assignment] De-couple assignment
strategy from SegmentAssignment (#4533)
Posted by xi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
xiangfu pushed a commit to branch winedepot-0.10
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git
commit f2baddd5601c03bd6161807e3b148aa54cf0167b
Author: Xiaotian (Jackie) Jiang <17...@users.noreply.github.com>
AuthorDate: Mon Aug 19 14:15:21 2019 -0700
[Instance Assignment] De-couple assignment strategy from SegmentAssignment (#4533)
For REALTIME segments, we should be able to assign CONSUMING and
COMPLETED segments with different strategies, i.e. one could
follows replica-group while the other follows balance-number. In
order to do so, we need to de-couple the assignment strategy from
the segment assignment, so each assignment can use different
strategies if necessary.
- Change SegmentAssignmentStrategy interface to SegmentAssignment
- Add OfflineSegmentAssignment and RealtimeSegmentAssignment
- Use InstancePartitions to determine the strategy to use
- For REALTIME table, support different strategies for CONSUMING
and COMPLETED segments
---
.../helix/core/assignment/InstancePartitions.java | 17 ++
...OfflineBalanceNumSegmentAssignmentStrategy.java | 96 --------
...flineReplicaGroupSegmentAssignmentStrategy.java | 200 ----------------
.../segment/OfflineSegmentAssignment.java | 260 +++++++++++++++++++++
...ealtimeBalanceNumSegmentAssignmentStrategy.java | 161 -------------
...ltimeReplicaGroupSegmentAssignmentStrategy.java | 165 -------------
.../segment/RealtimeSegmentAssignment.java | 218 +++++++++++++++++
...ignmentStrategy.java => SegmentAssignment.java} | 27 ++-
.../segment/SegmentAssignmentFactory.java | 43 ++++
.../segment/SegmentAssignmentStrategyFactory.java | 54 -----
.../assignment/segment/SegmentAssignmentUtils.java | 26 +--
...PinotInstanceAssignmentRestletResourceTest.java | 2 +-
...flineNonReplicaGroupSegmentAssignmentTest.java} | 43 ++--
... OfflineReplicaGroupSegmentAssignmentTest.java} | 115 ++++-----
...ltimeNonReplicaGroupSegmentAssignmentTest.java} | 65 ++----
...RealtimeReplicaGroupSegmentAssignmentTest.java} | 67 +++---
16 files changed, 692 insertions(+), 867 deletions(-)
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/InstancePartitions.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/InstancePartitions.java
index 4ed659e..4060f4b 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/InstancePartitions.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/InstancePartitions.java
@@ -22,10 +22,12 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.helix.ZNRecord;
+import org.apache.pinot.common.utils.JsonUtils;
/**
@@ -64,10 +66,12 @@ public class InstancePartitions {
}
}
+ @JsonProperty
public String getName() {
return _name;
}
+ @JsonProperty
public Map<String, List<String>> getPartitionToInstancesMap() {
return _partitionToInstancesMap;
}
@@ -102,4 +106,17 @@ public class InstancePartitions {
znRecord.setListFields(_partitionToInstancesMap);
return znRecord;
}
+
+ public String toJsonString() {
+ try {
+ return JsonUtils.objectToString(this);
+ } catch (JsonProcessingException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return toJsonString();
+ }
}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineBalanceNumSegmentAssignmentStrategy.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineBalanceNumSegmentAssignmentStrategy.java
deleted file mode 100644
index 4bd34b5..0000000
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineBalanceNumSegmentAssignmentStrategy.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/**
- * 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.assignment.segment;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.PriorityQueue;
-import org.apache.commons.configuration.Configuration;
-import org.apache.helix.HelixManager;
-import org.apache.pinot.common.config.TableConfig;
-import org.apache.pinot.common.utils.InstancePartitionsType;
-import org.apache.pinot.common.utils.Pairs;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-
-/**
- * Segment assignment strategy for offline segments that assigns segment to the instance with the least number of
- * segments. In case of a tie, assigns to the instance with the smallest index in the list. The strategy ensures that
- * replicas of the same segment are not assigned to the same server.
- * <p>To rebalance a table, use Helix AutoRebalanceStrategy.
- */
-public class OfflineBalanceNumSegmentAssignmentStrategy implements SegmentAssignmentStrategy {
- private static final Logger LOGGER = LoggerFactory.getLogger(OfflineBalanceNumSegmentAssignmentStrategy.class);
-
- private HelixManager _helixManager;
- private TableConfig _tableConfig;
- private String _tableNameWithType;
- private int _replication;
-
- @Override
- public void init(HelixManager helixManager, TableConfig tableConfig) {
- _helixManager = helixManager;
- _tableConfig = tableConfig;
- _tableNameWithType = tableConfig.getTableName();
- _replication = tableConfig.getValidationConfig().getReplicationNumber();
-
- LOGGER.info("Initialized OfflineBalanceNumSegmentAssignmentStrategy for table: {} with replication: {}",
- _tableNameWithType, _replication);
- }
-
- @Override
- public List<String> assignSegment(String segmentName, Map<String, Map<String, String>> currentAssignment) {
- List<String> instances = SegmentAssignmentUtils
- .getInstancesForBalanceNumStrategy(_helixManager, _tableConfig, _replication, InstancePartitionsType.OFFLINE);
- int[] numSegmentsAssignedPerInstance =
- SegmentAssignmentUtils.getNumSegmentsAssignedPerInstance(currentAssignment, instances);
-
- // Assign the segment to the instance with the least segments, or the smallest id if there is a tie
- int numInstances = numSegmentsAssignedPerInstance.length;
- PriorityQueue<Pairs.IntPair> heap = new PriorityQueue<>(numInstances, Pairs.intPairComparator());
- for (int instanceId = 0; instanceId < numInstances; instanceId++) {
- heap.add(new Pairs.IntPair(numSegmentsAssignedPerInstance[instanceId], instanceId));
- }
- List<String> instancesAssigned = new ArrayList<>(_replication);
- for (int i = 0; i < _replication; i++) {
- instancesAssigned.add(instances.get(heap.remove().getRight()));
- }
-
- LOGGER.info("Assigned segment: {} to instances: {} for table: {}", segmentName, instancesAssigned,
- _tableNameWithType);
- return instancesAssigned;
- }
-
- @Override
- public Map<String, Map<String, String>> rebalanceTable(Map<String, Map<String, String>> currentAssignment,
- Configuration config) {
- List<String> instances = SegmentAssignmentUtils
- .getInstancesForBalanceNumStrategy(_helixManager, _tableConfig, _replication, InstancePartitionsType.OFFLINE);
- Map<String, Map<String, String>> newAssignment =
- SegmentAssignmentUtils.rebalanceTableWithHelixAutoRebalanceStrategy(currentAssignment, instances, _replication);
-
- LOGGER.info(
- "Rebalanced {} segments to instances: {} for table: {} with replication: {}, number of segments to be moved to each instance: {}",
- currentAssignment.size(), instances, _tableNameWithType, _replication,
- SegmentAssignmentUtils.getNumSegmentsToBeMovedPerInstance(currentAssignment, newAssignment));
- return newAssignment;
- }
-}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineReplicaGroupSegmentAssignmentStrategy.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineReplicaGroupSegmentAssignmentStrategy.java
deleted file mode 100644
index 8124d30..0000000
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineReplicaGroupSegmentAssignmentStrategy.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/**
- * 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.assignment.segment;
-
-import com.google.common.base.Preconditions;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import org.apache.commons.configuration.Configuration;
-import org.apache.helix.HelixManager;
-import org.apache.pinot.common.config.ReplicaGroupStrategyConfig;
-import org.apache.pinot.common.config.TableConfig;
-import org.apache.pinot.common.metadata.ZKMetadataProvider;
-import org.apache.pinot.common.metadata.segment.ColumnPartitionMetadata;
-import org.apache.pinot.common.metadata.segment.OfflineSegmentZKMetadata;
-import org.apache.pinot.common.utils.InstancePartitionsType;
-import org.apache.pinot.controller.helix.core.assignment.InstancePartitions;
-import org.apache.pinot.controller.helix.core.assignment.InstancePartitionsUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-
-/**
- * Segment assignment strategy for offline segments that assigns segment to the instance in the replica with the least
- * number of segments.
- * <p>Among multiple replicas, always mirror the assignment (pick the same index of the instance).
- * <p>Inside each partition, assign the segment to the servers with the least segments already assigned. In case of a
- * tie, assign to the server with the smallest index in the list. Do this for one replica and mirror the assignment to
- * other replicas.
- * <p>To rebalance a table, inside each partition, first calculate the number of segments on each server, loop over all
- * the segments and keep the assignment if number of segments for the server has not been reached and track the not
- * assigned segments, then assign the left-over segments to the servers with the least segments, or the smallest index
- * if there is a tie. Repeat the process for all the partitions in one replica, and mirror the assignment to other
- * replicas. With this greedy algorithm, the result is deterministic and with minimum segment moves.
- */
-public class OfflineReplicaGroupSegmentAssignmentStrategy implements SegmentAssignmentStrategy {
- private static final Logger LOGGER = LoggerFactory.getLogger(OfflineReplicaGroupSegmentAssignmentStrategy.class);
-
- private HelixManager _helixManager;
- private TableConfig _tableConfig;
- private String _tableNameWithType;
- private String _partitionColumn;
-
- @Override
- public void init(HelixManager helixManager, TableConfig tableConfig) {
- _helixManager = helixManager;
- _tableConfig = tableConfig;
- _tableNameWithType = tableConfig.getTableName();
- ReplicaGroupStrategyConfig strategyConfig = tableConfig.getValidationConfig().getReplicaGroupStrategyConfig();
- _partitionColumn = strategyConfig != null ? strategyConfig.getPartitionColumn() : null;
-
- if (_partitionColumn == null) {
- LOGGER.info("Initialized OfflineReplicaGroupSegmentAssignmentStrategy for table: {} without partition column",
- _tableNameWithType);
- } else {
- LOGGER.info("Initialized OfflineReplicaGroupSegmentAssignmentStrategy for table: {} with partition column: {}",
- _tableNameWithType, _partitionColumn);
- }
- }
-
- @Override
- public List<String> assignSegment(String segmentName, Map<String, Map<String, String>> currentAssignment) {
- InstancePartitions instancePartitions = InstancePartitionsUtils
- .fetchOrComputeInstancePartitions(_helixManager, _tableConfig, InstancePartitionsType.OFFLINE);
-
- // Fetch partition id from segment ZK metadata if partition column is configured
- int partitionId = 0;
- if (_partitionColumn == null) {
- Preconditions.checkState(instancePartitions.getNumPartitions() == 1,
- "The instance partitions: %s should contain only 1 partition", instancePartitions.getName());
- } else {
- OfflineSegmentZKMetadata segmentZKMetadata = ZKMetadataProvider
- .getOfflineSegmentZKMetadata(_helixManager.getHelixPropertyStore(), _tableNameWithType, segmentName);
- Preconditions
- .checkState(segmentZKMetadata != null, "Failed to fetch segment ZK metadata for table: %s, segment: %s",
- _tableNameWithType, segmentName);
- // Uniformly spray the segment partitions over the instance partitions
- partitionId = getPartitionId(segmentZKMetadata) % instancePartitions.getNumPartitions();
- }
-
- // First assign the segment to replica 0
- List<String> instances = instancePartitions.getInstances(partitionId, 0);
- int[] numSegmentsAssignedPerInstance =
- SegmentAssignmentUtils.getNumSegmentsAssignedPerInstance(currentAssignment, instances);
- int minNumSegmentsAssigned = numSegmentsAssignedPerInstance[0];
- int instanceIdWithLeastSegmentsAssigned = 0;
- int numInstances = numSegmentsAssignedPerInstance.length;
- for (int instanceId = 1; instanceId < numInstances; instanceId++) {
- if (numSegmentsAssignedPerInstance[instanceId] < minNumSegmentsAssigned) {
- minNumSegmentsAssigned = numSegmentsAssignedPerInstance[instanceId];
- instanceIdWithLeastSegmentsAssigned = instanceId;
- }
- }
-
- // Mirror the assignment to all replicas
- int numReplicas = instancePartitions.getNumReplicas();
- List<String> instancesAssigned = new ArrayList<>(numReplicas);
- for (int replicaId = 0; replicaId < numReplicas; replicaId++) {
- instancesAssigned
- .add(instancePartitions.getInstances(partitionId, replicaId).get(instanceIdWithLeastSegmentsAssigned));
- }
-
- if (_partitionColumn == null) {
- LOGGER.info("Assigned segment: {} to instances: {} for table: {}", segmentName, instancesAssigned,
- _tableNameWithType);
- } else {
- LOGGER.info("Assigned segment: {} with partition id: {} to instances: {} for table: {}", segmentName, partitionId,
- instancesAssigned, _tableNameWithType);
- }
- return instancesAssigned;
- }
-
- @Override
- public Map<String, Map<String, String>> rebalanceTable(Map<String, Map<String, String>> currentAssignment,
- Configuration config) {
- InstancePartitions instancePartitions = InstancePartitionsUtils
- .fetchOrComputeInstancePartitions(_helixManager, _tableConfig, InstancePartitionsType.OFFLINE);
- if (_partitionColumn == null) {
- return rebalanceTableWithoutPartition(currentAssignment, instancePartitions);
- } else {
- return rebalanceTableWithPartition(currentAssignment, instancePartitions);
- }
- }
-
- private Map<String, Map<String, String>> rebalanceTableWithoutPartition(
- Map<String, Map<String, String>> currentAssignment, InstancePartitions instancePartitions) {
- Preconditions.checkState(instancePartitions.getNumPartitions() == 1,
- "The instance partitions: %s should contain only 1 partition", instancePartitions.getName());
-
- Map<String, Map<String, String>> newAssignment = new TreeMap<>();
- SegmentAssignmentUtils
- .rebalanceReplicaGroupBasedPartition(currentAssignment, instancePartitions, 0, currentAssignment.keySet(),
- newAssignment);
-
- LOGGER.info(
- "Rebalanced {} segments with instance partitions: {} for table: {} without partition column, number of segments to be moved to each instance: {}",
- currentAssignment.size(), instancePartitions.getPartitionToInstancesMap(), _tableNameWithType,
- SegmentAssignmentUtils.getNumSegmentsToBeMovedPerInstance(currentAssignment, newAssignment));
- return newAssignment;
- }
-
- private Map<String, Map<String, String>> rebalanceTableWithPartition(
- Map<String, Map<String, String>> currentAssignment, InstancePartitions instancePartitions) {
- // Fetch partition id from segment ZK metadata
- List<OfflineSegmentZKMetadata> segmentZKMetadataList = ZKMetadataProvider
- .getOfflineSegmentZKMetadataListForTable(_helixManager.getHelixPropertyStore(), _tableNameWithType);
- Map<String, OfflineSegmentZKMetadata> segmentZKMetadataMap = new HashMap<>();
- for (OfflineSegmentZKMetadata segmentZKMetadata : segmentZKMetadataList) {
- segmentZKMetadataMap.put(segmentZKMetadata.getSegmentName(), segmentZKMetadata);
- }
- Map<Integer, Set<String>> partitionIdToSegmentsMap = new HashMap<>();
- for (String segmentName : currentAssignment.keySet()) {
- int partitionId = getPartitionId(segmentZKMetadataMap.get(segmentName));
- partitionIdToSegmentsMap.computeIfAbsent(partitionId, k -> new HashSet<>()).add(segmentName);
- }
-
- Map<String, Map<String, String>> newAssignment = SegmentAssignmentUtils
- .rebalanceReplicaGroupBasedTable(currentAssignment, instancePartitions, partitionIdToSegmentsMap);
-
- LOGGER.info(
- "Rebalanced {} segments with instance partitions: {} for table: {} with partition column: {}, number of segments to be moved to each instance: {}",
- currentAssignment.size(), instancePartitions.getPartitionToInstancesMap(), _tableNameWithType, _partitionColumn,
- SegmentAssignmentUtils.getNumSegmentsToBeMovedPerInstance(currentAssignment, newAssignment));
- return newAssignment;
- }
-
- private int getPartitionId(OfflineSegmentZKMetadata segmentZKMetadata) {
- String segmentName = segmentZKMetadata.getSegmentName();
- ColumnPartitionMetadata partitionMetadata =
- segmentZKMetadata.getPartitionMetadata().getColumnPartitionMap().get(_partitionColumn);
- Preconditions.checkState(partitionMetadata != null,
- "Segment ZK metadata for table: %s, segment: %s does not contain partition metadata for column: %s",
- _tableNameWithType, segmentName, _partitionColumn);
- Set<Integer> partitions = partitionMetadata.getPartitions();
- Preconditions.checkState(partitions.size() == 1,
- "Segment ZK metadata for table: %s, segment: %s contains multiple partitions for column: %s",
- _tableNameWithType, segmentName, _partitionColumn);
- return partitions.iterator().next();
- }
-}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineSegmentAssignment.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineSegmentAssignment.java
new file mode 100644
index 0000000..5dfb034
--- /dev/null
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineSegmentAssignment.java
@@ -0,0 +1,260 @@
+/**
+ * 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.assignment.segment;
+
+import com.google.common.base.Preconditions;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.PriorityQueue;
+import java.util.Set;
+import java.util.TreeMap;
+import org.apache.commons.configuration.Configuration;
+import org.apache.helix.HelixManager;
+import org.apache.pinot.common.config.ReplicaGroupStrategyConfig;
+import org.apache.pinot.common.config.TableConfig;
+import org.apache.pinot.common.metadata.ZKMetadataProvider;
+import org.apache.pinot.common.metadata.segment.ColumnPartitionMetadata;
+import org.apache.pinot.common.metadata.segment.OfflineSegmentZKMetadata;
+import org.apache.pinot.common.utils.InstancePartitionsType;
+import org.apache.pinot.common.utils.Pairs;
+import org.apache.pinot.controller.helix.core.assignment.InstancePartitions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Segment assignment for offline table.
+ * <ul>
+ * <li>
+ * Non-replica-group based assignment (only 1 replica in instance partitions):
+ * <p>Assign the segment to the instance with the least number of segments. In case of a tie, assign the segment to
+ * the instance with the smallest index in the list. Use Helix AutoRebalanceStrategy to rebalance the table.
+ * </li>
+ * <li>
+ * Replica-group based assignment (more than 1 replicas in instance partitions):
+ * <p>Among replicas, always mirror the assignment (pick the same index of the instance).
+ * <p>Within each partition, assign the segment to the servers with the least segments already assigned. In case of
+ * a tie, assign to the server with the smallest index in the list. Do this for one replica and mirror the
+ * assignment to other replicas.
+ * <p>To rebalance a table, within each partition, first calculate the number of segments on each server, loop over
+ * all the segments and keep the assignment if number of segments for the server has not been reached and track the
+ * not assigned segments, then assign the left-over segments to the servers with the least segments, or the smallest
+ * index if there is a tie. Repeat the process for all the partitions in one replica, and mirror the assignment to
+ * other replicas. With this greedy algorithm, the result is deterministic and with minimum segment moves.
+ * </li>
+ * </ul>
+ */
+public class OfflineSegmentAssignment implements SegmentAssignment {
+ private static final Logger LOGGER = LoggerFactory.getLogger(OfflineSegmentAssignment.class);
+
+ private HelixManager _helixManager;
+ private String _offlineTableName;
+ private int _replication;
+ private String _partitionColumn;
+
+ @Override
+ public void init(HelixManager helixManager, TableConfig tableConfig) {
+ _helixManager = helixManager;
+ _offlineTableName = tableConfig.getTableName();
+ _replication = tableConfig.getValidationConfig().getReplicationNumber();
+ ReplicaGroupStrategyConfig strategyConfig = tableConfig.getValidationConfig().getReplicaGroupStrategyConfig();
+ _partitionColumn = strategyConfig != null ? strategyConfig.getPartitionColumn() : null;
+
+ if (_partitionColumn == null) {
+ LOGGER.info("Initialized OfflineSegmentAssignment with replication: {} without partition column for table: {} ",
+ _replication, _offlineTableName);
+ } else {
+ LOGGER.info("Initialized OfflineSegmentAssignment with replication: {} and partition column: {} for table: {}",
+ _replication, _partitionColumn, _offlineTableName);
+ }
+ }
+
+ @Override
+ public List<String> assignSegment(String segmentName, Map<String, Map<String, String>> currentAssignment,
+ Map<InstancePartitionsType, InstancePartitions> instancePartitionsMap) {
+ InstancePartitions instancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE);
+ Preconditions.checkState(instancePartitions != null, "Failed to find OFFLINE instance partitions for table: %s",
+ _offlineTableName);
+ LOGGER.info("Assigning segment: {} with instance partitions: {} for table: {}", segmentName, instancePartitions,
+ _offlineTableName);
+
+ List<String> instancesAssigned;
+ if (instancePartitions.getNumReplicas() == 1) {
+ // Non-replica-group based assignment
+
+ // Assign the segment to the instance with the least segments, or the smallest id if there is a tie
+ List<String> instances =
+ SegmentAssignmentUtils.getInstancesForNonReplicaGroupBasedAssignment(instancePartitions, _replication);
+ int[] numSegmentsAssignedPerInstance =
+ SegmentAssignmentUtils.getNumSegmentsAssignedPerInstance(currentAssignment, instances);
+ int numInstances = numSegmentsAssignedPerInstance.length;
+ PriorityQueue<Pairs.IntPair> heap = new PriorityQueue<>(numInstances, Pairs.intPairComparator());
+ for (int instanceId = 0; instanceId < numInstances; instanceId++) {
+ heap.add(new Pairs.IntPair(numSegmentsAssignedPerInstance[instanceId], instanceId));
+ }
+ instancesAssigned = new ArrayList<>(_replication);
+ for (int i = 0; i < _replication; i++) {
+ instancesAssigned.add(instances.get(heap.remove().getRight()));
+ }
+ } else {
+ // Replica-group based assignment
+
+ int numReplicas = instancePartitions.getNumReplicas();
+ if (numReplicas != _replication) {
+ LOGGER.warn(
+ "Number of replicas in instance partitions {}: {} does not match replication in table config: {} for table: {}, use: {}",
+ instancePartitions.getName(), numReplicas, _replication, _offlineTableName, numReplicas);
+ }
+
+ // Fetch partition id from segment ZK metadata if partition column is configured
+ int partitionId;
+ if (_partitionColumn == null) {
+ LOGGER.info("Assigning segment: {} without partition column for table: {}", segmentName, _offlineTableName);
+
+ Preconditions.checkState(instancePartitions.getNumPartitions() == 1,
+ "Instance partitions: %s should contain 1 partition without partition column",
+ instancePartitions.getName());
+ partitionId = 0;
+ } else {
+ LOGGER.info("Assigning segment: {} with partition column: {} for table: {}", segmentName, _partitionColumn,
+ _offlineTableName);
+
+ OfflineSegmentZKMetadata segmentZKMetadata = ZKMetadataProvider
+ .getOfflineSegmentZKMetadata(_helixManager.getHelixPropertyStore(), _offlineTableName, segmentName);
+ Preconditions
+ .checkState(segmentZKMetadata != null, "Failed to find segment ZK metadata for segment: %s of table: %s",
+ segmentName, _offlineTableName);
+ int segmentPartitionId = getPartitionId(segmentZKMetadata);
+
+ // Uniformly spray the segment partitions over the instance partitions
+ int numPartitions = instancePartitions.getNumPartitions();
+ partitionId = segmentPartitionId % numPartitions;
+ LOGGER.info("Assigning segment: {} with partition id: {} to partition: {}/{} for table: {}", segmentName,
+ segmentPartitionId, partitionId, numPartitions, _offlineTableName);
+ }
+
+ // First assign the segment to replica 0
+ List<String> instances = instancePartitions.getInstances(partitionId, 0);
+ int[] numSegmentsAssignedPerInstance =
+ SegmentAssignmentUtils.getNumSegmentsAssignedPerInstance(currentAssignment, instances);
+ int minNumSegmentsAssigned = numSegmentsAssignedPerInstance[0];
+ int instanceIdWithLeastSegmentsAssigned = 0;
+ int numInstances = numSegmentsAssignedPerInstance.length;
+ for (int instanceId = 1; instanceId < numInstances; instanceId++) {
+ if (numSegmentsAssignedPerInstance[instanceId] < minNumSegmentsAssigned) {
+ minNumSegmentsAssigned = numSegmentsAssignedPerInstance[instanceId];
+ instanceIdWithLeastSegmentsAssigned = instanceId;
+ }
+ }
+
+ // Mirror the assignment to all replicas
+ instancesAssigned = new ArrayList<>(numReplicas);
+ for (int replicaId = 0; replicaId < numReplicas; replicaId++) {
+ instancesAssigned
+ .add(instancePartitions.getInstances(partitionId, replicaId).get(instanceIdWithLeastSegmentsAssigned));
+ }
+ }
+
+ LOGGER
+ .info("Assigned segment: {} to instances: {} for table: {}", segmentName, instancesAssigned, _offlineTableName);
+ return instancesAssigned;
+ }
+
+ @Override
+ public Map<String, Map<String, String>> rebalanceTable(Map<String, Map<String, String>> currentAssignment,
+ Map<InstancePartitionsType, InstancePartitions> instancePartitionsMap, Configuration config) {
+ InstancePartitions instancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE);
+ Preconditions.checkState(instancePartitions != null, "Failed to find OFFLINE instance partitions for table: %s",
+ _offlineTableName);
+ LOGGER.info("Rebalancing table: {} with instance partitions: {}", _offlineTableName, instancePartitions);
+
+ Map<String, Map<String, String>> newAssignment;
+ if (instancePartitions.getNumReplicas() == 1) {
+ // Non-replica-group based assignment
+
+ List<String> instances =
+ SegmentAssignmentUtils.getInstancesForNonReplicaGroupBasedAssignment(instancePartitions, _replication);
+ newAssignment = SegmentAssignmentUtils
+ .rebalanceTableWithHelixAutoRebalanceStrategy(currentAssignment, instances, _replication);
+ } else {
+ // Replica-group based assignment
+
+ int numReplicas = instancePartitions.getNumReplicas();
+ if (numReplicas != _replication) {
+ LOGGER.warn(
+ "Number of replicas in instance partitions {}: {} does not match replication in table config: {} for table: {}, use: {}",
+ instancePartitions.getName(), numReplicas, _replication, _offlineTableName, numReplicas);
+ }
+
+ if (_partitionColumn == null) {
+ LOGGER.info("Rebalancing table: {} without partition column", _offlineTableName);
+ Preconditions.checkState(instancePartitions.getNumPartitions() == 1,
+ "Instance partitions: %s should contain 1 partition without partition column",
+ instancePartitions.getName());
+ newAssignment = new TreeMap<>();
+ SegmentAssignmentUtils
+ .rebalanceReplicaGroupBasedPartition(currentAssignment, instancePartitions, 0, currentAssignment.keySet(),
+ newAssignment);
+ } else {
+ LOGGER.info("Rebalancing table: {} with partition column: {}", _offlineTableName, _partitionColumn);
+ newAssignment = rebalanceTableWithPartition(currentAssignment, instancePartitions);
+ }
+ }
+
+ LOGGER.info("Rebalanced table: {}, number of segments to be moved to each instance: {}", _offlineTableName,
+ SegmentAssignmentUtils.getNumSegmentsToBeMovedPerInstance(currentAssignment, newAssignment));
+ return newAssignment;
+ }
+
+ private Map<String, Map<String, String>> rebalanceTableWithPartition(
+ Map<String, Map<String, String>> currentAssignment, InstancePartitions instancePartitions) {
+ // Fetch partition id from segment ZK metadata
+ List<OfflineSegmentZKMetadata> segmentZKMetadataList = ZKMetadataProvider
+ .getOfflineSegmentZKMetadataListForTable(_helixManager.getHelixPropertyStore(), _offlineTableName);
+ Map<String, OfflineSegmentZKMetadata> segmentZKMetadataMap = new HashMap<>();
+ for (OfflineSegmentZKMetadata segmentZKMetadata : segmentZKMetadataList) {
+ segmentZKMetadataMap.put(segmentZKMetadata.getSegmentName(), segmentZKMetadata);
+ }
+ Map<Integer, Set<String>> partitionIdToSegmentsMap = new HashMap<>();
+ for (String segmentName : currentAssignment.keySet()) {
+ int partitionId = getPartitionId(segmentZKMetadataMap.get(segmentName));
+ partitionIdToSegmentsMap.computeIfAbsent(partitionId, k -> new HashSet<>()).add(segmentName);
+ }
+
+ return SegmentAssignmentUtils
+ .rebalanceReplicaGroupBasedTable(currentAssignment, instancePartitions, partitionIdToSegmentsMap);
+ }
+
+ private int getPartitionId(OfflineSegmentZKMetadata segmentZKMetadata) {
+ String segmentName = segmentZKMetadata.getSegmentName();
+ ColumnPartitionMetadata partitionMetadata =
+ segmentZKMetadata.getPartitionMetadata().getColumnPartitionMap().get(_partitionColumn);
+ Preconditions.checkState(partitionMetadata != null,
+ "Segment ZK metadata for segment: %s of table: %s does not contain partition metadata for column: %s",
+ segmentName, _offlineTableName, _partitionColumn);
+ Set<Integer> partitions = partitionMetadata.getPartitions();
+ Preconditions.checkState(partitions.size() == 1,
+ "Segment ZK metadata for segment: %s of table: %s contains multiple partitions for column: %s", segmentName,
+ _offlineTableName, _partitionColumn);
+ return partitions.iterator().next();
+ }
+}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeBalanceNumSegmentAssignmentStrategy.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeBalanceNumSegmentAssignmentStrategy.java
deleted file mode 100644
index 3319ad7..0000000
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeBalanceNumSegmentAssignmentStrategy.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/**
- * 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.assignment.segment;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import org.apache.commons.configuration.Configuration;
-import org.apache.helix.HelixManager;
-import org.apache.pinot.common.config.TableConfig;
-import org.apache.pinot.common.utils.CommonConstants.Helix.StateModel.RealtimeSegmentOnlineOfflineStateModel;
-import org.apache.pinot.common.utils.InstancePartitionsType;
-import org.apache.pinot.common.utils.LLCSegmentName;
-import org.apache.pinot.controller.helix.core.rebalance.RebalanceUserConfigConstants;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-
-/**
- * Segment assignment strategy for LLC real-time segments without replica-group.
- * <ul>
- * <li>
- * For the CONSUMING segments, it is very similar to replica-group based segment assignment with the following
- * differences:
- * <ul>
- * <li>
- * 1. Within a replica, all segments of a partition (steam partition) always exist in exactly one one server
- * </li>
- * <li>
- * 2. Partition id for an instance is derived from the index of the instance, instead of explicitly stored in
- * the instance partitions
- * </li>
- * <li>
- * 3. In addition to the ONLINE segments, there are also CONSUMING segments to be assigned
- * </li>
- * </ul>
- * Since within a replica, each partition contains only one server, we can directly assign or rebalance the
- * CONSUMING segments to the servers based on the partition id.
- * <p>The strategy does not minimize segment movements for CONSUMING segments because within a replica, the server
- * is fixed for each partition. The instance assignment is responsible for keeping minimum changes to the instance
- * partitions to reduce the number of segments need to be moved.
- * </li>
- * <li>
- * For the COMPLETED segments, rebalance segments the same way as OfflineBalanceNumSegmentAssignmentStrategy.
- * </li>
- * </ul>
- */
-public class RealtimeBalanceNumSegmentAssignmentStrategy implements SegmentAssignmentStrategy {
- private static final Logger LOGGER = LoggerFactory.getLogger(RealtimeBalanceNumSegmentAssignmentStrategy.class);
-
- private HelixManager _helixManager;
- private TableConfig _tableConfig;
- private String _tableNameWithType;
- private int _replication;
-
- @Override
- public void init(HelixManager helixManager, TableConfig tableConfig) {
- _helixManager = helixManager;
- _tableConfig = tableConfig;
- _tableNameWithType = tableConfig.getTableName();
- _replication = tableConfig.getValidationConfig().getReplicasPerPartitionNumber();
-
- LOGGER.info("Initialized RealtimeBalanceNumSegmentAssignmentStrategy for table: {} with replication: {}",
- _tableNameWithType, _replication);
- }
-
- @Override
- public List<String> assignSegment(String segmentName, Map<String, Map<String, String>> currentAssignment) {
- List<String> instances = SegmentAssignmentUtils
- .getInstancesForBalanceNumStrategy(_helixManager, _tableConfig, _replication, InstancePartitionsType.CONSUMING);
- int partitionId = new LLCSegmentName(segmentName).getPartitionId();
- List<String> instancesAssigned = getInstances(instances, partitionId);
- LOGGER.info("Assigned segment: {} with partition id: {} to instances: {} for table: {}", segmentName, partitionId,
- instancesAssigned, _tableNameWithType);
- return instancesAssigned;
- }
-
- @Override
- public Map<String, Map<String, String>> rebalanceTable(Map<String, Map<String, String>> currentAssignment,
- Configuration config) {
- SegmentAssignmentUtils.CompletedConsumingOfflineSegmentAssignment completedConsumingOfflineSegmentAssignment =
- new SegmentAssignmentUtils.CompletedConsumingOfflineSegmentAssignment(currentAssignment);
-
- // Rebalance COMPLETED segments first
- Map<String, Map<String, String>> completedSegmentAssignment =
- completedConsumingOfflineSegmentAssignment.getCompletedSegmentAssignment();
- List<String> instancesForCompletedSegments = SegmentAssignmentUtils
- .getInstancesForBalanceNumStrategy(_helixManager, _tableConfig, _replication, InstancePartitionsType.COMPLETED);
- Map<String, Map<String, String>> newAssignment = SegmentAssignmentUtils
- .rebalanceTableWithHelixAutoRebalanceStrategy(completedSegmentAssignment, instancesForCompletedSegments,
- _replication);
-
- // Rebalance CONSUMING segments if needed
- Map<String, Map<String, String>> consumingSegmentAssignment =
- completedConsumingOfflineSegmentAssignment.getConsumingSegmentAssignment();
- if (config.getBoolean(RebalanceUserConfigConstants.INCLUDE_CONSUMING,
- RebalanceUserConfigConstants.DEFAULT_INCLUDE_CONSUMING)) {
- List<String> instancesForConsumingSegments = SegmentAssignmentUtils
- .getInstancesForBalanceNumStrategy(_helixManager, _tableConfig, _replication,
- InstancePartitionsType.CONSUMING);
- for (String segmentName : consumingSegmentAssignment.keySet()) {
- int partitionId = new LLCSegmentName(segmentName).getPartitionId();
- List<String> instancesAssigned = getInstances(instancesForConsumingSegments, partitionId);
- Map<String, String> instanceStateMap = SegmentAssignmentUtils
- .getInstanceStateMap(instancesAssigned, RealtimeSegmentOnlineOfflineStateModel.CONSUMING);
- newAssignment.put(segmentName, instanceStateMap);
- }
- LOGGER.info(
- "Rebalanced {} COMPLETED segments to instances: {} and {} CONSUMING segments to instances: {} for table: {} with replication: {}, number of segments to be moved to each instances: {}",
- completedSegmentAssignment.size(), instancesForCompletedSegments, consumingSegmentAssignment.size(),
- instancesForConsumingSegments, _tableNameWithType, _replication,
- SegmentAssignmentUtils.getNumSegmentsToBeMovedPerInstance(currentAssignment, newAssignment));
- } else {
- LOGGER.info(
- "Rebalanced {} COMPLETED segments to instances: {} for table: {} with replication: {}, number of segments to be moved to each instance: {}",
- completedSegmentAssignment.size(), instancesForCompletedSegments, _tableNameWithType, _replication,
- SegmentAssignmentUtils.getNumSegmentsToBeMovedPerInstance(completedSegmentAssignment, newAssignment));
- newAssignment.putAll(consumingSegmentAssignment);
- }
-
- // Keep the OFFLINE segments not moved, and RealtimeSegmentValidationManager will periodically detect the OFFLINE
- // segments and re-assign them
- newAssignment.putAll(completedConsumingOfflineSegmentAssignment.getOfflineSegmentAssignment());
-
- return newAssignment;
- }
-
- /**
- * Returns the instances for the given partition id for CONSUMING segments.
- * <p>Uniformly spray the partitions and replicas across the instances.
- * <p>E.g. (6 servers, 3 partitions, 4 replicas)
- * <pre>
- * "0_0": [i0, i1, i2, i3, i4, i5 ]
- * p0r0, p0r1, p0r2, p1r3, p1r0, p1r1
- * p1r2, p1r3, p2r0, p2r1, p2r2, p2r3
- * </pre>
- */
- private List<String> getInstances(List<String> instances, int partitionId) {
- List<String> instancesAssigned = new ArrayList<>(_replication);
- for (int replicaId = 0; replicaId < _replication; replicaId++) {
- instancesAssigned.add(instances.get((partitionId * _replication + replicaId) % instances.size()));
- }
- return instancesAssigned;
- }
-}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeReplicaGroupSegmentAssignmentStrategy.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeReplicaGroupSegmentAssignmentStrategy.java
deleted file mode 100644
index 05febc8..0000000
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeReplicaGroupSegmentAssignmentStrategy.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/**
- * 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.assignment.segment;
-
-import com.google.common.base.Preconditions;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import org.apache.commons.configuration.Configuration;
-import org.apache.helix.HelixManager;
-import org.apache.pinot.common.config.TableConfig;
-import org.apache.pinot.common.utils.CommonConstants;
-import org.apache.pinot.common.utils.InstancePartitionsType;
-import org.apache.pinot.common.utils.LLCSegmentName;
-import org.apache.pinot.controller.helix.core.assignment.InstancePartitions;
-import org.apache.pinot.controller.helix.core.assignment.InstancePartitionsUtils;
-import org.apache.pinot.controller.helix.core.rebalance.RebalanceUserConfigConstants;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-
-/**
- * Segment assignment strategy for LLC real-time segments (both consuming and completed).
- * <p>It is very similar to replica-group based segment assignment with the following differences:
- * <ul>
- * <li>1. Inside one replica, each partition (stream partition) always contains one server</li>
- * <li>
- * 2. Partition id for an instance is derived from the index of the instance in the replica group, instead of
- * explicitly stored in the instance partitions
- * </li>
- * <li>3. In addition to the ONLINE segments, there are also CONSUMING segments to be assigned</li>
- * </ul>
- * <p>
- * Since each partition contains only one server (in one replica), we can directly assign or rebalance segments to the
- * servers based on the partition id.
- * <p>
- * The real-time segment assignment does not minimize segment moves because the server is fixed for each partition in
- * each replica. The instance assignment is responsible for keeping minimum changes to the instance partitions to
- * reduce the number of segments need to be moved.
- */
-public class RealtimeReplicaGroupSegmentAssignmentStrategy implements SegmentAssignmentStrategy {
- private static final Logger LOGGER = LoggerFactory.getLogger(RealtimeReplicaGroupSegmentAssignmentStrategy.class);
-
- private HelixManager _helixManager;
- private TableConfig _tableConfig;
- private String _tableNameWithType;
-
- @Override
- public void init(HelixManager helixManager, TableConfig tableConfig) {
- _helixManager = helixManager;
- _tableConfig = tableConfig;
- _tableNameWithType = tableConfig.getTableName();
-
- LOGGER.info("Initialized RealtimeReplicaGroupSegmentAssignmentStrategy for table: {}", _tableNameWithType);
- }
-
- @Override
- public List<String> assignSegment(String segmentName, Map<String, Map<String, String>> currentAssignment) {
- InstancePartitions instancePartitions = InstancePartitionsUtils
- .fetchOrComputeInstancePartitions(_helixManager, _tableConfig, InstancePartitionsType.CONSUMING);
- Preconditions.checkState(instancePartitions.getNumPartitions() == 1,
- "The instance partitions: %s should contain only 1 partition", instancePartitions.getName());
-
- int partitionId = new LLCSegmentName(segmentName).getPartitionId();
- List<String> instancesAssigned = getInstances(instancePartitions, partitionId);
- LOGGER.info("Assigned segment: {} with partition id: {} to instances: {} for table: {}", segmentName, partitionId,
- instancesAssigned, _tableNameWithType);
- return instancesAssigned;
- }
-
- @Override
- public Map<String, Map<String, String>> rebalanceTable(Map<String, Map<String, String>> currentAssignment,
- Configuration config) {
- SegmentAssignmentUtils.CompletedConsumingOfflineSegmentAssignment completedConsumingOfflineSegmentAssignment =
- new SegmentAssignmentUtils.CompletedConsumingOfflineSegmentAssignment(currentAssignment);
-
- // Rebalance COMPLETED segments first
- Map<String, Map<String, String>> completedSegmentAssignment =
- completedConsumingOfflineSegmentAssignment.getCompletedSegmentAssignment();
- InstancePartitions instancePartitionsForCompletedSegments = InstancePartitionsUtils
- .fetchOrComputeInstancePartitions(_helixManager, _tableConfig, InstancePartitionsType.COMPLETED);
- Map<Integer, Set<String>> partitionIdToSegmentsMap = new HashMap<>();
- for (String segmentName : completedSegmentAssignment.keySet()) {
- int partitionId = new LLCSegmentName(segmentName).getPartitionId();
- partitionIdToSegmentsMap.computeIfAbsent(partitionId, k -> new HashSet<>()).add(segmentName);
- }
- Map<String, Map<String, String>> newAssignment = SegmentAssignmentUtils
- .rebalanceReplicaGroupBasedTable(completedSegmentAssignment, instancePartitionsForCompletedSegments,
- partitionIdToSegmentsMap);
-
- // Rebalance CONSUMING segments if needed
- Map<String, Map<String, String>> consumingSegmentAssignment =
- completedConsumingOfflineSegmentAssignment.getConsumingSegmentAssignment();
- if (config.getBoolean(RebalanceUserConfigConstants.INCLUDE_CONSUMING,
- RebalanceUserConfigConstants.DEFAULT_INCLUDE_CONSUMING)) {
- InstancePartitions instancePartitionsForConsumingSegments = InstancePartitionsUtils
- .fetchOrComputeInstancePartitions(_helixManager, _tableConfig, InstancePartitionsType.CONSUMING);
- for (String segmentName : consumingSegmentAssignment.keySet()) {
- int partitionId = new LLCSegmentName(segmentName).getPartitionId();
- List<String> instancesAssigned = getInstances(instancePartitionsForConsumingSegments, partitionId);
- Map<String, String> instanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(instancesAssigned,
- CommonConstants.Helix.StateModel.RealtimeSegmentOnlineOfflineStateModel.CONSUMING);
- newAssignment.put(segmentName, instanceStateMap);
- }
- LOGGER.info(
- "Rebalanced {} COMPLETED segments with instance partitions: {} and {} CONSUMING segments with instance partitions: {} for table: {}, number of segments to be moved to each instances: {}",
- completedSegmentAssignment.size(), instancePartitionsForCompletedSegments.getPartitionToInstancesMap(),
- consumingSegmentAssignment.size(), instancePartitionsForConsumingSegments.getPartitionToInstancesMap(),
- _tableNameWithType,
- SegmentAssignmentUtils.getNumSegmentsToBeMovedPerInstance(currentAssignment, newAssignment));
- } else {
- LOGGER.info(
- "Rebalanced {} COMPLETED segments with instance partitions: {} for table: {}, number of segments to be moved to each instance: {}",
- completedSegmentAssignment.size(), instancePartitionsForCompletedSegments.getPartitionToInstancesMap(),
- _tableNameWithType,
- SegmentAssignmentUtils.getNumSegmentsToBeMovedPerInstance(completedSegmentAssignment, newAssignment));
- newAssignment.putAll(consumingSegmentAssignment);
- }
-
- // Keep the OFFLINE segments not moved, and RealtimeSegmentValidationManager will periodically detect the OFFLINE
- // segments and re-assign them
- newAssignment.putAll(completedConsumingOfflineSegmentAssignment.getOfflineSegmentAssignment());
-
- return newAssignment;
- }
-
- /**
- * Returns the instances for the given partition id for CONSUMING segments.
- * <p>Within a replica, uniformly spray the partitions across the instances.
- * <p>E.g. (within a replica, 3 servers, 6 partitions)
- * <pre>
- * "0_0": [i0, i1, i2]
- * p0 p1 p2
- * p3 p4 p5
- * </pre>
- */
- private List<String> getInstances(InstancePartitions instancePartitions, int partitionId) {
- int numReplicas = instancePartitions.getNumReplicas();
- List<String> instancesAssigned = new ArrayList<>(numReplicas);
- for (int replicaId = 0; replicaId < numReplicas; replicaId++) {
- List<String> instances = instancePartitions.getInstances(0, replicaId);
- instancesAssigned.add(instances.get(partitionId % instances.size()));
- }
- return instancesAssigned;
- }
-}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeSegmentAssignment.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeSegmentAssignment.java
new file mode 100644
index 0000000..ab279fb
--- /dev/null
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeSegmentAssignment.java
@@ -0,0 +1,218 @@
+/**
+ * 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.assignment.segment;
+
+import com.google.common.base.Preconditions;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.configuration.Configuration;
+import org.apache.helix.HelixManager;
+import org.apache.pinot.common.config.TableConfig;
+import org.apache.pinot.common.utils.CommonConstants.Helix.StateModel.RealtimeSegmentOnlineOfflineStateModel;
+import org.apache.pinot.common.utils.InstancePartitionsType;
+import org.apache.pinot.common.utils.LLCSegmentName;
+import org.apache.pinot.controller.helix.core.assignment.InstancePartitions;
+import org.apache.pinot.controller.helix.core.rebalance.RebalanceUserConfigConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Segment assignment for LLC real-time table.
+ * <ul>
+ * <li>
+ * For the CONSUMING segments, it is very similar to replica-group based segment assignment with the following
+ * differences:
+ * <ul>
+ * <li>
+ * 1. Within a replica, all segments of the same partition (steam partition) are always assigned to exactly one
+ * server, and because of that we can directly assign or rebalance the CONSUMING segments to the servers based
+ * on the partition id
+ * </li>
+ * <li>
+ * 2. Partition id for an instance is derived from the index of the instance (within the replica-group for
+ * replica-group based assignment), instead of explicitly stored in the instance partitions
+ * </li>
+ * </ul>
+ * </li>
+ * <li>
+ * For the COMPLETED segments, rebalance segments the same way as OfflineSegmentAssignment.
+ * </li>
+ * </ul>
+ */
+public class RealtimeSegmentAssignment implements SegmentAssignment {
+ private static final Logger LOGGER = LoggerFactory.getLogger(RealtimeSegmentAssignment.class);
+
+ private String _realtimeTableName;
+ private int _replication;
+
+ @Override
+ public void init(HelixManager helixManager, TableConfig tableConfig) {
+ _realtimeTableName = tableConfig.getTableName();
+ _replication = tableConfig.getValidationConfig().getReplicasPerPartitionNumber();
+
+ LOGGER.info("Initialized RealtimeSegmentAssignment with replication: {} for table: {}", _replication,
+ _realtimeTableName);
+ }
+
+ @Override
+ public List<String> assignSegment(String segmentName, Map<String, Map<String, String>> currentAssignment,
+ Map<InstancePartitionsType, InstancePartitions> instancePartitionsMap) {
+ InstancePartitions instancePartitions = instancePartitionsMap.get(InstancePartitionsType.CONSUMING);
+ Preconditions.checkState(instancePartitions != null, "Failed to find CONSUMING instance partitions for table: %s",
+ _realtimeTableName);
+ Preconditions
+ .checkState(instancePartitions.getNumPartitions() == 1, "Instance partitions: %s should contain 1 partition",
+ instancePartitions.getName());
+ LOGGER.info("Assigning segment: {} with instance partitions: {} for table: {}", segmentName, instancePartitions,
+ _realtimeTableName);
+
+ List<String> instancesAssigned = assignSegment(segmentName, instancePartitions);
+ LOGGER.info("Assigned segment: {} to instances: {} for table: {}", segmentName, instancesAssigned,
+ _realtimeTableName);
+ return instancesAssigned;
+ }
+
+ /**
+ * Helper method to assign instances based on the segment partition id and instance partitions.
+ */
+ private List<String> assignSegment(String segmentName, InstancePartitions instancePartitions) {
+ int partitionId = new LLCSegmentName(segmentName).getPartitionId();
+
+ if (instancePartitions.getNumReplicas() == 1) {
+ // Non-replica-group based assignment:
+ // Uniformly spray the partitions and replicas across the instances.
+ // E.g. (6 servers, 3 partitions, 4 replicas)
+ // "0_0": [i0, i1, i2, i3, i4, i5 ]
+ // p0r0 p0r1 p0r2 p1r3 p1r0 p1r1
+ // p1r2 p1r3 p2r0 p2r1 p2r2 p2r3
+
+ List<String> instances =
+ SegmentAssignmentUtils.getInstancesForNonReplicaGroupBasedAssignment(instancePartitions, _replication);
+ int numInstances = instances.size();
+ List<String> instancesAssigned = new ArrayList<>(_replication);
+ for (int replicaId = 0; replicaId < _replication; replicaId++) {
+ instancesAssigned.add(instances.get((partitionId * _replication + replicaId) % numInstances));
+ }
+ return instancesAssigned;
+ } else {
+ // Replica-group based assignment:
+ // Within a replica, uniformly spray the partitions across the instances.
+ // E.g. (within a replica, 3 servers, 6 partitions)
+ // "0_0": [i0, i1, i2]
+ // p0 p1 p2
+ // p3 p4 p5
+
+ int numReplicas = instancePartitions.getNumReplicas();
+ if (numReplicas != _replication) {
+ LOGGER.warn(
+ "Number of replicas in instance partitions {}: {} does not match replication in table config: {} for table: {}, use: {}",
+ instancePartitions.getName(), numReplicas, _replication, _realtimeTableName, numReplicas);
+ }
+
+ List<String> instancesAssigned = new ArrayList<>(numReplicas);
+ for (int replicaId = 0; replicaId < numReplicas; replicaId++) {
+ List<String> instances = instancePartitions.getInstances(0, replicaId);
+ instancesAssigned.add(instances.get(partitionId % instances.size()));
+ }
+ return instancesAssigned;
+ }
+ }
+
+ @Override
+ public Map<String, Map<String, String>> rebalanceTable(Map<String, Map<String, String>> currentAssignment,
+ Map<InstancePartitionsType, InstancePartitions> instancePartitionsMap, Configuration config) {
+ SegmentAssignmentUtils.CompletedConsumingOfflineSegmentAssignment completedConsumingOfflineSegmentAssignment =
+ new SegmentAssignmentUtils.CompletedConsumingOfflineSegmentAssignment(currentAssignment);
+
+ // Rebalance COMPLETED segments first
+ Map<String, Map<String, String>> completedSegmentAssignment =
+ completedConsumingOfflineSegmentAssignment.getCompletedSegmentAssignment();
+ InstancePartitions completedInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.COMPLETED);
+ Preconditions
+ .checkState(completedInstancePartitions != null, "Failed to find COMPLETED instance partitions for table: %s",
+ _realtimeTableName);
+ LOGGER.info("Rebalancing COMPLETED segments for table: {} with instance partitions: {}", _realtimeTableName,
+ completedInstancePartitions);
+
+ Map<String, Map<String, String>> newAssignment;
+ if (completedInstancePartitions.getNumReplicas() == 1) {
+ // Non-replica-group based assignment
+
+ List<String> instances = SegmentAssignmentUtils
+ .getInstancesForNonReplicaGroupBasedAssignment(completedInstancePartitions, _replication);
+ newAssignment = SegmentAssignmentUtils
+ .rebalanceTableWithHelixAutoRebalanceStrategy(completedSegmentAssignment, instances, _replication);
+ } else {
+ // Replica-group based assignment
+
+ int numReplicas = completedInstancePartitions.getNumReplicas();
+ if (numReplicas != _replication) {
+ LOGGER.warn(
+ "Number of replicas in instance partitions {}: {} does not match replication in table config: {} for table: {}, use: {}",
+ completedInstancePartitions.getName(), numReplicas, _replication, _realtimeTableName, numReplicas);
+ }
+
+ Map<Integer, Set<String>> partitionIdToSegmentsMap = new HashMap<>();
+ for (String segmentName : completedSegmentAssignment.keySet()) {
+ int partitionId = new LLCSegmentName(segmentName).getPartitionId();
+ partitionIdToSegmentsMap.computeIfAbsent(partitionId, k -> new HashSet<>()).add(segmentName);
+ }
+ newAssignment = SegmentAssignmentUtils
+ .rebalanceReplicaGroupBasedTable(completedSegmentAssignment, completedInstancePartitions,
+ partitionIdToSegmentsMap);
+ }
+
+ // Rebalance CONSUMING segments if configured
+ Map<String, Map<String, String>> consumingSegmentAssignment =
+ completedConsumingOfflineSegmentAssignment.getConsumingSegmentAssignment();
+ if (config.getBoolean(RebalanceUserConfigConstants.INCLUDE_CONSUMING,
+ RebalanceUserConfigConstants.DEFAULT_INCLUDE_CONSUMING)) {
+ InstancePartitions consumingInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.CONSUMING);
+ Preconditions
+ .checkState(consumingInstancePartitions != null, "Failed to find CONSUMING instance partitions for table: %s",
+ _realtimeTableName);
+ Preconditions.checkState(consumingInstancePartitions.getNumPartitions() == 1,
+ "Instance partitions: %s should contain 1 partition", consumingInstancePartitions.getName());
+ LOGGER.info("Rebalancing CONSUMING segments for table: {} with instance partitions: {}", _realtimeTableName,
+ consumingInstancePartitions);
+
+ for (String segmentName : consumingSegmentAssignment.keySet()) {
+ List<String> instancesAssigned = assignSegment(segmentName, consumingInstancePartitions);
+ Map<String, String> instanceStateMap = SegmentAssignmentUtils
+ .getInstanceStateMap(instancesAssigned, RealtimeSegmentOnlineOfflineStateModel.CONSUMING);
+ newAssignment.put(segmentName, instanceStateMap);
+ }
+ } else {
+ newAssignment.putAll(consumingSegmentAssignment);
+ }
+
+ // Keep the OFFLINE segments not moved, and RealtimeSegmentValidationManager will periodically detect the OFFLINE
+ // segments and re-assign them
+ newAssignment.putAll(completedConsumingOfflineSegmentAssignment.getOfflineSegmentAssignment());
+
+ LOGGER.info("Rebalanced table: {}, number of segments to be moved to each instance: {}", _realtimeTableName,
+ SegmentAssignmentUtils.getNumSegmentsToBeMovedPerInstance(currentAssignment, newAssignment));
+ return newAssignment;
+ }
+}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/SegmentAssignmentStrategy.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/SegmentAssignment.java
similarity index 59%
rename from pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/SegmentAssignmentStrategy.java
rename to pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/SegmentAssignment.java
index de2ea0d..fd613ff 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/SegmentAssignmentStrategy.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/SegmentAssignment.java
@@ -23,15 +23,21 @@ import java.util.Map;
import org.apache.commons.configuration.Configuration;
import org.apache.helix.HelixManager;
import org.apache.pinot.common.config.TableConfig;
+import org.apache.pinot.common.utils.InstancePartitionsType;
+import org.apache.pinot.controller.helix.core.assignment.InstancePartitions;
/**
- * Strategy to assign segment to instances or rebalance all segments in a table.
+ * Interface for segment assignment and table rebalance.
+ * <p>
+ * TODO: Add SegmentAssignmentStrategy interface and support custom segment assignment strategy (e.g. cost based segment
+ * assignment). SegmentAssignmentStrategy should not be coupled with SegmentAssignment, and SegmentAssignment
+ * should be able to choose the segment assignment strategy based on the configuration.
*/
-public interface SegmentAssignmentStrategy {
+public interface SegmentAssignment {
/**
- * Initializes the segment assignment strategy.
+ * Initializes the segment assignment.
*
* @param helixManager Helix manager
* @param tableConfig Table config
@@ -39,21 +45,24 @@ public interface SegmentAssignmentStrategy {
void init(HelixManager helixManager, TableConfig tableConfig);
/**
- * Assigns a new segment.
+ * Assigns segment to instances.
*
* @param segmentName Name of the segment to be assigned
* @param currentAssignment Current segment assignment of the table (map from segment name to instance state map)
- * @return List of servers to assign the segment to
+ * @param instancePartitionsMap Map from type (OFFLINE|CONSUMING|COMPLETED) to instance partitions
+ * @return List of instances to assign the segment to
*/
- List<String> assignSegment(String segmentName, Map<String, Map<String, String>> currentAssignment);
+ List<String> assignSegment(String segmentName, Map<String, Map<String, String>> currentAssignment,
+ Map<InstancePartitionsType, InstancePartitions> instancePartitionsMap);
/**
- * Rebalances the segments for a table.
+ * Rebalances the segment assignment for a table.
*
* @param currentAssignment Current segment assignment of the table (map from segment name to instance state map)
+ * @param instancePartitionsMap Map from type (OFFLINE|CONSUMING|COMPLETED) to instance partitions
* @param config Configuration for the rebalance
- * @return the rebalanced assignment for the segments
+ * @return Rebalanced assignment for the segments
*/
Map<String, Map<String, String>> rebalanceTable(Map<String, Map<String, String>> currentAssignment,
- Configuration config);
+ Map<InstancePartitionsType, InstancePartitions> instancePartitionsMap, Configuration config);
}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/SegmentAssignmentFactory.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/SegmentAssignmentFactory.java
new file mode 100644
index 0000000..db531bb
--- /dev/null
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/SegmentAssignmentFactory.java
@@ -0,0 +1,43 @@
+/**
+ * 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.assignment.segment;
+
+import org.apache.helix.HelixManager;
+import org.apache.pinot.common.config.TableConfig;
+import org.apache.pinot.common.utils.CommonConstants.Helix.TableType;
+
+
+/**
+ * Factory for the {@link SegmentAssignment}.
+ */
+public class SegmentAssignmentFactory {
+ private SegmentAssignmentFactory() {
+ }
+
+ public static SegmentAssignment getSegmentAssignment(HelixManager helixManager, TableConfig tableConfig) {
+ SegmentAssignment segmentAssignment;
+ if (tableConfig.getTableType() == TableType.OFFLINE) {
+ segmentAssignment = new OfflineSegmentAssignment();
+ } else {
+ segmentAssignment = new RealtimeSegmentAssignment();
+ }
+ segmentAssignment.init(helixManager, tableConfig);
+ return segmentAssignment;
+ }
+}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/SegmentAssignmentStrategyFactory.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/SegmentAssignmentStrategyFactory.java
deleted file mode 100644
index 875b856..0000000
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/SegmentAssignmentStrategyFactory.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- * 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.assignment.segment;
-
-import org.apache.helix.HelixManager;
-import org.apache.pinot.common.config.TableConfig;
-import org.apache.pinot.common.utils.CommonConstants.Helix.TableType;
-import org.apache.pinot.common.utils.CommonConstants.Segment.AssignmentStrategy;
-
-
-/**
- * Factory for the {@link SegmentAssignmentStrategy}.
- */
-public class SegmentAssignmentStrategyFactory {
- private SegmentAssignmentStrategyFactory() {
- }
-
- public static SegmentAssignmentStrategy getSegmentAssignmentStrategy(HelixManager helixManager,
- TableConfig tableConfig) {
- SegmentAssignmentStrategy segmentAssignmentStrategy;
- if (AssignmentStrategy.REPLICA_GROUP_SEGMENT_ASSIGNMENT_STRATEGY
- .equalsIgnoreCase(tableConfig.getValidationConfig().getSegmentAssignmentStrategy())) {
- if (tableConfig.getTableType() == TableType.OFFLINE) {
- segmentAssignmentStrategy = new OfflineReplicaGroupSegmentAssignmentStrategy();
- } else {
- segmentAssignmentStrategy = new RealtimeReplicaGroupSegmentAssignmentStrategy();
- }
- } else {
- if (tableConfig.getTableType() == TableType.OFFLINE) {
- segmentAssignmentStrategy = new OfflineBalanceNumSegmentAssignmentStrategy();
- } else {
- segmentAssignmentStrategy = new RealtimeBalanceNumSegmentAssignmentStrategy();
- }
- }
- segmentAssignmentStrategy.init(helixManager, tableConfig);
- return segmentAssignmentStrategy;
- }
-}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/SegmentAssignmentUtils.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/SegmentAssignmentUtils.java
index 0f3fe6f..38d9681 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/SegmentAssignmentUtils.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/assignment/segment/SegmentAssignmentUtils.java
@@ -27,15 +27,11 @@ import java.util.Map;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.TreeMap;
-import org.apache.helix.HelixManager;
import org.apache.helix.controller.rebalancer.strategy.AutoRebalanceStrategy;
-import org.apache.pinot.common.config.TableConfig;
import org.apache.pinot.common.utils.CommonConstants.Helix.StateModel.RealtimeSegmentOnlineOfflineStateModel;
import org.apache.pinot.common.utils.CommonConstants.Helix.StateModel.SegmentOnlineOfflineStateModel;
-import org.apache.pinot.common.utils.InstancePartitionsType;
import org.apache.pinot.common.utils.Pairs;
import org.apache.pinot.controller.helix.core.assignment.InstancePartitions;
-import org.apache.pinot.controller.helix.core.assignment.InstancePartitionsUtils;
/**
@@ -73,23 +69,23 @@ class SegmentAssignmentUtils {
}
/**
- * Returns the instances for the balance number segment assignment strategy.
+ * Returns instances for non-replica-group based assignment.
*/
- static List<String> getInstancesForBalanceNumStrategy(HelixManager helixManager, TableConfig tableConfig,
- int replication, InstancePartitionsType instancePartitionsType) {
- InstancePartitions instancePartitions =
- InstancePartitionsUtils.fetchOrComputeInstancePartitions(helixManager, tableConfig, instancePartitionsType);
- Preconditions.checkArgument(instancePartitions.getNumPartitions() == 1 && instancePartitions.getNumReplicas() == 1,
- "The instance partitions: %s should contain only 1 partition and 1 replica", instancePartitions.getName());
+ static List<String> getInstancesForNonReplicaGroupBasedAssignment(InstancePartitions instancePartitions,
+ int replication) {
+ Preconditions.checkState(instancePartitions.getNumReplicas() == 1 && instancePartitions.getNumPartitions() == 1,
+ "Instance partitions: %s should contain 1 replica and 1 partition for non-replica-group based assignment",
+ instancePartitions.getName());
List<String> instances = instancePartitions.getInstances(0, 0);
- Preconditions.checkState(instances.size() >= replication,
- "There are less instances: %d than the replication: %d for table: %s", instances.size(), replication,
- tableConfig.getTableName());
+ int numInstances = instances.size();
+ Preconditions.checkState(numInstances >= replication,
+ "There are less instances: %s in instance partitions: %s than the table replication: %s", numInstances,
+ instancePartitions.getName(), replication);
return instances;
}
/**
- * Rebalances the table with Helix AutoRebalanceStrategy for the balance number segment assignment strategy.
+ * Rebalances the table with Helix AutoRebalanceStrategy.
*/
static Map<String, Map<String, String>> rebalanceTableWithHelixAutoRebalanceStrategy(
Map<String, Map<String, String>> currentAssignment, List<String> instances, int replication) {
diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceAssignmentRestletResourceTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceAssignmentRestletResourceTest.java
index 9d6e7d7..004ff1f 100644
--- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceAssignmentRestletResourceTest.java
+++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceAssignmentRestletResourceTest.java
@@ -283,7 +283,7 @@ public class PinotInstanceAssignmentRestletResourceTest extends ControllerTest {
// Post the CONSUMING instance partitions
instancePartitionsMap = deserializeInstancePartitionsMap(
sendPutRequest(_controllerRequestURLBuilder.forInstancePartitions(RAW_TABLE_NAME, null),
- JsonUtils.objectToString(consumingInstancePartitions)));
+ consumingInstancePartitions.toJsonString()));
assertEquals(instancePartitionsMap.size(), 1);
assertEquals(instancePartitionsMap.get(InstancePartitionsType.CONSUMING).getInstances(0, 0),
Collections.singletonList(realtimeInstanceId));
diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineBalanceNumSegmentAssignmentStrategyTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineNonReplicaGroupSegmentAssignmentTest.java
similarity index 76%
rename from pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineBalanceNumSegmentAssignmentStrategyTest.java
rename to pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineNonReplicaGroupSegmentAssignmentTest.java
index 762773b..c455274 100644
--- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineBalanceNumSegmentAssignmentStrategyTest.java
+++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineNonReplicaGroupSegmentAssignmentTest.java
@@ -19,14 +19,11 @@
package org.apache.pinot.controller.helix.core.assignment.segment;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
-import org.apache.helix.HelixManager;
-import org.apache.helix.ZNRecord;
-import org.apache.helix.store.zk.ZkHelixPropertyStore;
import org.apache.pinot.common.config.TableConfig;
-import org.apache.pinot.common.metadata.ZKMetadataProvider;
import org.apache.pinot.common.utils.CommonConstants.Helix.StateModel.SegmentOnlineOfflineStateModel;
import org.apache.pinot.common.utils.CommonConstants.Helix.TableType;
import org.apache.pinot.common.utils.InstancePartitionsType;
@@ -34,16 +31,11 @@ import org.apache.pinot.controller.helix.core.assignment.InstancePartitions;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
-public class OfflineBalanceNumSegmentAssignmentStrategyTest {
+public class OfflineNonReplicaGroupSegmentAssignmentTest {
private static final int NUM_REPLICAS = 3;
private static final String SEGMENT_NAME_PREFIX = "segment_";
private static final int NUM_SEGMENTS = 100;
@@ -57,33 +49,26 @@ public class OfflineBalanceNumSegmentAssignmentStrategyTest {
private static final String INSTANCE_PARTITIONS_NAME =
InstancePartitionsType.OFFLINE.getInstancePartitionsName(RAW_TABLE_NAME);
- private SegmentAssignmentStrategy _strategy;
+ private SegmentAssignment _segmentAssignment;
+ private Map<InstancePartitionsType, InstancePartitions> _instancePartitionsMap;
@BeforeClass
public void setUp() {
+ TableConfig tableConfig =
+ new TableConfig.Builder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setNumReplicas(NUM_REPLICAS).build();
+ _segmentAssignment = SegmentAssignmentFactory.getSegmentAssignment(null, tableConfig);
+
// {
// 0_0=[instance_0, instance_1, instance_2, instance_3, instance_4, instance_5, instance_6, instance_7, instance_8, instance_9]
// }
InstancePartitions instancePartitions = new InstancePartitions(INSTANCE_PARTITIONS_NAME);
instancePartitions.setInstances(0, 0, INSTANCES);
-
- // Mock HelixManager
- @SuppressWarnings("unchecked")
- ZkHelixPropertyStore<ZNRecord> propertyStore = mock(ZkHelixPropertyStore.class);
- when(propertyStore
- .get(eq(ZKMetadataProvider.constructPropertyStorePathForInstancePartitions(INSTANCE_PARTITIONS_NAME)), any(),
- anyInt())).thenReturn(instancePartitions.toZNRecord());
- HelixManager helixManager = mock(HelixManager.class);
- when(helixManager.getHelixPropertyStore()).thenReturn(propertyStore);
-
- TableConfig tableConfig =
- new TableConfig.Builder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setNumReplicas(NUM_REPLICAS).build();
- _strategy = SegmentAssignmentStrategyFactory.getSegmentAssignmentStrategy(helixManager, tableConfig);
+ _instancePartitionsMap = Collections.singletonMap(InstancePartitionsType.OFFLINE, instancePartitions);
}
@Test
public void testFactory() {
- assertTrue(_strategy instanceof OfflineBalanceNumSegmentAssignmentStrategy);
+ assertTrue(_segmentAssignment instanceof OfflineSegmentAssignment);
}
@Test
@@ -98,7 +83,8 @@ public class OfflineBalanceNumSegmentAssignmentStrategyTest {
// ...
int expectedAssignedInstanceId = 0;
for (String segmentName : SEGMENTS) {
- List<String> instancesAssigned = _strategy.assignSegment(segmentName, currentAssignment);
+ List<String> instancesAssigned =
+ _segmentAssignment.assignSegment(segmentName, currentAssignment, _instancePartitionsMap);
assertEquals(instancesAssigned.size(), NUM_REPLICAS);
for (int replicaId = 0; replicaId < NUM_REPLICAS; replicaId++) {
assertEquals(instancesAssigned.get(replicaId), INSTANCES.get(expectedAssignedInstanceId));
@@ -113,7 +99,8 @@ public class OfflineBalanceNumSegmentAssignmentStrategyTest {
public void testTableBalanced() {
Map<String, Map<String, String>> currentAssignment = new TreeMap<>();
for (String segmentName : SEGMENTS) {
- List<String> instancesAssigned = _strategy.assignSegment(segmentName, currentAssignment);
+ List<String> instancesAssigned =
+ _segmentAssignment.assignSegment(segmentName, currentAssignment, _instancePartitionsMap);
currentAssignment.put(segmentName,
SegmentAssignmentUtils.getInstanceStateMap(instancesAssigned, SegmentOnlineOfflineStateModel.ONLINE));
}
@@ -132,6 +119,6 @@ public class OfflineBalanceNumSegmentAssignmentStrategyTest {
Arrays.fill(expectedNumSegmentsAssignedPerInstance, numSegmentsPerInstance);
assertEquals(numSegmentsAssignedPerInstance, expectedNumSegmentsAssignedPerInstance);
// Current assignment should already be balanced
- assertEquals(_strategy.rebalanceTable(currentAssignment, null), currentAssignment);
+ assertEquals(_segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, null), currentAssignment);
}
}
diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineReplicaGroupSegmentAssignmentStrategyTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineReplicaGroupSegmentAssignmentTest.java
similarity index 86%
rename from pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineReplicaGroupSegmentAssignmentStrategyTest.java
rename to pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineReplicaGroupSegmentAssignmentTest.java
index 28586f0..089c016 100644
--- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineReplicaGroupSegmentAssignmentStrategyTest.java
+++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineReplicaGroupSegmentAssignmentTest.java
@@ -51,7 +51,7 @@ import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
-public class OfflineReplicaGroupSegmentAssignmentStrategyTest {
+public class OfflineReplicaGroupSegmentAssignmentTest {
private static final int NUM_REPLICAS = 3;
private static final String SEGMENT_NAME_PREFIX = "segment_";
private static final int NUM_SEGMENTS = 90;
@@ -72,11 +72,20 @@ public class OfflineReplicaGroupSegmentAssignmentStrategyTest {
private static final String PARTITION_COLUMN = "partitionColumn";
private static final int NUM_PARTITIONS = 3;
- private SegmentAssignmentStrategy _strategyWithoutPartition;
- private SegmentAssignmentStrategy _strategyWithPartition;
+ private SegmentAssignment _segmentAssignmentWithoutPartition;
+ private Map<InstancePartitionsType, InstancePartitions> _instancePartitionsMapWithoutPartition;
+ private SegmentAssignment _segmentAssignmentWithPartition;
+ private Map<InstancePartitionsType, InstancePartitions> _instancePartitionsMapWithPartition;
@BeforeClass
public void setUp() {
+ TableConfig tableConfigWithoutPartitions =
+ new TableConfig.Builder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME_WITHOUT_PARTITION)
+ .setNumReplicas(NUM_REPLICAS)
+ .setSegmentAssignmentStrategy(AssignmentStrategy.REPLICA_GROUP_SEGMENT_ASSIGNMENT_STRATEGY).build();
+ _segmentAssignmentWithoutPartition =
+ SegmentAssignmentFactory.getSegmentAssignment(null, tableConfigWithoutPartitions);
+
// {
// 0_0=[instance_0, instance_1, instance_2, instance_3, instance_4, instance_5],
// 0_1=[instance_6, instance_7, instance_8, instance_9, instance_10, instance_11],
@@ -93,54 +102,12 @@ public class OfflineReplicaGroupSegmentAssignmentStrategyTest {
}
instancePartitionsWithoutPartition.setInstances(0, replicaId, instancesForReplica);
}
-
- // Mock HelixManager
- @SuppressWarnings("unchecked")
- ZkHelixPropertyStore<ZNRecord> propertyStoreWithoutPartitions = mock(ZkHelixPropertyStore.class);
- when(propertyStoreWithoutPartitions.get(eq(ZKMetadataProvider
- .constructPropertyStorePathForInstancePartitions(INSTANCE_PARTITIONS_NAME_WITHOUT_PARTITION)), any(), anyInt()))
- .thenReturn(instancePartitionsWithoutPartition.toZNRecord());
- HelixManager helixManagerWithoutPartitions = mock(HelixManager.class);
- when(helixManagerWithoutPartitions.getHelixPropertyStore()).thenReturn(propertyStoreWithoutPartitions);
-
- TableConfig tableConfigWithoutPartitions =
- new TableConfig.Builder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME_WITHOUT_PARTITION)
- .setNumReplicas(NUM_REPLICAS)
- .setSegmentAssignmentStrategy(AssignmentStrategy.REPLICA_GROUP_SEGMENT_ASSIGNMENT_STRATEGY).build();
- _strategyWithoutPartition = SegmentAssignmentStrategyFactory
- .getSegmentAssignmentStrategy(helixManagerWithoutPartitions, tableConfigWithoutPartitions);
-
- // {
- // 0_0=[instance_0, instance_1],
- // 0_1=[instance_6, instance_7],
- // 0_2=[instance_12, instance_13],
- // 1_0=[instance_2, instance_3],
- // 1_1=[instance_8, instance_9],
- // 1_2=[instance_14, instance_15],
- // 2_0=[instance_4, instance_5],
- // 2_1=[instance_10, instance_11],
- // 2_2=[instance_16, instance_17]
- // }
- InstancePartitions instancePartitionsWithPartition =
- new InstancePartitions(INSTANCE_PARTITIONS_NAME_WITH_PARTITION);
- int numInstancesPerPartition = numInstancesPerReplica / NUM_REPLICAS;
- instanceIdToAdd = 0;
- for (int replicaId = 0; replicaId < NUM_REPLICAS; replicaId++) {
- for (int partitionId = 0; partitionId < NUM_PARTITIONS; partitionId++) {
- List<String> instancesForPartition = new ArrayList<>(numInstancesPerPartition);
- for (int i = 0; i < numInstancesPerPartition; i++) {
- instancesForPartition.add(INSTANCES.get(instanceIdToAdd++));
- }
- instancePartitionsWithPartition.setInstances(partitionId, replicaId, instancesForPartition);
- }
- }
+ _instancePartitionsMapWithoutPartition =
+ Collections.singletonMap(InstancePartitionsType.OFFLINE, instancePartitionsWithoutPartition);
// Mock HelixManager
@SuppressWarnings("unchecked")
ZkHelixPropertyStore<ZNRecord> propertyStoreWithPartitions = mock(ZkHelixPropertyStore.class);
- when(propertyStoreWithPartitions.get(
- eq(ZKMetadataProvider.constructPropertyStorePathForInstancePartitions(INSTANCE_PARTITIONS_NAME_WITH_PARTITION)),
- any(), anyInt())).thenReturn(instancePartitionsWithPartition.toZNRecord());
List<ZNRecord> segmentZKMetadataZNRecords = new ArrayList<>(NUM_SEGMENTS);
for (int segmentId = 0; segmentId < NUM_SEGMENTS; segmentId++) {
String segmentName = SEGMENTS.get(segmentId);
@@ -168,14 +135,41 @@ public class OfflineReplicaGroupSegmentAssignmentStrategyTest {
.setNumReplicas(NUM_REPLICAS)
.setSegmentAssignmentStrategy(AssignmentStrategy.REPLICA_GROUP_SEGMENT_ASSIGNMENT_STRATEGY).build();
tableConfigWithPartitions.getValidationConfig().setReplicaGroupStrategyConfig(strategyConfig);
- _strategyWithPartition = SegmentAssignmentStrategyFactory
- .getSegmentAssignmentStrategy(helixManagerWithPartitions, tableConfigWithPartitions);
+ _segmentAssignmentWithPartition =
+ SegmentAssignmentFactory.getSegmentAssignment(helixManagerWithPartitions, tableConfigWithPartitions);
+
+ // {
+ // 0_0=[instance_0, instance_1],
+ // 0_1=[instance_6, instance_7],
+ // 0_2=[instance_12, instance_13],
+ // 1_0=[instance_2, instance_3],
+ // 1_1=[instance_8, instance_9],
+ // 1_2=[instance_14, instance_15],
+ // 2_0=[instance_4, instance_5],
+ // 2_1=[instance_10, instance_11],
+ // 2_2=[instance_16, instance_17]
+ // }
+ InstancePartitions instancePartitionsWithPartition =
+ new InstancePartitions(INSTANCE_PARTITIONS_NAME_WITH_PARTITION);
+ int numInstancesPerPartition = numInstancesPerReplica / NUM_REPLICAS;
+ instanceIdToAdd = 0;
+ for (int replicaId = 0; replicaId < NUM_REPLICAS; replicaId++) {
+ for (int partitionId = 0; partitionId < NUM_PARTITIONS; partitionId++) {
+ List<String> instancesForPartition = new ArrayList<>(numInstancesPerPartition);
+ for (int i = 0; i < numInstancesPerPartition; i++) {
+ instancesForPartition.add(INSTANCES.get(instanceIdToAdd++));
+ }
+ instancePartitionsWithPartition.setInstances(partitionId, replicaId, instancesForPartition);
+ }
+ }
+ _instancePartitionsMapWithPartition =
+ Collections.singletonMap(InstancePartitionsType.OFFLINE, instancePartitionsWithPartition);
}
@Test
public void testFactory() {
- assertTrue(_strategyWithoutPartition instanceof OfflineReplicaGroupSegmentAssignmentStrategy);
- assertTrue(_strategyWithPartition instanceof OfflineReplicaGroupSegmentAssignmentStrategy);
+ assertTrue(_segmentAssignmentWithoutPartition instanceof OfflineSegmentAssignment);
+ assertTrue(_segmentAssignmentWithPartition instanceof OfflineSegmentAssignment);
}
@Test
@@ -184,7 +178,8 @@ public class OfflineReplicaGroupSegmentAssignmentStrategyTest {
Map<String, Map<String, String>> currentAssignment = new TreeMap<>();
for (int segmentId = 0; segmentId < NUM_SEGMENTS; segmentId++) {
String segmentName = SEGMENTS.get(segmentId);
- List<String> instancesAssigned = _strategyWithoutPartition.assignSegment(segmentName, currentAssignment);
+ List<String> instancesAssigned = _segmentAssignmentWithoutPartition
+ .assignSegment(segmentName, currentAssignment, _instancePartitionsMapWithoutPartition);
assertEquals(instancesAssigned.size(), NUM_REPLICAS);
for (int replicaId = 0; replicaId < NUM_REPLICAS; replicaId++) {
@@ -212,7 +207,8 @@ public class OfflineReplicaGroupSegmentAssignmentStrategyTest {
int numInstancesPerPartition = numInstancesPerReplica / NUM_PARTITIONS;
for (int segmentId = 0; segmentId < NUM_SEGMENTS; segmentId++) {
String segmentName = SEGMENTS.get(segmentId);
- List<String> instancesAssigned = _strategyWithPartition.assignSegment(segmentName, currentAssignment);
+ List<String> instancesAssigned = _segmentAssignmentWithPartition
+ .assignSegment(segmentName, currentAssignment, _instancePartitionsMapWithPartition);
assertEquals(instancesAssigned.size(), NUM_REPLICAS);
int partitionId = segmentId % NUM_PARTITIONS;
for (int replicaId = 0; replicaId < NUM_REPLICAS; replicaId++) {
@@ -240,7 +236,8 @@ public class OfflineReplicaGroupSegmentAssignmentStrategyTest {
public void testTableBalancedWithoutPartition() {
Map<String, Map<String, String>> currentAssignment = new TreeMap<>();
for (String segmentName : SEGMENTS) {
- List<String> instancesAssigned = _strategyWithoutPartition.assignSegment(segmentName, currentAssignment);
+ List<String> instancesAssigned = _segmentAssignmentWithoutPartition
+ .assignSegment(segmentName, currentAssignment, _instancePartitionsMapWithoutPartition);
currentAssignment.put(segmentName,
SegmentAssignmentUtils.getInstanceStateMap(instancesAssigned, SegmentOnlineOfflineStateModel.ONLINE));
}
@@ -259,14 +256,16 @@ public class OfflineReplicaGroupSegmentAssignmentStrategyTest {
Arrays.fill(expectedNumSegmentsAssignedPerInstance, numSegmentsPerInstance);
assertEquals(numSegmentsAssignedPerInstance, expectedNumSegmentsAssignedPerInstance);
// Current assignment should already be balanced
- assertEquals(_strategyWithoutPartition.rebalanceTable(currentAssignment, null), currentAssignment);
+ assertEquals(_segmentAssignmentWithoutPartition
+ .rebalanceTable(currentAssignment, _instancePartitionsMapWithoutPartition, null), currentAssignment);
}
@Test
public void testTableBalancedWithPartition() {
Map<String, Map<String, String>> currentAssignment = new TreeMap<>();
for (String segmentName : SEGMENTS) {
- List<String> instancesAssigned = _strategyWithPartition.assignSegment(segmentName, currentAssignment);
+ List<String> instancesAssigned = _segmentAssignmentWithPartition
+ .assignSegment(segmentName, currentAssignment, _instancePartitionsMapWithPartition);
currentAssignment.put(segmentName,
SegmentAssignmentUtils.getInstanceStateMap(instancesAssigned, SegmentOnlineOfflineStateModel.ONLINE));
}
@@ -285,6 +284,8 @@ public class OfflineReplicaGroupSegmentAssignmentStrategyTest {
Arrays.fill(expectedNumSegmentsAssignedPerInstance, numSegmentsPerInstance);
assertEquals(numSegmentsAssignedPerInstance, expectedNumSegmentsAssignedPerInstance);
// Current assignment should already be balanced
- assertEquals(_strategyWithPartition.rebalanceTable(currentAssignment, null), currentAssignment);
+ assertEquals(
+ _segmentAssignmentWithPartition.rebalanceTable(currentAssignment, _instancePartitionsMapWithPartition, null),
+ currentAssignment);
}
}
diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeBalanceNumSegmentAssignmentStrategyTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeNonReplicaGroupSegmentAssignmentTest.java
similarity index 80%
rename from pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeBalanceNumSegmentAssignmentStrategyTest.java
rename to pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeNonReplicaGroupSegmentAssignmentTest.java
index 0aa454c..5fee01a 100644
--- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeBalanceNumSegmentAssignmentStrategyTest.java
+++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeNonReplicaGroupSegmentAssignmentTest.java
@@ -23,11 +23,7 @@ import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.commons.configuration.BaseConfiguration;
-import org.apache.helix.HelixManager;
-import org.apache.helix.ZNRecord;
-import org.apache.helix.store.zk.ZkHelixPropertyStore;
import org.apache.pinot.common.config.TableConfig;
-import org.apache.pinot.common.metadata.ZKMetadataProvider;
import org.apache.pinot.common.utils.CommonConstants;
import org.apache.pinot.common.utils.CommonConstants.Helix.StateModel.RealtimeSegmentOnlineOfflineStateModel;
import org.apache.pinot.common.utils.InstancePartitionsType;
@@ -37,16 +33,11 @@ import org.apache.pinot.controller.helix.core.rebalance.RebalanceUserConfigConst
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
-public class RealtimeBalanceNumSegmentAssignmentStrategyTest {
+public class RealtimeNonReplicaGroupSegmentAssignmentTest {
private static final int NUM_REPLICAS = 3;
private static final int NUM_PARTITIONS = 4;
private static final int NUM_SEGMENTS = 100;
@@ -65,7 +56,8 @@ public class RealtimeBalanceNumSegmentAssignmentStrategyTest {
InstancePartitionsType.COMPLETED.getInstancePartitionsName(RAW_TABLE_NAME);
private List<String> _segments;
- private SegmentAssignmentStrategy _strategy;
+ private SegmentAssignment _segmentAssignment;
+ private Map<InstancePartitionsType, InstancePartitions> _instancePartitionsMap;
@BeforeClass
public void setUp() {
@@ -75,7 +67,13 @@ public class RealtimeBalanceNumSegmentAssignmentStrategyTest {
System.currentTimeMillis()).getSegmentName());
}
- // Consuming instances:
+ TableConfig tableConfig =
+ new TableConfig.Builder(CommonConstants.Helix.TableType.REALTIME).setTableName(RAW_TABLE_NAME)
+ .setNumReplicas(NUM_REPLICAS).setLLC(true).build();
+ _segmentAssignment = SegmentAssignmentFactory.getSegmentAssignment(null, tableConfig);
+
+ _instancePartitionsMap = new TreeMap<>();
+ // CONSUMING instances:
// {
// 0_0=[instance_0, instance_1, instance_2, instance_3, instance_4, instance_5, instance_6, instance_7, instance_8]
// }
@@ -83,35 +81,20 @@ public class RealtimeBalanceNumSegmentAssignmentStrategyTest {
// p3r0 p3r1 p3r2
InstancePartitions consumingInstancePartitions = new InstancePartitions(CONSUMING_INSTANCE_PARTITIONS_NAME);
consumingInstancePartitions.setInstances(0, 0, CONSUMING_INSTANCES);
+ _instancePartitionsMap.put(InstancePartitionsType.CONSUMING, consumingInstancePartitions);
- // Completed instances:
+ // COMPLETED instances:
// {
// 0_0=[instance_0, instance_1, instance_2, instance_3, instance_4, instance_5, instance_6, instance_7, instance_8, instance_9]
// }
InstancePartitions completedInstancePartitions = new InstancePartitions(COMPLETED_INSTANCE_PARTITIONS_NAME);
completedInstancePartitions.setInstances(0, 0, COMPLETED_INSTANCES);
-
- // Mock HelixManager
- @SuppressWarnings("unchecked")
- ZkHelixPropertyStore<ZNRecord> propertyStore = mock(ZkHelixPropertyStore.class);
- when(propertyStore
- .get(eq(ZKMetadataProvider.constructPropertyStorePathForInstancePartitions(CONSUMING_INSTANCE_PARTITIONS_NAME)),
- any(), anyInt())).thenReturn(consumingInstancePartitions.toZNRecord());
- when(propertyStore
- .get(eq(ZKMetadataProvider.constructPropertyStorePathForInstancePartitions(COMPLETED_INSTANCE_PARTITIONS_NAME)),
- any(), anyInt())).thenReturn(completedInstancePartitions.toZNRecord());
- HelixManager helixManager = mock(HelixManager.class);
- when(helixManager.getHelixPropertyStore()).thenReturn(propertyStore);
-
- TableConfig tableConfig =
- new TableConfig.Builder(CommonConstants.Helix.TableType.REALTIME).setTableName(RAW_TABLE_NAME)
- .setNumReplicas(NUM_REPLICAS).setLLC(true).build();
- _strategy = SegmentAssignmentStrategyFactory.getSegmentAssignmentStrategy(helixManager, tableConfig);
+ _instancePartitionsMap.put(InstancePartitionsType.COMPLETED, completedInstancePartitions);
}
@Test
public void testFactory() {
- assertTrue(_strategy instanceof RealtimeBalanceNumSegmentAssignmentStrategy);
+ assertTrue(_segmentAssignment instanceof RealtimeSegmentAssignment);
}
@Test
@@ -119,7 +102,8 @@ public class RealtimeBalanceNumSegmentAssignmentStrategyTest {
Map<String, Map<String, String>> currentAssignment = new TreeMap<>();
for (int segmentId = 0; segmentId < NUM_SEGMENTS; segmentId++) {
String segmentName = _segments.get(segmentId);
- List<String> instancesAssigned = _strategy.assignSegment(segmentName, currentAssignment);
+ List<String> instancesAssigned =
+ _segmentAssignment.assignSegment(segmentName, currentAssignment, _instancePartitionsMap);
assertEquals(instancesAssigned.size(), NUM_REPLICAS);
for (int replicaId = 0; replicaId < NUM_REPLICAS; replicaId++) {
@@ -143,7 +127,8 @@ public class RealtimeBalanceNumSegmentAssignmentStrategyTest {
Map<String, Map<String, String>> currentAssignment = new TreeMap<>();
for (int segmentId = 0; segmentId < NUM_SEGMENTS; segmentId++) {
String segmentName = _segments.get(segmentId);
- List<String> instancesAssigned = _strategy.assignSegment(segmentName, currentAssignment);
+ List<String> instancesAssigned =
+ _segmentAssignment.assignSegment(segmentName, currentAssignment, _instancePartitionsMap);
addToAssignment(currentAssignment, segmentId, instancesAssigned);
}
@@ -154,20 +139,20 @@ public class RealtimeBalanceNumSegmentAssignmentStrategyTest {
assertEquals(instanceStateMap.size(), NUM_REPLICAS);
}
- // Rebalance should relocate all completed (ONLINE) segments to the completed instances
+ // Rebalance should relocate all COMPLETED (ONLINE) segments to the COMPLETED instances
Map<String, Map<String, String>> newAssignment =
- _strategy.rebalanceTable(currentAssignment, new BaseConfiguration());
+ _segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, new BaseConfiguration());
assertEquals(newAssignment.size(), NUM_SEGMENTS);
for (int segmentId = 0; segmentId < NUM_SEGMENTS; segmentId++) {
if (segmentId < NUM_SEGMENTS - NUM_PARTITIONS) {
- // Completed (ONLINE) segments
+ // COMPLETED (ONLINE) segments
Map<String, String> instanceStateMap = newAssignment.get(_segments.get(segmentId));
for (Map.Entry<String, String> entry : instanceStateMap.entrySet()) {
assertTrue(entry.getKey().startsWith(COMPLETED_INSTANCE_NAME_PREFIX));
assertEquals(entry.getValue(), RealtimeSegmentOnlineOfflineStateModel.ONLINE);
}
} else {
- // Consuming (CONSUMING) segments
+ // CONSUMING segments
Map<String, String> instanceStateMap = newAssignment.get(_segments.get(segmentId));
for (Map.Entry<String, String> entry : instanceStateMap.entrySet()) {
assertTrue(entry.getKey().startsWith(CONSUMING_INSTANCE_NAME_PREFIX));
@@ -184,10 +169,10 @@ public class RealtimeBalanceNumSegmentAssignmentStrategyTest {
assertTrue(numSegmentsAssignedPerInstance[i] >= expectedMinNumSegmentsPerInstance);
}
- // Rebalance all segments (both completed and consuming) should give the same assignment
+ // Rebalance all segments (including CONSUMING) should give the same assignment
BaseConfiguration config = new BaseConfiguration();
config.setProperty(RebalanceUserConfigConstants.INCLUDE_CONSUMING, true);
- assertEquals(_strategy.rebalanceTable(currentAssignment, config), newAssignment);
+ assertEquals(_segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, config), newAssignment);
// Rebalance should not change the assignment for the OFFLINE segments
String offlineSegmentName = "offlineSegment";
@@ -196,7 +181,7 @@ public class RealtimeBalanceNumSegmentAssignmentStrategyTest {
RealtimeSegmentOnlineOfflineStateModel.OFFLINE);
currentAssignment.put(offlineSegmentName, offlineSegmentInstanceStateMap);
newAssignment.put(offlineSegmentName, offlineSegmentInstanceStateMap);
- assertEquals(_strategy.rebalanceTable(currentAssignment, config), newAssignment);
+ assertEquals(_segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, config), newAssignment);
}
private void addToAssignment(Map<String, Map<String, String>> currentAssignment, int segmentId,
diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeReplicaGroupSegmentAssignmentStrategyTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeReplicaGroupSegmentAssignmentTest.java
similarity index 82%
rename from pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeReplicaGroupSegmentAssignmentStrategyTest.java
rename to pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeReplicaGroupSegmentAssignmentTest.java
index f74a7b2..04fc344 100644
--- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeReplicaGroupSegmentAssignmentStrategyTest.java
+++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeReplicaGroupSegmentAssignmentTest.java
@@ -24,11 +24,7 @@ import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.commons.configuration.BaseConfiguration;
-import org.apache.helix.HelixManager;
-import org.apache.helix.ZNRecord;
-import org.apache.helix.store.zk.ZkHelixPropertyStore;
import org.apache.pinot.common.config.TableConfig;
-import org.apache.pinot.common.metadata.ZKMetadataProvider;
import org.apache.pinot.common.utils.CommonConstants.Helix.StateModel.RealtimeSegmentOnlineOfflineStateModel;
import org.apache.pinot.common.utils.CommonConstants.Helix.TableType;
import org.apache.pinot.common.utils.CommonConstants.Segment.AssignmentStrategy;
@@ -39,16 +35,11 @@ import org.apache.pinot.controller.helix.core.rebalance.RebalanceUserConfigConst
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
-public class RealtimeReplicaGroupSegmentAssignmentStrategyTest {
+public class RealtimeReplicaGroupSegmentAssignmentTest {
private static final int NUM_REPLICAS = 3;
private static final int NUM_PARTITIONS = 4;
private static final int NUM_SEGMENTS = 100;
@@ -67,7 +58,8 @@ public class RealtimeReplicaGroupSegmentAssignmentStrategyTest {
InstancePartitionsType.COMPLETED.getInstancePartitionsName(RAW_TABLE_NAME);
private List<String> _segments;
- private SegmentAssignmentStrategy _strategy;
+ private SegmentAssignment _segmentAssignment;
+ private Map<InstancePartitionsType, InstancePartitions> _instancePartitionsMap;
@BeforeClass
public void setUp() {
@@ -77,7 +69,14 @@ public class RealtimeReplicaGroupSegmentAssignmentStrategyTest {
System.currentTimeMillis()).getSegmentName());
}
- // Consuming instances:
+ TableConfig tableConfig =
+ new TableConfig.Builder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setNumReplicas(NUM_REPLICAS)
+ .setLLC(true).setSegmentAssignmentStrategy(AssignmentStrategy.REPLICA_GROUP_SEGMENT_ASSIGNMENT_STRATEGY)
+ .build();
+ _segmentAssignment = SegmentAssignmentFactory.getSegmentAssignment(null, tableConfig);
+
+ _instancePartitionsMap = new TreeMap<>();
+ // CONSUMING instances:
// {
// 0_0=[instance_0, instance_1, instance_2],
// 0_1=[instance_3, instance_4, instance_5],
@@ -95,8 +94,9 @@ public class RealtimeReplicaGroupSegmentAssignmentStrategyTest {
}
consumingInstancePartitions.setInstances(0, replicaId, consumingInstancesForReplica);
}
+ _instancePartitionsMap.put(InstancePartitionsType.CONSUMING, consumingInstancePartitions);
- // Completed instances:
+ // COMPLETED instances:
// {
// 0_0=[instance_0, instance_1, instance_2, instance_3],
// 0_1=[instance_4, instance_5, instance_6, instance_7],
@@ -112,29 +112,12 @@ public class RealtimeReplicaGroupSegmentAssignmentStrategyTest {
}
completedInstancePartitions.setInstances(0, replicaId, completedInstancesForReplica);
}
-
- // Mock HelixManager
- @SuppressWarnings("unchecked")
- ZkHelixPropertyStore<ZNRecord> propertyStore = mock(ZkHelixPropertyStore.class);
- when(propertyStore
- .get(eq(ZKMetadataProvider.constructPropertyStorePathForInstancePartitions(CONSUMING_INSTANCE_PARTITIONS_NAME)),
- any(), anyInt())).thenReturn(consumingInstancePartitions.toZNRecord());
- when(propertyStore
- .get(eq(ZKMetadataProvider.constructPropertyStorePathForInstancePartitions(COMPLETED_INSTANCE_PARTITIONS_NAME)),
- any(), anyInt())).thenReturn(completedInstancePartitions.toZNRecord());
- HelixManager helixManager = mock(HelixManager.class);
- when(helixManager.getHelixPropertyStore()).thenReturn(propertyStore);
-
- TableConfig tableConfig =
- new TableConfig.Builder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setNumReplicas(NUM_REPLICAS)
- .setLLC(true).setSegmentAssignmentStrategy(AssignmentStrategy.REPLICA_GROUP_SEGMENT_ASSIGNMENT_STRATEGY)
- .build();
- _strategy = SegmentAssignmentStrategyFactory.getSegmentAssignmentStrategy(helixManager, tableConfig);
+ _instancePartitionsMap.put(InstancePartitionsType.COMPLETED, completedInstancePartitions);
}
@Test
public void testFactory() {
- assertTrue(_strategy instanceof RealtimeReplicaGroupSegmentAssignmentStrategy);
+ assertTrue(_segmentAssignment instanceof RealtimeSegmentAssignment);
}
@Test
@@ -143,7 +126,8 @@ public class RealtimeReplicaGroupSegmentAssignmentStrategyTest {
Map<String, Map<String, String>> currentAssignment = new TreeMap<>();
for (int segmentId = 0; segmentId < NUM_SEGMENTS; segmentId++) {
String segmentName = _segments.get(segmentId);
- List<String> instancesAssigned = _strategy.assignSegment(segmentName, currentAssignment);
+ List<String> instancesAssigned =
+ _segmentAssignment.assignSegment(segmentName, currentAssignment, _instancePartitionsMap);
assertEquals(instancesAssigned.size(), NUM_REPLICAS);
for (int replicaId = 0; replicaId < NUM_REPLICAS; replicaId++) {
@@ -167,7 +151,8 @@ public class RealtimeReplicaGroupSegmentAssignmentStrategyTest {
Map<String, Map<String, String>> currentAssignment = new TreeMap<>();
for (int segmentId = 0; segmentId < NUM_SEGMENTS; segmentId++) {
String segmentName = _segments.get(segmentId);
- List<String> instancesAssigned = _strategy.assignSegment(segmentName, currentAssignment);
+ List<String> instancesAssigned =
+ _segmentAssignment.assignSegment(segmentName, currentAssignment, _instancePartitionsMap);
addToAssignment(currentAssignment, segmentId, instancesAssigned);
}
@@ -178,20 +163,20 @@ public class RealtimeReplicaGroupSegmentAssignmentStrategyTest {
assertEquals(instanceStateMap.size(), NUM_REPLICAS);
}
- // Rebalance should relocate all completed (ONLINE) segments to the completed instances
+ // Rebalance should relocate all COMPLETED (ONLINE) segments to the COMPLETED instances
Map<String, Map<String, String>> newAssignment =
- _strategy.rebalanceTable(currentAssignment, new BaseConfiguration());
+ _segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, new BaseConfiguration());
assertEquals(newAssignment.size(), NUM_SEGMENTS);
for (int segmentId = 0; segmentId < NUM_SEGMENTS; segmentId++) {
if (segmentId < NUM_SEGMENTS - NUM_PARTITIONS) {
- // Completed (ONLINE) segments
+ // COMPLETED (ONLINE) segments
Map<String, String> instanceStateMap = newAssignment.get(_segments.get(segmentId));
for (Map.Entry<String, String> entry : instanceStateMap.entrySet()) {
assertTrue(entry.getKey().startsWith(COMPLETED_INSTANCE_NAME_PREFIX));
assertEquals(entry.getValue(), RealtimeSegmentOnlineOfflineStateModel.ONLINE);
}
} else {
- // Consuming (CONSUMING) segments
+ // CONSUMING segments
Map<String, String> instanceStateMap = newAssignment.get(_segments.get(segmentId));
for (Map.Entry<String, String> entry : instanceStateMap.entrySet()) {
assertTrue(entry.getKey().startsWith(CONSUMING_INSTANCE_NAME_PREFIX));
@@ -207,10 +192,10 @@ public class RealtimeReplicaGroupSegmentAssignmentStrategyTest {
Arrays.fill(expectedNumSegmentsAssignedPerInstance, numSegmentsPerInstance);
assertEquals(numSegmentsAssignedPerInstance, expectedNumSegmentsAssignedPerInstance);
- // Rebalance all segments (both completed and consuming) should give the same assignment
+ // Rebalance all segments (including CONSUMING) should give the same assignment
BaseConfiguration config = new BaseConfiguration();
config.setProperty(RebalanceUserConfigConstants.INCLUDE_CONSUMING, true);
- assertEquals(_strategy.rebalanceTable(currentAssignment, config), newAssignment);
+ assertEquals(_segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, config), newAssignment);
// Rebalance should not change the assignment for the OFFLINE segments
String offlineSegmentName = "offlineSegment";
@@ -219,7 +204,7 @@ public class RealtimeReplicaGroupSegmentAssignmentStrategyTest {
RealtimeSegmentOnlineOfflineStateModel.OFFLINE);
currentAssignment.put(offlineSegmentName, offlineSegmentInstanceStateMap);
newAssignment.put(offlineSegmentName, offlineSegmentInstanceStateMap);
- assertEquals(_strategy.rebalanceTable(currentAssignment, config), newAssignment);
+ assertEquals(_segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, config), newAssignment);
}
private void addToAssignment(Map<String, Map<String, String>> currentAssignment, int segmentId,
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org
[incubator-pinot] 03/08: [Controller Separation] Add logic for lead
controller resource (#4323)
Posted by xi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
xiangfu pushed a commit to branch winedepot-0.10
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git
commit 179377c232d56cc6b26352b3034f1206167aeaa2
Author: Jialiang Li <jl...@linkedin.com>
AuthorDate: Thu Aug 15 15:26:29 2019 -0700
[Controller Separation] Add logic for lead controller resource (#4323)
* Add logic for leveraging lead controller resource
* Add logic for lead controller resource on controller side
* Remove API design out of this PR
* Move murmur2 functions to an util class in pinot-common
* Use resource config to check whether resource is enabled
* Create a customized MasterSlaveStateModelFactory instead of inherating one from Helix
* Check resource config is enabled from server
---
.../apache/pinot/common/utils/CommonConstants.java | 2 +
.../org/apache/pinot/common/utils/HashUtil.java | 47 ++++
.../common/utils/helix/LeadControllerUtils.java | 61 +++++
.../controller/ControllerLeadershipManager.java | 172 --------------
.../apache/pinot/controller/ControllerStarter.java | 162 +++++++------
.../pinot/controller/LeadControllerManager.java | 229 ++++++++++++++++++
.../PinotSegmentUploadRestletResource.java | 6 +-
.../controller/api/upload/SegmentValidator.java | 10 +-
.../controller/helix/SegmentStatusChecker.java | 8 +-
.../helix/core/PinotHelixResourceManager.java | 6 +-
.../helix/core/minion/PinotTaskManager.java | 8 +-
.../core/periodictask/ControllerPeriodicTask.java | 10 +-
.../ControllerPeriodicTaskScheduler.java | 60 -----
.../realtime/PinotLLCRealtimeSegmentManager.java | 32 ++-
.../core/realtime/PinotRealtimeSegmentManager.java | 43 +---
.../core/realtime/SegmentCompletionManager.java | 54 +++--
.../core/relocation/RealtimeSegmentRelocator.java | 8 +-
.../helix/core/retention/RetentionManager.java | 8 +-
...rollerResourceMasterSlaveStateModelFactory.java | 84 +++++++
.../helix/core/util/HelixSetupUtils.java | 17 +-
.../BrokerResourceValidationManager.java | 5 +-
.../validation/OfflineSegmentIntervalChecker.java | 13 +-
.../RealtimeSegmentValidationManager.java | 8 +-
.../controller/validation/StorageQuotaChecker.java | 17 +-
.../controller/LeadControllerManagerTest.java | 125 ++++++++++
.../pinot/controller/helix/ControllerTest.java | 14 ++
.../controller/helix/PinotControllerModeTest.java | 169 ++++++++-----
.../controller/helix/SegmentStatusCheckerTest.java | 73 +++++-
.../helix/core/PinotHelixResourceManagerTest.java | 21 +-
.../periodictask/ControllerPeriodicTaskTest.java | 7 +-
.../PinotLLCRealtimeSegmentManagerTest.java | 7 +-
.../helix/core/realtime/SegmentCompletionTest.java | 263 ++++++++++-----------
.../relocation/RealtimeSegmentRelocatorTest.java | 14 +-
.../helix/core/retention/RetentionManagerTest.java | 12 +-
.../validation/StorageQuotaCheckerTest.java | 36 +--
.../realtime/LLRealtimeSegmentDataManager.java | 4 +-
.../manager/realtime/RealtimeTableDataManager.java | 2 +-
.../realtime/SegmentBuildTimeLeaseExtender.java | 8 +-
.../data/partition/MurmurPartitionFunction.java | 41 +---
.../server/realtime/ControllerLeaderLocator.java | 153 ++++++++++--
.../ServerSegmentCompletionProtocolHandler.java | 10 +-
.../realtime/LLRealtimeSegmentDataManagerTest.java | 2 +-
.../realtime/ControllerLeaderLocatorTest.java | 99 +++++++-
...abledLeadControllerResourceIntegrationTest.java | 22 +-
.../tests/SegmentCompletionIntegrationTest.java | 2 +-
45 files changed, 1394 insertions(+), 760 deletions(-)
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/CommonConstants.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/CommonConstants.java
index d17eb79..964a376 100644
--- a/pinot-common/src/main/java/org/apache/pinot/common/utils/CommonConstants.java
+++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/CommonConstants.java
@@ -39,6 +39,8 @@ public class CommonConstants {
public static final String BROKER_RESOURCE_INSTANCE = "brokerResource";
public static final String LEAD_CONTROLLER_RESOURCE_NAME = "leadControllerResource";
+ public static final String LEAD_CONTROLLER_RESOURCE_ENABLED_KEY = "RESOURCE_ENABLED";
+
// More information on why these numbers are set can be found in the following doc:
// https://cwiki.apache.org/confluence/display/PINOT/Controller+Separation+between+Helix+and+Pinot
public static final int NUMBER_OF_PARTITIONS_IN_LEAD_CONTROLLER_RESOURCE = 24;
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/HashUtil.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/HashUtil.java
index 955ab84..be12cc1 100644
--- a/pinot-common/src/main/java/org/apache/pinot/common/utils/HashUtil.java
+++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/HashUtil.java
@@ -105,4 +105,51 @@ public class HashUtil {
h ^= h >>> r;
return h;
}
+
+ /**
+ * Generates 32 bit murmur2 hash from byte array
+ * @param data byte array to hash
+ * @return 32 bit hash of the given array
+ */
+ public static int murmur2(final byte[] data) {
+ int length = data.length;
+ int seed = 0x9747b28c;
+ // 'm' and 'r' are mixing constants generated offline.
+ // They're not really 'magic', they just happen to work well.
+ final int m = 0x5bd1e995;
+ final int r = 24;
+
+ // Initialize the hash to a random value
+ int h = seed ^ length;
+ int length4 = length / 4;
+
+ for (int i = 0; i < length4; i++) {
+ final int i4 = i * 4;
+ int k =
+ (data[i4 + 0] & 0xff) + ((data[i4 + 1] & 0xff) << 8) + ((data[i4 + 2] & 0xff) << 16) + ((data[i4 + 3] & 0xff)
+ << 24);
+ k *= m;
+ k ^= k >>> r;
+ k *= m;
+ h *= m;
+ h ^= k;
+ }
+
+ // Handle the last few bytes of the input array
+ switch (length % 4) {
+ case 3:
+ h ^= (data[(length & ~3) + 2] & 0xff) << 16;
+ case 2:
+ h ^= (data[(length & ~3) + 1] & 0xff) << 8;
+ case 1:
+ h ^= data[length & ~3] & 0xff;
+ h *= m;
+ }
+
+ h ^= h >>> 13;
+ h *= m;
+ h ^= h >>> 15;
+
+ return h;
+ }
}
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/helix/LeadControllerUtils.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/helix/LeadControllerUtils.java
new file mode 100644
index 0000000..e6fffc8
--- /dev/null
+++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/helix/LeadControllerUtils.java
@@ -0,0 +1,61 @@
+/**
+ * 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.common.utils.helix;
+
+import org.apache.pinot.common.utils.CommonConstants.Helix;
+import org.apache.pinot.common.utils.HashUtil;
+import org.apache.pinot.common.utils.StringUtil;
+
+
+public class LeadControllerUtils {
+
+ /**
+ * Given a raw table name and number of partitions, returns the partition id in lead controller resource.
+ * Uses murmur2 function to get hashcode for table, ignores the most significant bit.
+ * Note: This method CANNOT be changed when lead controller resource is enabled.
+ * Otherwise it will assign different controller for the same table, which will mess up the controller periodic tasks and realtime segment completion.
+ * @param rawTableName raw table name
+ * @return partition id in lead controller resource.
+ */
+ public static int getPartitionIdForTable(String rawTableName) {
+ return (HashUtil.murmur2(StringUtil.encodeUtf8(rawTableName)) & Integer.MAX_VALUE)
+ % Helix.NUMBER_OF_PARTITIONS_IN_LEAD_CONTROLLER_RESOURCE;
+ }
+
+ /**
+ * Generates participant instance id, e.g. returns Controller_localhost_9000 given localhost as hostname and 9000 as port.
+ */
+ public static String generateParticipantInstanceId(String controllerHost, int controllerPort) {
+ return Helix.PREFIX_OF_CONTROLLER_INSTANCE + controllerHost + "_" + controllerPort;
+ }
+
+ /**
+ * Generates partition name, e.g. returns leadControllerResource_0 given 0 as partition index.
+ */
+ public static String generatePartitionName(int partitionId) {
+ return Helix.LEAD_CONTROLLER_RESOURCE_NAME + "_" + partitionId;
+ }
+
+ /**
+ * Extracts partition index from partition name, e.g. returns 0 given leadControllerResource_0 as partition name.
+ */
+ public static int extractPartitionId(String partitionName) {
+ return Integer.parseInt(partitionName.substring(partitionName.lastIndexOf('_') + 1));
+ }
+}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/ControllerLeadershipManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/ControllerLeadershipManager.java
deleted file mode 100644
index 92779d1..0000000
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/ControllerLeadershipManager.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/**
- * 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;
-
-import java.util.HashMap;
-import java.util.Map;
-import javax.annotation.concurrent.ThreadSafe;
-import org.apache.helix.HelixManager;
-import org.apache.pinot.common.metrics.ControllerGauge;
-import org.apache.pinot.common.metrics.ControllerMeter;
-import org.apache.pinot.common.metrics.ControllerMetrics;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-
-/**
- * Single place for listening on controller changes.
- * This should be created at controller startup and everyone who wants to listen to controller changes should subscribe.
- */
-@ThreadSafe
-public class ControllerLeadershipManager {
- private static final Logger LOGGER = LoggerFactory.getLogger(ControllerLeadershipManager.class);
- private static final long CONTROLLER_LEADERSHIP_FETCH_INTERVAL = 60_000L;
-
- private final HelixManager _helixControllerManager;
- private final ControllerMetrics _controllerMetrics;
- private final Map<String, LeadershipChangeSubscriber> _subscribers = new HashMap<>();
- private final Thread _controllerLeadershipFetchingThread;
-
- private boolean _amILeader = false;
- private boolean _stopped = false;
-
- public ControllerLeadershipManager(HelixManager helixControllerManager, ControllerMetrics controllerMetrics) {
- _helixControllerManager = helixControllerManager;
- _controllerMetrics = controllerMetrics;
- _controllerMetrics.setValueOfGlobalGauge(ControllerGauge.PINOT_CONTROLLER_LEADER, 0L);
-
- // Create a thread to periodically fetch controller leadership as a work-around of Helix callback delay
- _controllerLeadershipFetchingThread = new Thread("ControllerLeadershipFetchingThread") {
- @Override
- public void run() {
- while (true) {
- try {
- synchronized (ControllerLeadershipManager.this) {
- if (_stopped) {
- return;
- }
- if (_helixControllerManager.isLeader()) {
- if (!_amILeader) {
- _amILeader = true;
- LOGGER.warn("Becoming leader without getting Helix change callback");
- _controllerMetrics
- .addMeteredGlobalValue(ControllerMeter.CONTROLLER_LEADERSHIP_CHANGE_WITHOUT_CALLBACK, 1L);
- onBecomingLeader();
- }
- } else {
- if (_amILeader) {
- _amILeader = false;
- LOGGER.warn("Losing leadership without getting Helix change callback");
- _controllerMetrics
- .addMeteredGlobalValue(ControllerMeter.CONTROLLER_LEADERSHIP_CHANGE_WITHOUT_CALLBACK, 1L);
- onBecomingNonLeader();
- }
- }
- ControllerLeadershipManager.this.wait(CONTROLLER_LEADERSHIP_FETCH_INTERVAL);
- }
- } catch (Exception e) {
- // Ignore all exceptions. The thread keeps running until ControllerLeadershipManager.stop() is invoked.
- LOGGER.error("Caught exception within controller leadership fetching thread", e);
- }
- }
- }
- };
- }
-
- /**
- * Subscribes to changes in the controller leadership.
- * <p>If controller is already leader, invoke {@link LeadershipChangeSubscriber#onBecomingLeader()}
- */
- public synchronized void subscribe(String name, LeadershipChangeSubscriber subscriber) {
- LOGGER.info("{} subscribing to leadership changes", name);
- _subscribers.put(name, subscriber);
- if (_amILeader) {
- subscriber.onBecomingLeader();
- }
- }
-
- public boolean isLeader() {
- return _amILeader;
- }
-
- /**
- * Starts the service.
- */
- public synchronized void start() {
- onControllerChange();
- _controllerLeadershipFetchingThread.start();
- }
-
- /**
- * Stops the service.
- * <p>If controller is leader, invoke {@link ControllerLeadershipManager#onBecomingNonLeader()}
- */
- public void stop() {
- synchronized (this) {
- if (_amILeader) {
- onBecomingNonLeader();
- }
- _stopped = true;
- notify();
- }
- try {
- _controllerLeadershipFetchingThread.join();
- } catch (InterruptedException e) {
- LOGGER.error("Caught InterruptedException while waiting for controller leadership fetching thread to die");
- Thread.currentThread().interrupt();
- }
- }
-
- /**
- * Callback on changes in the controller. Should be registered to the controller callback.
- */
- synchronized void onControllerChange() {
- if (_helixControllerManager.isLeader()) {
- if (!_amILeader) {
- _amILeader = true;
- LOGGER.info("Becoming leader");
- onBecomingLeader();
- }
- } else {
- if (_amILeader) {
- _amILeader = false;
- LOGGER.info("Losing leadership");
- onBecomingNonLeader();
- }
- }
- }
-
- private void onBecomingLeader() {
- long startTimeMs = System.currentTimeMillis();
- _controllerMetrics.setValueOfGlobalGauge(ControllerGauge.PINOT_CONTROLLER_LEADER, 1L);
- for (LeadershipChangeSubscriber subscriber : _subscribers.values()) {
- subscriber.onBecomingLeader();
- }
- LOGGER.info("Finished on becoming leader in {}ms", System.currentTimeMillis() - startTimeMs);
- }
-
- private void onBecomingNonLeader() {
- long startTimeMs = System.currentTimeMillis();
- _controllerMetrics.setValueOfGlobalGauge(ControllerGauge.PINOT_CONTROLLER_LEADER, 0L);
- for (LeadershipChangeSubscriber subscriber : _subscribers.values()) {
- subscriber.onBecomingNonLeader();
- }
- LOGGER.info("Finished on becoming non-leader in {}ms", System.currentTimeMillis() - startTimeMs);
- }
-}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/ControllerStarter.java b/pinot-controller/src/main/java/org/apache/pinot/controller/ControllerStarter.java
index 7c04510..cab9783 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/ControllerStarter.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/ControllerStarter.java
@@ -19,7 +19,6 @@
package org.apache.pinot.controller;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
import com.google.common.primitives.Longs;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.yammer.metrics.core.MetricsRegistry;
@@ -42,7 +41,6 @@ import org.apache.helix.HelixManagerFactory;
import org.apache.helix.InstanceType;
import org.apache.helix.SystemPropertyKeys;
import org.apache.helix.api.listeners.ControllerChangeListener;
-import org.apache.helix.examples.MasterSlaveStateModelFactory;
import org.apache.helix.model.MasterSlaveSMD;
import org.apache.helix.task.TaskDriver;
import org.apache.pinot.common.Utils;
@@ -54,6 +52,7 @@ import org.apache.pinot.common.segment.fetcher.SegmentFetcherFactory;
import org.apache.pinot.common.utils.CommonConstants;
import org.apache.pinot.common.utils.NetUtil;
import org.apache.pinot.common.utils.ServiceStatus;
+import org.apache.pinot.common.utils.helix.LeadControllerUtils;
import org.apache.pinot.controller.api.ControllerAdminApiApplication;
import org.apache.pinot.controller.api.access.AccessControlFactory;
import org.apache.pinot.controller.api.events.MetadataEventNotifierFactory;
@@ -61,13 +60,13 @@ import org.apache.pinot.controller.helix.SegmentStatusChecker;
import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.apache.pinot.controller.helix.core.minion.PinotHelixTaskResourceManager;
import org.apache.pinot.controller.helix.core.minion.PinotTaskManager;
-import org.apache.pinot.controller.helix.core.periodictask.ControllerPeriodicTaskScheduler;
import org.apache.pinot.controller.helix.core.realtime.PinotLLCRealtimeSegmentManager;
import org.apache.pinot.controller.helix.core.realtime.PinotRealtimeSegmentManager;
import org.apache.pinot.controller.helix.core.realtime.SegmentCompletionManager;
import org.apache.pinot.controller.helix.core.rebalance.RebalanceSegmentStrategyFactory;
import org.apache.pinot.controller.helix.core.relocation.RealtimeSegmentRelocator;
import org.apache.pinot.controller.helix.core.retention.RetentionManager;
+import org.apache.pinot.controller.helix.core.statemodel.LeadControllerResourceMasterSlaveStateModelFactory;
import org.apache.pinot.controller.helix.core.util.HelixSetupUtils;
import org.apache.pinot.controller.helix.starter.HelixConfig;
import org.apache.pinot.controller.validation.BrokerResourceValidationManager;
@@ -75,6 +74,7 @@ import org.apache.pinot.controller.validation.OfflineSegmentIntervalChecker;
import org.apache.pinot.controller.validation.RealtimeSegmentValidationManager;
import org.apache.pinot.core.crypt.PinotCrypterFactory;
import org.apache.pinot.core.periodictask.PeriodicTask;
+import org.apache.pinot.core.periodictask.PeriodicTaskScheduler;
import org.apache.pinot.filesystem.PinotFSFactory;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.slf4j.Logger;
@@ -106,6 +106,7 @@ public class ControllerStarter {
private final ControllerConf.ControllerMode _controllerMode;
private HelixManager _helixControllerManager;
+ private HelixManager _helixParticipantManager;
// Can only be constructed after resource manager getting started
private OfflineSegmentIntervalChecker _offlineSegmentIntervalChecker;
@@ -115,12 +116,12 @@ public class ControllerStarter {
private RetentionManager _retentionManager;
private SegmentStatusChecker _segmentStatusChecker;
private PinotTaskManager _taskManager;
- private ControllerPeriodicTaskScheduler _controllerPeriodicTaskScheduler;
+ private PeriodicTaskScheduler _periodicTaskScheduler;
private PinotHelixTaskResourceManager _helixTaskResourceManager;
private PinotRealtimeSegmentManager _realtimeSegmentsManager;
private PinotLLCRealtimeSegmentManager _pinotLLCRealtimeSegmentManager;
private SegmentCompletionManager _segmentCompletionManager;
- private ControllerLeadershipManager _controllerLeadershipManager;
+ private LeadControllerManager _leadControllerManager;
private List<ServiceStatus.ServiceStatusCallback> _serviceStatusCallbackList;
public ControllerStarter(ControllerConf conf) {
@@ -132,8 +133,10 @@ public class ControllerStarter {
// Helix related settings.
_helixZkURL = HelixConfig.getAbsoluteZkPathForHelix(_config.getZkStr());
_helixClusterName = _config.getHelixClusterName();
- _helixControllerInstanceId = conf.getControllerHost() + "_" + conf.getControllerPort();
- _helixParticipantInstanceId = CommonConstants.Helix.PREFIX_OF_CONTROLLER_INSTANCE + _helixControllerInstanceId;
+ String host = conf.getControllerHost();
+ int port = Integer.parseInt(conf.getControllerPort());
+ _helixControllerInstanceId = host + "_" + port;
+ _helixParticipantInstanceId = LeadControllerUtils.generateParticipantInstanceId(host, port);
_isUpdateStateModel = _config.isUpdateSegmentStateModel();
_enableBatchMessageMode = _config.getEnableBatchMessageMode();
@@ -188,6 +191,10 @@ public class ControllerStarter {
return _helixControllerManager;
}
+ public LeadControllerManager getLeadControllerManager() {
+ return _leadControllerManager;
+ }
+
public OfflineSegmentIntervalChecker getOfflineSegmentIntervalChecker() {
return _offlineSegmentIntervalChecker;
}
@@ -208,10 +215,6 @@ public class ControllerStarter {
return _taskManager;
}
- public ControllerLeadershipManager getControllerLeadershipManager() {
- return _controllerLeadershipManager;
- }
-
public void start() {
LOGGER.info("Starting Pinot controller in mode: {}.", _controllerMode.name());
Utils.logVersions();
@@ -243,8 +246,8 @@ public class ControllerStarter {
private void setUpHelixController() {
// Register and connect instance as Helix controller.
LOGGER.info("Starting Helix controller");
- _helixControllerManager = HelixSetupUtils.setupHelixController(_helixClusterName, _helixZkURL,
- _helixControllerInstanceId);
+ _helixControllerManager =
+ HelixSetupUtils.setupHelixController(_helixClusterName, _helixZkURL, _helixControllerInstanceId);
// Emit helix controller metrics
_controllerMetrics.addCallbackGauge(CommonConstants.Helix.INSTANCE_CONNECTED_METRIC_NAME,
@@ -255,18 +258,10 @@ public class ControllerStarter {
() -> _controllerMetrics.addMeteredGlobalValue(ControllerMeter.HELIX_ZOOKEEPER_RECONNECTS, 1L));
_serviceStatusCallbackList.add(generateServiceStatusCallback(_helixControllerManager));
-
- LOGGER.info("Initializing controller leadership manager");
- _controllerLeadershipManager = new ControllerLeadershipManager(_helixControllerManager, _controllerMetrics);
}
private void setUpPinotController() {
- // Note: Right now we don't allow Pinot-only controller as ControllerLeadershipManager is setup in Helix controller
- // and Pinot controller relies on it
- // TODO: Remove ControllerLeadershipManager
- Preconditions.checkState(_controllerLeadershipManager != null);
-
- // Set up Pinot cluster in Helix
+ // Set up Pinot cluster in Helix if needed
HelixSetupUtils.setupPinotCluster(_helixClusterName, _helixZkURL, _isUpdateStateModel, _enableBatchMessageMode);
// Start all components
@@ -274,37 +269,41 @@ public class ControllerStarter {
initSegmentFetcherFactory();
initPinotCrypterFactory();
- LOGGER.info("Starting Helix manager as Helix participant");
- HelixManager helixParticipantManager = registerAndConnectAsHelixParticipant();
+ LOGGER.info("Initializing Helix participant manager");
+ _helixParticipantManager = HelixManagerFactory
+ .getZKHelixManager(_helixClusterName, _helixParticipantInstanceId, InstanceType.PARTICIPANT, _helixZkURL);
- LOGGER.info("Starting Pinot Helix resource manager and connecting to Zookeeper");
- _helixResourceManager.start(helixParticipantManager);
+ // LeadControllerManager needs to be initialized before registering as Helix participant.
+ LOGGER.info("Initializing lead controller manager");
+ _leadControllerManager = new LeadControllerManager(_helixParticipantManager, _controllerMetrics);
+
+ LOGGER.info("Registering and connecting Helix participant manager as Helix Participant role");
+ registerAndConnectAsHelixParticipant();
+
+ // LeadControllerManager needs to be started after the connection
+ // as it can check Helix leadership and resource config only after connecting to Helix cluster.
+ LOGGER.info("Starting lead controller manager");
+ _leadControllerManager.start();
- LOGGER.info("Registering controller leadership manager");
- // TODO: when Helix separation is completed, leadership only depends on the master in leadControllerResource, remove
- // ControllerLeadershipManager and this callback.
- _controllerLeadershipManager.start();
- helixParticipantManager.addControllerListener(
- (ControllerChangeListener) changeContext -> _controllerLeadershipManager.onControllerChange());
+ LOGGER.info("Starting Pinot Helix resource manager and connecting to Zookeeper");
+ _helixResourceManager.start(_helixParticipantManager);
LOGGER.info("Starting task resource manager");
- _helixTaskResourceManager = new PinotHelixTaskResourceManager(new TaskDriver(helixParticipantManager));
+ _helixTaskResourceManager = new PinotHelixTaskResourceManager(new TaskDriver(_helixParticipantManager));
// Helix resource manager must be started in order to create PinotLLCRealtimeSegmentManager
LOGGER.info("Starting realtime segment manager");
-
_pinotLLCRealtimeSegmentManager =
- new PinotLLCRealtimeSegmentManager(_helixResourceManager, _config, _controllerMetrics,
- _controllerLeadershipManager);
- // TODO: Need to put this inside HelixResourceManager when ControllerLeadershipManager is removed.
+ new PinotLLCRealtimeSegmentManager(_helixResourceManager, _config, _controllerMetrics, _leadControllerManager);
+ // TODO: Need to put this inside HelixResourceManager when HelixControllerLeadershipManager is removed.
_helixResourceManager.registerPinotLLCRealtimeSegmentManager(_pinotLLCRealtimeSegmentManager);
_segmentCompletionManager =
- new SegmentCompletionManager(helixParticipantManager, _pinotLLCRealtimeSegmentManager, _controllerMetrics,
- _controllerLeadershipManager, _config.getSegmentCommitTimeoutSeconds());
+ new SegmentCompletionManager(_helixParticipantManager, _pinotLLCRealtimeSegmentManager, _controllerMetrics,
+ _leadControllerManager, _config.getSegmentCommitTimeoutSeconds());
if (_config.getHLCTablesAllowed()) {
LOGGER.info("Realtime tables with High Level consumers will be supported");
- _realtimeSegmentsManager = new PinotRealtimeSegmentManager(_helixResourceManager, _controllerLeadershipManager);
+ _realtimeSegmentsManager = new PinotRealtimeSegmentManager(_helixResourceManager, _leadControllerManager);
_realtimeSegmentsManager.start(_controllerMetrics);
} else {
LOGGER.info("Realtime tables with High Level consumers will NOT be supported");
@@ -314,12 +313,14 @@ public class ControllerStarter {
// Setting up periodic tasks
List<PeriodicTask> controllerPeriodicTasks = setupControllerPeriodicTasks();
LOGGER.info("Init controller periodic tasks scheduler");
- _controllerPeriodicTaskScheduler = new ControllerPeriodicTaskScheduler();
- _controllerPeriodicTaskScheduler.init(controllerPeriodicTasks, _controllerLeadershipManager);
+ _periodicTaskScheduler = new PeriodicTaskScheduler();
+ _periodicTaskScheduler.init(controllerPeriodicTasks);
+
+ _periodicTaskScheduler.start();
LOGGER.info("Registering rebalance segments factory");
_helixResourceManager
- .registerRebalanceSegmentStrategyFactory(new RebalanceSegmentStrategyFactory(helixParticipantManager));
+ .registerRebalanceSegmentStrategyFactory(new RebalanceSegmentStrategyFactory(_helixParticipantManager));
String accessControlFactoryClass = _config.getAccessControlFactoryClass();
LOGGER.info("Use class: {} as the AccessControlFactory", accessControlFactoryClass);
@@ -353,7 +354,7 @@ public class ControllerStarter {
bind(_controllerMetrics).to(ControllerMetrics.class);
bind(accessControlFactory).to(AccessControlFactory.class);
bind(metadataEventNotifierFactory).to(MetadataEventNotifierFactory.class);
- bind(_controllerLeadershipManager).to(ControllerLeadershipManager.class);
+ bind(_leadControllerManager).to(LeadControllerManager.class);
}
});
@@ -384,7 +385,7 @@ public class ControllerStarter {
}
});
- _serviceStatusCallbackList.add(generateServiceStatusCallback(helixParticipantManager));
+ _serviceStatusCallbackList.add(generateServiceStatusCallback(_helixParticipantManager));
}
private ServiceStatus.ServiceStatusCallback generateServiceStatusCallback(HelixManager helixManager) {
@@ -452,25 +453,38 @@ public class ControllerStarter {
}
/**
- * Register and connect to Helix cluster as PARTICIPANT role.
+ * Registers, connects to Helix cluster as PARTICIPANT role, and adds listeners.
*/
- private HelixManager registerAndConnectAsHelixParticipant() {
- HelixManager helixManager = HelixManagerFactory
- .getZKHelixManager(_helixClusterName, _helixParticipantInstanceId, InstanceType.PARTICIPANT, _helixZkURL);
-
- // Registers Master-Slave state model to state machine engine, which is for calculating participant assignment in lead controller resource.
- helixManager.getStateMachineEngine()
- .registerStateModelFactory(MasterSlaveSMD.name, new MasterSlaveStateModelFactory());
+ private void registerAndConnectAsHelixParticipant() {
+ // Registers customized Master-Slave state model to state machine engine, which is for calculating participant assignment in lead controller resource.
+ _helixParticipantManager.getStateMachineEngine().registerStateModelFactory(MasterSlaveSMD.name,
+ new LeadControllerResourceMasterSlaveStateModelFactory(_leadControllerManager));
+ // Connects to cluster.
try {
- helixManager.connect();
- return helixManager;
+ _helixParticipantManager.connect();
} catch (Exception e) {
String errorMsg = String.format("Exception when connecting the instance %s as Participant role to Helix.",
_helixParticipantInstanceId);
LOGGER.error(errorMsg, e);
throw new RuntimeException(errorMsg);
}
+
+ LOGGER.info("Registering helix controller listener");
+ // This registration is not needed when the leadControllerResource is enabled.
+ // However, the resource can be disabled sometime while the cluster is in operation, so we keep it here. Plus, it does not add much overhead.
+ // At some point in future when we stop supporting the disabled resource, we will remove this line altogether and the logic that goes with it.
+ _helixParticipantManager.addControllerListener(
+ (ControllerChangeListener) changeContext -> _leadControllerManager.onHelixControllerChange());
+
+ LOGGER.info("Registering resource config listener");
+ try {
+ _helixParticipantManager.addResourceConfigChangeListener(
+ (resourceConfigList, changeContext) -> _leadControllerManager.onResourceConfigChange());
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Error registering resource config listener for " + CommonConstants.Helix.LEAD_CONTROLLER_RESOURCE_NAME, e);
+ }
}
public ControllerConf.ControllerMode getControllerMode() {
@@ -481,24 +495,29 @@ public class ControllerStarter {
protected List<PeriodicTask> setupControllerPeriodicTasks() {
LOGGER.info("Setting up periodic tasks");
List<PeriodicTask> periodicTasks = new ArrayList<>();
- _taskManager = new PinotTaskManager(_helixTaskResourceManager, _helixResourceManager, _config, _controllerMetrics);
+ _taskManager =
+ new PinotTaskManager(_helixTaskResourceManager, _helixResourceManager, _leadControllerManager, _config,
+ _controllerMetrics);
periodicTasks.add(_taskManager);
- _retentionManager = new RetentionManager(_helixResourceManager, _config, _controllerMetrics);
+ _retentionManager =
+ new RetentionManager(_helixResourceManager, _leadControllerManager, _config, _controllerMetrics);
periodicTasks.add(_retentionManager);
_offlineSegmentIntervalChecker =
- new OfflineSegmentIntervalChecker(_config, _helixResourceManager, new ValidationMetrics(_metricsRegistry),
- _controllerMetrics);
+ new OfflineSegmentIntervalChecker(_config, _helixResourceManager, _leadControllerManager,
+ new ValidationMetrics(_metricsRegistry), _controllerMetrics);
periodicTasks.add(_offlineSegmentIntervalChecker);
_realtimeSegmentValidationManager =
- new RealtimeSegmentValidationManager(_config, _helixResourceManager, _pinotLLCRealtimeSegmentManager,
- new ValidationMetrics(_metricsRegistry), _controllerMetrics);
+ new RealtimeSegmentValidationManager(_config, _helixResourceManager, _leadControllerManager,
+ _pinotLLCRealtimeSegmentManager, new ValidationMetrics(_metricsRegistry), _controllerMetrics);
periodicTasks.add(_realtimeSegmentValidationManager);
_brokerResourceValidationManager =
- new BrokerResourceValidationManager(_config, _helixResourceManager, _controllerMetrics);
+ new BrokerResourceValidationManager(_config, _helixResourceManager, _leadControllerManager, _controllerMetrics);
periodicTasks.add(_brokerResourceValidationManager);
- _segmentStatusChecker = new SegmentStatusChecker(_helixResourceManager, _config, _controllerMetrics);
+ _segmentStatusChecker =
+ new SegmentStatusChecker(_helixResourceManager, _leadControllerManager, _config, _controllerMetrics);
periodicTasks.add(_segmentStatusChecker);
- _realtimeSegmentRelocator = new RealtimeSegmentRelocator(_helixResourceManager, _config, _controllerMetrics);
+ _realtimeSegmentRelocator =
+ new RealtimeSegmentRelocator(_helixResourceManager, _leadControllerManager, _config, _controllerMetrics);
periodicTasks.add(_realtimeSegmentRelocator);
return periodicTasks;
@@ -520,15 +539,19 @@ public class ControllerStarter {
}
private void stopHelixController() {
- LOGGER.info("Disconnecting helix zk manager");
+ LOGGER.info("Disconnecting helix controller zk manager");
_helixControllerManager.disconnect();
}
private void stopPinotController() {
try {
- // Stopping ControllerLeadershipManager has to be done before stopping HelixResourceManager.
- LOGGER.info("Stopping controller leadership manager");
- _controllerLeadershipManager.stop();
+ // Stopping periodic tasks has to be done before stopping HelixResourceManager.
+ // Stop controller periodic task.
+ LOGGER.info("Stopping controller periodic tasks");
+ _periodicTaskScheduler.stop();
+
+ LOGGER.info("Stopping lead controller manager");
+ _leadControllerManager.stop();
// Stop PinotLLCSegmentManager before stopping Jersey API. It is possible that stopping Jersey API
// may interrupt the handlers waiting on an I/O.
@@ -548,6 +571,9 @@ public class ControllerStarter {
LOGGER.info("Stopping resource manager");
_helixResourceManager.stop();
+ LOGGER.info("Disconnecting helix participant zk manager");
+ _helixParticipantManager.disconnect();
+
LOGGER.info("Shutting down executor service");
_executorService.shutdownNow();
_executorService.awaitTermination(10L, TimeUnit.SECONDS);
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/LeadControllerManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/LeadControllerManager.java
new file mode 100644
index 0000000..b03268c
--- /dev/null
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/LeadControllerManager.java
@@ -0,0 +1,229 @@
+/**
+ * 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;
+
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.annotation.concurrent.ThreadSafe;
+import org.apache.helix.HelixDataAccessor;
+import org.apache.helix.HelixManager;
+import org.apache.helix.PropertyKey;
+import org.apache.helix.model.LiveInstance;
+import org.apache.helix.model.ResourceConfig;
+import org.apache.pinot.common.config.TableNameBuilder;
+import org.apache.pinot.common.metrics.ControllerMeter;
+import org.apache.pinot.common.metrics.ControllerMetrics;
+import org.apache.pinot.common.utils.CommonConstants.Helix;
+import org.apache.pinot.common.utils.helix.LeadControllerUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class for handling lead controller assignments given the table names. This should be created at controller startup.
+ */
+@ThreadSafe
+public class LeadControllerManager {
+ private static final Logger LOGGER = LoggerFactory.getLogger(LeadControllerManager.class);
+ private static final long CONTROLLER_LEADERSHIP_FETCH_INTERVAL_MS = 60_000L;
+
+ private final Set<Integer> _leadForPartitions;
+ private final String _instanceId;
+ private final HelixManager _helixManager;
+ private final ControllerMetrics _controllerMetrics;
+ private final Thread _controllerLeadershipFetchingThread;
+
+ private volatile boolean _isLeadControllerResourceEnabled = false;
+ private volatile boolean _amIHelixLeader = false;
+ private volatile boolean _isShuttingDown = false;
+
+ public LeadControllerManager(HelixManager helixParticipantManager, ControllerMetrics controllerMetrics) {
+ _helixManager = helixParticipantManager;
+ _controllerMetrics = controllerMetrics;
+ _instanceId = helixParticipantManager.getInstanceName();
+ _leadForPartitions = ConcurrentHashMap.newKeySet();
+
+ // Create a thread to periodically fetch controller leadership as a work-around of Helix callback delay
+ _controllerLeadershipFetchingThread = new Thread("ControllerLeadershipFetchingThread") {
+ @Override
+ public void run() {
+ while (true) {
+ try {
+ synchronized (LeadControllerManager.this) {
+ if (_isShuttingDown) {
+ return;
+ }
+ if (isHelixLeader()) {
+ if (!_amIHelixLeader) {
+ _amIHelixLeader = true;
+ LOGGER.warn("Becoming leader without getting Helix change callback");
+ _controllerMetrics
+ .addMeteredGlobalValue(ControllerMeter.CONTROLLER_LEADERSHIP_CHANGE_WITHOUT_CALLBACK, 1L);
+ }
+ } else {
+ if (_amIHelixLeader) {
+ _amIHelixLeader = false;
+ LOGGER.warn("Losing leadership without getting Helix change callback");
+ _controllerMetrics
+ .addMeteredGlobalValue(ControllerMeter.CONTROLLER_LEADERSHIP_CHANGE_WITHOUT_CALLBACK, 1L);
+ }
+ }
+ LeadControllerManager.this.wait(CONTROLLER_LEADERSHIP_FETCH_INTERVAL_MS);
+ }
+ } catch (Exception e) {
+ // Ignore all exceptions. The thread keeps running until LeadControllerManager.stop() is invoked.
+ LOGGER.error("Caught exception within controller leadership fetching thread", e);
+ }
+ }
+ }
+ };
+ }
+
+ /**
+ * Checks whether the current controller is the leader for the given table. Return true if current controller is the leader for this table.
+ * Otherwise check whether the current controller is helix leader if the resource is disabled.
+ * @param tableName table name with/without table type.
+ */
+ public boolean isLeaderForTable(String tableName) {
+ if (_isLeadControllerResourceEnabled) {
+ String rawTableName = TableNameBuilder.extractRawTableName(tableName);
+ int partitionId = LeadControllerUtils.getPartitionIdForTable(rawTableName);
+ return _leadForPartitions.contains(partitionId);
+ } else {
+ // Checks if it's Helix leader if lead controller resource is disabled.
+ return _amIHelixLeader;
+ }
+ }
+
+ /**
+ * Given a partition name, marks current controller as lead controller for this partition by caching the partition id to current controller.
+ * @param partitionName partition name in lead controller resource, e.g. leadControllerResource_0.
+ */
+ public synchronized void addPartitionLeader(String partitionName) {
+ LOGGER.info("Add Partition: {} to LeadControllerManager", partitionName);
+ int partitionId = LeadControllerUtils.extractPartitionId(partitionName);
+ _leadForPartitions.add(partitionId);
+ }
+
+ /**
+ * Given a partition name, removes current controller as lead controller for this partition by removing the partition id from current controller.
+ * @param partitionName partition name in lead controller resource, e.g. leadControllerResource_0.
+ */
+ public synchronized void removePartitionLeader(String partitionName) {
+ LOGGER.info("Remove Partition: {} from LeadControllerManager", partitionName);
+ int partitionId = LeadControllerUtils.extractPartitionId(partitionName);
+ _leadForPartitions.remove(partitionId);
+ }
+
+ /**
+ * Checks from ZK if the current controller host is Helix cluster leader.
+ */
+ private boolean isHelixLeader() {
+ HelixDataAccessor helixDataAccessor = _helixManager.getHelixDataAccessor();
+ PropertyKey propertyKey = helixDataAccessor.keyBuilder().controllerLeader();
+ LiveInstance liveInstance = helixDataAccessor.getProperty(propertyKey);
+ if (liveInstance == null) {
+ LOGGER.warn("Helix leader ZNode is missing");
+ return false;
+ }
+ String helixLeaderInstanceId = liveInstance.getInstanceName();
+ // The instance name from Helix leader ZNode is without controller prefix.
+ // It is essential to convert to participant id for fair comparison.
+ return _instanceId.equals(Helix.PREFIX_OF_CONTROLLER_INSTANCE + helixLeaderInstanceId);
+ }
+
+ /**
+ * Checks from ZK if resource config of leadControllerResource is enabled.
+ */
+ public boolean isLeadControllerResourceEnabled() {
+ HelixDataAccessor helixDataAccessor = _helixManager.getHelixDataAccessor();
+ PropertyKey propertyKey = helixDataAccessor.keyBuilder().resourceConfig(Helix.LEAD_CONTROLLER_RESOURCE_NAME);
+ ResourceConfig resourceConfig = helixDataAccessor.getProperty(propertyKey);
+ String enableResource = resourceConfig.getSimpleConfig(Helix.LEAD_CONTROLLER_RESOURCE_ENABLED_KEY);
+ return Boolean.parseBoolean(enableResource);
+ }
+
+ /**
+ * Starts the fetching thread to actively fetch helix leadership and resource config of lead controller resource.
+ */
+ public synchronized void start() {
+ _controllerLeadershipFetchingThread.start();
+ }
+
+ /**
+ * Marks the cached indices invalid and isShuttingDown to be true.
+ * Adding the synchronized block here and in the following callback methods
+ * to make sure that {@link HelixManager} won't be closed when the callback changes happened.
+ */
+ public void stop() {
+ synchronized (this) {
+ _isShuttingDown = true;
+ notify();
+ }
+ _leadForPartitions.clear();
+
+ try {
+ _controllerLeadershipFetchingThread.join();
+ } catch (InterruptedException e) {
+ LOGGER.error("Caught InterruptedException while waiting for controller leadership fetching thread to die");
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ /**
+ * Callback on changes in the controller. Should be registered to the controller callback. This callback is not needed when the resource is enabled.
+ * However, the resource can be disabled sometime while the cluster is in operation, so we keep it here. Plus, it does not add much overhead.
+ * At some point in future when we stop supporting the disabled resource, we will remove this line altogether and the logic that goes with it.
+ */
+ synchronized void onHelixControllerChange() {
+ if (_isShuttingDown) {
+ return;
+ }
+ if (isHelixLeader()) {
+ if (!_amIHelixLeader) {
+ _amIHelixLeader = true;
+ LOGGER.info("Became Helix leader");
+ } else {
+ LOGGER.info("Already Helix leader. Duplicate notification");
+ }
+ } else {
+ if (_amIHelixLeader) {
+ _amIHelixLeader = false;
+ LOGGER.info("Lost Helix leadership");
+ } else {
+ LOGGER.info("Already not Helix leader. Duplicate notification");
+ }
+ }
+ }
+
+ /**
+ * Callback on changes in resource config.
+ */
+ synchronized void onResourceConfigChange() {
+ if (_isShuttingDown) {
+ return;
+ }
+ if (isLeadControllerResourceEnabled()) {
+ LOGGER.info("Lead controller resource is enabled.");
+ _isLeadControllerResourceEnabled = true;
+ } else {
+ LOGGER.info("Lead controller resource is disabled.");
+ _isLeadControllerResourceEnabled = false;
+ }
+ }
+}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentUploadRestletResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentUploadRestletResource.java
index 6f74f8a..b32909c 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentUploadRestletResource.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentUploadRestletResource.java
@@ -66,7 +66,7 @@ import org.apache.pinot.common.utils.JsonUtils;
import org.apache.pinot.common.utils.URIUtils;
import org.apache.pinot.common.utils.helix.HelixHelper;
import org.apache.pinot.controller.ControllerConf;
-import org.apache.pinot.controller.ControllerLeadershipManager;
+import org.apache.pinot.controller.LeadControllerManager;
import org.apache.pinot.controller.api.access.AccessControl;
import org.apache.pinot.controller.api.access.AccessControlFactory;
import org.apache.pinot.controller.api.upload.SegmentValidator;
@@ -113,7 +113,7 @@ public class PinotSegmentUploadRestletResource {
AccessControlFactory _accessControlFactory;
@Inject
- ControllerLeadershipManager _controllerLeadershipManager;
+ LeadControllerManager _leadControllerManager;
@GET
@Produces(MediaType.APPLICATION_JSON)
@@ -321,7 +321,7 @@ public class PinotSegmentUploadRestletResource {
// Validate segment
SegmentValidatorResponse segmentValidatorResponse =
new SegmentValidator(_pinotHelixResourceManager, _controllerConf, _executor, _connectionManager,
- _controllerMetrics, _controllerLeadershipManager)
+ _controllerMetrics, _leadControllerManager.isLeaderForTable(offlineTableName))
.validateSegment(rawTableName, segmentMetadata, tempSegmentDir);
// Zk operations
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/upload/SegmentValidator.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/upload/SegmentValidator.java
index bea9f74..47eb90b 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/upload/SegmentValidator.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/upload/SegmentValidator.java
@@ -34,7 +34,6 @@ import org.apache.pinot.common.metrics.ControllerMetrics;
import org.apache.pinot.common.segment.SegmentMetadata;
import org.apache.pinot.common.utils.time.TimeUtils;
import org.apache.pinot.controller.ControllerConf;
-import org.apache.pinot.controller.ControllerLeadershipManager;
import org.apache.pinot.controller.api.resources.ControllerApplicationException;
import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.apache.pinot.controller.util.TableSizeReader;
@@ -55,17 +54,17 @@ public class SegmentValidator {
private final Executor _executor;
private final HttpConnectionManager _connectionManager;
private final ControllerMetrics _controllerMetrics;
- private final ControllerLeadershipManager _controllerLeadershipManager;
+ private final boolean _isLeaderForTable;
public SegmentValidator(PinotHelixResourceManager pinotHelixResourceManager, ControllerConf controllerConf,
Executor executor, HttpConnectionManager connectionManager, ControllerMetrics controllerMetrics,
- ControllerLeadershipManager controllerLeadershipManager) {
+ boolean isLeaderForTable) {
_pinotHelixResourceManager = pinotHelixResourceManager;
_controllerConf = controllerConf;
_executor = executor;
_connectionManager = connectionManager;
_controllerMetrics = controllerMetrics;
- _controllerLeadershipManager = controllerLeadershipManager;
+ _isLeaderForTable = isLeaderForTable;
}
public SegmentValidatorResponse validateSegment(String rawTableName, SegmentMetadata segmentMetadata,
@@ -134,8 +133,7 @@ public class SegmentValidator {
TableSizeReader tableSizeReader =
new TableSizeReader(_executor, _connectionManager, _controllerMetrics, _pinotHelixResourceManager);
StorageQuotaChecker quotaChecker =
- new StorageQuotaChecker(offlineTableConfig, tableSizeReader, _controllerMetrics, _pinotHelixResourceManager,
- _controllerLeadershipManager);
+ new StorageQuotaChecker(offlineTableConfig, tableSizeReader, _controllerMetrics, _isLeaderForTable);
return quotaChecker.isSegmentStorageWithinQuota(segmentFile, metadata.getName(),
_controllerConf.getServerAdminRequestTimeoutSeconds() * 1000);
}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/SegmentStatusChecker.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/SegmentStatusChecker.java
index f40e2b3..9261420 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/SegmentStatusChecker.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/SegmentStatusChecker.java
@@ -29,6 +29,7 @@ import org.apache.pinot.common.metrics.ControllerGauge;
import org.apache.pinot.common.metrics.ControllerMetrics;
import org.apache.pinot.common.utils.CommonConstants.Helix.TableType;
import org.apache.pinot.controller.ControllerConf;
+import org.apache.pinot.controller.LeadControllerManager;
import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.apache.pinot.controller.helix.core.periodictask.ControllerPeriodicTask;
import org.slf4j.Logger;
@@ -56,10 +57,11 @@ public class SegmentStatusChecker extends ControllerPeriodicTask<SegmentStatusCh
* @param pinotHelixResourceManager The resource checker used to interact with Helix
* @param config The controller configuration object
*/
- public SegmentStatusChecker(PinotHelixResourceManager pinotHelixResourceManager, ControllerConf config,
- ControllerMetrics controllerMetrics) {
+ public SegmentStatusChecker(PinotHelixResourceManager pinotHelixResourceManager,
+ LeadControllerManager leadControllerManager, ControllerConf config, ControllerMetrics controllerMetrics) {
super("SegmentStatusChecker", config.getStatusCheckerFrequencyInSeconds(),
- config.getStatusCheckerInitialDelayInSeconds(), pinotHelixResourceManager, controllerMetrics);
+ config.getStatusCheckerInitialDelayInSeconds(), pinotHelixResourceManager, leadControllerManager,
+ controllerMetrics);
_waitForPushTimeSeconds = config.getStatusCheckerWaitForPushTimeInSeconds();
}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java
index 6714044..d4579de 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java
@@ -161,7 +161,10 @@ public class PinotHelixResourceManager {
}
/**
- * Create Helix cluster if needed, and then start a Pinot controller instance.
+ * Starts a Pinot controller instance.
+ * Note: Helix instance type should be explicitly set to PARTICIPANT ONLY in ControllerStarter.
+ * Other places like PerfBenchmarkDriver which directly call {@link PinotHelixResourceManager} should NOT register as PARTICIPANT,
+ * which would be put to lead controller resource and mess up the leadership assignment. Those places should use SPECTATOR other than PARTICIPANT.
*/
public synchronized void start(HelixManager helixZkManager) {
_helixZkManager = helixZkManager;
@@ -191,7 +194,6 @@ public class PinotHelixResourceManager {
*/
public synchronized void stop() {
_segmentDeletionManager.stop();
- _helixZkManager.disconnect();
}
/**
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/PinotTaskManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/PinotTaskManager.java
index 7a09c00..649b966 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/PinotTaskManager.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/minion/PinotTaskManager.java
@@ -29,6 +29,7 @@ import org.apache.pinot.common.config.TableTaskConfig;
import org.apache.pinot.common.metrics.ControllerMeter;
import org.apache.pinot.common.metrics.ControllerMetrics;
import org.apache.pinot.controller.ControllerConf;
+import org.apache.pinot.controller.LeadControllerManager;
import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.apache.pinot.controller.helix.core.minion.generator.PinotTaskGenerator;
import org.apache.pinot.controller.helix.core.minion.generator.TaskGeneratorRegistry;
@@ -51,10 +52,11 @@ public class PinotTaskManager extends ControllerPeriodicTask<Void> {
private final TaskGeneratorRegistry _taskGeneratorRegistry;
public PinotTaskManager(PinotHelixTaskResourceManager helixTaskResourceManager,
- PinotHelixResourceManager helixResourceManager, ControllerConf controllerConf,
- ControllerMetrics controllerMetrics) {
+ PinotHelixResourceManager helixResourceManager, LeadControllerManager leadControllerManager,
+ ControllerConf controllerConf, ControllerMetrics controllerMetrics) {
super("PinotTaskManager", controllerConf.getTaskManagerFrequencyInSeconds(),
- controllerConf.getPinotTaskManagerInitialDelaySeconds(), helixResourceManager, controllerMetrics);
+ controllerConf.getPinotTaskManagerInitialDelaySeconds(), helixResourceManager, leadControllerManager,
+ controllerMetrics);
_helixTaskResourceManager = helixTaskResourceManager;
_clusterInfoProvider = new ClusterInfoProvider(helixResourceManager, helixTaskResourceManager, controllerConf);
_taskGeneratorRegistry = new TaskGeneratorRegistry(_clusterInfoProvider);
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/periodictask/ControllerPeriodicTask.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/periodictask/ControllerPeriodicTask.java
index 7e764f3..a782385 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/periodictask/ControllerPeriodicTask.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/periodictask/ControllerPeriodicTask.java
@@ -23,6 +23,7 @@ import javax.annotation.concurrent.ThreadSafe;
import org.apache.pinot.common.metrics.ControllerGauge;
import org.apache.pinot.common.metrics.ControllerMeter;
import org.apache.pinot.common.metrics.ControllerMetrics;
+import org.apache.pinot.controller.LeadControllerManager;
import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.apache.pinot.core.periodictask.BasePeriodicTask;
import org.slf4j.Logger;
@@ -40,12 +41,15 @@ public abstract class ControllerPeriodicTask<C> extends BasePeriodicTask {
private static final Logger LOGGER = LoggerFactory.getLogger(ControllerPeriodicTask.class);
protected final PinotHelixResourceManager _pinotHelixResourceManager;
+ protected final LeadControllerManager _leadControllerManager;
protected final ControllerMetrics _controllerMetrics;
public ControllerPeriodicTask(String taskName, long runFrequencyInSeconds, long initialDelayInSeconds,
- PinotHelixResourceManager pinotHelixResourceManager, ControllerMetrics controllerMetrics) {
+ PinotHelixResourceManager pinotHelixResourceManager, LeadControllerManager leadControllerManager,
+ ControllerMetrics controllerMetrics) {
super(taskName, runFrequencyInSeconds, initialDelayInSeconds);
_pinotHelixResourceManager = pinotHelixResourceManager;
+ _leadControllerManager = leadControllerManager;
_controllerMetrics = controllerMetrics;
}
@@ -75,6 +79,10 @@ public abstract class ControllerPeriodicTask<C> extends BasePeriodicTask {
LOGGER.info("Task: {} is stopped, early terminate the task", _taskName);
break;
}
+ // Check if current controller is the leader for this table.
+ if (!_leadControllerManager.isLeaderForTable(tableNameWithType)) {
+ continue;
+ }
try {
processTable(tableNameWithType, context);
} catch (Exception e) {
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/periodictask/ControllerPeriodicTaskScheduler.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/periodictask/ControllerPeriodicTaskScheduler.java
deleted file mode 100644
index 8a2b63c..0000000
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/periodictask/ControllerPeriodicTaskScheduler.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/**
- * 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.periodictask;
-
-import java.util.List;
-import org.apache.pinot.controller.ControllerLeadershipManager;
-import org.apache.pinot.controller.LeadershipChangeSubscriber;
-import org.apache.pinot.core.periodictask.PeriodicTask;
-import org.apache.pinot.core.periodictask.PeriodicTaskScheduler;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-
-/**
- * A {@link PeriodicTaskScheduler} for scheduling {@link ControllerPeriodicTask} according to controller leadership changes.
- * Any controllerPeriodicTasks provided during initialization, will run only on leadership, and stop when leadership lost
- */
-public class ControllerPeriodicTaskScheduler extends PeriodicTaskScheduler implements LeadershipChangeSubscriber {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(ControllerPeriodicTaskScheduler.class);
-
- /**
- * Initialize the {@link ControllerPeriodicTaskScheduler} with the list of {@link ControllerPeriodicTask} created at startup
- * This is called only once during controller startup
- * @param controllerPeriodicTasks
- * @param controllerLeadershipManager
- */
- public void init(List<PeriodicTask> controllerPeriodicTasks, ControllerLeadershipManager controllerLeadershipManager) {
- super.init(controllerPeriodicTasks);
- controllerLeadershipManager.subscribe(ControllerPeriodicTaskScheduler.class.getName(), this);
- }
-
- @Override
- public void onBecomingLeader() {
- LOGGER.info("Received callback for controller leadership gain. Starting PeriodicTaskScheduler.");
- start();
- }
-
- @Override
- public void onBecomingNonLeader() {
- LOGGER.info("Received callback for controller leadership loss. Stopping PeriodicTaskScheduler.");
- stop();
- }
-}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManager.java
index 22239f3..f515c66 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManager.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManager.java
@@ -66,7 +66,7 @@ import org.apache.pinot.common.utils.URIUtils;
import org.apache.pinot.common.utils.helix.HelixHelper;
import org.apache.pinot.common.utils.retry.RetryPolicies;
import org.apache.pinot.controller.ControllerConf;
-import org.apache.pinot.controller.ControllerLeadershipManager;
+import org.apache.pinot.controller.LeadControllerManager;
import org.apache.pinot.controller.api.events.MetadataEventNotifierFactory;
import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.apache.pinot.controller.helix.core.PinotHelixSegmentOnlineOfflineStateModelGenerator;
@@ -123,13 +123,13 @@ public class PinotLLCRealtimeSegmentManager {
private final TableConfigCache _tableConfigCache;
private final StreamPartitionAssignmentGenerator _streamPartitionAssignmentGenerator;
private final FlushThresholdUpdateManager _flushThresholdUpdateManager;
- private final ControllerLeadershipManager _controllerLeadershipManager;
+ private final LeadControllerManager _leadControllerManager;
private volatile boolean _isStopping = false;
private AtomicInteger _numCompletingSegments = new AtomicInteger(0);
public PinotLLCRealtimeSegmentManager(PinotHelixResourceManager helixResourceManager, ControllerConf controllerConf,
- ControllerMetrics controllerMetrics, ControllerLeadershipManager controllerLeadershipManager) {
+ ControllerMetrics controllerMetrics, LeadControllerManager leadControllerManager) {
_helixAdmin = helixResourceManager.getHelixAdmin();
_helixManager = helixResourceManager.getHelixZkManager();
_propertyStore = helixResourceManager.getPropertyStore();
@@ -145,7 +145,7 @@ public class PinotLLCRealtimeSegmentManager {
_tableConfigCache = new TableConfigCache(_propertyStore);
_streamPartitionAssignmentGenerator = new StreamPartitionAssignmentGenerator(_helixManager);
_flushThresholdUpdateManager = new FlushThresholdUpdateManager();
- _controllerLeadershipManager = controllerLeadershipManager;
+ _leadControllerManager = leadControllerManager;
}
public boolean getIsSplitCommitEnabled() {
@@ -181,8 +181,8 @@ public class PinotLLCRealtimeSegmentManager {
LOGGER.info("Wait completed: Number of completing segments = {}", _numCompletingSegments.get());
}
- protected boolean isLeader() {
- return _controllerLeadershipManager.isLeader();
+ protected boolean isLeader(String tableName) {
+ return _leadControllerManager.isLeaderForTable(tableName);
}
protected boolean isConnected() {
@@ -339,10 +339,10 @@ public class PinotLLCRealtimeSegmentManager {
URI uriToMoveTo = URIUtils.getUri(_controllerConf.getDataDir(), tableName, URIUtils.encode(segmentName));
PinotFS pinotFS = PinotFSFactory.create(tableDirURI.getScheme());
- if (!isConnected() || !isLeader()) {
+ if (!isConnected() || !isLeader(tableName)) {
// We can potentially log a different value than what we saw ....
LOGGER.warn("Lost leadership while committing segment file {}, {} for table {}: isLeader={}, isConnected={}",
- segmentName, segmentLocation, tableName, isLeader(), isConnected());
+ segmentName, segmentLocation, tableName, isLeader(tableName), isConnected());
_controllerMetrics.addMeteredGlobalValue(ControllerMeter.CONTROLLER_NOT_LEADER, 1L);
return false;
}
@@ -537,17 +537,17 @@ public class PinotLLCRealtimeSegmentManager {
final String oldZnodePath =
ZKMetadataProvider.constructPropertyStorePathForSegment(realtimeTableName, committingSegmentNameStr);
- if (!isConnected() || !isLeader()) {
+ if (!isConnected() || !isLeader(realtimeTableName)) {
// We can potentially log a different value than what we saw ....
LOGGER.warn("Lost leadership while committing segment metadata for {} for table {}: isLeader={}, isConnected={}",
- committingSegmentNameStr, realtimeTableName, isLeader(), isConnected());
+ committingSegmentNameStr, realtimeTableName, isLeader(realtimeTableName), isConnected());
_controllerMetrics.addMeteredGlobalValue(ControllerMeter.CONTROLLER_NOT_LEADER, 1L);
return false;
}
boolean success = writeSegmentToPropertyStore(oldZnodePath, oldZnRecord, realtimeTableName, stat.getVersion());
if (!success) {
LOGGER.warn("Fail to write old segment to property store for {} for table {}: isLeader={}, isConnected={}",
- committingSegmentNameStr, realtimeTableName, isLeader(), isConnected());
+ committingSegmentNameStr, realtimeTableName, isLeader(realtimeTableName), isConnected());
}
return success;
}
@@ -596,11 +596,11 @@ public class PinotLLCRealtimeSegmentManager {
ZKMetadataProvider.constructPropertyStorePathForSegment(realtimeTableName, newSegmentNameStr);
if (!isNewTableSetup) {
- if (!isLeader() || !isConnected()) {
+ if (!isLeader(realtimeTableName) || !isConnected()) {
// We can potentially log a different value than what we saw ....
LOGGER.warn(
"Lost leadership while committing new segment metadata for {} for table {}: isLeader={}, isConnected={}",
- newSegmentNameStr, rawTableName, isLeader(), isConnected());
+ newSegmentNameStr, rawTableName, isLeader(realtimeTableName), isConnected());
_controllerMetrics.addMeteredGlobalValue(ControllerMeter.CONTROLLER_NOT_LEADER, 1L);
return false;
}
@@ -609,7 +609,7 @@ public class PinotLLCRealtimeSegmentManager {
boolean success = writeSegmentToPropertyStore(newZnodePath, newZnRecord, realtimeTableName);
if (!success) {
LOGGER.warn("Fail to write new segment to property store for {} for table {}: isLeader={}, isConnected={}",
- newSegmentNameStr, rawTableName, isLeader(), isConnected());
+ newSegmentNameStr, rawTableName, isLeader(realtimeTableName), isConnected());
}
return success;
}
@@ -1345,8 +1345,4 @@ public class PinotLLCRealtimeSegmentManager {
return idealState;
}
-
- public ControllerLeadershipManager getControllerLeadershipManager() {
- return _controllerLeadershipManager;
- }
}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotRealtimeSegmentManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotRealtimeSegmentManager.java
index 32642dd..0de057a 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotRealtimeSegmentManager.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/PinotRealtimeSegmentManager.java
@@ -52,8 +52,7 @@ import org.apache.pinot.common.utils.HLCSegmentName;
import org.apache.pinot.common.utils.SegmentName;
import org.apache.pinot.common.utils.helix.HelixHelper;
import org.apache.pinot.common.utils.retry.RetryPolicies;
-import org.apache.pinot.controller.ControllerLeadershipManager;
-import org.apache.pinot.controller.LeadershipChangeSubscriber;
+import org.apache.pinot.controller.LeadControllerManager;
import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.apache.pinot.controller.helix.core.PinotTableIdealStateBuilder;
import org.apache.pinot.core.query.utils.Pair;
@@ -66,7 +65,7 @@ import org.slf4j.LoggerFactory;
/**
* Realtime segment manager, which assigns realtime segments to server instances so that they can consume from the stream.
*/
-public class PinotRealtimeSegmentManager implements HelixPropertyListener, IZkChildListener, IZkDataListener, LeadershipChangeSubscriber {
+public class PinotRealtimeSegmentManager implements HelixPropertyListener, IZkChildListener, IZkDataListener {
private static final Logger LOGGER = LoggerFactory.getLogger(PinotRealtimeSegmentManager.class);
private static final String TABLE_CONFIG = "/CONFIGS/TABLE";
private static final String SEGMENTS_PATH = "/SEGMENTS";
@@ -80,12 +79,12 @@ public class PinotRealtimeSegmentManager implements HelixPropertyListener, IZkCh
private final PinotHelixResourceManager _pinotHelixResourceManager;
private ZkClient _zkClient;
private ControllerMetrics _controllerMetrics;
- private final ControllerLeadershipManager _controllerLeadershipManager;
+ private final LeadControllerManager _leadControllerManager;
public PinotRealtimeSegmentManager(PinotHelixResourceManager pinotManager,
- ControllerLeadershipManager controllerLeadershipManager) {
+ LeadControllerManager leadControllerManager) {
_pinotHelixResourceManager = pinotManager;
- _controllerLeadershipManager = controllerLeadershipManager;
+ _leadControllerManager = leadControllerManager;
String clusterName = _pinotHelixResourceManager.getHelixClusterName();
_propertyStorePath = PropertyPathConfig.getPath(PropertyType.PROPERTYSTORE, clusterName);
_tableConfigPath = _propertyStorePath + TABLE_CONFIG;
@@ -104,9 +103,6 @@ public class PinotRealtimeSegmentManager implements HelixPropertyListener, IZkCh
_zkClient.subscribeChildChanges(_tableConfigPath, this);
_zkClient.subscribeDataChanges(_tableConfigPath, this);
- // Subscribe to leadership changes
- _controllerLeadershipManager.subscribe(PinotLLCRealtimeSegmentManager.class.getName(), this);
-
// Setup change listeners for already existing tables, if any.
processPropertyStoreChange(_tableConfigPath);
}
@@ -128,6 +124,11 @@ public class PinotRealtimeSegmentManager implements HelixPropertyListener, IZkCh
continue;
}
+ // Skip if the current controller isn't the leader of this table
+ if (!_leadControllerManager.isLeaderForTable(realtimeTableName)) {
+ continue;
+ }
+
StreamConfig metadata = new StreamConfig(tableConfig.getIndexingConfig().getStreamConfigs());
if (metadata.hasHighLevelConsumerType()) {
idealStateMap.put(realtimeTableName, _pinotHelixResourceManager.getHelixAdmin()
@@ -273,10 +274,6 @@ public class PinotRealtimeSegmentManager implements HelixPropertyListener, IZkCh
}
}
- private boolean isLeader() {
- return _controllerLeadershipManager.isLeader();
- }
-
@Override
public synchronized void onDataChange(String path) {
LOGGER.info("PinotRealtimeSegmentManager.onDataChange: {}", path);
@@ -300,13 +297,9 @@ public class PinotRealtimeSegmentManager implements HelixPropertyListener, IZkCh
LOGGER.info("Processing change notification for path: {}", path);
refreshWatchers(path);
- if (isLeader()) {
- if (path.matches(REALTIME_SEGMENT_PROPERTY_STORE_PATH_PATTERN) || path
- .matches(REALTIME_TABLE_CONFIG_PROPERTY_STORE_PATH_PATTERN) || path.equals(CONTROLLER_LEADER_CHANGE)) {
- assignRealtimeSegmentsToServerInstancesIfNecessary();
- }
- } else {
- LOGGER.info("Not the leader of this cluster, ignoring realtime segment property store change.");
+ if (path.matches(REALTIME_SEGMENT_PROPERTY_STORE_PATH_PATTERN) || path
+ .matches(REALTIME_TABLE_CONFIG_PROPERTY_STORE_PATH_PATTERN) || path.equals(CONTROLLER_LEADER_CHANGE)) {
+ assignRealtimeSegmentsToServerInstancesIfNecessary();
}
} catch (Exception e) {
LOGGER.error("Caught exception while processing change for path {}", path, e);
@@ -415,14 +408,4 @@ public class PinotRealtimeSegmentManager implements HelixPropertyListener, IZkCh
LOGGER.info("PinotRealtimeSegmentManager.handleDataDeleted: {}", dataPath);
processPropertyStoreChange(dataPath);
}
-
- @Override
- public void onBecomingLeader() {
- processPropertyStoreChange(CONTROLLER_LEADER_CHANGE);
- }
-
- @Override
- public void onBecomingNonLeader() {
- processPropertyStoreChange(CONTROLLER_LEADER_CHANGE);
- }
}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/SegmentCompletionManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/SegmentCompletionManager.java
index a4da6b4..8935c6b 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/SegmentCompletionManager.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/realtime/SegmentCompletionManager.java
@@ -19,7 +19,6 @@
package org.apache.pinot.controller.helix.core.realtime;
import com.google.common.annotations.VisibleForTesting;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -29,7 +28,6 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.helix.HelixManager;
-import org.apache.helix.ZNRecord;
import org.apache.pinot.common.config.TableNameBuilder;
import org.apache.pinot.common.metadata.segment.LLCRealtimeSegmentZKMetadata;
import org.apache.pinot.common.metrics.ControllerMeter;
@@ -37,12 +35,13 @@ import org.apache.pinot.common.metrics.ControllerMetrics;
import org.apache.pinot.common.protocols.SegmentCompletionProtocol;
import org.apache.pinot.common.utils.CommonConstants;
import org.apache.pinot.common.utils.LLCSegmentName;
-import org.apache.pinot.controller.ControllerConf;
-import org.apache.pinot.controller.ControllerLeadershipManager;
+import org.apache.pinot.controller.LeadControllerManager;
import org.apache.pinot.controller.helix.core.realtime.segment.CommittingSegmentDescriptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import static org.apache.pinot.common.utils.SegmentName.SEPARATOR;
+
/**
* This is a singleton class in the controller that drives the state machines for segments that are in the
@@ -73,7 +72,7 @@ public class SegmentCompletionManager {
private final Map<String, Long> _commitTimeMap = new ConcurrentHashMap<>();
private final PinotLLCRealtimeSegmentManager _segmentManager;
private final ControllerMetrics _controllerMetrics;
- private final ControllerLeadershipManager _controllerLeadershipManager;
+ private final LeadControllerManager _leadControllerManager;
private final Lock[] _fsmLocks;
private static final int NUM_FSM_LOCKS = 20;
@@ -87,12 +86,12 @@ public class SegmentCompletionManager {
// TODO keep some history of past committed segments so that we can avoid looking up PROPERTYSTORE if some server comes in late.
public SegmentCompletionManager(HelixManager helixManager, PinotLLCRealtimeSegmentManager segmentManager,
- ControllerMetrics controllerMetrics, ControllerLeadershipManager controllerLeadershipManager,
+ ControllerMetrics controllerMetrics, LeadControllerManager leadControllerManager,
int segmentCommitTimeoutSeconds) {
_helixManager = helixManager;
_segmentManager = segmentManager;
_controllerMetrics = controllerMetrics;
- _controllerLeadershipManager = controllerLeadershipManager;
+ _leadControllerManager = leadControllerManager;
SegmentCompletionProtocol
.setMaxSegmentCommitTimeMs(TimeUnit.MILLISECONDS.convert(segmentCommitTimeoutSeconds, TimeUnit.SECONDS));
_fsmLocks = new Lock[NUM_FSM_LOCKS];
@@ -163,11 +162,12 @@ public class SegmentCompletionManager {
* that it currently has (i.e. next offset that it will consume, if it continues to consume).
*/
public SegmentCompletionProtocol.Response segmentConsumed(SegmentCompletionProtocol.Request.Params reqParams) {
- if (!isLeader() || !_helixManager.isConnected()) {
+ final String segmentNameStr = reqParams.getSegmentName();
+ final String tableName = segmentNameStr.split(SEPARATOR)[0];
+ if (!isLeader(tableName) || !_helixManager.isConnected()) {
_controllerMetrics.addMeteredGlobalValue(ControllerMeter.CONTROLLER_NOT_LEADER, 1L);
return SegmentCompletionProtocol.RESP_NOT_LEADER;
}
- final String segmentNameStr = reqParams.getSegmentName();
final String instanceId = reqParams.getInstanceId();
final String stopReason = reqParams.getReason();
final long offset = reqParams.getOffset();
@@ -201,11 +201,12 @@ public class SegmentCompletionManager {
*/
public SegmentCompletionProtocol.Response segmentCommitStart(
final SegmentCompletionProtocol.Request.Params reqParams) {
- if (!isLeader() || !_helixManager.isConnected()) {
+ final String segmentNameStr = reqParams.getSegmentName();
+ final String tableName = segmentNameStr.split(SEPARATOR)[0];
+ if (!isLeader(tableName) || !_helixManager.isConnected()) {
_controllerMetrics.addMeteredGlobalValue(ControllerMeter.CONTROLLER_NOT_LEADER, 1L);
return SegmentCompletionProtocol.RESP_NOT_LEADER;
}
- final String segmentNameStr = reqParams.getSegmentName();
final String instanceId = reqParams.getInstanceId();
final long offset = reqParams.getOffset();
LLCSegmentName segmentName = new LLCSegmentName(segmentNameStr);
@@ -225,11 +226,12 @@ public class SegmentCompletionManager {
}
public SegmentCompletionProtocol.Response extendBuildTime(final SegmentCompletionProtocol.Request.Params reqParams) {
- if (!isLeader() || !_helixManager.isConnected()) {
+ final String segmentNameStr = reqParams.getSegmentName();
+ final String tableName = segmentNameStr.split(SEPARATOR)[0];
+ if (!isLeader(tableName) || !_helixManager.isConnected()) {
_controllerMetrics.addMeteredGlobalValue(ControllerMeter.CONTROLLER_NOT_LEADER, 1L);
return SegmentCompletionProtocol.RESP_NOT_LEADER;
}
- final String segmentNameStr = reqParams.getSegmentName();
final String instanceId = reqParams.getInstanceId();
final long offset = reqParams.getOffset();
final int extTimeSec = reqParams.getExtraTimeSec();
@@ -256,11 +258,12 @@ public class SegmentCompletionManager {
*/
public SegmentCompletionProtocol.Response segmentStoppedConsuming(
SegmentCompletionProtocol.Request.Params reqParams) {
- if (!isLeader() || !_helixManager.isConnected()) {
+ final String segmentNameStr = reqParams.getSegmentName();
+ final String tableName = segmentNameStr.split(SEPARATOR)[0];
+ if (!isLeader(tableName) || !_helixManager.isConnected()) {
_controllerMetrics.addMeteredGlobalValue(ControllerMeter.CONTROLLER_NOT_LEADER, 1L);
return SegmentCompletionProtocol.RESP_NOT_LEADER;
}
- final String segmentNameStr = reqParams.getSegmentName();
final String instanceId = reqParams.getInstanceId();
final long offset = reqParams.getOffset();
final String reason = reqParams.getReason();
@@ -292,11 +295,12 @@ public class SegmentCompletionManager {
*/
public SegmentCompletionProtocol.Response segmentCommitEnd(SegmentCompletionProtocol.Request.Params reqParams,
boolean success, boolean isSplitCommit, CommittingSegmentDescriptor committingSegmentDescriptor) {
- if (!isLeader() || !_helixManager.isConnected()) {
+ final String segmentNameStr = reqParams.getSegmentName();
+ final String tableName = segmentNameStr.split(SEPARATOR)[0];
+ if (!isLeader(tableName) || !_helixManager.isConnected()) {
_controllerMetrics.addMeteredGlobalValue(ControllerMeter.CONTROLLER_NOT_LEADER, 1L);
return SegmentCompletionProtocol.RESP_NOT_LEADER;
}
- final String segmentNameStr = reqParams.getSegmentName();
LLCSegmentName segmentName = new LLCSegmentName(segmentNameStr);
SegmentCompletionFSM fsm = null;
SegmentCompletionProtocol.Response response = SegmentCompletionProtocol.RESP_FAILED;
@@ -352,6 +356,7 @@ public class SegmentCompletionManager {
State _state = State.HOLDING; // Typically start off in HOLDING state.
final long _startTimeMs;
private final LLCSegmentName _segmentName;
+ private final String _realtimeTableName;
private final int _numReplicas;
private final Set<String> _excludedServerStateMap;
private final Map<String, Long> _commitStateMap;
@@ -394,6 +399,7 @@ public class SegmentCompletionManager {
private SegmentCompletionFSM(PinotLLCRealtimeSegmentManager segmentManager,
SegmentCompletionManager segmentCompletionManager, LLCSegmentName segmentName, int numReplicas) {
_segmentName = segmentName;
+ _realtimeTableName = _segmentName.getTableName();
_numReplicas = numReplicas;
_segmentManager = segmentManager;
_commitStateMap = new HashMap<>(_numReplicas);
@@ -403,8 +409,8 @@ public class SegmentCompletionManager {
_maxTimeToPickWinnerMs = _startTimeMs + MAX_TIME_TO_PICK_WINNER_MS;
_maxTimeToNotifyWinnerMs = _startTimeMs + MAX_TIME_TO_NOTIFY_WINNER_MS;
long initialCommitTimeMs =
- MAX_TIME_TO_NOTIFY_WINNER_MS + _segmentManager.getCommitTimeoutMS(_segmentName.getTableName());
- Long savedCommitTime = _segmentCompletionManager._commitTimeMap.get(segmentName.getTableName());
+ MAX_TIME_TO_NOTIFY_WINNER_MS + _segmentManager.getCommitTimeoutMS(_realtimeTableName);
+ Long savedCommitTime = _segmentCompletionManager._commitTimeMap.get(_realtimeTableName);
if (savedCommitTime != null && savedCommitTime > initialCommitTimeMs) {
initialCommitTimeMs = savedCommitTime;
}
@@ -413,7 +419,7 @@ public class SegmentCompletionManager {
// The table has a really high value configured for max commit time. Set it to a higher value than default
// and go from there.
LOGGER.info("Configured max commit time {}s too high for table {}, changing to {}s", initialCommitTimeMs / 1000,
- segmentName.getTableName(), MAX_COMMIT_TIME_FOR_ALL_SEGMENTS_SECONDS);
+ _realtimeTableName, MAX_COMMIT_TIME_FOR_ALL_SEGMENTS_SECONDS);
initialCommitTimeMs = MAX_COMMIT_TIME_FOR_ALL_SEGMENTS_SECONDS * 1000;
}
_initialCommitTimeMs = initialCommitTimeMs;
@@ -681,14 +687,14 @@ public class SegmentCompletionManager {
private SegmentCompletionProtocol.Response abortAndReturnHold(long now, String instanceId, long offset) {
_state = State.ABORTED;
_segmentCompletionManager._controllerMetrics
- .addMeteredTableValue(_segmentName.getTableName(), ControllerMeter.LLC_STATE_MACHINE_ABORTS, 1);
+ .addMeteredTableValue(_realtimeTableName, ControllerMeter.LLC_STATE_MACHINE_ABORTS, 1);
return hold(instanceId, offset);
}
private SegmentCompletionProtocol.Response abortAndReturnFailed() {
_state = State.ABORTED;
_segmentCompletionManager._controllerMetrics
- .addMeteredTableValue(_segmentName.getTableName(), ControllerMeter.LLC_STATE_MACHINE_ABORTS, 1);
+ .addMeteredTableValue(_realtimeTableName, ControllerMeter.LLC_STATE_MACHINE_ABORTS, 1);
return SegmentCompletionProtocol.RESP_FAILED;
}
@@ -1117,7 +1123,7 @@ public class SegmentCompletionManager {
}
@VisibleForTesting
- protected boolean isLeader() {
- return _controllerLeadershipManager.isLeader();
+ protected boolean isLeader(String tableName) {
+ return _leadControllerManager.isLeaderForTable(tableName);
}
}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/relocation/RealtimeSegmentRelocator.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/relocation/RealtimeSegmentRelocator.java
index ea5b05b..2c2c017 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/relocation/RealtimeSegmentRelocator.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/relocation/RealtimeSegmentRelocator.java
@@ -38,6 +38,7 @@ import org.apache.pinot.common.utils.helix.HelixHelper;
import org.apache.pinot.common.utils.retry.RetryPolicies;
import org.apache.pinot.common.utils.time.TimeUtils;
import org.apache.pinot.controller.ControllerConf;
+import org.apache.pinot.controller.LeadControllerManager;
import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.apache.pinot.controller.helix.core.PinotHelixSegmentOnlineOfflineStateModelGenerator;
import org.apache.pinot.controller.helix.core.periodictask.ControllerPeriodicTask;
@@ -55,10 +56,11 @@ import org.slf4j.LoggerFactory;
public class RealtimeSegmentRelocator extends ControllerPeriodicTask<Void> {
private static final Logger LOGGER = LoggerFactory.getLogger(RealtimeSegmentRelocator.class);
- public RealtimeSegmentRelocator(PinotHelixResourceManager pinotHelixResourceManager, ControllerConf config,
- ControllerMetrics controllerMetrics) {
+ public RealtimeSegmentRelocator(PinotHelixResourceManager pinotHelixResourceManager,
+ LeadControllerManager leadControllerManager, ControllerConf config, ControllerMetrics controllerMetrics) {
super("RealtimeSegmentRelocator", getRunFrequencySeconds(config.getRealtimeSegmentRelocatorFrequency()),
- config.getRealtimeSegmentRelocationInitialDelayInSeconds(), pinotHelixResourceManager, controllerMetrics);
+ config.getRealtimeSegmentRelocationInitialDelayInSeconds(), pinotHelixResourceManager, leadControllerManager,
+ controllerMetrics);
}
@Override
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/retention/RetentionManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/retention/RetentionManager.java
index 7d5182d..fecb4ac 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/retention/RetentionManager.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/retention/RetentionManager.java
@@ -35,6 +35,7 @@ import org.apache.pinot.common.utils.CommonConstants;
import org.apache.pinot.common.utils.CommonConstants.Segment.Realtime.Status;
import org.apache.pinot.common.utils.SegmentName;
import org.apache.pinot.controller.ControllerConf;
+import org.apache.pinot.controller.LeadControllerManager;
import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.apache.pinot.controller.helix.core.periodictask.ControllerPeriodicTask;
import org.apache.pinot.controller.helix.core.retention.strategy.RetentionStrategy;
@@ -54,10 +55,11 @@ public class RetentionManager extends ControllerPeriodicTask<Void> {
private final int _deletedSegmentsRetentionInDays;
- public RetentionManager(PinotHelixResourceManager pinotHelixResourceManager, ControllerConf config,
- ControllerMetrics controllerMetrics) {
+ public RetentionManager(PinotHelixResourceManager pinotHelixResourceManager,
+ LeadControllerManager leadControllerManager, ControllerConf config, ControllerMetrics controllerMetrics) {
super("RetentionManager", config.getRetentionControllerFrequencyInSeconds(),
- config.getRetentionManagerInitialDelayInSeconds(), pinotHelixResourceManager, controllerMetrics);
+ config.getRetentionManagerInitialDelayInSeconds(), pinotHelixResourceManager, leadControllerManager,
+ controllerMetrics);
_deletedSegmentsRetentionInDays = config.getDeletedSegmentsRetentionInDays();
LOGGER.info("Starting RetentionManager with runFrequencyInSeconds: {}, deletedSegmentsRetentionInDays: {}",
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/statemodel/LeadControllerResourceMasterSlaveStateModelFactory.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/statemodel/LeadControllerResourceMasterSlaveStateModelFactory.java
new file mode 100644
index 0000000..5655de8
--- /dev/null
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/statemodel/LeadControllerResourceMasterSlaveStateModelFactory.java
@@ -0,0 +1,84 @@
+/**
+ * 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.statemodel;
+
+import org.apache.helix.NotificationContext;
+import org.apache.helix.model.Message;
+import org.apache.helix.participant.statemachine.StateModel;
+import org.apache.helix.participant.statemachine.StateModelFactory;
+import org.apache.helix.participant.statemachine.StateModelInfo;
+import org.apache.helix.participant.statemachine.Transition;
+import org.apache.pinot.controller.LeadControllerManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class LeadControllerResourceMasterSlaveStateModelFactory extends StateModelFactory<StateModel> {
+ private final LeadControllerManager _leadControllerManager;
+
+ public LeadControllerResourceMasterSlaveStateModelFactory(LeadControllerManager leadControllerManager) {
+ _leadControllerManager = leadControllerManager;
+ }
+
+ @Override
+ public StateModel createNewStateModel(String resourceName, String partitionName) {
+ return new LeadControllerResourceMasterSlaveStateModel(_leadControllerManager, partitionName);
+ }
+
+ @StateModelInfo(states = "{'MASTER','SLAVE', 'OFFLINE', 'DROPPED'}", initialState = "OFFLINE")
+ public static class LeadControllerResourceMasterSlaveStateModel extends StateModel {
+ private static final Logger LOGGER = LoggerFactory.getLogger(LeadControllerResourceMasterSlaveStateModel.class);
+
+ private final LeadControllerManager _leadControllerManager;
+ private final String _partitionName;
+
+ private LeadControllerResourceMasterSlaveStateModel(LeadControllerManager leadControllerManager,
+ String partitionName) {
+ _leadControllerManager = leadControllerManager;
+ _partitionName = partitionName;
+ }
+
+ @Transition(from = "OFFLINE", to = "SLAVE")
+ public void onBecomeSlaveFromOffline(Message message, NotificationContext context) {
+ LOGGER.info("Got state transition from OFFLINE to SLAVE for partition: {}", _partitionName);
+ }
+
+ @Transition(from = "SLAVE", to = "MASTER")
+ public void onBecomeMasterFromSlave(Message message, NotificationContext context) {
+ LOGGER.info("Got state transition from SLAVE to MASTER for partition: {}", _partitionName);
+ _leadControllerManager.addPartitionLeader(_partitionName);
+ }
+
+ @Transition(from = "MASTER", to = "SLAVE")
+ public void onBecomeSlaveFromMaster(Message message, NotificationContext context) {
+ LOGGER.info("Got state transition from MASTER to SLAVE for partition: {}", _partitionName);
+ _leadControllerManager.removePartitionLeader(_partitionName);
+ }
+
+ @Transition(from = "SLAVE", to = "OFFLINE")
+ public void onBecomeOfflineFromSlave(Message message, NotificationContext context) {
+ LOGGER.info("Got state transition from SLAVE to OFFLINE for partition: {}", _partitionName);
+ }
+
+ @Transition(from = "OFFLINE", to = "DROPPED")
+ public void onBecomeDroppedFromOffline(Message message, NotificationContext context) {
+ LOGGER.info("Got state transition from OFFLINE to DROPPED for partition: {}", _partitionName);
+ }
+ }
+}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/util/HelixSetupUtils.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/util/HelixSetupUtils.java
index 37dec22..8564374 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/util/HelixSetupUtils.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/util/HelixSetupUtils.java
@@ -43,6 +43,7 @@ import org.apache.helix.model.StateModelDefinition;
import org.apache.helix.model.builder.CustomModeISBuilder;
import org.apache.helix.model.builder.FullAutoModeISBuilder;
import org.apache.helix.model.builder.HelixConfigScopeBuilder;
+import org.apache.pinot.common.utils.helix.LeadControllerUtils;
import org.apache.pinot.controller.helix.core.PinotHelixBrokerResourceOnlineOfflineStateModelGenerator;
import org.apache.pinot.controller.helix.core.PinotHelixSegmentOnlineOfflineStateModelGenerator;
import org.slf4j.Logger;
@@ -148,7 +149,8 @@ public class HelixSetupUtils {
private static void createLeadControllerResourceIfNeeded(String helixClusterName, HelixAdmin helixAdmin,
ConfigAccessor configAccessor, boolean enableBatchMessageMode) {
- if (helixAdmin.getResourceIdealState(helixClusterName, LEAD_CONTROLLER_RESOURCE_NAME) == null) {
+ IdealState idealState = helixAdmin.getResourceIdealState(helixClusterName, LEAD_CONTROLLER_RESOURCE_NAME);
+ if (idealState == null) {
LOGGER.info("Adding resource: {}", LEAD_CONTROLLER_RESOURCE_NAME);
// FULL-AUTO Master-Slave state model with CrushED rebalance strategy
@@ -158,7 +160,7 @@ public class HelixSetupUtils {
// Initialize partitions and replicas
idealStateBuilder.setNumPartitions(NUMBER_OF_PARTITIONS_IN_LEAD_CONTROLLER_RESOURCE);
for (int i = 0; i < NUMBER_OF_PARTITIONS_IN_LEAD_CONTROLLER_RESOURCE; i++) {
- idealStateBuilder.add(LEAD_CONTROLLER_RESOURCE_NAME + "_" + i);
+ idealStateBuilder.add(LeadControllerUtils.generatePartitionName(i));
}
idealStateBuilder.setNumReplica(LEAD_CONTROLLER_RESOURCE_REPLICA_COUNT);
// The below config guarantees if active number of replicas is no less than minimum active replica, there will not be partition movements happened.
@@ -169,16 +171,19 @@ public class HelixSetupUtils {
idealStateBuilder.setRebalanceDelay(REBALANCE_DELAY_MS);
idealStateBuilder.enableDelayRebalance();
// Set instance group tag
- IdealState idealState = idealStateBuilder.build();
+ idealState = idealStateBuilder.build();
idealState.setInstanceGroupTag(CONTROLLER_INSTANCE);
// Set batch message mode
idealState.setBatchMessageMode(enableBatchMessageMode);
// Explicitly disable this resource when creating this new resource.
// When all the controllers are running the code with the logic to handle this resource, it can be enabled for backward compatibility.
// In the next major release, we can enable this resource by default, so that all the controller logic can be separated.
- idealState.enable(false);
helixAdmin.addResource(helixClusterName, LEAD_CONTROLLER_RESOURCE_NAME, idealState);
+ } else if (!idealState.isEnabled()) {
+ // Enable lead controller resource and let resource config be the only switch for enabling logic of lead controller resource.
+ idealState.enable(true);
+ helixAdmin.updateIdealState(helixClusterName, LEAD_CONTROLLER_RESOURCE_NAME, idealState);
}
// Create resource config for lead controller resource if it doesn't exist
@@ -187,8 +192,8 @@ public class HelixSetupUtils {
resourceConfig = new ResourceConfig(LEAD_CONTROLLER_RESOURCE_NAME);
}
// Set RESOURCE_ENABLED to false if it's absent in resource config
- if (resourceConfig.getSimpleConfig("RESOURCE_ENABLED") == null) {
- resourceConfig.putSimpleConfig("RESOURCE_ENABLED", Boolean.FALSE.toString());
+ if (resourceConfig.getSimpleConfig(LEAD_CONTROLLER_RESOURCE_ENABLED_KEY) == null) {
+ resourceConfig.putSimpleConfig(LEAD_CONTROLLER_RESOURCE_ENABLED_KEY, Boolean.FALSE.toString());
}
configAccessor.setResourceConfig(helixClusterName, LEAD_CONTROLLER_RESOURCE_NAME, resourceConfig);
}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/validation/BrokerResourceValidationManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/validation/BrokerResourceValidationManager.java
index 5748d3c..a82a161 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/validation/BrokerResourceValidationManager.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/validation/BrokerResourceValidationManager.java
@@ -24,6 +24,7 @@ import org.apache.helix.model.InstanceConfig;
import org.apache.pinot.common.config.TableConfig;
import org.apache.pinot.common.metrics.ControllerMetrics;
import org.apache.pinot.controller.ControllerConf;
+import org.apache.pinot.controller.LeadControllerManager;
import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.apache.pinot.controller.helix.core.periodictask.ControllerPeriodicTask;
import org.slf4j.Logger;
@@ -37,9 +38,9 @@ public class BrokerResourceValidationManager extends ControllerPeriodicTask<Brok
private static final Logger LOGGER = LoggerFactory.getLogger(BrokerResourceValidationManager.class);
public BrokerResourceValidationManager(ControllerConf config, PinotHelixResourceManager pinotHelixResourceManager,
- ControllerMetrics controllerMetrics) {
+ LeadControllerManager leadControllerManager, ControllerMetrics controllerMetrics) {
super("BrokerResourceValidationManager", config.getBrokerResourceValidationFrequencyInSeconds(),
- config.getBrokerResourceValidationInitialDelayInSeconds(), pinotHelixResourceManager, controllerMetrics);
+ config.getBrokerResourceValidationInitialDelayInSeconds(), pinotHelixResourceManager, leadControllerManager, controllerMetrics);
}
@Override
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/validation/OfflineSegmentIntervalChecker.java b/pinot-controller/src/main/java/org/apache/pinot/controller/validation/OfflineSegmentIntervalChecker.java
index 7f19395..0eeadc7 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/validation/OfflineSegmentIntervalChecker.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/validation/OfflineSegmentIntervalChecker.java
@@ -30,6 +30,7 @@ import org.apache.pinot.common.metrics.ControllerMetrics;
import org.apache.pinot.common.metrics.ValidationMetrics;
import org.apache.pinot.common.utils.CommonConstants;
import org.apache.pinot.controller.ControllerConf;
+import org.apache.pinot.controller.LeadControllerManager;
import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.apache.pinot.controller.helix.core.periodictask.ControllerPeriodicTask;
import org.apache.pinot.controller.util.SegmentIntervalUtils;
@@ -50,9 +51,11 @@ public class OfflineSegmentIntervalChecker extends ControllerPeriodicTask<Void>
private final ValidationMetrics _validationMetrics;
public OfflineSegmentIntervalChecker(ControllerConf config, PinotHelixResourceManager pinotHelixResourceManager,
- ValidationMetrics validationMetrics, ControllerMetrics controllerMetrics) {
+ LeadControllerManager leadControllerManager, ValidationMetrics validationMetrics,
+ ControllerMetrics controllerMetrics) {
super("OfflineSegmentIntervalChecker", config.getOfflineSegmentIntervalCheckerFrequencyInSeconds(),
- config.getOfflineSegmentIntervalCheckerInitialDelayInSeconds(), pinotHelixResourceManager, controllerMetrics);
+ config.getOfflineSegmentIntervalCheckerInitialDelayInSeconds(), pinotHelixResourceManager,
+ leadControllerManager, controllerMetrics);
_validationMetrics = validationMetrics;
}
@@ -88,12 +91,12 @@ public class OfflineSegmentIntervalChecker extends ControllerPeriodicTask<Void>
if (SegmentIntervalUtils.isValidInterval(timeInterval)) {
segmentIntervals.add(timeInterval);
} else {
- numSegmentsWithInvalidIntervals ++;
+ numSegmentsWithInvalidIntervals++;
}
}
if (numSegmentsWithInvalidIntervals > 0) {
- LOGGER.warn("Table: {} has {} segments with invalid interval", offlineTableName,
- numSegmentsWithInvalidIntervals);
+ LOGGER
+ .warn("Table: {} has {} segments with invalid interval", offlineTableName, numSegmentsWithInvalidIntervals);
}
Duration frequency = SegmentIntervalUtils.convertToDuration(validationConfig.getSegmentPushFrequency());
numMissingSegments = computeNumMissingSegments(segmentIntervals, frequency);
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/validation/RealtimeSegmentValidationManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/validation/RealtimeSegmentValidationManager.java
index 5eb5a6f..88ad642 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/validation/RealtimeSegmentValidationManager.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/validation/RealtimeSegmentValidationManager.java
@@ -32,6 +32,7 @@ import org.apache.pinot.common.utils.CommonConstants;
import org.apache.pinot.common.utils.HLCSegmentName;
import org.apache.pinot.common.utils.SegmentName;
import org.apache.pinot.controller.ControllerConf;
+import org.apache.pinot.controller.LeadControllerManager;
import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.apache.pinot.controller.helix.core.periodictask.ControllerPeriodicTask;
import org.apache.pinot.controller.helix.core.realtime.PinotLLCRealtimeSegmentManager;
@@ -53,10 +54,11 @@ public class RealtimeSegmentValidationManager extends ControllerPeriodicTask<Rea
private long _lastUpdateRealtimeDocumentCountTimeMs = 0L;
public RealtimeSegmentValidationManager(ControllerConf config, PinotHelixResourceManager pinotHelixResourceManager,
- PinotLLCRealtimeSegmentManager llcRealtimeSegmentManager, ValidationMetrics validationMetrics,
- ControllerMetrics controllerMetrics) {
+ LeadControllerManager leadControllerManager, PinotLLCRealtimeSegmentManager llcRealtimeSegmentManager,
+ ValidationMetrics validationMetrics, ControllerMetrics controllerMetrics) {
super("RealtimeSegmentValidationManager", config.getRealtimeSegmentValidationFrequencyInSeconds(),
- config.getRealtimeSegmentValidationManagerInitialDelaySeconds(), pinotHelixResourceManager, controllerMetrics);
+ config.getRealtimeSegmentValidationManagerInitialDelaySeconds(), pinotHelixResourceManager,
+ leadControllerManager, controllerMetrics);
_llcRealtimeSegmentManager = llcRealtimeSegmentManager;
_validationMetrics = validationMetrics;
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/validation/StorageQuotaChecker.java b/pinot-controller/src/main/java/org/apache/pinot/controller/validation/StorageQuotaChecker.java
index e895242..6a3066c 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/validation/StorageQuotaChecker.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/validation/StorageQuotaChecker.java
@@ -28,8 +28,6 @@ import org.apache.pinot.common.exception.InvalidConfigException;
import org.apache.pinot.common.metrics.ControllerGauge;
import org.apache.pinot.common.metrics.ControllerMetrics;
import org.apache.pinot.common.utils.DataSize;
-import org.apache.pinot.controller.ControllerLeadershipManager;
-import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.apache.pinot.controller.util.TableSizeReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -45,17 +43,14 @@ public class StorageQuotaChecker {
private final TableSizeReader _tableSizeReader;
private final TableConfig _tableConfig;
private final ControllerMetrics _controllerMetrics;
- private final PinotHelixResourceManager _pinotHelixResourceManager;
- private final ControllerLeadershipManager _controllerLeadershipManager;
+ private final boolean _isLeaderForTable;
public StorageQuotaChecker(TableConfig tableConfig, TableSizeReader tableSizeReader,
- ControllerMetrics controllerMetrics, PinotHelixResourceManager pinotHelixResourceManager,
- ControllerLeadershipManager controllerLeadershipManager) {
+ ControllerMetrics controllerMetrics, boolean isLeaderForTable) {
_tableConfig = tableConfig;
_tableSizeReader = tableSizeReader;
_controllerMetrics = controllerMetrics;
- _pinotHelixResourceManager = pinotHelixResourceManager;
- _controllerLeadershipManager = controllerLeadershipManager;
+ _isLeaderForTable = isLeaderForTable;
}
public static class QuotaCheckerResponse {
@@ -157,7 +152,7 @@ public class StorageQuotaChecker {
tableNameWithType, tableSubtypeSize.estimatedSizeInBytes, tableSubtypeSize.reportedSizeInBytes);
// Only emit the real percentage of storage quota usage by lead controller, otherwise emit 0L.
- if (isLeader()) {
+ if (_isLeaderForTable) {
long existingStorageQuotaUtilization = tableSubtypeSize.estimatedSizeInBytes * 100 / allowedStorageBytes;
_controllerMetrics.setValueOfTableGauge(tableNameWithType, ControllerGauge.TABLE_STORAGE_QUOTA_UTILIZATION,
existingStorageQuotaUtilization);
@@ -212,8 +207,4 @@ public class StorageQuotaChecker {
return failure(message);
}
}
-
- protected boolean isLeader() {
- return _controllerLeadershipManager.isLeader();
- }
}
diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/LeadControllerManagerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/LeadControllerManagerTest.java
new file mode 100644
index 0000000..c9e4e74
--- /dev/null
+++ b/pinot-controller/src/test/java/org/apache/pinot/controller/LeadControllerManagerTest.java
@@ -0,0 +1,125 @@
+/**
+ * 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;
+
+import com.yammer.metrics.core.MetricsRegistry;
+import org.apache.helix.HelixDataAccessor;
+import org.apache.helix.HelixManager;
+import org.apache.helix.PropertyKey;
+import org.apache.helix.model.LiveInstance;
+import org.apache.helix.model.ResourceConfig;
+import org.apache.pinot.common.metrics.ControllerMetrics;
+import org.apache.pinot.common.utils.helix.LeadControllerUtils;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+
+public class LeadControllerManagerTest {
+ private static final String CONTROLLER_HOST = "localhost";
+ private static final int CONTROLLER_PORT = 18998;
+
+ private HelixManager _helixManager;
+ private ControllerMetrics _controllerMetrics;
+ private LiveInstance _liveInstance;
+ private ResourceConfig _resourceConfig;
+
+ @BeforeMethod
+ public void setup() {
+ _controllerMetrics = new ControllerMetrics(new MetricsRegistry());
+ _helixManager = mock(HelixManager.class);
+ HelixDataAccessor helixDataAccessor = mock(HelixDataAccessor.class);
+ when(_helixManager.getHelixDataAccessor()).thenReturn(helixDataAccessor);
+
+ PropertyKey.Builder keyBuilder = mock(PropertyKey.Builder.class);
+ when(helixDataAccessor.keyBuilder()).thenReturn(keyBuilder);
+
+ PropertyKey controllerLeader = mock(PropertyKey.class);
+ when(keyBuilder.controllerLeader()).thenReturn(controllerLeader);
+ _liveInstance = mock(LiveInstance.class);
+ when(helixDataAccessor.getProperty(controllerLeader)).thenReturn(_liveInstance);
+
+ PropertyKey resourceConfigPropertyKey = mock(PropertyKey.class);
+ when(keyBuilder.resourceConfig(any())).thenReturn(resourceConfigPropertyKey);
+ _resourceConfig = mock(ResourceConfig.class);
+ when(helixDataAccessor.getProperty(resourceConfigPropertyKey)).thenReturn(_resourceConfig);
+
+ String instanceId = LeadControllerUtils.generateParticipantInstanceId(CONTROLLER_HOST, CONTROLLER_PORT);
+ when(_helixManager.getInstanceName()).thenReturn(instanceId);
+ }
+
+ @Test
+ public void testLeadControllerManager() {
+ LeadControllerManager leadControllerManager = new LeadControllerManager(_helixManager, _controllerMetrics);
+ String tableName = "testTable";
+ int expectedPartitionIndex = LeadControllerUtils.getPartitionIdForTable(tableName);
+ String partitionName = LeadControllerUtils.generatePartitionName(expectedPartitionIndex);
+
+ becomeHelixLeader(false);
+ leadControllerManager.onHelixControllerChange();
+
+ // When there's no resource config change nor helix controller change, leadControllerManager should return false.
+ Assert.assertFalse(leadControllerManager.isLeaderForTable(tableName));
+
+ enableResourceConfig(true);
+ leadControllerManager.onResourceConfigChange();
+
+ // Even resource config is enabled, leadControllerManager should return false because no index is cached yet.
+ Assert.assertFalse(leadControllerManager.isLeaderForTable(tableName));
+ Assert.assertTrue(leadControllerManager.isLeadControllerResourceEnabled());
+
+ // After the target partition index is cached, leadControllerManager should return true.
+ leadControllerManager.addPartitionLeader(partitionName);
+ Assert.assertTrue(leadControllerManager.isLeaderForTable(tableName));
+
+ // When the target partition index is removed, leadControllerManager should return false.
+ leadControllerManager.removePartitionLeader(partitionName);
+ Assert.assertFalse(leadControllerManager.isLeaderForTable(tableName));
+
+ // When resource config is set to false, the cache should be disabled, even if the target partition index is in the cache.
+ // The leader depends on whether the current controller is helix leader.
+ enableResourceConfig(false);
+ leadControllerManager.onResourceConfigChange();
+
+ Assert.assertFalse(leadControllerManager.isLeadControllerResourceEnabled());
+ Assert.assertFalse(leadControllerManager.isLeaderForTable(tableName));
+ leadControllerManager.addPartitionLeader(partitionName);
+ Assert.assertFalse(leadControllerManager.isLeaderForTable(tableName));
+
+ // When the current controller becomes helix leader and resource is disabled, leadControllerManager should return false.
+ becomeHelixLeader(true);
+ leadControllerManager.onHelixControllerChange();
+ Assert.assertTrue(leadControllerManager.isLeaderForTable(tableName));
+ }
+
+ private void becomeHelixLeader(boolean becomeHelixLeader) {
+ if (becomeHelixLeader) {
+ when(_liveInstance.getInstanceName()).thenReturn(CONTROLLER_HOST + "_" + CONTROLLER_PORT);
+ }
+ }
+
+ private void enableResourceConfig(boolean enable) {
+ when(_resourceConfig.getSimpleConfig(anyString())).thenReturn(Boolean.toString(enable));
+ }
+}
diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerTest.java
index ab1ea56..5f51010 100644
--- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerTest.java
+++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerTest.java
@@ -41,6 +41,7 @@ import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.StringPart;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
+import org.apache.helix.ConfigAccessor;
import org.apache.helix.HelixAdmin;
import org.apache.helix.HelixManager;
import org.apache.helix.HelixManagerFactory;
@@ -50,6 +51,7 @@ import org.apache.helix.ZNRecord;
import org.apache.helix.model.ClusterConfig;
import org.apache.helix.model.HelixConfigScope;
import org.apache.helix.model.Message;
+import org.apache.helix.model.ResourceConfig;
import org.apache.helix.model.builder.HelixConfigScopeBuilder;
import org.apache.helix.participant.statemachine.StateModel;
import org.apache.helix.participant.statemachine.StateModelFactory;
@@ -73,6 +75,8 @@ import org.slf4j.LoggerFactory;
import org.testng.Assert;
import static org.apache.pinot.common.utils.CommonConstants.Helix.Instance.ADMIN_PORT_KEY;
+import static org.apache.pinot.common.utils.CommonConstants.Helix.LEAD_CONTROLLER_RESOURCE_ENABLED_KEY;
+import static org.apache.pinot.common.utils.CommonConstants.Helix.LEAD_CONTROLLER_RESOURCE_NAME;
import static org.apache.pinot.common.utils.CommonConstants.Helix.UNTAGGED_BROKER_INSTANCE;
import static org.apache.pinot.common.utils.CommonConstants.Helix.UNTAGGED_SERVER_INSTANCE;
import static org.apache.pinot.common.utils.CommonConstants.Server.DEFAULT_ADMIN_API_PORT;
@@ -440,6 +444,16 @@ public abstract class ControllerTest {
getServerTenantRequestPayload(tenantName, numOfflineServers, numRealtimeServers));
}
+ public void enableResourceConfigForLeadControllerResource(boolean enable) {
+ ConfigAccessor configAccessor = _helixManager.getConfigAccessor();
+ ResourceConfig resourceConfig =
+ configAccessor.getResourceConfig(getHelixClusterName(), LEAD_CONTROLLER_RESOURCE_NAME);
+ if (!Boolean.toString(enable).equals(resourceConfig.getSimpleConfig(LEAD_CONTROLLER_RESOURCE_ENABLED_KEY))) {
+ resourceConfig.putSimpleConfig(LEAD_CONTROLLER_RESOURCE_ENABLED_KEY, Boolean.toString(enable));
+ configAccessor.setResourceConfig(getHelixClusterName(), LEAD_CONTROLLER_RESOURCE_NAME, resourceConfig);
+ }
+ }
+
public static String sendGetRequest(String urlString)
throws IOException {
return constructResponse(new URL(urlString).openStream());
diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/PinotControllerModeTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/PinotControllerModeTest.java
index 7cd5612..c1f3b8a 100644
--- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/PinotControllerModeTest.java
+++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/PinotControllerModeTest.java
@@ -22,19 +22,23 @@ import java.util.Map;
import java.util.Set;
import org.apache.helix.HelixAdmin;
import org.apache.helix.HelixManager;
+import org.apache.helix.manager.zk.ZkClient;
import org.apache.helix.model.ExternalView;
+import org.apache.helix.model.IdealState;
+import org.apache.pinot.common.utils.CommonConstants.Helix;
+import org.apache.pinot.common.utils.ZkStarter;
+import org.apache.pinot.common.utils.helix.LeadControllerUtils;
import org.apache.pinot.controller.ControllerConf;
import org.apache.pinot.controller.ControllerStarter;
+import org.apache.pinot.controller.LeadControllerManager;
import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.apache.pinot.util.TestUtils;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
-import static org.apache.pinot.common.utils.CommonConstants.Helix.LEAD_CONTROLLER_RESOURCE_NAME;
-import static org.apache.pinot.common.utils.CommonConstants.Helix.NUMBER_OF_PARTITIONS_IN_LEAD_CONTROLLER_RESOURCE;
-
public class PinotControllerModeTest extends ControllerTest {
private static long TIMEOUT_IN_MS = 10_000L;
@@ -56,25 +60,41 @@ public class PinotControllerModeTest extends ControllerTest {
stopController();
}
- @Test(enabled = false)
+ @Test
public void testDualModeController() {
// Start the first dual-mode controller
ControllerConf firstDualModeControllerConfig = getDefaultControllerConfiguration();
firstDualModeControllerConfig.setControllerMode(ControllerConf.ControllerMode.DUAL);
startController(firstDualModeControllerConfig);
+
+ // Disable delay rebalance feature
+ IdealState idealState = _helixResourceManager.getTableIdealState(Helix.LEAD_CONTROLLER_RESOURCE_NAME);
+ idealState.setDelayRebalanceEnabled(false);
+ idealState.setMinActiveReplicas(1);
+ _helixAdmin.updateIdealState(getHelixClusterName(), Helix.LEAD_CONTROLLER_RESOURCE_NAME, idealState);
+
TestUtils.waitForCondition(aVoid -> _helixManager.isConnected(), TIMEOUT_IN_MS,
"Failed to start the first dual-mode controller");
+ // The first controller should be the MASTER for all partitions
+ checkInstanceState(_helixAdmin);
- // There should be no partition in the external view because the resource is disabled
- TestUtils.waitForCondition(aVoid -> {
- ExternalView leadControllerResourceExternalView =
- _helixAdmin.getResourceExternalView(getHelixClusterName(), LEAD_CONTROLLER_RESOURCE_NAME);
- return leadControllerResourceExternalView.getPartitionSet().isEmpty();
- }, TIMEOUT_IN_MS, "There should be no partition in the disabled resource's external view");
+ final LeadControllerManager firstLeadControllerManager = _controllerStarter.getLeadControllerManager();
+ String firstTableName = "firstTableName";
+ String secondTableName = "secondTableName";
+
+ Assert.assertNotEquals(LeadControllerUtils.getPartitionIdForTable(firstTableName),
+ LeadControllerUtils.getPartitionIdForTable(secondTableName));
- // Enable the lead controller resource, and the first controller should be the MASTER for all partitions
- _helixAdmin.enableResource(getHelixClusterName(), LEAD_CONTROLLER_RESOURCE_NAME, true);
- checkInstanceState(_helixAdmin, "MASTER");
+ // The first controller should be the leader for both tables, which is the Helix leader, before the resource is enabled.
+ Assert.assertTrue(firstLeadControllerManager.isLeaderForTable(firstTableName));
+ Assert.assertTrue(firstLeadControllerManager.isLeaderForTable(secondTableName));
+
+ enableResourceConfigForLeadControllerResource(true);
+ TestUtils.waitForCondition(aVoid -> firstLeadControllerManager.isLeadControllerResourceEnabled(), TIMEOUT_IN_MS,
+ "Failed to mark lead controller resource as enabled");
+ // The first controller should still be the leader for both tables, which is the partition leader, after the resource is enabled.
+ Assert.assertTrue(firstLeadControllerManager.isLeaderForTable(firstTableName));
+ Assert.assertTrue(firstLeadControllerManager.isLeaderForTable(secondTableName));
// Start the second dual-mode controller
ControllerConf secondDualModeControllerConfig = getDefaultControllerConfiguration();
@@ -85,29 +105,76 @@ public class PinotControllerModeTest extends ControllerTest {
TestUtils
.waitForCondition(aVoid -> secondDualModeController.getHelixResourceManager().getHelixZkManager().isConnected(),
TIMEOUT_IN_MS, "Failed to start the second dual-mode controller");
-
// There should still be only one MASTER instance for each partition
- checkInstanceState(_helixAdmin, "MASTER");
+ checkInstanceState(_helixAdmin);
- // Disable the lead controller resource, and there should be only one OFFLINE instance for each partition
- _helixAdmin.enableResource(getHelixClusterName(), LEAD_CONTROLLER_RESOURCE_NAME, false);
- checkInstanceState(_helixAdmin, "OFFLINE");
+ LeadControllerManager secondLeadControllerManager = secondDualModeController.getLeadControllerManager();
+ Assert.assertTrue(secondLeadControllerManager.isLeadControllerResourceEnabled());
- // Re-enable the lead controller resource, and there should be only one MASTER instance for each partition
- _helixAdmin.enableResource(getHelixClusterName(), LEAD_CONTROLLER_RESOURCE_NAME, true);
- checkInstanceState(_helixAdmin, "MASTER");
+ // Either one of the controllers is the only leader for a given table name.
+ TestUtils.waitForCondition(aVoid -> {
+ boolean result;
+ result = firstLeadControllerManager.isLeaderForTable(firstTableName) ^ secondLeadControllerManager
+ .isLeaderForTable(firstTableName);
+ result &= firstLeadControllerManager.isLeaderForTable(secondTableName) ^ secondLeadControllerManager
+ .isLeaderForTable(secondTableName);
+ return result;
+ }, TIMEOUT_IN_MS, "Either one of the controllers is the only leader for a given table");
// Stop the second controller, and there should still be only one MASTER instance for each partition
secondDualModeController.stop();
- checkInstanceState(_helixAdmin, "MASTER");
+ checkInstanceState(_helixAdmin);
// Stop the first controller
stopController();
+
+ // Both controllers are stopped and no one should be the partition leader for tables.
+ TestUtils.waitForCondition(aVoid -> {
+ boolean result;
+ result = !firstLeadControllerManager.isLeaderForTable(firstTableName);
+ result &= !firstLeadControllerManager.isLeaderForTable(secondTableName);
+ result &= !secondLeadControllerManager.isLeaderForTable(firstTableName);
+ result &= !secondLeadControllerManager.isLeaderForTable(secondTableName);
+ return result;
+ }, TIMEOUT_IN_MS, "No one should be the partition leader for tables");
+
+ ZkClient zkClient = new ZkClient(ZkStarter.DEFAULT_ZK_STR);
+ TestUtils
+ .waitForCondition(aVoid -> !zkClient.exists("/" + getHelixClusterName() + "/CONTROLLER/LEADER"), TIMEOUT_IN_MS,
+ "No cluster leader should be shown in Helix cluster");
+ zkClient.close();
+
+ ControllerConf thirdDualModeControllerConfig = getDefaultControllerConfiguration();
+ thirdDualModeControllerConfig.setControllerMode(ControllerConf.ControllerMode.DUAL);
+ thirdDualModeControllerConfig.setControllerPort(Integer.toString(DEFAULT_CONTROLLER_PORT + 2));
+ ControllerStarter thirdDualModeController = getControllerStarter(thirdDualModeControllerConfig);
+ thirdDualModeController.start();
+ PinotHelixResourceManager pinotHelixResourceManager = thirdDualModeController.getHelixResourceManager();
+ _helixManager = pinotHelixResourceManager.getHelixZkManager();
+ _helixAdmin = _helixManager.getClusterManagmentTool();
+ TestUtils
+ .waitForCondition(aVoid -> thirdDualModeController.getHelixResourceManager().getHelixZkManager().isConnected(),
+ TIMEOUT_IN_MS, "Failed to start the 3rd dual-mode controller");
+ // There should still be only one MASTER instance for each partition
+ checkInstanceState(_helixAdmin);
+
+ enableResourceConfigForLeadControllerResource(false);
+ final LeadControllerManager thirdLeadControllerManager = thirdDualModeController.getLeadControllerManager();
+
+ TestUtils.waitForCondition(aVoid -> !thirdLeadControllerManager.isLeadControllerResourceEnabled(), TIMEOUT_IN_MS,
+ "Lead controller resource should be disabled.");
+
+ // After disabling lead controller resource, helix leader should be the leader for both tables.
+ TestUtils.waitForCondition(
+ aVoid -> thirdLeadControllerManager.isLeaderForTable(firstTableName) && thirdLeadControllerManager
+ .isLeaderForTable(secondTableName), TIMEOUT_IN_MS,
+ "The 3rd controller should be the leader for both tables, which is the Helix leader.");
+
+ // Stop the 3rd controller
+ thirdDualModeController.stop();
}
- // TODO: enable it after removing ControllerLeadershipManager which requires both CONTROLLER and PARTICIPANT
- // HelixManager
- @Test(enabled = false)
+ @Test
public void testPinotOnlyController() {
ControllerConf firstPinotOnlyControllerConfig = getDefaultControllerConfiguration();
firstPinotOnlyControllerConfig.setControllerMode(ControllerConf.ControllerMode.PINOT_ONLY);
@@ -132,30 +199,13 @@ public class PinotControllerModeTest extends ControllerTest {
TestUtils.waitForCondition(aVoid -> helixControllerManager.isConnected(), TIMEOUT_IN_MS,
"Failed to start the Helix-only controller");
- // Enabling the lead controller resource before setting up the Pinot cluster should fail
- try {
- helixAdmin.enableResource(getHelixClusterName(), LEAD_CONTROLLER_RESOURCE_NAME, true);
- Assert.fail("Enabling the lead controller resource before setting up the Pinot cluster should fail");
- } catch (Exception e) {
- // Expected
- }
-
// Start the first Pinot-only controller
firstPinotOnlyController.start();
PinotHelixResourceManager helixResourceManager = firstPinotOnlyController.getHelixResourceManager();
TestUtils.waitForCondition(aVoid -> helixResourceManager.getHelixZkManager().isConnected(), TIMEOUT_IN_MS,
"Failed to start the first Pinot-only controller");
-
- // There should be no partition in the external view because the resource is disabled
- TestUtils.waitForCondition(aVoid -> {
- ExternalView leadControllerResourceExternalView =
- helixAdmin.getResourceExternalView(getHelixClusterName(), LEAD_CONTROLLER_RESOURCE_NAME);
- return leadControllerResourceExternalView.getPartitionSet().isEmpty();
- }, TIMEOUT_IN_MS, "There should be no partition in the disabled resource's external view");
-
- // Enable the lead controller resource, and the first Pinot-only controller should be the MASTER for all partitions
- helixAdmin.enableResource(getHelixClusterName(), LEAD_CONTROLLER_RESOURCE_NAME, true);
- checkInstanceState(helixAdmin, "MASTER");
+ // The first Pinot-only controller should be the MASTER for all partitions
+ checkInstanceState(helixAdmin);
// Start the second Pinot-only controller
ControllerConf secondPinotOnlyControllerConfig = getDefaultControllerConfiguration();
@@ -166,27 +216,18 @@ public class PinotControllerModeTest extends ControllerTest {
TestUtils.waitForCondition(
aVoid -> secondPinotOnlyController.getHelixResourceManager().getHelixZkManager().isConnected(), TIMEOUT_IN_MS,
"Failed to start the second Pinot-only controller");
-
// There should still be only one MASTER instance for each partition
- checkInstanceState(helixAdmin, "MASTER");
-
- // Disable the lead controller resource, and there should be only one OFFLINE instance for each partition
- helixAdmin.enableResource(getHelixClusterName(), LEAD_CONTROLLER_RESOURCE_NAME, false);
- checkInstanceState(helixAdmin, "OFFLINE");
-
- // Re-enable the lead controller resource, and there should be only one MASTER instance for each partition
- helixAdmin.enableResource(getHelixClusterName(), LEAD_CONTROLLER_RESOURCE_NAME, true);
- checkInstanceState(helixAdmin, "MASTER");
+ checkInstanceState(helixAdmin);
// Stop the second Pinot-only controller, and there should still be only one MASTER instance for each partition
secondPinotOnlyController.stop();
- checkInstanceState(helixAdmin, "MASTER");
+ checkInstanceState(helixAdmin);
// Stop the first Pinot-only controller, and there should be no partition in the external view
firstPinotOnlyController.stop();
TestUtils.waitForCondition(aVoid -> {
ExternalView leadControllerResourceExternalView =
- helixAdmin.getResourceExternalView(getHelixClusterName(), LEAD_CONTROLLER_RESOURCE_NAME);
+ helixAdmin.getResourceExternalView(getHelixClusterName(), Helix.LEAD_CONTROLLER_RESOURCE_NAME);
return leadControllerResourceExternalView.getPartitionSet().isEmpty();
}, TIMEOUT_IN_MS, "Without live instance, there should be no partition in the external view");
@@ -194,12 +235,13 @@ public class PinotControllerModeTest extends ControllerTest {
helixOnlyController.stop();
}
- private void checkInstanceState(HelixAdmin helixAdmin, String expectedInstanceState) {
+ private void checkInstanceState(HelixAdmin helixAdmin) {
+ String expectedInstanceState = "MASTER";
TestUtils.waitForCondition(aVoid -> {
ExternalView leadControllerResourceExternalView =
- helixAdmin.getResourceExternalView(getHelixClusterName(), LEAD_CONTROLLER_RESOURCE_NAME);
+ helixAdmin.getResourceExternalView(getHelixClusterName(), Helix.LEAD_CONTROLLER_RESOURCE_NAME);
Set<String> partitionSet = leadControllerResourceExternalView.getPartitionSet();
- if (partitionSet.size() != NUMBER_OF_PARTITIONS_IN_LEAD_CONTROLLER_RESOURCE) {
+ if (partitionSet.size() != Helix.NUMBER_OF_PARTITIONS_IN_LEAD_CONTROLLER_RESOURCE) {
return false;
}
for (String partition : partitionSet) {
@@ -212,6 +254,15 @@ public class PinotControllerModeTest extends ControllerTest {
}, TIMEOUT_IN_MS, "Failed to pick only one instance as: " + expectedInstanceState);
}
+ @AfterMethod
+ public void cleanUpCluster() {
+ ZkClient zkClient = new ZkClient(ZkStarter.DEFAULT_ZK_STR);
+ if (zkClient.exists("/" + getHelixClusterName())) {
+ zkClient.deleteRecursive("/" + getHelixClusterName());
+ }
+ zkClient.close();
+ }
+
@AfterClass
public void tearDown() {
stopZk();
diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/SegmentStatusCheckerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/SegmentStatusCheckerTest.java
index c235483..066040e 100644
--- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/SegmentStatusCheckerTest.java
+++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/SegmentStatusCheckerTest.java
@@ -35,10 +35,12 @@ import org.apache.pinot.common.metrics.ControllerMetrics;
import org.apache.pinot.common.utils.CommonConstants;
import org.apache.pinot.common.utils.LLCSegmentName;
import org.apache.pinot.controller.ControllerConf;
+import org.apache.pinot.controller.LeadControllerManager;
import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.testng.Assert;
import org.testng.annotations.Test;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -46,6 +48,7 @@ import static org.mockito.Mockito.when;
public class SegmentStatusCheckerTest {
private SegmentStatusChecker segmentStatusChecker;
private PinotHelixResourceManager helixResourceManager;
+ private LeadControllerManager leadControllerManager;
private MetricsRegistry metricsRegistry;
private ControllerMetrics controllerMetrics;
private ControllerConf config;
@@ -84,9 +87,14 @@ public class SegmentStatusCheckerTest {
when(config.getStatusCheckerFrequencyInSeconds()).thenReturn(300);
when(config.getStatusCheckerWaitForPushTimeInSeconds()).thenReturn(300);
}
+ {
+ leadControllerManager = mock(LeadControllerManager.class);
+ when(leadControllerManager.isLeaderForTable(anyString())).thenReturn(true);
+ }
metricsRegistry = new MetricsRegistry();
controllerMetrics = new ControllerMetrics(metricsRegistry);
- segmentStatusChecker = new SegmentStatusChecker(helixResourceManager, config, controllerMetrics);
+ segmentStatusChecker =
+ new SegmentStatusChecker(helixResourceManager, leadControllerManager, config, controllerMetrics);
segmentStatusChecker.start();
segmentStatusChecker.run();
Assert.assertEquals(
@@ -146,9 +154,14 @@ public class SegmentStatusCheckerTest {
when(config.getStatusCheckerFrequencyInSeconds()).thenReturn(300);
when(config.getStatusCheckerWaitForPushTimeInSeconds()).thenReturn(300);
}
+ {
+ leadControllerManager = mock(LeadControllerManager.class);
+ when(leadControllerManager.isLeaderForTable(anyString())).thenReturn(true);
+ }
metricsRegistry = new MetricsRegistry();
controllerMetrics = new ControllerMetrics(metricsRegistry);
- segmentStatusChecker = new SegmentStatusChecker(helixResourceManager, config, controllerMetrics);
+ segmentStatusChecker =
+ new SegmentStatusChecker(helixResourceManager, leadControllerManager, config, controllerMetrics);
segmentStatusChecker.start();
segmentStatusChecker.run();
Assert.assertEquals(
@@ -222,9 +235,14 @@ public class SegmentStatusCheckerTest {
when(config.getStatusCheckerFrequencyInSeconds()).thenReturn(300);
when(config.getStatusCheckerWaitForPushTimeInSeconds()).thenReturn(0);
}
+ {
+ leadControllerManager = mock(LeadControllerManager.class);
+ when(leadControllerManager.isLeaderForTable(anyString())).thenReturn(true);
+ }
metricsRegistry = new MetricsRegistry();
controllerMetrics = new ControllerMetrics(metricsRegistry);
- segmentStatusChecker = new SegmentStatusChecker(helixResourceManager, config, controllerMetrics);
+ segmentStatusChecker =
+ new SegmentStatusChecker(helixResourceManager, leadControllerManager, config, controllerMetrics);
segmentStatusChecker.start();
segmentStatusChecker.run();
Assert.assertEquals(
@@ -264,9 +282,14 @@ public class SegmentStatusCheckerTest {
when(config.getStatusCheckerFrequencyInSeconds()).thenReturn(300);
when(config.getStatusCheckerWaitForPushTimeInSeconds()).thenReturn(300);
}
+ {
+ leadControllerManager = mock(LeadControllerManager.class);
+ when(leadControllerManager.isLeaderForTable(anyString())).thenReturn(true);
+ }
metricsRegistry = new MetricsRegistry();
controllerMetrics = new ControllerMetrics(metricsRegistry);
- segmentStatusChecker = new SegmentStatusChecker(helixResourceManager, config, controllerMetrics);
+ segmentStatusChecker =
+ new SegmentStatusChecker(helixResourceManager, leadControllerManager, config, controllerMetrics);
segmentStatusChecker.start();
segmentStatusChecker.run();
Assert.assertEquals(controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.SEGMENTS_IN_ERROR_STATE), 0);
@@ -291,9 +314,14 @@ public class SegmentStatusCheckerTest {
when(config.getStatusCheckerFrequencyInSeconds()).thenReturn(300);
when(config.getStatusCheckerWaitForPushTimeInSeconds()).thenReturn(300);
}
+ {
+ leadControllerManager = mock(LeadControllerManager.class);
+ when(leadControllerManager.isLeaderForTable(anyString())).thenReturn(true);
+ }
metricsRegistry = new MetricsRegistry();
controllerMetrics = new ControllerMetrics(metricsRegistry);
- segmentStatusChecker = new SegmentStatusChecker(helixResourceManager, config, controllerMetrics);
+ segmentStatusChecker =
+ new SegmentStatusChecker(helixResourceManager, leadControllerManager, config, controllerMetrics);
segmentStatusChecker.start();
segmentStatusChecker.run();
Assert.assertEquals(controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.SEGMENTS_IN_ERROR_STATE),
@@ -349,9 +377,14 @@ public class SegmentStatusCheckerTest {
when(config.getStatusCheckerFrequencyInSeconds()).thenReturn(300);
when(config.getStatusCheckerWaitForPushTimeInSeconds()).thenReturn(300);
}
+ {
+ leadControllerManager = mock(LeadControllerManager.class);
+ when(leadControllerManager.isLeaderForTable(anyString())).thenReturn(true);
+ }
metricsRegistry = new MetricsRegistry();
controllerMetrics = new ControllerMetrics(metricsRegistry);
- segmentStatusChecker = new SegmentStatusChecker(helixResourceManager, config, controllerMetrics);
+ segmentStatusChecker =
+ new SegmentStatusChecker(helixResourceManager, leadControllerManager, config, controllerMetrics);
segmentStatusChecker.start();
segmentStatusChecker.run();
Assert.assertEquals(
@@ -390,9 +423,14 @@ public class SegmentStatusCheckerTest {
when(config.getStatusCheckerFrequencyInSeconds()).thenReturn(300);
when(config.getStatusCheckerWaitForPushTimeInSeconds()).thenReturn(300);
}
+ {
+ leadControllerManager = mock(LeadControllerManager.class);
+ when(leadControllerManager.isLeaderForTable(anyString())).thenReturn(true);
+ }
metricsRegistry = new MetricsRegistry();
controllerMetrics = new ControllerMetrics(metricsRegistry);
- segmentStatusChecker = new SegmentStatusChecker(helixResourceManager, config, controllerMetrics);
+ segmentStatusChecker =
+ new SegmentStatusChecker(helixResourceManager, leadControllerManager, config, controllerMetrics);
segmentStatusChecker.start();
segmentStatusChecker.run();
Assert.assertEquals(controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.SEGMENTS_IN_ERROR_STATE), 0);
@@ -429,9 +467,14 @@ public class SegmentStatusCheckerTest {
when(config.getStatusCheckerFrequencyInSeconds()).thenReturn(300);
when(config.getStatusCheckerWaitForPushTimeInSeconds()).thenReturn(300);
}
+ {
+ leadControllerManager = mock(LeadControllerManager.class);
+ when(leadControllerManager.isLeaderForTable(anyString())).thenReturn(true);
+ }
metricsRegistry = new MetricsRegistry();
controllerMetrics = new ControllerMetrics(metricsRegistry);
- segmentStatusChecker = new SegmentStatusChecker(helixResourceManager, config, controllerMetrics);
+ segmentStatusChecker =
+ new SegmentStatusChecker(helixResourceManager, leadControllerManager, config, controllerMetrics);
// verify state before test
Assert.assertEquals(controllerMetrics.getValueOfGlobalGauge(ControllerGauge.DISABLED_TABLE_COUNT), 0);
// update metrics
@@ -463,9 +506,14 @@ public class SegmentStatusCheckerTest {
when(config.getStatusCheckerFrequencyInSeconds()).thenReturn(300);
when(config.getStatusCheckerWaitForPushTimeInSeconds()).thenReturn(300);
}
+ {
+ leadControllerManager = mock(LeadControllerManager.class);
+ when(leadControllerManager.isLeaderForTable(anyString())).thenReturn(true);
+ }
metricsRegistry = new MetricsRegistry();
controllerMetrics = new ControllerMetrics(metricsRegistry);
- segmentStatusChecker = new SegmentStatusChecker(helixResourceManager, config, controllerMetrics);
+ segmentStatusChecker =
+ new SegmentStatusChecker(helixResourceManager, leadControllerManager, config, controllerMetrics);
// verify state before test
Assert.assertEquals(controllerMetrics.getValueOfGlobalGauge(ControllerGauge.DISABLED_TABLE_COUNT), 0);
// update metrics
@@ -508,9 +556,14 @@ public class SegmentStatusCheckerTest {
when(config.getStatusCheckerFrequencyInSeconds()).thenReturn(300);
when(config.getStatusCheckerWaitForPushTimeInSeconds()).thenReturn(300);
}
+ {
+ leadControllerManager = mock(LeadControllerManager.class);
+ when(leadControllerManager.isLeaderForTable(anyString())).thenReturn(true);
+ }
metricsRegistry = new MetricsRegistry();
controllerMetrics = new ControllerMetrics(metricsRegistry);
- segmentStatusChecker = new SegmentStatusChecker(helixResourceManager, config, controllerMetrics);
+ segmentStatusChecker =
+ new SegmentStatusChecker(helixResourceManager, leadControllerManager, config, controllerMetrics);
segmentStatusChecker.start();
segmentStatusChecker.run();
Assert.assertEquals(controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.SEGMENTS_IN_ERROR_STATE),
diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManagerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManagerTest.java
index cdf3d9e..d8479dc 100644
--- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManagerTest.java
+++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManagerTest.java
@@ -38,6 +38,7 @@ import org.apache.helix.model.ClusterConfig;
import org.apache.helix.model.ExternalView;
import org.apache.helix.model.IdealState;
import org.apache.helix.model.InstanceConfig;
+import org.apache.helix.model.MasterSlaveSMD;
import org.apache.pinot.common.config.TableConfig;
import org.apache.pinot.common.config.TableNameBuilder;
import org.apache.pinot.common.config.TagNameUtils;
@@ -50,6 +51,7 @@ import org.apache.pinot.common.metadata.segment.RealtimeSegmentZKMetadata;
import org.apache.pinot.common.utils.CommonConstants;
import org.apache.pinot.common.utils.CommonConstants.Helix.TableType;
import org.apache.pinot.common.utils.TenantRole;
+import org.apache.pinot.common.utils.helix.LeadControllerUtils;
import org.apache.pinot.controller.ControllerConf;
import org.apache.pinot.controller.helix.ControllerTest;
import org.apache.pinot.util.TestUtils;
@@ -61,7 +63,6 @@ import org.testng.annotations.Test;
import static org.apache.pinot.common.utils.CommonConstants.Helix.LEAD_CONTROLLER_RESOURCE_NAME;
import static org.apache.pinot.common.utils.CommonConstants.Helix.LEAD_CONTROLLER_RESOURCE_REPLICA_COUNT;
import static org.apache.pinot.common.utils.CommonConstants.Helix.NUMBER_OF_PARTITIONS_IN_LEAD_CONTROLLER_RESOURCE;
-import static org.apache.pinot.common.utils.CommonConstants.Helix.PREFIX_OF_CONTROLLER_INSTANCE;
import static org.apache.pinot.controller.helix.core.PinotHelixResourceManager.InvalidTableConfigException;
@@ -94,7 +95,8 @@ public class PinotHelixResourceManagerTest extends ControllerTest {
.build();
_helixResourceManager.createServerTenant(serverTenant);
- _helixAdmin.enableResource(getHelixClusterName(), LEAD_CONTROLLER_RESOURCE_NAME, true);
+ // Enable lead controller resource
+ enableResourceConfigForLeadControllerResource(true);
}
@Test
@@ -421,8 +423,9 @@ public class PinotHelixResourceManagerTest extends ControllerTest {
for (String partition : leadControllerResourceExternalView.getPartitionSet()) {
Map<String, String> stateMap = leadControllerResourceExternalView.getStateMap(partition);
Map.Entry<String, String> entry = stateMap.entrySet().iterator().next();
- boolean result = (PREFIX_OF_CONTROLLER_INSTANCE + LOCAL_HOST + "_" + _controllerPort).equals(entry.getKey());
- result &= "MASTER".equals(entry.getValue());
+ boolean result =
+ (LeadControllerUtils.generateParticipantInstanceId(LOCAL_HOST, _controllerPort)).equals(entry.getKey());
+ result &= MasterSlaveSMD.States.MASTER.name().equals(entry.getValue());
if (!result) {
return false;
}
@@ -438,19 +441,19 @@ public class PinotHelixResourceManagerTest extends ControllerTest {
List<String> instanceNames = new ArrayList<>(nInstances);
List<Integer> ports = new ArrayList<>(nInstances);
for (int i = 0; i < nInstances; i++) {
- instanceNames.add(PREFIX_OF_CONTROLLER_INSTANCE + LOCAL_HOST + "_" + i);
+ instanceNames.add(LeadControllerUtils.generateParticipantInstanceId(LOCAL_HOST, i));
ports.add(i);
}
List<String> partitions = new ArrayList<>(NUMBER_OF_PARTITIONS_IN_LEAD_CONTROLLER_RESOURCE);
for (int i = 0; i < NUMBER_OF_PARTITIONS_IN_LEAD_CONTROLLER_RESOURCE; i++) {
- partitions.add(LEAD_CONTROLLER_RESOURCE_NAME + "_" + i);
+ partitions.add(LeadControllerUtils.generatePartitionName(i));
}
LinkedHashMap<String, Integer> states = new LinkedHashMap<>(2);
- states.put("OFFLINE", 0);
- states.put("SLAVE", LEAD_CONTROLLER_RESOURCE_REPLICA_COUNT - 1);
- states.put("MASTER", 1);
+ states.put(MasterSlaveSMD.States.OFFLINE.name(), 0);
+ states.put(MasterSlaveSMD.States.SLAVE.name(), LEAD_CONTROLLER_RESOURCE_REPLICA_COUNT - 1);
+ states.put(MasterSlaveSMD.States.MASTER.name(), 1);
CrushEdRebalanceStrategy crushEdRebalanceStrategy = new CrushEdRebalanceStrategy();
crushEdRebalanceStrategy.init(LEAD_CONTROLLER_RESOURCE_NAME, partitions, states, Integer.MAX_VALUE);
diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/periodictask/ControllerPeriodicTaskTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/periodictask/ControllerPeriodicTaskTest.java
index a928922..dcd5469 100644
--- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/periodictask/ControllerPeriodicTaskTest.java
+++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/periodictask/ControllerPeriodicTaskTest.java
@@ -27,10 +27,12 @@ import java.util.stream.IntStream;
import org.apache.pinot.common.metrics.ControllerGauge;
import org.apache.pinot.common.metrics.ControllerMetrics;
import org.apache.pinot.controller.ControllerConf;
+import org.apache.pinot.controller.LeadControllerManager;
import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
@@ -43,6 +45,7 @@ public class ControllerPeriodicTaskTest {
private final ControllerConf _controllerConf = new ControllerConf();
private final PinotHelixResourceManager _resourceManager = mock(PinotHelixResourceManager.class);
+ private final LeadControllerManager _leadControllerManager = mock(LeadControllerManager.class);
private final ControllerMetrics _controllerMetrics = new ControllerMetrics(new MetricsRegistry());
private final AtomicBoolean _startTaskCalled = new AtomicBoolean();
private final AtomicBoolean _stopTaskCalled = new AtomicBoolean();
@@ -52,7 +55,8 @@ public class ControllerPeriodicTaskTest {
private static final String TASK_NAME = "TestTask";
private final ControllerPeriodicTask _task = new ControllerPeriodicTask<Void>(TASK_NAME, RUN_FREQUENCY_IN_SECONDS,
- _controllerConf.getPeriodicTaskInitialDelayInSeconds(), _resourceManager, _controllerMetrics) {
+ _controllerConf.getPeriodicTaskInitialDelayInSeconds(), _resourceManager, _leadControllerManager,
+ _controllerMetrics) {
@Override
protected void setUpTask() {
@@ -81,6 +85,7 @@ public class ControllerPeriodicTaskTest {
List<String> tables = new ArrayList<>(_numTables);
IntStream.range(0, _numTables).forEach(i -> tables.add("table_" + i + " _OFFLINE"));
when(_resourceManager.getAllTables()).thenReturn(tables);
+ when(_leadControllerManager.isLeaderForTable(anyString())).thenReturn(true);
}
private void resetState() {
diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManagerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManagerTest.java
index 04e2b7b..3396066 100644
--- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManagerTest.java
+++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManagerTest.java
@@ -56,7 +56,7 @@ import org.apache.pinot.common.protocols.SegmentCompletionProtocol;
import org.apache.pinot.common.utils.CommonConstants;
import org.apache.pinot.common.utils.LLCSegmentName;
import org.apache.pinot.controller.ControllerConf;
-import org.apache.pinot.controller.ControllerLeadershipManager;
+import org.apache.pinot.controller.LeadControllerManager;
import org.apache.pinot.controller.api.resources.LLCSegmentCompletionHandlers;
import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.apache.pinot.controller.helix.core.PinotTableIdealStateBuilder;
@@ -66,7 +66,6 @@ import org.apache.pinot.core.indexsegment.generator.SegmentVersion;
import org.apache.pinot.core.realtime.impl.fakestream.FakeStreamConfigUtils;
import org.apache.pinot.core.realtime.stream.OffsetCriteria;
import org.apache.pinot.core.realtime.stream.StreamConfig;
-import org.apache.pinot.core.realtime.stream.StreamConfigProperties;
import org.apache.pinot.core.segment.index.SegmentMetadataImpl;
import org.apache.pinot.filesystem.PinotFSFactory;
import org.apache.zookeeper.data.Stat;
@@ -1323,7 +1322,7 @@ public class PinotLLCRealtimeSegmentManagerTest {
protected FakePinotLLCRealtimeSegmentManager(PinotHelixResourceManager pinotHelixResourceManager,
List<String> existingLLCSegments, ControllerMetrics controllerMetrics) {
super(pinotHelixResourceManager, CONTROLLER_CONF, controllerMetrics,
- new ControllerLeadershipManager(pinotHelixResourceManager.getHelixZkManager(), controllerMetrics));
+ new LeadControllerManager(pinotHelixResourceManager.getHelixZkManager(), controllerMetrics));
try {
TableConfigCache mockCache = mock(TableConfigCache.class);
@@ -1513,7 +1512,7 @@ public class PinotLLCRealtimeSegmentManagerTest {
}
@Override
- protected boolean isLeader() {
+ protected boolean isLeader(String tableName) {
return IS_LEADER;
}
diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/SegmentCompletionTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/SegmentCompletionTest.java
index 51ddf67..7dd2944 100644
--- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/SegmentCompletionTest.java
+++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/SegmentCompletionTest.java
@@ -31,7 +31,7 @@ import org.apache.pinot.common.utils.CommonConstants;
import org.apache.pinot.common.utils.LLCSegmentName;
import org.apache.pinot.common.utils.URIUtils;
import org.apache.pinot.controller.ControllerConf;
-import org.apache.pinot.controller.ControllerLeadershipManager;
+import org.apache.pinot.controller.LeadControllerManager;
import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.apache.pinot.controller.helix.core.realtime.segment.CommittingSegmentDescriptor;
import org.apache.zookeeper.data.Stat;
@@ -98,9 +98,9 @@ public class SegmentCompletionTest {
// but segment metadata is fine in zk
private void replaceSegmentCompletionManager()
throws Exception {
- long oldSecs = segmentCompletionMgr._secconds;
+ long oldSecs = segmentCompletionMgr._seconds;
segmentCompletionMgr = new MockSegmentCompletionManager(segmentManager, true, true);
- segmentCompletionMgr._secconds = oldSecs;
+ segmentCompletionMgr._seconds = oldSecs;
Field fsmMapField = SegmentCompletionManager.class.getDeclaredField("_fsmMap");
fsmMapField.setAccessible(true);
fsmMap = (Map<String, Object>) fsmMapField.get(segmentCompletionMgr);
@@ -114,17 +114,17 @@ public class SegmentCompletionTest {
final String reason = "IAmLazy";
// s1 sends offset of 20, gets HOLD at t = 5s;
- segmentCompletionMgr._secconds = 5;
+ segmentCompletionMgr._seconds = 5;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s2 sends offset of 40, gets HOLD
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s3 sends offset of 30, gets catchup to 40
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params =
new Request.Params().withInstanceId(s3).withOffset(s3Offset).withSegmentName(segmentNameStr).withReason(reason);
response = segmentCompletionMgr.segmentStoppedConsuming(params);
@@ -135,27 +135,27 @@ public class SegmentCompletionTest {
segmentManager._stoppedInstance = null;
// Now s1 comes back, and is asked to catchup.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP);
// s2 is asked to commit.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT);
// s3 comes back with new caught up offset, it should get a HOLD, since commit is not done yet.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s2 executes a successful commit
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentCommitStart(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_CONTINUE);
- segmentCompletionMgr._secconds += 5;
+ segmentCompletionMgr._seconds += 5;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr
.segmentCommitEnd(params, true, false, CommittingSegmentDescriptor.fromSegmentCompletionReqParams(params));
@@ -165,7 +165,7 @@ public class SegmentCompletionTest {
Assert.assertFalse(fsmMap.containsKey(segmentNameStr));
// Now if s3 or s1 come back, they are asked to keep the segment they have.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s1).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.KEEP);
@@ -181,7 +181,7 @@ public class SegmentCompletionTest {
Request.Params params;
final String reason = "IAmLazy";
// s1 stops consuming at t = 5;
- segmentCompletionMgr._secconds = 5;
+ segmentCompletionMgr._seconds = 5;
params =
new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr).withReason(reason);
response = segmentCompletionMgr.segmentStoppedConsuming(params);
@@ -192,14 +192,14 @@ public class SegmentCompletionTest {
segmentManager._stoppedInstance = null;
// s2 sends offset of 40, gets HOLD
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s3 sends offset of 30, gets catchup to 40, s2 should have been decided as the winner now
// since we are never expecting to hear back from s1
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s3Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP);
@@ -208,24 +208,24 @@ public class SegmentCompletionTest {
// s3 happens to come back (after catchup to s2offset) before s2 should get a hold since s2 has been decided as
// the winner.
// TODO Can optimize here since s2 is not notified yet.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
Assert.assertEquals(response.getOffset(), s2Offset);
// s2 is asked to commit.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT);
// s2 executes a successful commit
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentCommitStart(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_CONTINUE);
- segmentCompletionMgr._secconds += 5;
+ segmentCompletionMgr._seconds += 5;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr
.segmentCommitEnd(params, true, false, CommittingSegmentDescriptor.fromSegmentCompletionReqParams(params));
@@ -235,7 +235,7 @@ public class SegmentCompletionTest {
Assert.assertFalse(fsmMap.containsKey(segmentNameStr));
// Now if s3 or s1 come back, they are asked to keep the segment they have.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s1).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.KEEP);
@@ -250,7 +250,7 @@ public class SegmentCompletionTest {
throws Exception {
SegmentCompletionProtocol.Response response;
Request.Params params;
- segmentCompletionMgr._secconds = 5;
+ segmentCompletionMgr._seconds = 5;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr)
.withReason("some reason");
@@ -284,7 +284,7 @@ public class SegmentCompletionTest {
SegmentCompletionProtocol.Response response;
Request.Params params;
- segmentCompletionMgr._secconds = 10;
+ segmentCompletionMgr._seconds = 10;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), ControllerResponseStatus.FAILED);
@@ -297,46 +297,46 @@ public class SegmentCompletionTest {
SegmentCompletionProtocol.Response response;
Request.Params params;
// s1 sends offset of 20, gets HOLD at t = 5s;
- segmentCompletionMgr._secconds = 5L;
+ segmentCompletionMgr._seconds = 5L;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s2 sends offset of 40, gets HOLD
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s3 sends offset of 30, gets catchup to 40
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s3Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP);
Assert.assertEquals(response.getOffset(), s2Offset);
// Now s1 comes back, and is asked to catchup.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP);
Assert.assertEquals(response.getOffset(), s2Offset);
// s2 is asked to commit.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
// TODO: Verify controller asked to do a split commit
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT);
// s3 comes back with new caught up offset, it should get a HOLD, since commit is not done yet.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s2 executes a successful commit start
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentCommitStart(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_CONTINUE);
// s2's file does not successfully commit because MockPinotLLCRealtimeSegmentManager.commitSegmentFile() returns
// false when detecting SegmentLocation == "doNotCommitMe";
- segmentCompletionMgr._secconds += 5;
+ segmentCompletionMgr._seconds += 5;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr)
.withSegmentLocation("doNotCommitMe");
response = segmentCompletionMgr
@@ -347,30 +347,30 @@ public class SegmentCompletionTest {
Assert.assertFalse(fsmMap.containsKey(segmentNameStr));
// Now s1 comes back; it is asked to hold.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s1).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// Now s3 comes back; it is asked to commit
- segmentCompletionMgr._secconds += 5;
+ segmentCompletionMgr._seconds += 5;
params = new Request.Params().withInstanceId(s3).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), ControllerResponseStatus.COMMIT);
// Now s2 comes back; it is asked to hold
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), ControllerResponseStatus.HOLD);
// s3 executes a successful commit start
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentCommitStart(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_CONTINUE);
// s3's file successfully commits
- segmentCompletionMgr._secconds += 5;
+ segmentCompletionMgr._seconds += 5;
params = new Request.Params().withInstanceId(s3).withOffset(s2Offset).withSegmentName(segmentNameStr)
.withSegmentLocation("location");
response = segmentCompletionMgr
@@ -385,45 +385,45 @@ public class SegmentCompletionTest {
SegmentCompletionProtocol.Response response;
Request.Params params;
// s1 sends offset of 20, gets HOLD at t = 5s;
- segmentCompletionMgr._secconds = startTime;
+ segmentCompletionMgr._seconds = startTime;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s2 sends offset of 40, gets HOLD
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s3 sends offset of 30, gets catchup to 40
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s3Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP);
Assert.assertEquals(response.getOffset(), s2Offset);
// Now s1 comes back, and is asked to catchup.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP);
Assert.assertEquals(response.getOffset(), s2Offset);
// s2 is asked to commit.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
// TODO: Verify controller asked to do a split commit
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT);
// s3 comes back with new caught up offset, it should get a HOLD, since commit is not done yet.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s2 executes a successful commit
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentCommitStart(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_CONTINUE);
- segmentCompletionMgr._secconds += 5;
+ segmentCompletionMgr._seconds += 5;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr)
.withSegmentLocation("location");
response = segmentCompletionMgr
@@ -434,7 +434,7 @@ public class SegmentCompletionTest {
Assert.assertFalse(fsmMap.containsKey(segmentNameStr));
// Now if s3 or s1 come back, they are asked to keep the segment they have.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s1).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.KEEP);
@@ -450,30 +450,30 @@ public class SegmentCompletionTest {
SegmentCompletionProtocol.Response response;
Request.Params params;
// s1 sends offset of 20, gets HOLD at t = 5s;
- segmentCompletionMgr._secconds = 5L;
+ segmentCompletionMgr._seconds = 5L;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s2 sends offset of 20, gets HOLD
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s3 sends offset of 20, gets commit
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), ControllerResponseStatus.COMMIT);
Assert.assertEquals(response.getOffset(), s1Offset);
// s3 sends a commit start with 20
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentCommitStart(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_CONTINUE);
// s3 comes back to try to commit with a different offset
- segmentCompletionMgr._secconds += 5;
+ segmentCompletionMgr._seconds += 5;
params = new Request.Params().withInstanceId(s3).withOffset(s3Offset).withSegmentName(segmentNameStr)
.withSegmentLocation("location");
response = segmentCompletionMgr
@@ -484,7 +484,7 @@ public class SegmentCompletionTest {
Assert.assertFalse(fsmMap.containsKey(segmentNameStr));
// Now if s2 or s1 come back, they are asked to hold.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
@@ -498,43 +498,43 @@ public class SegmentCompletionTest {
SegmentCompletionProtocol.Response response;
Request.Params params;
// s1 sends offset of 20, gets HOLD at t = 5s;
- segmentCompletionMgr._secconds = startTime;
+ segmentCompletionMgr._seconds = startTime;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s2 sends offset of 40, gets HOLD
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s3 sends offset of 30, gets catchup to 40
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s3Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP);
Assert.assertEquals(response.getOffset(), s2Offset);
// Now s1 comes back, and is asked to catchup.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP);
// s2 is asked to commit.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT);
// s3 comes back with new caught up offset, it should get a HOLD, since commit is not done yet.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s2 executes a successful commit
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentCommitStart(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_CONTINUE);
- segmentCompletionMgr._secconds += 5;
+ segmentCompletionMgr._seconds += 5;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr
.segmentCommitEnd(params, true, false, CommittingSegmentDescriptor.fromSegmentCompletionReqParams(params));
@@ -544,7 +544,7 @@ public class SegmentCompletionTest {
Assert.assertFalse(fsmMap.containsKey(segmentNameStr));
// Now if s3 or s1 come back, they are asked to keep the segment they have.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s1).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.KEEP);
@@ -560,7 +560,7 @@ public class SegmentCompletionTest {
SegmentCompletionProtocol.Response response;
Request.Params params;
// s1 sends offset of 20, gets HOLD at t = 5s;
- segmentCompletionMgr._secconds = 5L;
+ segmentCompletionMgr._seconds = 5L;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr)
.withReason("rowLimit");
response = segmentCompletionMgr.segmentConsumed(params);
@@ -572,7 +572,7 @@ public class SegmentCompletionTest {
throws Exception {
SegmentCompletionProtocol.Response response;
Request.Params params;
- segmentCompletionMgr._secconds = 10L;
+ segmentCompletionMgr._seconds = 10L;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr)
.withReason(SegmentCompletionProtocol.REASON_TIME_LIMIT);
response = segmentCompletionMgr.segmentConsumed(params);
@@ -584,30 +584,30 @@ public class SegmentCompletionTest {
throws Exception {
SegmentCompletionProtocol.Response response;
Request.Params params;
- segmentCompletionMgr._secconds = 10L;
+ segmentCompletionMgr._seconds = 10L;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr)
.withReason(SegmentCompletionProtocol.REASON_ROW_LIMIT);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), ControllerResponseStatus.COMMIT);
// S2 comes with the same offset as S1
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s1Offset).withSegmentName(segmentNameStr)
.withReason(SegmentCompletionProtocol.REASON_ROW_LIMIT);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), ControllerResponseStatus.HOLD);
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
// S3 comes with a different offset and without row limit. we ask it to hold even though it is higher.
params = new Request.Params().withInstanceId(s3).withOffset(s3Offset).withSegmentName(segmentNameStr)
.withReason(SegmentCompletionProtocol.REASON_TIME_LIMIT);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), ControllerResponseStatus.HOLD);
// S1 comes back to commit the segment
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentCommitStart(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_CONTINUE);
- segmentCompletionMgr._secconds += 5;
+ segmentCompletionMgr._seconds += 5;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr
.segmentCommitEnd(params, true, false, CommittingSegmentDescriptor.fromSegmentCompletionReqParams(params));
@@ -643,37 +643,37 @@ public class SegmentCompletionTest {
Request.Params params;
// s1 sends offset of 20, gets HOLD at t = 5s;
final int startTimeSecs = 5;
- segmentCompletionMgr._secconds = startTimeSecs;
+ segmentCompletionMgr._seconds = startTimeSecs;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s2 sends offset of 40, gets HOLD
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// Now s1 comes back again, and is asked to hold
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s2 is asked to commit.
- segmentCompletionMgr._secconds += SegmentCompletionProtocol.MAX_HOLD_TIME_MS / 1000;
+ segmentCompletionMgr._seconds += SegmentCompletionProtocol.MAX_HOLD_TIME_MS / 1000;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT);
// Now s3 comes up with a better offset, but we ask it to hold, since the committer has not committed yet.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s2Offset + 10).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s2 commits.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentCommitStart(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_CONTINUE);
- segmentCompletionMgr._secconds += 5;
+ segmentCompletionMgr._seconds += 5;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr)
.withSegmentLocation("location");
response = segmentCompletionMgr.segmentCommitEnd(params, true, isSplitCommit,
@@ -683,7 +683,7 @@ public class SegmentCompletionTest {
Assert.assertFalse(fsmMap.containsKey(segmentNameStr));
// Now s3 comes back to get a discard.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s2Offset + 10).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.DISCARD);
@@ -699,30 +699,30 @@ public class SegmentCompletionTest {
Request.Params params;
// s1 sends offset of 20, gets HOLD at t = 5s;
final int startTimeSecs = 5;
- segmentCompletionMgr._secconds = startTimeSecs;
+ segmentCompletionMgr._seconds = startTimeSecs;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s2 sends offset of 40, gets HOLD
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s3 is asked to catch up.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s3Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP);
Assert.assertEquals(response.getOffset(), s2Offset);
// Now s2 is asked to commit.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT);
// All servers are dead
- segmentCompletionMgr._secconds += 3600;
+ segmentCompletionMgr._seconds += 3600;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
@@ -734,7 +734,7 @@ public class SegmentCompletionTest {
Assert.assertTrue(fsmMap.containsKey(segmentNameStr));
// Now s2 is asked to commit because the max time to pick committer has passed.
- segmentCompletionMgr._secconds += 4;
+ segmentCompletionMgr._seconds += 4;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), ControllerResponseStatus.COMMIT);
@@ -748,29 +748,29 @@ public class SegmentCompletionTest {
Request.Params params;
// s1 sends offset of 20, gets HOLD at t = 5s;
final int startTimeSecs = 5;
- segmentCompletionMgr._secconds = startTimeSecs;
+ segmentCompletionMgr._seconds = startTimeSecs;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s2 sends offset of 40, gets HOLD
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s3 is asked to hold.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s3Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP);
Assert.assertEquals(response.getOffset(), s2Offset);
// Now s2 is asked to commit.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT);
// Time passes, s2 never comes back.
- segmentCompletionMgr._secconds += SegmentCompletionProtocol.MAX_HOLD_TIME_MS / 1000;
+ segmentCompletionMgr._seconds += SegmentCompletionProtocol.MAX_HOLD_TIME_MS / 1000;
// But since s1 and s3 are in HOLDING state, they should come back again. s1 is asked to catchup
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
@@ -778,7 +778,7 @@ public class SegmentCompletionTest {
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP);
// Max time to commit passes
- segmentCompletionMgr._secconds +=
+ segmentCompletionMgr._seconds +=
SegmentCompletionProtocol.getMaxSegmentCommitTimeMs() * SegmentCompletionProtocol.MAX_HOLD_TIME_MS / 1000;
// s1 comes back with the updated offset, since it was asked to catch up.
@@ -790,19 +790,19 @@ public class SegmentCompletionTest {
Assert.assertFalse(fsmMap.containsKey(segmentNameStr));
// s1 comes back again, a new FSM created
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s1).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s3 comes back
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// And winner chosen when the last one does not come back at all
- segmentCompletionMgr._secconds += 5;
+ segmentCompletionMgr._seconds += 5;
params = new Request.Params().withInstanceId(s3).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT);
@@ -820,28 +820,28 @@ public class SegmentCompletionTest {
final long startTime = 1509242466;
final String tableName = new LLCSegmentName(segmentNameStr).getTableName();
Assert.assertNull(commitTimeMap.get(tableName));
- segmentCompletionMgr._secconds = startTime;
+ segmentCompletionMgr._seconds = startTime;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s2 sends offset of 40, gets HOLD
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s3 sends offset of 30, gets catchup to 40
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s3Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP);
Assert.assertEquals(response.getOffset(), s2Offset);
// Now s1 comes back, and is asked to catchup.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP);
// s2 is asked to commit.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT);
@@ -849,7 +849,7 @@ public class SegmentCompletionTest {
Assert.assertTrue(commitTimeSec > 0);
// Fast forward to one second before commit time, and send a lease renewal request for 20s
- segmentCompletionMgr._secconds = startTime + commitTimeSec - 1;
+ segmentCompletionMgr._seconds = startTime + commitTimeSec - 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr)
.withExtraTimeSec(20);
response = segmentCompletionMgr.extendBuildTime(params);
@@ -857,7 +857,7 @@ public class SegmentCompletionTest {
Assert.assertTrue((fsmMap.containsKey(segmentNameStr)));
// Another lease extension in 19s.
- segmentCompletionMgr._secconds += 19;
+ segmentCompletionMgr._seconds += 19;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr)
.withExtraTimeSec(20);
response = segmentCompletionMgr.extendBuildTime(params);
@@ -865,13 +865,13 @@ public class SegmentCompletionTest {
Assert.assertTrue((fsmMap.containsKey(segmentNameStr)));
// Commit in 15s
- segmentCompletionMgr._secconds += 15;
+ segmentCompletionMgr._seconds += 15;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentCommitStart(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_CONTINUE);
- long commitTimeMs = (segmentCompletionMgr._secconds - startTime) * 1000;
+ long commitTimeMs = (segmentCompletionMgr._seconds - startTime) * 1000;
Assert.assertEquals(commitTimeMap.get(tableName).longValue(), commitTimeMs);
- segmentCompletionMgr._secconds += 55;
+ segmentCompletionMgr._seconds += 55;
response = segmentCompletionMgr
.segmentCommitEnd(params, true, false, CommittingSegmentDescriptor.fromSegmentCompletionReqParams(params));
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_SUCCESS);
@@ -887,28 +887,28 @@ public class SegmentCompletionTest {
final String tableName = new LLCSegmentName(segmentNameStr).getTableName();
// s1 sends offset of 20, gets HOLD at t = 5s;
final long startTime = 5;
- segmentCompletionMgr._secconds = startTime;
+ segmentCompletionMgr._seconds = startTime;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s2 sends offset of 40, gets HOLD
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s3 sends offset of 30, gets catchup to 40
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s3Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP);
Assert.assertEquals(response.getOffset(), s2Offset);
// Now s1 comes back, and is asked to catchup.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP);
// s2 is asked to commit.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT);
@@ -916,7 +916,7 @@ public class SegmentCompletionTest {
Assert.assertTrue(commitTimeSec > 0);
// Fast forward to one second before commit time, and send a lease renewal request for 20s
- segmentCompletionMgr._secconds = startTime + commitTimeSec - 1;
+ segmentCompletionMgr._seconds = startTime + commitTimeSec - 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr)
.withExtraTimeSec(20);
response = segmentCompletionMgr.extendBuildTime(params);
@@ -924,7 +924,7 @@ public class SegmentCompletionTest {
Assert.assertTrue((fsmMap.containsKey(segmentNameStr)));
// Come back too late.
- segmentCompletionMgr._secconds += 25;
+ segmentCompletionMgr._seconds += 25;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentCommitStart(params);
Assert.assertEquals(response.getStatus(), ControllerResponseStatus.HOLD);
@@ -940,28 +940,28 @@ public class SegmentCompletionTest {
Request.Params params;
// s1 sends offset of 20, gets HOLD at t = 5s;
final long startTime = 5;
- segmentCompletionMgr._secconds = startTime;
+ segmentCompletionMgr._seconds = startTime;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s2 sends offset of 40, gets HOLD
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s3 sends offset of 30, gets catchup to 40
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s3Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP);
Assert.assertEquals(response.getOffset(), s2Offset);
// Now s1 comes back, and is asked to catchup.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP);
// s2 is asked to commit.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT);
@@ -969,7 +969,7 @@ public class SegmentCompletionTest {
Assert.assertTrue(commitTimeSec > 0);
// Fast forward to one second before commit time, and send a lease renewal request for 20s
- segmentCompletionMgr._secconds = startTime + commitTimeSec - 1;
+ segmentCompletionMgr._seconds = startTime + commitTimeSec - 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr)
.withExtraTimeSec(20);
response = segmentCompletionMgr.extendBuildTime(params);
@@ -978,14 +978,14 @@ public class SegmentCompletionTest {
final int leaseTimeSec = 20;
// Lease will not be granted if the time taken so far plus lease time exceeds the max allowabale.
- while (segmentCompletionMgr._secconds + leaseTimeSec <= startTime + SegmentCompletionManager
+ while (segmentCompletionMgr._seconds + leaseTimeSec <= startTime + SegmentCompletionManager
.getMaxCommitTimeForAllSegmentsSeconds()) {
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr)
.withExtraTimeSec(leaseTimeSec);
response = segmentCompletionMgr.extendBuildTime(params);
Assert.assertEquals(response.getStatus(), ControllerResponseStatus.PROCESSED);
Assert.assertTrue((fsmMap.containsKey(segmentNameStr)));
- segmentCompletionMgr._secconds += leaseTimeSec;
+ segmentCompletionMgr._seconds += leaseTimeSec;
}
// Now the lease request should fail.
@@ -1002,28 +1002,28 @@ public class SegmentCompletionTest {
SegmentCompletionProtocol.Response response;
Request.Params params;
// s1 sends offset of 20, gets HOLD at t = 5s;
- segmentCompletionMgr._secconds = 5;
+ segmentCompletionMgr._seconds = 5;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s2 sends offset of 40, gets HOLD
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s3 sends offset of 30, gets catchup to 40
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s3Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP);
Assert.assertEquals(response.getOffset(), s2Offset);
// Now s1 comes back, and is asked to catchup.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP);
// s2 is asked to commit.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT);
@@ -1043,7 +1043,7 @@ public class SegmentCompletionTest {
// s2 has no idea the controller failed, so it comes back with a commit,but the controller asks it to hold,
// (essentially a commit failure)
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentCommitStart(params);
Assert.assertTrue(response.getStatus().equals(SegmentCompletionProtocol.ControllerResponseStatus.HOLD));
@@ -1062,28 +1062,28 @@ public class SegmentCompletionTest {
SegmentCompletionProtocol.Response response;
Request.Params params;
// s1 sends offset of 20, gets HOLD at t = 5s;
- segmentCompletionMgr._secconds = 5;
+ segmentCompletionMgr._seconds = 5;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s2 sends offset of 40, gets HOLD
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD);
// s3 sends offset of 30, gets catchup to 40
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s3).withOffset(s3Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP);
Assert.assertEquals(response.getOffset(), s2Offset);
// Now s1 comes back, and is asked to catchup.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s1).withOffset(s1Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP);
// s2 is asked to commit.
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentConsumed(params);
Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT);
@@ -1103,7 +1103,7 @@ public class SegmentCompletionTest {
// s2 has no idea the controller failed, so it comes back with a commit,but the controller asks it to hold,
// (essentially a commit failure)
- segmentCompletionMgr._secconds += 1;
+ segmentCompletionMgr._seconds += 1;
params = new Request.Params().withInstanceId(s2).withOffset(s2Offset).withSegmentName(segmentNameStr);
response = segmentCompletionMgr.segmentCommitStart(params);
Assert.assertTrue(response.getStatus().equals(SegmentCompletionProtocol.ControllerResponseStatus.HOLD));
@@ -1154,7 +1154,7 @@ public class SegmentCompletionTest {
protected MockPinotLLCRealtimeSegmentManager(PinotHelixResourceManager pinotHelixResourceManager,
ControllerMetrics controllerMetrics) {
super(pinotHelixResourceManager, CONTROLLER_CONF, controllerMetrics,
- new ControllerLeadershipManager(pinotHelixResourceManager.getHelixZkManager(), controllerMetrics));
+ new LeadControllerManager(pinotHelixResourceManager.getHelixZkManager(), controllerMetrics));
}
@Override
@@ -1195,7 +1195,7 @@ public class SegmentCompletionTest {
}
public static class MockSegmentCompletionManager extends SegmentCompletionManager {
- public long _secconds;
+ public long _seconds;
private boolean _isLeader;
protected MockSegmentCompletionManager(PinotLLCRealtimeSegmentManager segmentManager, boolean isLeader,
@@ -1210,19 +1210,18 @@ public class SegmentCompletionTest {
protected MockSegmentCompletionManager(HelixManager helixManager, PinotLLCRealtimeSegmentManager segmentManager,
boolean isLeader, ControllerMetrics controllerMetrics) {
- super(helixManager, segmentManager, controllerMetrics,
- new ControllerLeadershipManager(helixManager, controllerMetrics),
+ super(helixManager, segmentManager, controllerMetrics, new LeadControllerManager(helixManager, controllerMetrics),
SegmentCompletionProtocol.getDefaultMaxSegmentCommitTimeSeconds());
_isLeader = isLeader;
}
@Override
protected long getCurrentTimeMs() {
- return _secconds * 1000L;
+ return _seconds * 1000L;
}
@Override
- protected boolean isLeader() {
+ protected boolean isLeader(String tableName) {
return _isLeader;
}
}
diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/relocation/RealtimeSegmentRelocatorTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/relocation/RealtimeSegmentRelocatorTest.java
index 8c5c8ec..9316062 100644
--- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/relocation/RealtimeSegmentRelocatorTest.java
+++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/relocation/RealtimeSegmentRelocatorTest.java
@@ -34,12 +34,14 @@ import org.apache.helix.model.builder.CustomModeISBuilder;
import org.apache.pinot.common.config.RealtimeTagConfig;
import org.apache.pinot.common.metrics.ControllerMetrics;
import org.apache.pinot.controller.ControllerConf;
+import org.apache.pinot.controller.LeadControllerManager;
import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.apache.pinot.controller.helix.core.PinotHelixSegmentOnlineOfflineStateModelGenerator;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -48,6 +50,7 @@ public class RealtimeSegmentRelocatorTest {
private TestRealtimeSegmentRelocator _realtimeSegmentRelocator;
private HelixManager _mockHelixManager;
+ private LeadControllerManager _leadControllerManager;
private String[] serverNames;
private String[] consumingServerNames;
@@ -69,10 +72,13 @@ public class RealtimeSegmentRelocatorTest {
PinotHelixResourceManager mockPinotHelixResourceManager = mock(PinotHelixResourceManager.class);
_mockHelixManager = mock(HelixManager.class);
when(mockPinotHelixResourceManager.getHelixZkManager()).thenReturn(_mockHelixManager);
+ LeadControllerManager mockLeadControllerManager = mock(LeadControllerManager.class);
+ when(mockLeadControllerManager.isLeaderForTable(anyString())).thenReturn(true);
ControllerConf controllerConfig = new ControllerConf();
ControllerMetrics controllerMetrics = new ControllerMetrics(new MetricsRegistry());
_realtimeSegmentRelocator =
- new TestRealtimeSegmentRelocator(mockPinotHelixResourceManager, controllerConfig, controllerMetrics);
+ new TestRealtimeSegmentRelocator(mockPinotHelixResourceManager, mockLeadControllerManager, controllerConfig,
+ controllerMetrics);
final int maxInstances = 20;
serverNames = new String[maxInstances];
@@ -268,9 +274,9 @@ public class RealtimeSegmentRelocatorTest {
private Map<String, List<String>> tagToInstances;
- public TestRealtimeSegmentRelocator(PinotHelixResourceManager pinotHelixResourceManager, ControllerConf config,
- ControllerMetrics controllerMetrics) {
- super(pinotHelixResourceManager, config, controllerMetrics);
+ public TestRealtimeSegmentRelocator(PinotHelixResourceManager pinotHelixResourceManager,
+ LeadControllerManager leadControllerManager, ControllerConf config, ControllerMetrics controllerMetrics) {
+ super(pinotHelixResourceManager, leadControllerManager, config, controllerMetrics);
tagToInstances = new HashedMap();
}
diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/retention/RetentionManagerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/retention/RetentionManagerTest.java
index bb20ca3..96915ff 100644
--- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/retention/RetentionManagerTest.java
+++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/retention/RetentionManagerTest.java
@@ -35,6 +35,7 @@ import org.apache.pinot.common.segment.SegmentMetadata;
import org.apache.pinot.common.utils.CommonConstants;
import org.apache.pinot.common.utils.LLCSegmentName;
import org.apache.pinot.controller.ControllerConf;
+import org.apache.pinot.controller.LeadControllerManager;
import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.apache.pinot.controller.helix.core.PinotTableIdealStateBuilder;
import org.apache.pinot.controller.helix.core.SegmentDeletionManager;
@@ -84,6 +85,9 @@ public class RetentionManagerTest {
PinotHelixResourceManager pinotHelixResourceManager = mock(PinotHelixResourceManager.class);
setupPinotHelixResourceManager(tableConfig, removedSegments, pinotHelixResourceManager);
+ LeadControllerManager leadControllerManager = mock(LeadControllerManager.class);
+ when(leadControllerManager.isLeaderForTable(anyString())).thenReturn(true);
+
when(pinotHelixResourceManager.getTableConfig(OFFLINE_TABLE_NAME)).thenReturn(tableConfig);
when(pinotHelixResourceManager.getOfflineSegmentMetadata(OFFLINE_TABLE_NAME)).thenReturn(metadataList);
@@ -91,7 +95,8 @@ public class RetentionManagerTest {
ControllerMetrics controllerMetrics = new ControllerMetrics(new MetricsRegistry());
conf.setRetentionControllerFrequencyInSeconds(0);
conf.setDeletedSegmentsRetentionInDays(0);
- RetentionManager retentionManager = new RetentionManager(pinotHelixResourceManager, conf, controllerMetrics);
+ RetentionManager retentionManager =
+ new RetentionManager(pinotHelixResourceManager, leadControllerManager, conf, controllerMetrics);
retentionManager.start();
retentionManager.run();
@@ -210,11 +215,14 @@ public class RetentionManagerTest {
setupSegmentMetadata(tableConfig, now, initialNumSegments, removedSegments);
setupPinotHelixResourceManager(tableConfig, removedSegments, pinotHelixResourceManager);
+ LeadControllerManager leadControllerManager = mock(LeadControllerManager.class);
+ when(leadControllerManager.isLeaderForTable(anyString())).thenReturn(true);
+
ControllerConf conf = new ControllerConf();
ControllerMetrics controllerMetrics = new ControllerMetrics(new MetricsRegistry());
conf.setRetentionControllerFrequencyInSeconds(0);
conf.setDeletedSegmentsRetentionInDays(0);
- RetentionManager retentionManager = new RetentionManager(pinotHelixResourceManager, conf, controllerMetrics);
+ RetentionManager retentionManager = new RetentionManager(pinotHelixResourceManager, leadControllerManager, conf, controllerMetrics);
retentionManager.start();
retentionManager.run();
diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/validation/StorageQuotaCheckerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/validation/StorageQuotaCheckerTest.java
index e84a947..a4a2a3d 100644
--- a/pinot-controller/src/test/java/org/apache/pinot/controller/validation/StorageQuotaCheckerTest.java
+++ b/pinot-controller/src/test/java/org/apache/pinot/controller/validation/StorageQuotaCheckerTest.java
@@ -30,8 +30,6 @@ import org.apache.pinot.common.config.TableConfig;
import org.apache.pinot.common.exception.InvalidConfigException;
import org.apache.pinot.common.metrics.ControllerGauge;
import org.apache.pinot.common.metrics.ControllerMetrics;
-import org.apache.pinot.controller.ControllerLeadershipManager;
-import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
import org.apache.pinot.controller.util.TableSizeReader;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -48,8 +46,7 @@ public class StorageQuotaCheckerTest {
private TableSizeReader _tableSizeReader;
private TableConfig _tableConfig;
private ControllerMetrics _controllerMetrics;
- private PinotHelixResourceManager _pinotHelixResourceManager;
- private ControllerLeadershipManager _controllerLeadershipManager;
+ private boolean _isLeaderForTable;
private QuotaConfig _quotaConfig;
private SegmentsValidationAndRetentionConfig _validationConfig;
private static final File TEST_DIR = new File(StorageQuotaCheckerTest.class.getName());
@@ -61,8 +58,7 @@ public class StorageQuotaCheckerTest {
_quotaConfig = mock(QuotaConfig.class);
_controllerMetrics = new ControllerMetrics(new MetricsRegistry());
_validationConfig = mock(SegmentsValidationAndRetentionConfig.class);
- _pinotHelixResourceManager = mock(PinotHelixResourceManager.class);
- _controllerLeadershipManager = mock(ControllerLeadershipManager.class);
+ _isLeaderForTable = true;
when(_tableConfig.getValidationConfig()).thenReturn(_validationConfig);
when(_validationConfig.getReplicationNumber()).thenReturn(2);
TEST_DIR.mkdirs();
@@ -77,11 +73,9 @@ public class StorageQuotaCheckerTest {
public void testNoQuota()
throws InvalidConfigException {
StorageQuotaChecker checker =
- new MockStorageQuotaChecker(_tableConfig, _tableSizeReader, _controllerMetrics, _pinotHelixResourceManager,
- _controllerLeadershipManager);
+ new MockStorageQuotaChecker(_tableConfig, _tableSizeReader, _controllerMetrics, _isLeaderForTable);
when(_tableConfig.getQuotaConfig()).thenReturn(null);
- StorageQuotaChecker.QuotaCheckerResponse res =
- checker.isSegmentStorageWithinQuota(TEST_DIR, "segment", 1000);
+ StorageQuotaChecker.QuotaCheckerResponse res = checker.isSegmentStorageWithinQuota(TEST_DIR, "segment", 1000);
Assert.assertTrue(res.isSegmentWithinQuota);
}
@@ -89,12 +83,10 @@ public class StorageQuotaCheckerTest {
public void testNoStorageQuotaConfig()
throws InvalidConfigException {
StorageQuotaChecker checker =
- new MockStorageQuotaChecker(_tableConfig, _tableSizeReader, _controllerMetrics, _pinotHelixResourceManager,
- _controllerLeadershipManager);
+ new MockStorageQuotaChecker(_tableConfig, _tableSizeReader, _controllerMetrics, _isLeaderForTable);
when(_tableConfig.getQuotaConfig()).thenReturn(_quotaConfig);
when(_quotaConfig.storageSizeBytes()).thenReturn(-1L);
- StorageQuotaChecker.QuotaCheckerResponse res =
- checker.isSegmentStorageWithinQuota(TEST_DIR, "segment", 1000);
+ StorageQuotaChecker.QuotaCheckerResponse res = checker.isSegmentStorageWithinQuota(TEST_DIR, "segment", 1000);
Assert.assertTrue(res.isSegmentWithinQuota);
}
@@ -133,10 +125,8 @@ public class StorageQuotaCheckerTest {
when(_quotaConfig.storageSizeBytes()).thenReturn(3000L);
when(_quotaConfig.getStorage()).thenReturn("3K");
StorageQuotaChecker checker =
- new MockStorageQuotaChecker(_tableConfig, _tableSizeReader, _controllerMetrics, _pinotHelixResourceManager,
- _controllerLeadershipManager);
- StorageQuotaChecker.QuotaCheckerResponse response =
- checker.isSegmentStorageWithinQuota(TEST_DIR, "segment1", 1000);
+ new MockStorageQuotaChecker(_tableConfig, _tableSizeReader, _controllerMetrics, _isLeaderForTable);
+ StorageQuotaChecker.QuotaCheckerResponse response = checker.isSegmentStorageWithinQuota(TEST_DIR, "segment1", 1000);
Assert.assertTrue(response.isSegmentWithinQuota);
Assert.assertEquals(
_controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.TABLE_STORAGE_QUOTA_UTILIZATION), 80L);
@@ -183,14 +173,8 @@ public class StorageQuotaCheckerTest {
private class MockStorageQuotaChecker extends StorageQuotaChecker {
public MockStorageQuotaChecker(TableConfig tableConfig, TableSizeReader tableSizeReader,
- ControllerMetrics controllerMetrics, PinotHelixResourceManager pinotHelixResourceManager,
- ControllerLeadershipManager controllerLeadershipManager) {
- super(tableConfig, tableSizeReader, controllerMetrics, pinotHelixResourceManager, controllerLeadershipManager);
- }
-
- @Override
- protected boolean isLeader() {
- return true;
+ ControllerMetrics controllerMetrics, boolean isLeaderForTable) {
+ super(tableConfig, tableSizeReader, controllerMetrics, isLeaderForTable);
}
}
}
diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/LLRealtimeSegmentDataManager.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/LLRealtimeSegmentDataManager.java
index 04cfec7..fb1a5b3 100644
--- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/LLRealtimeSegmentDataManager.java
+++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/LLRealtimeSegmentDataManager.java
@@ -1063,6 +1063,7 @@ public class LLRealtimeSegmentDataManager extends RealtimeSegmentDataManager {
_segBuildSemaphore = realtimeTableDataManager.getSegmentBuildSemaphore();
_segmentZKMetadata = (LLCRealtimeSegmentZKMetadata) segmentZKMetadata;
_tableConfig = tableConfig;
+ _tableNameWithType = _tableConfig.getTableName();
_realtimeTableDataManager = realtimeTableDataManager;
_resourceDataDir = resourceDataDir;
_indexLoadingConfig = indexLoadingConfig;
@@ -1071,7 +1072,7 @@ public class LLRealtimeSegmentDataManager extends RealtimeSegmentDataManager {
_segmentVersion = indexLoadingConfig.getSegmentVersion();
_instanceId = _realtimeTableDataManager.getServerInstance();
_leaseExtender = SegmentBuildTimeLeaseExtender.getLeaseExtender(_instanceId);
- _protocolHandler = new ServerSegmentCompletionProtocolHandler(_serverMetrics);
+ _protocolHandler = new ServerSegmentCompletionProtocolHandler(_serverMetrics, _tableNameWithType);
// TODO Validate configs
IndexingConfig indexingConfig = _tableConfig.getIndexingConfig();
@@ -1081,7 +1082,6 @@ public class LLRealtimeSegmentDataManager extends RealtimeSegmentDataManager {
_segmentNameStr = _segmentZKMetadata.getSegmentName();
_segmentName = new LLCSegmentName(_segmentNameStr);
_streamPartitionId = _segmentName.getPartitionId();
- _tableNameWithType = _tableConfig.getTableName();
_timeColumnName = tableConfig.getValidationConfig().getTimeColumnName();
_metricKeyName = _tableNameWithType + "-" + _streamTopic + "-" + _streamPartitionId;
segmentLogger = LoggerFactory.getLogger(LLRealtimeSegmentDataManager.class.getName() + "_" + _segmentNameStr);
diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeTableDataManager.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeTableDataManager.java
index 8c8d4a4..47664d2 100644
--- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeTableDataManager.java
+++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeTableDataManager.java
@@ -89,7 +89,7 @@ public class RealtimeTableDataManager extends BaseTableDataManager {
@Override
protected void doInit() {
- _leaseExtender = SegmentBuildTimeLeaseExtender.create(_instanceId, _serverMetrics);
+ _leaseExtender = SegmentBuildTimeLeaseExtender.create(_instanceId, _serverMetrics, _tableNameWithType);
File statsFile = new File(_tableDataDir, STATS_FILE_NAME);
try {
diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentBuildTimeLeaseExtender.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentBuildTimeLeaseExtender.java
index 6676619..fcd6422 100644
--- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentBuildTimeLeaseExtender.java
+++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentBuildTimeLeaseExtender.java
@@ -57,20 +57,20 @@ public class SegmentBuildTimeLeaseExtender {
}
public static synchronized SegmentBuildTimeLeaseExtender create(final String instanceId,
- ServerMetrics serverMetrics) {
+ ServerMetrics serverMetrics, String tableNameWithType) {
SegmentBuildTimeLeaseExtender leaseExtender = INSTANCE_TO_LEASE_EXTENDER.get(instanceId);
if (leaseExtender != null) {
LOGGER.warn("Instance already exists");
} else {
- leaseExtender = new SegmentBuildTimeLeaseExtender(instanceId, serverMetrics);
+ leaseExtender = new SegmentBuildTimeLeaseExtender(instanceId, serverMetrics, tableNameWithType);
INSTANCE_TO_LEASE_EXTENDER.put(instanceId, leaseExtender);
}
return leaseExtender;
}
- private SegmentBuildTimeLeaseExtender(String instanceId, ServerMetrics serverMetrics) {
+ private SegmentBuildTimeLeaseExtender(String instanceId, ServerMetrics serverMetrics, String tableNameWithType) {
_instanceId = instanceId;
- _protocolHandler = new ServerSegmentCompletionProtocolHandler(serverMetrics);
+ _protocolHandler = new ServerSegmentCompletionProtocolHandler(serverMetrics, tableNameWithType);
_executor = new ScheduledThreadPoolExecutor(1);
}
diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/partition/MurmurPartitionFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/data/partition/MurmurPartitionFunction.java
index 24da4dd..7e01655 100644
--- a/pinot-core/src/main/java/org/apache/pinot/core/data/partition/MurmurPartitionFunction.java
+++ b/pinot-core/src/main/java/org/apache/pinot/core/data/partition/MurmurPartitionFunction.java
@@ -20,6 +20,7 @@ package org.apache.pinot.core.data.partition;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
+import org.apache.pinot.common.utils.HashUtil;
import org.apache.pinot.common.utils.StringUtil;
@@ -64,44 +65,6 @@ public class MurmurPartitionFunction implements PartitionFunction {
*/
@VisibleForTesting
int murmur2(final byte[] data) {
- int length = data.length;
- int seed = 0x9747b28c;
- // 'm' and 'r' are mixing constants generated offline.
- // They're not really 'magic', they just happen to work well.
- final int m = 0x5bd1e995;
- final int r = 24;
-
- // Initialize the hash to a random value
- int h = seed ^ length;
- int length4 = length / 4;
-
- for (int i = 0; i < length4; i++) {
- final int i4 = i * 4;
- int k =
- (data[i4 + 0] & 0xff) + ((data[i4 + 1] & 0xff) << 8) + ((data[i4 + 2] & 0xff) << 16) + ((data[i4 + 3] & 0xff)
- << 24);
- k *= m;
- k ^= k >>> r;
- k *= m;
- h *= m;
- h ^= k;
- }
-
- // Handle the last few bytes of the input array
- switch (length % 4) {
- case 3:
- h ^= (data[(length & ~3) + 2] & 0xff) << 16;
- case 2:
- h ^= (data[(length & ~3) + 1] & 0xff) << 8;
- case 1:
- h ^= data[length & ~3] & 0xff;
- h *= m;
- }
-
- h ^= h >>> 13;
- h *= m;
- h ^= h >>> 15;
-
- return h;
+ return HashUtil.murmur2(data);
}
}
diff --git a/pinot-core/src/main/java/org/apache/pinot/server/realtime/ControllerLeaderLocator.java b/pinot-core/src/main/java/org/apache/pinot/server/realtime/ControllerLeaderLocator.java
index 8a3b0fb..12b5eb8 100644
--- a/pinot-core/src/main/java/org/apache/pinot/server/realtime/ControllerLeaderLocator.java
+++ b/pinot-core/src/main/java/org/apache/pinot/server/realtime/ControllerLeaderLocator.java
@@ -19,11 +19,16 @@
package org.apache.pinot.server.realtime;
import com.google.common.annotations.VisibleForTesting;
+import java.util.Map;
import org.apache.helix.AccessOption;
import org.apache.helix.BaseDataAccessor;
import org.apache.helix.HelixManager;
import org.apache.helix.ZNRecord;
-import org.apache.pinot.core.query.utils.Pair;
+import org.apache.helix.model.ExternalView;
+import org.apache.helix.model.MasterSlaveSMD;
+import org.apache.pinot.common.utils.CommonConstants;
+import org.apache.pinot.common.utils.helix.LeadControllerUtils;
+import org.apache.pinot.pql.parsers.utils.Pair;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,7 +42,6 @@ public class ControllerLeaderLocator {
public static final Logger LOGGER = LoggerFactory.getLogger(ControllerLeaderLocator.class);
private final HelixManager _helixManager;
- private final String _clusterName;
// Co-ordinates of the last known controller leader.
private Pair<String, Integer> _controllerLeaderHostPort = null;
@@ -51,7 +55,6 @@ public class ControllerLeaderLocator {
ControllerLeaderLocator(HelixManager helixManager) {
_helixManager = helixManager;
- _clusterName = helixManager.getClusterName();
}
/**
@@ -76,38 +79,148 @@ public class ControllerLeaderLocator {
}
/**
- * Locate the controller leader so that we can send LLC segment completion requests to it.
+ * Locates the controller leader so that we can send LLC segment completion requests to it.
* Checks the {@link ControllerLeaderLocator::_cachedControllerLeaderInvalid} flag and fetches the leader from helix if cached value is invalid
- *
- * @return The host:port string of the current controller leader.
+ * @param rawTableName table name without type.
+ * @return The host-port pair of the current controller leader.
*/
- public synchronized Pair<String, Integer> getControllerLeader() {
+ public synchronized Pair<String, Integer> getControllerLeader(String rawTableName) {
if (!_cachedControllerLeaderInvalid) {
return _controllerLeaderHostPort;
}
+ Pair<String, Integer> leaderForTable = getLeaderForTable(rawTableName);
+ if (leaderForTable == null) {
+ LOGGER.warn("Failed to find a leader for Table: {}", rawTableName);
+ _cachedControllerLeaderInvalid = true;
+ return null;
+ } else {
+ _controllerLeaderHostPort = leaderForTable;
+ _cachedControllerLeaderInvalid = false;
+ LOGGER.info("Setting controller leader to be {}:{}", _controllerLeaderHostPort.getFirst(),
+ _controllerLeaderHostPort.getSecond());
+ return _controllerLeaderHostPort;
+ }
+ }
+
+ /**
+ * Firstly checks whether lead controller resource has been enabled or not.
+ * If yes, use this as the leader for realtime segment completion once partition leader exists.
+ * Otherwise, try to use Helix leader.
+ * @param rawTableName table name without type.
+ * @return the controller leader id with hostname and port for this table, e.g. localhost_9000
+ */
+ private Pair<String, Integer> getLeaderForTable(String rawTableName) {
+ // Checks whether lead controller resource has been enabled or not.
+ if (isLeadControllerResourceEnabled()) {
+ // Gets leader from lead controller resource.
+ return getLeaderFromLeadControllerResource(rawTableName);
+ } else {
+ // Gets Helix leader to be the leader to this table, otherwise returns null.
+ return getHelixClusterLeader();
+ }
+ }
+
+ /**
+ * Checks whether lead controller resource is enabled or not. The switch is in resource config.
+ */
+ private boolean isLeadControllerResourceEnabled() {
BaseDataAccessor<ZNRecord> dataAccessor = _helixManager.getHelixDataAccessor().getBaseDataAccessor();
Stat stat = new Stat();
try {
- ZNRecord znRecord =
- dataAccessor.get("/" + _clusterName + "/CONTROLLER/LEADER", stat, AccessOption.THROW_EXCEPTION_IFNOTEXIST);
- String leader = znRecord.getId();
- int index = leader.lastIndexOf('_');
- String leaderHost = leader.substring(0, index);
- int leaderPort = Integer.valueOf(leader.substring(index + 1));
- _controllerLeaderHostPort = new Pair<>(leaderHost, leaderPort);
- _cachedControllerLeaderInvalid = false;
- LOGGER.info("Setting controller leader to be {}:{} as per znode version {}, mtime {}", leaderHost, leaderPort,
- stat.getVersion(), stat.getMtime());
- return _controllerLeaderHostPort;
+ ZNRecord znRecord = dataAccessor.get("/" + _helixManager.getClusterName() + "/CONFIGS/RESOURCE/"
+ + CommonConstants.Helix.LEAD_CONTROLLER_RESOURCE_NAME, stat, AccessOption.THROW_EXCEPTION_IFNOTEXIST);
+ return Boolean.parseBoolean(znRecord.getSimpleField(CommonConstants.Helix.LEAD_CONTROLLER_RESOURCE_ENABLED_KEY));
} catch (Exception e) {
- LOGGER.warn("Could not locate controller leader, exception", e);
- _cachedControllerLeaderInvalid = true;
+ LOGGER.warn("Could not get whether lead controller resource is enabled or not.", e);
+ return false;
+ }
+ }
+
+ /**
+ * Gets leader from lead controller resource. Null if there is no leader.
+ * @param rawTableName raw table name.
+ * @return pair of instance hostname and port of Helix cluster leader, e.g. {localhost, 9000}.
+ */
+ private Pair<String, Integer> getLeaderFromLeadControllerResource(String rawTableName) {
+ Pair<String, Integer> leaderHostAndPortPair = getLeadControllerInstanceIdForTable(rawTableName);
+ if (leaderHostAndPortPair != null) {
+ return leaderHostAndPortPair;
+ } else {
+ LOGGER.warn("Could not locate leader for table: {}", rawTableName);
+ return null;
+ }
+ }
+
+ /**
+ * Gets Helix leader in the cluster. Null if there is no leader.
+ * @return instance id of Helix cluster leader, e.g. localhost_9000.
+ */
+ private Pair<String, Integer> getHelixClusterLeader() {
+ BaseDataAccessor<ZNRecord> dataAccessor = _helixManager.getHelixDataAccessor().getBaseDataAccessor();
+ Stat stat = new Stat();
+ try {
+ ZNRecord znRecord = dataAccessor.get("/" + _helixManager.getClusterName() + "/CONTROLLER/LEADER", stat,
+ AccessOption.THROW_EXCEPTION_IFNOTEXIST);
+ String helixLeader = znRecord.getId();
+ LOGGER.info("Getting Helix leader: {} as per znode version {}, mtime {}", helixLeader, stat.getVersion(),
+ stat.getMtime());
+ return convertToHostAndPortPair(helixLeader);
+ } catch (Exception e) {
+ LOGGER.warn("Could not locate Helix leader!", e);
return null;
}
}
/**
+ * Gets lead controller participant id for table from lead controller resource.
+ * If the resource is disabled or no controller registered as participant, there is no instance in "MASTER" state.
+ * @param rawTableName table name without type
+ * @return Helix controller instance id for partition leader, e.g. localhost_9000. Null if not found or resource is disabled.
+ */
+ private Pair<String, Integer> getLeadControllerInstanceIdForTable(String rawTableName) {
+ try {
+ ExternalView leadControllerResourceExternalView = _helixManager.getClusterManagmentTool()
+ .getResourceExternalView(_helixManager.getClusterName(), CommonConstants.Helix.LEAD_CONTROLLER_RESOURCE_NAME);
+ if (leadControllerResourceExternalView == null) {
+ LOGGER.warn("External view of lead controller resource is null!");
+ return null;
+ }
+ int partitionId = LeadControllerUtils.getPartitionIdForTable(rawTableName);
+ String partitionName = LeadControllerUtils.generatePartitionName(partitionId);
+ Map<String, String> partitionStateMap = leadControllerResourceExternalView.getStateMap(partitionName);
+
+ // Get master host from partition map. Return null if no master found.
+ for (Map.Entry<String, String> entry : partitionStateMap.entrySet()) {
+ if (MasterSlaveSMD.States.MASTER.name().equals(entry.getValue())) {
+ // Found the controller in master state.
+ // Converts participant id (with Prefix "Controller_") to controller id and assigns it as the leader,
+ // since realtime segment completion protocol doesn't need the prefix in controller instance id.
+ String participantInstanceId = entry.getKey();
+ String controllerInstanceId = participantInstanceId.substring(participantInstanceId.indexOf('_') + 1);
+ return convertToHostAndPortPair(controllerInstanceId);
+ }
+ }
+ LOGGER
+ .warn("There is no controller in MASTER state for partition: {} in lead controller resource", partitionName);
+ } catch (Exception e) {
+ LOGGER.warn("Caught exception when getting lead controller instance Id for table: {}", rawTableName, e);
+ }
+ return null;
+ }
+
+ /**
+ * Converts instance id to a pair of hostname and port.
+ * @param instanceId instance id without any prefix, e.g. localhost_9000
+ * */
+ private Pair<String, Integer> convertToHostAndPortPair(String instanceId) {
+ int index = instanceId.lastIndexOf('_');
+ String leaderHost = instanceId.substring(0, index);
+ int leaderPort = Integer.valueOf(instanceId.substring(index + 1));
+ return new Pair<>(leaderHost, leaderPort);
+ }
+
+ /**
* Invalidates the cached controller leader value by setting the {@link ControllerLeaderLocator::_cacheControllerLeadeInvalid} flag.
* This flag is always checked first by {@link ControllerLeaderLocator::getControllerLeader()} method before returning the leader. If set, leader is fetched from helix, else cached leader value is returned.
*
diff --git a/pinot-core/src/main/java/org/apache/pinot/server/realtime/ServerSegmentCompletionProtocolHandler.java b/pinot-core/src/main/java/org/apache/pinot/server/realtime/ServerSegmentCompletionProtocolHandler.java
index bcfa7ee..7c2ff68 100644
--- a/pinot-core/src/main/java/org/apache/pinot/server/realtime/ServerSegmentCompletionProtocolHandler.java
+++ b/pinot-core/src/main/java/org/apache/pinot/server/realtime/ServerSegmentCompletionProtocolHandler.java
@@ -23,13 +23,14 @@ import java.net.URI;
import java.util.Map;
import javax.net.ssl.SSLContext;
import org.apache.commons.configuration.Configuration;
+import org.apache.pinot.common.config.TableNameBuilder;
import org.apache.pinot.common.metrics.ServerMeter;
import org.apache.pinot.common.metrics.ServerMetrics;
import org.apache.pinot.common.protocols.SegmentCompletionProtocol;
import org.apache.pinot.common.utils.ClientSSLContextGenerator;
import org.apache.pinot.common.utils.CommonConstants;
import org.apache.pinot.common.utils.FileUploadDownloadClient;
-import org.apache.pinot.core.query.utils.Pair;
+import org.apache.pinot.pql.parsers.utils.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,6 +41,7 @@ import static org.apache.pinot.common.utils.CommonConstants.Server.SegmentComple
* A class that handles sending segment completion protocol requests to the controller and getting
* back responses
*/
+// TODO: Use exception based code to handle different types of exceptions.
public class ServerSegmentCompletionProtocolHandler {
private static Logger LOGGER = LoggerFactory.getLogger(ServerSegmentCompletionProtocolHandler.class);
private static final String HTTPS_PROTOCOL = "https";
@@ -51,6 +53,7 @@ public class ServerSegmentCompletionProtocolHandler {
private final FileUploadDownloadClient _fileUploadDownloadClient;
private final ServerMetrics _serverMetrics;
+ private final String _rawTableName;
public static void init(Configuration uploaderConfig) {
Configuration httpsConfig = uploaderConfig.subset(HTTPS_PROTOCOL);
@@ -62,9 +65,10 @@ public class ServerSegmentCompletionProtocolHandler {
uploaderConfig.getInt(CONFIG_OF_SEGMENT_UPLOAD_REQUEST_TIMEOUT_MS, DEFAULT_SEGMENT_UPLOAD_REQUEST_TIMEOUT_MS);
}
- public ServerSegmentCompletionProtocolHandler(ServerMetrics serverMetrics) {
+ public ServerSegmentCompletionProtocolHandler(ServerMetrics serverMetrics, String tableNameWithType) {
_fileUploadDownloadClient = new FileUploadDownloadClient(_sslContext);
_serverMetrics = serverMetrics;
+ _rawTableName = TableNameBuilder.extractRawTableName(tableNameWithType);
}
public SegmentCompletionProtocol.Response segmentCommitStart(SegmentCompletionProtocol.Request.Params params) {
@@ -159,7 +163,7 @@ public class ServerSegmentCompletionProtocolHandler {
private String createSegmentCompletionUrl(SegmentCompletionProtocol.Request request) {
ControllerLeaderLocator leaderLocator = ControllerLeaderLocator.getInstance();
- final Pair<String, Integer> leaderHostPort = leaderLocator.getControllerLeader();
+ final Pair<String, Integer> leaderHostPort = leaderLocator.getControllerLeader(_rawTableName);
if (leaderHostPort == null) {
LOGGER.warn("No leader found while trying to send {}", request.toString());
return null;
diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/LLRealtimeSegmentDataManagerTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/LLRealtimeSegmentDataManagerTest.java
index fd5505d..0598cd2 100644
--- a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/LLRealtimeSegmentDataManagerTest.java
+++ b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/LLRealtimeSegmentDataManagerTest.java
@@ -111,7 +111,7 @@ public class LLRealtimeSegmentDataManagerTest {
private RealtimeTableDataManager createTableDataManager() {
final String instanceId = "server-1";
- SegmentBuildTimeLeaseExtender.create(instanceId, new ServerMetrics(new MetricsRegistry()));
+ SegmentBuildTimeLeaseExtender.create(instanceId, new ServerMetrics(new MetricsRegistry()), _tableName);
RealtimeTableDataManager tableDataManager = mock(RealtimeTableDataManager.class);
when(tableDataManager.getServerInstance()).thenReturn(instanceId);
RealtimeSegmentStatsHistory statsHistory = mock(RealtimeSegmentStatsHistory.class);
diff --git a/pinot-core/src/test/java/org/apache/pinot/server/realtime/ControllerLeaderLocatorTest.java b/pinot-core/src/test/java/org/apache/pinot/server/realtime/ControllerLeaderLocatorTest.java
index 34e955d..a4b94e4 100644
--- a/pinot-core/src/test/java/org/apache/pinot/server/realtime/ControllerLeaderLocatorTest.java
+++ b/pinot-core/src/test/java/org/apache/pinot/server/realtime/ControllerLeaderLocatorTest.java
@@ -18,10 +18,17 @@
*/
package org.apache.pinot.server.realtime;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
import org.apache.helix.BaseDataAccessor;
+import org.apache.helix.HelixAdmin;
import org.apache.helix.HelixDataAccessor;
import org.apache.helix.HelixManager;
import org.apache.helix.ZNRecord;
+import org.apache.helix.model.ExternalView;
+import org.apache.pinot.common.utils.helix.LeadControllerUtils;
import org.apache.pinot.core.query.utils.Pair;
import org.apache.zookeeper.data.Stat;
import org.testng.Assert;
@@ -35,6 +42,7 @@ import static org.mockito.Mockito.when;
public class ControllerLeaderLocatorTest {
+ private String testTable = "testTable";
/**
* Tests the invalidate logic for cached controller leader
@@ -45,6 +53,7 @@ public class ControllerLeaderLocatorTest {
HelixManager helixManager = mock(HelixManager.class);
HelixDataAccessor helixDataAccessor = mock(HelixDataAccessor.class);
BaseDataAccessor<ZNRecord> baseDataAccessor = mock(BaseDataAccessor.class);
+ HelixAdmin helixAdmin = mock(HelixAdmin.class);
ZNRecord znRecord = mock(ZNRecord.class);
final String leaderHost = "host";
final int leaderPort = 12345;
@@ -54,6 +63,8 @@ public class ControllerLeaderLocatorTest {
when(znRecord.getId()).thenReturn(leaderHost + "_" + leaderPort);
when(baseDataAccessor.get(anyString(), any(), anyInt())).thenReturn(znRecord);
when(helixManager.getClusterName()).thenReturn("testCluster");
+ when(helixManager.getClusterManagmentTool()).thenReturn(helixAdmin);
+ when(helixAdmin.getResourceExternalView(anyString(), anyString())).thenReturn(null);
// Create Controller Leader Locator
FakeControllerLeaderLocator.create(helixManager);
@@ -78,7 +89,7 @@ public class ControllerLeaderLocatorTest {
Assert.assertEquals(controllerLeaderLocator.getLastCacheInvalidateMillis(), lastCacheInvalidateMillis);
// getControllerLeader, which validates the cache
- controllerLeaderLocator.getControllerLeader();
+ controllerLeaderLocator.getControllerLeader(testTable);
Assert.assertFalse(controllerLeaderLocator.isCachedControllerLeaderInvalid());
Assert.assertEquals(controllerLeaderLocator.getLastCacheInvalidateMillis(), lastCacheInvalidateMillis);
@@ -104,16 +115,19 @@ public class ControllerLeaderLocatorTest {
HelixManager helixManager = mock(HelixManager.class);
HelixDataAccessor helixDataAccessor = mock(HelixDataAccessor.class);
BaseDataAccessor<ZNRecord> baseDataAccessor = mock(BaseDataAccessor.class);
+ HelixAdmin helixAdmin = mock(HelixAdmin.class);
when(helixManager.getHelixDataAccessor()).thenReturn(helixDataAccessor);
when(helixDataAccessor.getBaseDataAccessor()).thenReturn(baseDataAccessor);
when(baseDataAccessor.get(anyString(), (Stat) any(), anyInt())).thenThrow(new RuntimeException());
+ when(helixManager.getClusterManagmentTool()).thenReturn(helixAdmin);
+ when(helixAdmin.getResourceExternalView(anyString(), anyString())).thenReturn(null);
// Create Controller Leader Locator
FakeControllerLeaderLocator.create(helixManager);
ControllerLeaderLocator controllerLeaderLocator = FakeControllerLeaderLocator.getInstance();
- Assert.assertEquals(controllerLeaderLocator.getControllerLeader(), null);
+ Assert.assertEquals(controllerLeaderLocator.getControllerLeader(testTable), null);
}
@Test
@@ -121,6 +135,7 @@ public class ControllerLeaderLocatorTest {
HelixManager helixManager = mock(HelixManager.class);
HelixDataAccessor helixDataAccessor = mock(HelixDataAccessor.class);
BaseDataAccessor<ZNRecord> baseDataAccessor = mock(BaseDataAccessor.class);
+ HelixAdmin helixAdmin = mock(HelixAdmin.class);
ZNRecord znRecord = mock(ZNRecord.class);
final String leaderHost = "host";
final int leaderPort = 12345;
@@ -130,14 +145,90 @@ public class ControllerLeaderLocatorTest {
when(znRecord.getId()).thenReturn(leaderHost + "_" + leaderPort);
when(baseDataAccessor.get(anyString(), (Stat) any(), anyInt())).thenReturn(znRecord);
when(helixManager.getClusterName()).thenReturn("myCluster");
+ when(helixManager.getClusterManagmentTool()).thenReturn(helixAdmin);
+ when(helixAdmin.getResourceExternalView(anyString(), anyString())).thenReturn(null);
// Create Controller Leader Locator
FakeControllerLeaderLocator.create(helixManager);
ControllerLeaderLocator controllerLeaderLocator = FakeControllerLeaderLocator.getInstance();
Pair<String, Integer> expectedLeaderLocation = new Pair<>(leaderHost, leaderPort);
- Assert.assertEquals(controllerLeaderLocator.getControllerLeader().getFirst(), expectedLeaderLocation.getFirst());
- Assert.assertEquals(controllerLeaderLocator.getControllerLeader().getSecond(), expectedLeaderLocation.getSecond());
+ Assert.assertEquals(controllerLeaderLocator.getControllerLeader(testTable).getFirst(),
+ expectedLeaderLocation.getFirst());
+ Assert.assertEquals(controllerLeaderLocator.getControllerLeader(testTable).getSecond(),
+ expectedLeaderLocation.getSecond());
+ }
+
+ @Test
+ public void testWhenLeadControllerResourceEnabled() {
+ HelixManager helixManager = mock(HelixManager.class);
+ HelixDataAccessor helixDataAccessor = mock(HelixDataAccessor.class);
+ BaseDataAccessor<ZNRecord> baseDataAccessor = mock(BaseDataAccessor.class);
+ HelixAdmin helixAdmin = mock(HelixAdmin.class);
+ ZNRecord znRecord = mock(ZNRecord.class);
+ final String leaderHost = "host";
+ final int leaderPort = 12345;
+
+ when(helixManager.getHelixDataAccessor()).thenReturn(helixDataAccessor);
+ when(helixDataAccessor.getBaseDataAccessor()).thenReturn(baseDataAccessor);
+ when(znRecord.getId()).thenReturn(leaderHost + "_" + leaderPort);
+ when(baseDataAccessor.get(anyString(), (Stat) any(), anyInt())).thenReturn(znRecord);
+ when(helixManager.getClusterName()).thenReturn("myCluster");
+ when(helixManager.getClusterManagmentTool()).thenReturn(helixAdmin);
+ when(helixAdmin.getResourceExternalView(anyString(), anyString())).thenReturn(null);
+
+ // Create Controller Leader Locator
+ FakeControllerLeaderLocator.create(helixManager);
+ ControllerLeaderLocator controllerLeaderLocator = FakeControllerLeaderLocator.getInstance();
+ Pair<String, Integer> expectedLeaderLocation = new Pair<>(leaderHost, leaderPort);
+
+ // Before enabling lead controller resource config, the helix leader should be used.
+ Assert.assertEquals(controllerLeaderLocator.getControllerLeader(testTable).getFirst(),
+ expectedLeaderLocation.getFirst());
+ Assert.assertEquals(controllerLeaderLocator.getControllerLeader(testTable).getSecond(),
+ expectedLeaderLocation.getSecond());
+
+ // Mock the behavior that 40 seconds have passed.
+ controllerLeaderLocator.setLastCacheInvalidateMillis(System.currentTimeMillis() - 40_000L);
+ controllerLeaderLocator.invalidateCachedControllerLeader();
+
+ // After enabling lead controller resource config, the leader in lead controller resource should be used.
+ when(znRecord.getSimpleField(anyString())).thenReturn("true");
+
+ // External view is null, should return null.
+ Assert.assertNull(controllerLeaderLocator.getControllerLeader(testTable));
+
+ ExternalView externalView = mock(ExternalView.class);
+ when(helixAdmin.getResourceExternalView(anyString(), anyString())).thenReturn(externalView);
+ Set<String> partitionSet = new HashSet<>();
+ when(externalView.getPartitionSet()).thenReturn(partitionSet);
+ Map<String, String> partitionStateMap = new HashMap<>();
+ when(externalView.getStateMap(anyString())).thenReturn(partitionStateMap);
+
+ // External view is empty, should return null.
+ Assert.assertNull(controllerLeaderLocator.getControllerLeader(testTable));
+
+ // Adding one host as master, should return the correct host-port pair.
+ partitionSet.add(LeadControllerUtils.generatePartitionName(LeadControllerUtils.getPartitionIdForTable(testTable)));
+ partitionStateMap.put(LeadControllerUtils.generateParticipantInstanceId(leaderHost, leaderPort), "MASTER");
+
+ Assert.assertEquals(controllerLeaderLocator.getControllerLeader(testTable).getFirst(),
+ expectedLeaderLocation.getFirst());
+ Assert.assertEquals(controllerLeaderLocator.getControllerLeader(testTable).getSecond(),
+ expectedLeaderLocation.getSecond());
+
+ // The participant host is in offline state, should return null.
+ partitionStateMap.put(LeadControllerUtils.generateParticipantInstanceId(leaderHost, leaderPort), "OFFLINE");
+
+ // The leader is still valid since the leader is just updated within 30 seconds.
+ Assert.assertNotNull(controllerLeaderLocator.getControllerLeader(testTable));
+
+ // Mock the behavior that 40 seconds have passed.
+ controllerLeaderLocator.setLastCacheInvalidateMillis(System.currentTimeMillis() - 40_000L);
+ controllerLeaderLocator.invalidateCachedControllerLeader();
+
+ // No controller in MASTER state, should return null.
+ Assert.assertNull(controllerLeaderLocator.getControllerLeader(testTable));
}
static class FakeControllerLeaderLocator extends ControllerLeaderLocator {
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/LeadershipChangeSubscriber.java b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/LLCRealtimeClusterWithEnabledLeadControllerResourceIntegrationTest.java
similarity index 64%
rename from pinot-controller/src/main/java/org/apache/pinot/controller/LeadershipChangeSubscriber.java
rename to pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/LLCRealtimeClusterWithEnabledLeadControllerResourceIntegrationTest.java
index d2c646f..681e235 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/LeadershipChangeSubscriber.java
+++ b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/LLCRealtimeClusterWithEnabledLeadControllerResourceIntegrationTest.java
@@ -16,20 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.pinot.controller;
+package org.apache.pinot.integration.tests;
-/**
- * Interface for a subscriber to the {@link ControllerLeadershipManager}
- */
-public interface LeadershipChangeSubscriber {
+import org.apache.pinot.controller.ControllerConf;
- /**
- * Callback to invoke on becoming leader
- */
- void onBecomingLeader();
- /**
- * Callback to invoke on losing leadership
- */
- void onBecomingNonLeader();
+public class LLCRealtimeClusterWithEnabledLeadControllerResourceIntegrationTest extends LLCRealtimeClusterIntegrationTest {
+ @Override
+ public void startController() {
+ ControllerConf controllerConfig = getDefaultControllerConfiguration();
+ startController(controllerConfig);
+ enableResourceConfigForLeadControllerResource(true);
+ }
}
diff --git a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/SegmentCompletionIntegrationTest.java b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/SegmentCompletionIntegrationTest.java
index 84ea948..3e04c0e 100644
--- a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/SegmentCompletionIntegrationTest.java
+++ b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/SegmentCompletionIntegrationTest.java
@@ -139,7 +139,7 @@ public class SegmentCompletionIntegrationTest extends BaseClusterIntegrationTest
// Now report to the controller that we had to stop consumption
ServerSegmentCompletionProtocolHandler protocolHandler =
- new ServerSegmentCompletionProtocolHandler(new ServerMetrics(new MetricsRegistry()));
+ new ServerSegmentCompletionProtocolHandler(new ServerMetrics(new MetricsRegistry()), realtimeTableName);
SegmentCompletionProtocol.Request.Params params = new SegmentCompletionProtocol.Request.Params();
params.withOffset(45688L).withSegmentName(_currentSegment).withReason("RandomReason")
.withInstanceId(_serverInstance);
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org
[incubator-pinot] 01/08: Handle NPE from getting live instance
(#4528)
Posted by xi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
xiangfu pushed a commit to branch winedepot-0.10
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git
commit 09ce670935dd32eaf215f83de4c5fff6cbf32229
Author: Jialiang Li <jl...@linkedin.com>
AuthorDate: Wed Aug 14 10:41:17 2019 -0700
Handle NPE from getting live instance (#4528)
---
.../java/org/apache/pinot/common/utils/ServiceStatus.java | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/ServiceStatus.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/ServiceStatus.java
index 6f00981..2079dbe 100644
--- a/pinot-common/src/main/java/org/apache/pinot/common/utils/ServiceStatus.java
+++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/ServiceStatus.java
@@ -24,6 +24,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import javax.annotation.Nullable;
import org.apache.helix.HelixAdmin;
import org.apache.helix.HelixDataAccessor;
import org.apache.helix.HelixManager;
@@ -209,6 +210,7 @@ public class ServiceStatus {
getResourceListAsString(), _instanceName);
}
+ @Nullable
protected abstract T getState(String resourceName);
protected abstract Map<String, String> getPartitionStateMap(T state);
@@ -381,12 +383,21 @@ public class ServiceStatus {
super(helixManager, clusterName, instanceName, resourcesToMonitor, minResourcesStartPercent);
}
+ /**
+ * Returns the current state for the given resource, or {@code null} if instance is not live or current state does
+ * not exist.
+ */
+ @Nullable
@Override
protected CurrentState getState(String resourceName) {
PropertyKey.Builder keyBuilder = _helixDataAccessor.keyBuilder();
LiveInstance liveInstance = _helixDataAccessor.getProperty(keyBuilder.liveInstance(_instanceName));
- String sessionId = liveInstance.getSessionId();
- return _helixDataAccessor.getProperty(keyBuilder.currentState(_instanceName, sessionId, resourceName));
+ if (liveInstance == null) {
+ return null;
+ } else {
+ String sessionId = liveInstance.getSessionId();
+ return _helixDataAccessor.getProperty(keyBuilder.currentState(_instanceName, sessionId, resourceName));
+ }
}
@Override
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org