xiangfu0 commented on code in PR #19958:
URL: https://github.com/apache/pinot/pull/19958#discussion_r4329944969
##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/TableConfigUtils.java:
##########
@@ -1448,6 +1515,37 @@ static void
validateInstanceAssignmentConfigs(TableConfig tableConfig) {
"numInstancesPerPartition must be 1 when using
IMPLICIT_REALTIME_TABLE_PARTITION_SELECTOR");
}
// TODO: Add more validations for other partition selectors here
+
+ // Partition based placement of OFFLINE segments reads the partition of
a segment from its metadata
+ InstanceReplicaGroupPartitionConfig replicaGroupPartitionConfig =
+ instanceAssignmentConfig.getReplicaGroupPartitionConfig();
+ if (offlineSegmentKeys.contains(instancePartitionsType) &&
replicaGroupPartitionConfig.isReplicaGroupBased()
+ && replicaGroupPartitionConfig.getNumPartitions() > 1) {
+ validateSegmentAssignmentPartitionColumn(tableConfig);
+ }
+ }
+ }
+
+ /// Validates that the partition column for partition based placement of
OFFLINE segments can be resolved, which
+ /// is required when the instance partitions placing them, the OFFLINE ones
or those of a tier of an OFFLINE table,
+ /// have multiple partitions. The partition column configured for segment
assignment must be a column of the
+ /// segment partition config; without a configured column the segment
partition config must have exactly one
+ /// column, which is how the partition id of a segment is read from its
metadata. The OFFLINE instance partitions
+ /// may also be pre-configured, in which case the controller runs this
validation on the fetched ones.
+ public static void validateSegmentAssignmentPartitionColumn(TableConfig
tableConfig) {
+ Map<String, ColumnPartitionConfig> columnPartitionMap =
getColumnPartitionMap(tableConfig);
+ Preconditions.checkState(!columnPartitionMap.isEmpty(),
Review Comment:
**[CRITICAL] Upgrade break: existing partitioned OFFLINE tables without
`segmentPartitionConfig` can no longer be updated.**
Pre-PR, an OFFLINE table with replica-group instance assignment and
`numPartitions > 1` but no `segmentPartitionConfig` validated fine and worked:
`ReplicaGroupSegmentAssignmentStrategy` resolves each segment via
`SegmentUtils.getSegmentPartitionIdOrDefault`, which falls back to a hash of
the segment name when no partition metadata/column exists. This new check
(triggered from `validateInstanceAssignmentConfigs` and again from
`TableConfigValidationUtils.validateInstanceAssignment` for
computed/fetched/pre-configured OFFLINE instance partitions) now throws on
**every** table config update — even unrelated ones like changing retention —
until the operator adds a `segmentPartitionConfig` they never needed. The PR
description only discloses the "multiple columns without a configured column"
case; the no-`segmentPartitionConfig`-at-all case is an undisclosed break for
working production tables.
##########
pinot-spi/src/main/java/org/apache/pinot/spi/config/table/assignment/SegmentAssignmentConfig.java:
##########
@@ -21,21 +21,39 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
+import javax.annotation.Nullable;
import org.apache.pinot.spi.config.BaseJsonConfig;
+/// Config of how segments are assigned to the instance partitions of an
instance partitions type (`OFFLINE` or
+/// `COMPLETED`) or a tier, keyed by its name in `segmentAssignmentConfigMap`
of the table config. Both fields are
+/// optional: the strategy defaults to the one derived from the instance
partitions, and the partition column defaults
+/// to the single column of the segment partition config.
public class SegmentAssignmentConfig extends BaseJsonConfig {
- @JsonPropertyDescription("Configuration for Segment Assignment Strategy")
+ @JsonPropertyDescription("Segment assignment strategy: balanced,
replicagroup, roundrobin or roundrobinreplicagroup "
+ + "(allservers for a dimension table), derived from the instance
partitions if not specified")
@JsonProperty("segmentAssignmentStrategy")
private final String _assignmentStrategy;
+ @JsonPropertyDescription("Column used to resolve the partition of a segment
for partition based segment assignment, "
+ + "the single column of the segment partition config if not specified")
+ private final String _partitionColumn;
+
@JsonCreator
- public SegmentAssignmentConfig(@JsonProperty(value =
"segmentAssignmentStrategy") String assignmentStrategy) {
+ public SegmentAssignmentConfig(@JsonProperty("segmentAssignmentStrategy")
@Nullable String assignmentStrategy,
+ @JsonProperty("partitionColumn") @Nullable String partitionColumn) {
Review Comment:
**[CRITICAL] Mixed-version hazard: a strategy-less `SegmentAssignmentConfig`
NPEs on pre-PR controllers.**
This PR makes `segmentAssignmentStrategy` optional and blesses `{"OFFLINE":
{"partitionColumn": "..."}}` (validation accepts it;
`SegmentValidationUtilsTest`/`OfflineUpsertTableTest` model it). But the null
guard on `getAssignmentStrategy()` only ships in this PR's
`SegmentAssignmentStrategyFactory` change — a controller still running the
previous release executes
`segmentAssignmentConfig.getAssignmentStrategy().toLowerCase()` and throws NPE
at segment assignment time. During a rolling upgrade (or after a rollback,
since the config persists in ZK), every segment upload/rebalance for such a
table fails on the old controller. Per repo guidance, mixed-version
broker/server/controller compatibility must be preserved; consider gating
strategy-less entries for one release or documenting the ordering constraint.
##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/TableConfigUtils.java:
##########
@@ -431,26 +442,70 @@ private static void validateValidationConfig(TableConfig
tableConfig, Schema sch
validateRetentionConfig(tableConfig);
}
- private static void validateSegmentAssignmentConfig(TableConfig tableConfig)
{
+ /// Validates the segment assignment configs: the strategy must be a known
one (`allservers` for a dimension table),
+ /// and the partition column, which only applies to partition based
strategies, must be a column of the segment
+ /// partition config, the same across all entries, and the same as the
deprecated partition columns of the instance
+ /// assignment configs and `replicaGroupStrategyConfig` when those are still
configured.
+ @VisibleForTesting
+ static void validateSegmentAssignmentConfigs(TableConfig tableConfig) {
Map<String, SegmentAssignmentConfig> segmentAssignmentConfigMap =
tableConfig.getSegmentAssignmentConfigMap();
- if (segmentAssignmentConfigMap == null) {
+ if (MapUtils.isEmpty(segmentAssignmentConfigMap)) {
return;
}
- if (tableConfig.isDimTable()) {
- SegmentAssignmentConfig segmentAssignmentConfig =
-
segmentAssignmentConfigMap.get(InstancePartitionsType.OFFLINE.toString());
- if (segmentAssignmentConfig == null) {
- return;
- }
- String segmentAssignmentStrategy =
segmentAssignmentConfig.getAssignmentStrategy();
- if (segmentAssignmentStrategy != null
- &&
!segmentAssignmentStrategy.equalsIgnoreCase(AssignmentStrategy.DIM_TABLE_SEGMENT_ASSIGNMENT_STRATEGY))
{
- throw new IllegalStateException(
- String.format("Dimension table: %s can only use '%s' segment
assignment strategy, found: %s",
- tableConfig.getTableName(),
-
CommonConstants.Segment.AssignmentStrategy.DIM_TABLE_SEGMENT_ASSIGNMENT_STRATEGY,
- segmentAssignmentStrategy));
+ String tableName = tableConfig.getTableName();
+ boolean isDimTable = tableConfig.isDimTable();
+ String partitionColumn = null;
+ for (Map.Entry<String, SegmentAssignmentConfig> entry :
segmentAssignmentConfigMap.entrySet()) {
+ String key = entry.getKey();
+ SegmentAssignmentConfig segmentAssignmentConfig = entry.getValue();
+ String assignmentStrategy =
segmentAssignmentConfig.getAssignmentStrategy();
+ boolean partitionBased = !isDimTable;
+ if (assignmentStrategy != null) {
+ assignmentStrategy = assignmentStrategy.toLowerCase();
+ if (isDimTable) {
+
Preconditions.checkState(assignmentStrategy.equals(AssignmentStrategy.DIM_TABLE_SEGMENT_ASSIGNMENT_STRATEGY),
+ "Dimension table: %s can only use '%s' segment assignment
strategy, found: %s", tableName,
+ AssignmentStrategy.DIM_TABLE_SEGMENT_ASSIGNMENT_STRATEGY,
assignmentStrategy);
+ } else {
+
Preconditions.checkState(SEGMENT_ASSIGNMENT_STRATEGIES.contains(assignmentStrategy),
Review Comment:
**[MAJOR] Strategy whitelist bricks config updates for tables carrying
legacy long-form strategy names.**
Pre-PR, validation never checked the strategy for non-dim tables and
`SegmentAssignmentStrategyFactory` silently ran unknown strings as balanced.
Long-form names demonstrably exist in the wild — this repo's own
`TableConfigSerDeUtilsTest` used `"ReplicaGroupSegmentAssignmentStrategy"`
until this PR — and they lowercase to strings outside `{balanced, roundrobin,
replicagroup, roundrobinreplicagroup}`. After upgrade, any config update of
such a table fails with `Invalid segment assignment strategy`, and "fixing" the
name to `replicagroup` silently flips live assignment behavior from balanced to
replica-group, requiring an unexpected rebalance. The PR acknowledges rejecting
unknown strategies, but blocking all updates of previously-accepted persisted
configs deserves at least a release-note/migration callout — or a warn-only
grace period.
##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/TableConfigUtils.java:
##########
@@ -1448,6 +1515,37 @@ static void
validateInstanceAssignmentConfigs(TableConfig tableConfig) {
"numInstancesPerPartition must be 1 when using
IMPLICIT_REALTIME_TABLE_PARTITION_SELECTOR");
}
// TODO: Add more validations for other partition selectors here
+
+ // Partition based placement of OFFLINE segments reads the partition of
a segment from its metadata
+ InstanceReplicaGroupPartitionConfig replicaGroupPartitionConfig =
+ instanceAssignmentConfig.getReplicaGroupPartitionConfig();
+ if (offlineSegmentKeys.contains(instancePartitionsType) &&
replicaGroupPartitionConfig.isReplicaGroupBased()
+ && replicaGroupPartitionConfig.getNumPartitions() > 1) {
+ validateSegmentAssignmentPartitionColumn(tableConfig);
+ }
+ }
+ }
+
+ /// Validates that the partition column for partition based placement of
OFFLINE segments can be resolved, which
+ /// is required when the instance partitions placing them, the OFFLINE ones
or those of a tier of an OFFLINE table,
+ /// have multiple partitions. The partition column configured for segment
assignment must be a column of the
+ /// segment partition config; without a configured column the segment
partition config must have exactly one
+ /// column, which is how the partition id of a segment is read from its
metadata. The OFFLINE instance partitions
+ /// may also be pre-configured, in which case the controller runs this
validation on the fetched ones.
+ public static void validateSegmentAssignmentPartitionColumn(TableConfig
tableConfig) {
+ Map<String, ColumnPartitionConfig> columnPartitionMap =
getColumnPartitionMap(tableConfig);
+ Preconditions.checkState(!columnPartitionMap.isEmpty(),
+ "Offline table with partitioned instance partitions must have segment
partition config "
+ + "(segmentPartitionConfig in the indexingConfig) to resolve the
partition of a segment");
+ String partitionColumn = getPartitionColumn(tableConfig);
+ if (partitionColumn != null) {
+ Preconditions.checkState(columnPartitionMap.containsKey(partitionColumn),
Review Comment:
**[MAJOR] Legacy-only partition columns are now validated against
`segmentPartitionConfig`, contradicting "still accepted and honored".**
`getPartitionColumn` falls back to the deprecated
`replicaGroupPartitionConfig.partitionColumn` /
`replicaGroupStrategyConfig.partitionColumn`, and this check then requires that
legacy column to exist in `segmentPartitionConfig`. Pre-PR, a legacy column
absent from `segmentPartitionConfig` merely meant the segment ZK-metadata
lookup missed and assignment fell back to name-hash — never a validation error.
Example: an existing table with
`replicaGroupStrategyConfig.partitionColumn="memberId"` (legacy) and
`segmentPartitionConfig` on `"userId"` (added later for query pruning)
validates today, but post-upgrade every config update fails with `Partition
column: memberId ... not found in segment partition config`, even though the
user never touched the new field.
##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/TableConfigUtils.java:
##########
@@ -431,26 +442,70 @@ private static void validateValidationConfig(TableConfig
tableConfig, Schema sch
validateRetentionConfig(tableConfig);
}
- private static void validateSegmentAssignmentConfig(TableConfig tableConfig)
{
+ /// Validates the segment assignment configs: the strategy must be a known
one (`allservers` for a dimension table),
+ /// and the partition column, which only applies to partition based
strategies, must be a column of the segment
+ /// partition config, the same across all entries, and the same as the
deprecated partition columns of the instance
+ /// assignment configs and `replicaGroupStrategyConfig` when those are still
configured.
+ @VisibleForTesting
+ static void validateSegmentAssignmentConfigs(TableConfig tableConfig) {
Map<String, SegmentAssignmentConfig> segmentAssignmentConfigMap =
tableConfig.getSegmentAssignmentConfigMap();
- if (segmentAssignmentConfigMap == null) {
+ if (MapUtils.isEmpty(segmentAssignmentConfigMap)) {
return;
}
- if (tableConfig.isDimTable()) {
- SegmentAssignmentConfig segmentAssignmentConfig =
-
segmentAssignmentConfigMap.get(InstancePartitionsType.OFFLINE.toString());
- if (segmentAssignmentConfig == null) {
- return;
- }
- String segmentAssignmentStrategy =
segmentAssignmentConfig.getAssignmentStrategy();
- if (segmentAssignmentStrategy != null
- &&
!segmentAssignmentStrategy.equalsIgnoreCase(AssignmentStrategy.DIM_TABLE_SEGMENT_ASSIGNMENT_STRATEGY))
{
- throw new IllegalStateException(
- String.format("Dimension table: %s can only use '%s' segment
assignment strategy, found: %s",
- tableConfig.getTableName(),
-
CommonConstants.Segment.AssignmentStrategy.DIM_TABLE_SEGMENT_ASSIGNMENT_STRATEGY,
- segmentAssignmentStrategy));
+ String tableName = tableConfig.getTableName();
+ boolean isDimTable = tableConfig.isDimTable();
+ String partitionColumn = null;
+ for (Map.Entry<String, SegmentAssignmentConfig> entry :
segmentAssignmentConfigMap.entrySet()) {
+ String key = entry.getKey();
+ SegmentAssignmentConfig segmentAssignmentConfig = entry.getValue();
+ String assignmentStrategy =
segmentAssignmentConfig.getAssignmentStrategy();
+ boolean partitionBased = !isDimTable;
+ if (assignmentStrategy != null) {
+ assignmentStrategy = assignmentStrategy.toLowerCase();
+ if (isDimTable) {
+
Preconditions.checkState(assignmentStrategy.equals(AssignmentStrategy.DIM_TABLE_SEGMENT_ASSIGNMENT_STRATEGY),
+ "Dimension table: %s can only use '%s' segment assignment
strategy, found: %s", tableName,
+ AssignmentStrategy.DIM_TABLE_SEGMENT_ASSIGNMENT_STRATEGY,
assignmentStrategy);
+ } else {
+
Preconditions.checkState(SEGMENT_ASSIGNMENT_STRATEGIES.contains(assignmentStrategy),
+ "Invalid segment assignment strategy: %s for: %s of table: %s",
assignmentStrategy, key, tableName);
+ }
+ partitionBased =
PARTITION_BASED_SEGMENT_ASSIGNMENT_STRATEGIES.contains(assignmentStrategy);
+ }
+ String entryPartitionColumn =
segmentAssignmentConfig.getPartitionColumn();
+ if (StringUtils.isNotEmpty(entryPartitionColumn)) {
+ Preconditions.checkState(partitionBased,
+ "Partition column: %s does not apply to the segment assignment
strategy for: %s of table: %s",
+ entryPartitionColumn, key, tableName);
+ Preconditions.checkState(partitionColumn == null ||
partitionColumn.equals(entryPartitionColumn),
+ "Segment assignment configs of table: %s must configure the same
partition column, found: %s and %s",
+ tableName, partitionColumn, entryPartitionColumn);
+ partitionColumn = entryPartitionColumn;
+ }
+ }
+ if (partitionColumn != null) {
+
Preconditions.checkState(getColumnPartitionMap(tableConfig).containsKey(partitionColumn),
Review Comment:
**[MAJOR] No migration path for REALTIME tables: the new `partitionColumn`
requires `segmentPartitionConfig`, which the deprecated column never did.**
A realtime (e.g. upsert) table with
`replicaGroupStrategyConfig.partitionColumn="event_id"` and no
`segmentPartitionConfig` works today — uploaded/committed segments carry
partition metadata in ZK independently of the table-level config (exactly the
shape this PR removed from `simpleMeetup_realtime_table_config.json` and
`StrictRealtimeSegmentAssignmentTest`). Migrating to the advertised replacement
`{"COMPLETED": {"segmentAssignmentStrategy": "replicagroup", "partitionColumn":
"event_id"}}` is rejected here because `getColumnPartitionMap` is empty. The
deprecated field is marked `forRemoval` yet has no valid replacement for such
tables.
##########
pinot-spi/src/main/java/org/apache/pinot/spi/config/table/SegmentsValidationAndRetentionConfig.java:
##########
@@ -233,13 +234,15 @@ public void setReplicasPerPartition(String
replicasPerPartition) {
_replicasPerPartition = replicasPerPartition;
}
- /// @deprecated Use
[org.apache.pinot.spi.config.table.assignment.InstanceAssignmentConfig] instead.
+ /// @deprecated Configure the instance assignment in
`instanceAssignmentConfigMap` with
Review Comment:
**[MAJOR] The advertised new-style config cannot actually replace
`replicaGroupStrategyConfig` on the derived instance-assignment path.**
`InstanceAssignmentConfigUtils.getInstanceAssignmentConfig` (pinot-common,
~line 118) still hard-requires the deprecated config: a table configured the
new way — `segmentAssignmentConfigMap: {OFFLINE: {segmentAssignmentStrategy:
"replicagroup", partitionColumn: "col"}}` with no `instanceAssignmentConfigMap`
and no `replicaGroupStrategyConfig` — passes `allowInstanceAssignment()`
(replica-group strategy implies OFFLINE instance assignment) and then dies with
`Failed to find the replica-group strategy config` during instance-partition
computation at table creation. The new `partitionColumn` is never consulted on
this path, so deleting the `forRemoval` config per the deprecation guidance
breaks table creation. (Pre-existing precondition, but this PR's deprecation
story makes it a dead end.)
##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/TableConfigUtils.java:
##########
@@ -1448,6 +1515,37 @@ static void
validateInstanceAssignmentConfigs(TableConfig tableConfig) {
"numInstancesPerPartition must be 1 when using
IMPLICIT_REALTIME_TABLE_PARTITION_SELECTOR");
}
// TODO: Add more validations for other partition selectors here
+
+ // Partition based placement of OFFLINE segments reads the partition of
a segment from its metadata
+ InstanceReplicaGroupPartitionConfig replicaGroupPartitionConfig =
+ instanceAssignmentConfig.getReplicaGroupPartitionConfig();
+ if (offlineSegmentKeys.contains(instancePartitionsType) &&
replicaGroupPartitionConfig.isReplicaGroupBased()
Review Comment:
**[MAJOR] This trigger ignores an explicitly configured non-partition-based
strategy.**
An OFFLINE table with `segmentAssignmentConfigMap.OFFLINE =
{segmentAssignmentStrategy: "balanced"}` (or `roundrobin`) plus replica-group
instance assignment with `numPartitions > 1` builds
`BalancedNumSegmentAssignmentStrategy`, which never reads a partition column —
yet this condition only checks `isReplicaGroupBased() && getNumPartitions() >
1` and rejects the config with a misleading "must have segment partition
config" error. `validateSegmentAssignmentConfigs` already computes exactly the
`partitionBased` property of the configured strategy but it is not consulted
here (nor in the parallel controller-side check in
`TableConfigValidationUtils`).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]