This is an automated email from the ASF dual-hosted git repository.
czy006 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/amoro.git
The following commit(s) were added to refs/heads/master by this push:
new 8b03fec11 [AMORO-4253] AIP-5 Phase 1: dynamic allocation configuration
and validation (#4254)
8b03fec11 is described below
commit 8b03fec1105aa4b29b5f4d09815c62628187c115
Author: Jiwon Park <[email protected]>
AuthorDate: Fri Jul 10 12:22:05 2026 +0900
[AMORO-4253] AIP-5 Phase 1: dynamic allocation configuration and validation
(#4254)
* [AMORO-4253] AIP-5 Phase 1: dynamic allocation configuration and
validation
First implementation phase of AIP-5 (Dynamic Resource Allocation for
Optimizer, #4191). Configuration foundation only; no runtime scaling
behavior. Dynamic allocation is opt-in and disabled by default, so
existing groups are unaffected.
- OptimizerProperties: declare the dynamic-allocation.* constants (with
@since); deprecate the flat min-parallelism in favor of the namespaced
dynamic-allocation.min-parallelism, still honored as a fallback.
- DynamicAllocationConfig: parse(ResourceGroup) + validate() enforcing
the AIP-5 constraints only when enabled (max-parallelism mandatory,
min <= max <= 1024, executor-idle-timeout >= 30s, positive durations,
reject the external container). resolveMinParallelism centralizes the
namespaced -> legacy -> 0 resolution.
- OptimizerGroupController: reject an invalid DRA config with HTTP 400 on
create/update before persisting.
- DefaultOptimizingService: startup load is fail-safe (invalid config
logs a warning and falls back to DRA-disabled instead of crashing AMS);
the keeper min-parallelism auto-reset is skipped when DRA is
effectively enabled, and otherwise writes the key the group actually
reads (fixing an endless no-op loop for namespaced groups).
Signed-off-by: Jiwon Park <[email protected]>
* [AMORO-4253] Strictly validate min-parallelism on opted-in DRA groups
resolveMinParallelism() is intentionally lenient (unparsable -> 0) for the
keeper hot path and legacy compatibility, but validate() reused that
leniency
for opted-in groups: an enabled group with
'dynamic-allocation.min-parallelism'
set to a non-numeric value silently degraded to 0, and a negative value
passed
the max >= min check unchecked. This was asymmetric with max-parallelism,
which
is parsed strictly and rejected with HTTP 400.
Retain the raw min-parallelism string at parse time so validate() can tell
"explicitly configured but unparsable" from "unset" (the resolved int
collapses
both to 0). When DRA is enabled, an explicitly configured min-parallelism
that
is unparsable or negative is now rejected. The keeper hot path stays
lenient.
Also document the getMaxParallelism() unboxing precondition (nullable until
validated) and leave a TODO for realistic lower bounds on the polling
timeouts.
Signed-off-by: Jiwon Park <[email protected]>
* [AMORO-4253] Unify min-parallelism with max-parallelism as nullable
Integer
Drop the dual int/String representation of min-parallelism in favor of a
single nullable Integer, mirroring max-parallelism: the value is strictly
parsed in parse() (a malformed numeric throws there, honoring parse()'s
contract, instead of being silently degraded to 0 by the lenient keeper
resolver), and null encodes "unset". validate() then only checks the
semantic bounds (non-negative, max >= min).
Also align the trim semantics of the lenient resolveMinParallelism() path
with the strict one so a whitespace-padded value (e.g. " 5 ") that
validate() accepts resolves to the same floor the keeper reads, instead of
silently degrading to 0 at runtime.
Signed-off-by: Jiwon Park <[email protected]>
* [AMORO-4253] Cite the actually-used key in the min-parallelism parse error
When min-parallelism is configured only via the deprecated flat key, a
malformed value reported the namespaced 'dynamic-allocation.min-parallelism'
the user never set. Pick the value and its source key together so the error
names the key the value actually came from, folding the former
rawMinParallelism helper into parseMinParallelism.
Signed-off-by: Jiwon Park <[email protected]>
* [AMORO-4253] Drop the inert DRA validation block from queue loading
The startup validate/fallback block in loadOptimizingQueues was
behaviorally inert: the disable is already implicit (isEffectivelyEnabled()
treats an invalid config as disabled wherever DRA is consumed), and in
Phase 1 nothing strictly parses DRA config at load, so there was no crash
to guard against. Validating inside the queue loader also mixed an unrelated
concern into it. Surfacing invalid configs as an alertable signal belongs
with the metric that lands in the observability phase.
Signed-off-by: Jiwon Park <[email protected]>
---------
Signed-off-by: Jiwon Park <[email protected]>
---
.../amoro/server/DefaultOptimizingService.java | 28 +-
.../controller/OptimizerGroupController.java | 18 +-
.../optimizing/dra/DynamicAllocationConfig.java | 362 +++++++++++++++++++++
.../amoro/server/TestOptimizerGroupController.java | 44 +++
.../amoro/server/TestOptimizerGroupKeeper.java | 61 ++++
.../dra/TestDynamicAllocationConfig.java | 245 ++++++++++++++
.../java/org/apache/amoro/OptimizerProperties.java | 58 +++-
7 files changed, 799 insertions(+), 17 deletions(-)
diff --git
a/amoro-ams/src/main/java/org/apache/amoro/server/DefaultOptimizingService.java
b/amoro-ams/src/main/java/org/apache/amoro/server/DefaultOptimizingService.java
index efc1c375b..52345d2cc 100644
---
a/amoro-ams/src/main/java/org/apache/amoro/server/DefaultOptimizingService.java
+++
b/amoro-ams/src/main/java/org/apache/amoro/server/DefaultOptimizingService.java
@@ -45,6 +45,7 @@ import org.apache.amoro.server.optimizing.OptimizingProcess;
import org.apache.amoro.server.optimizing.OptimizingQueue;
import org.apache.amoro.server.optimizing.OptimizingStatus;
import org.apache.amoro.server.optimizing.TaskRuntime;
+import org.apache.amoro.server.optimizing.dra.DynamicAllocationConfig;
import org.apache.amoro.server.persistence.StatedPersistentBase;
import org.apache.amoro.server.persistence.mapper.OptimizerMapper;
import org.apache.amoro.server.persistence.mapper.ResourceMapper;
@@ -840,19 +841,7 @@ public class DefaultOptimizingService extends
StatedPersistentBase
}
public int getMinParallelism(ResourceGroup resourceGroup) {
- if (!resourceGroup
- .getProperties()
- .containsKey(OptimizerProperties.OPTIMIZER_GROUP_MIN_PARALLELISM)) {
- return 0;
- }
- String minParallelism =
-
resourceGroup.getProperties().get(OptimizerProperties.OPTIMIZER_GROUP_MIN_PARALLELISM);
- try {
- return Integer.parseInt(minParallelism);
- } catch (Throwable t) {
- LOG.warn("Illegal minParallelism : {}, will use default value 0",
minParallelism, t);
- return 0;
- }
+ return DynamicAllocationConfig.resolveMinParallelism(resourceGroup);
}
public int tryKeeping(ResourceGroup resourceGroup) {
@@ -921,6 +910,17 @@ public class DefaultOptimizingService extends
StatedPersistentBase
if (keepingTask.getAttempts() > groupMaxKeepingAttempts) {
int minParallelism = keepingTask.getMinParallelism(resourceGroup);
+ if (DynamicAllocationConfig.isEffectivelyEnabled(resourceGroup)) {
+ // Dynamic allocation owns scale decisions for the group; never
erode its
+ // min-parallelism floor automatically.
+ LOG.warn(
+ "Resource Group:{}, creating optimizer {} times in a row,
optimizers still below min-parallel:{}; dynamic allocation is enabled so
min-parallel is kept",
+ resourceGroup.getName(),
+ keepingTask.getAttempts(),
+ minParallelism);
+ keepInTouch(resourceGroup.getName(), 1);
+ return;
+ }
LOG.warn(
"Resource Group:{}, creating optimizer {} times in a row,
optimizers still below min-parallel:{}, will reset min-parallel to {}",
resourceGroup.getName(),
@@ -930,7 +930,7 @@ public class DefaultOptimizingService extends
StatedPersistentBase
resourceGroup
.getProperties()
.put(
- OptimizerProperties.OPTIMIZER_GROUP_MIN_PARALLELISM,
+
DynamicAllocationConfig.effectiveMinParallelismKey(resourceGroup),
String.valueOf(minParallelism - requiredCores));
updateResourceGroup(resourceGroup);
optimizerManager.updateResourceGroup(resourceGroup);
diff --git
a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/controller/OptimizerGroupController.java
b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/controller/OptimizerGroupController.java
index fac5b8967..2362018b6 100644
---
a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/controller/OptimizerGroupController.java
+++
b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/controller/OptimizerGroupController.java
@@ -31,6 +31,7 @@ import org.apache.amoro.server.dashboard.response.PageResult;
import org.apache.amoro.server.dashboard.utils.PropertiesUtil;
import org.apache.amoro.server.manager.AbstractOptimizerContainer;
import org.apache.amoro.server.optimizing.OptimizingStatus;
+import org.apache.amoro.server.optimizing.dra.DynamicAllocationConfig;
import org.apache.amoro.server.resource.ContainerMetadata;
import org.apache.amoro.server.resource.Containers;
import org.apache.amoro.server.resource.OptimizerInstance;
@@ -242,7 +243,9 @@ public class OptimizerGroupController {
validateGroupName(name);
ResourceGroup.Builder builder = new ResourceGroup.Builder(name, container);
builder.addProperties(properties);
- optimizerManager.createResourceGroup(builder.build());
+ ResourceGroup resourceGroup = builder.build();
+ validateDynamicAllocation(resourceGroup);
+ optimizerManager.createResourceGroup(resourceGroup);
ctx.json(OkResponse.of("The optimizer group has been successfully
created."));
}
@@ -257,7 +260,9 @@ public class OptimizerGroupController {
Map<String, String> properties = PropertiesUtil.sanitizeProperties((Map)
map.get("properties"));
ResourceGroup.Builder builder = new ResourceGroup.Builder(name, container);
builder.addProperties(properties);
- optimizerManager.updateResourceGroup(builder.build());
+ ResourceGroup resourceGroup = builder.build();
+ validateDynamicAllocation(resourceGroup);
+ optimizerManager.updateResourceGroup(resourceGroup);
ctx.json(OkResponse.of("The optimizer group has been successfully
updated."));
}
@@ -283,6 +288,15 @@ public class OptimizerGroupController {
.collect(Collectors.toList())));
}
+ private void validateDynamicAllocation(ResourceGroup resourceGroup) {
+ DynamicAllocationConfig.warnDeprecatedMinParallelism(resourceGroup);
+ try {
+ DynamicAllocationConfig.parse(resourceGroup).validate();
+ } catch (IllegalArgumentException e) {
+ throw new BadRequestException(e.getMessage());
+ }
+ }
+
private void validateGroupName(String groupName) {
if (StringUtils.isEmpty(groupName)) {
throw new BadRequestException(
diff --git
a/amoro-ams/src/main/java/org/apache/amoro/server/optimizing/dra/DynamicAllocationConfig.java
b/amoro-ams/src/main/java/org/apache/amoro/server/optimizing/dra/DynamicAllocationConfig.java
new file mode 100644
index 000000000..ad62ad291
--- /dev/null
+++
b/amoro-ams/src/main/java/org/apache/amoro/server/optimizing/dra/DynamicAllocationConfig.java
@@ -0,0 +1,362 @@
+/*
+ * 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.amoro.server.optimizing.dra;
+
+import org.apache.amoro.Constants;
+import org.apache.amoro.OptimizerProperties;
+import org.apache.amoro.config.ConfigHelpers;
+import org.apache.amoro.resource.ResourceGroup;
+import org.apache.amoro.utils.PropertyUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.Duration;
+import java.util.Map;
+
+/**
+ * Dynamic resource allocation (DRA) configuration of a resource group
(AIP-5). Parsed from the
+ * group's properties; {@link #validate()} enforces the AIP-5 constraints.
+ */
+public class DynamicAllocationConfig {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(DynamicAllocationConfig.class);
+
+ private final String groupName;
+ private final String container;
+ private final boolean enabled;
+ private final Integer minParallelism;
+ private final Integer maxParallelism;
+ private final Duration schedulerBacklogTimeout;
+ private final Duration sustainedBacklogTimeout;
+ private final Duration executorIdleTimeout;
+ private final Duration scaleDownCooldown;
+ private final Duration drainTimeout;
+
+ private DynamicAllocationConfig(
+ String groupName,
+ String container,
+ boolean enabled,
+ Integer minParallelism,
+ Integer maxParallelism,
+ Duration schedulerBacklogTimeout,
+ Duration sustainedBacklogTimeout,
+ Duration executorIdleTimeout,
+ Duration scaleDownCooldown,
+ Duration drainTimeout) {
+ this.groupName = groupName;
+ this.container = container;
+ this.enabled = enabled;
+ this.minParallelism = minParallelism;
+ this.maxParallelism = maxParallelism;
+ this.schedulerBacklogTimeout = schedulerBacklogTimeout;
+ this.sustainedBacklogTimeout = sustainedBacklogTimeout;
+ this.executorIdleTimeout = executorIdleTimeout;
+ this.scaleDownCooldown = scaleDownCooldown;
+ this.drainTimeout = drainTimeout;
+ }
+
+ /**
+ * Parse the DRA configuration of a resource group. Malformed numeric or
duration values throw
+ * {@link IllegalArgumentException}; semantic constraints are checked by
{@link #validate()}.
+ */
+ public static DynamicAllocationConfig parse(ResourceGroup group) {
+ Map<String, String> properties = group.getProperties();
+ boolean enabled =
+ PropertyUtil.propertyAsBoolean(
+ properties,
+ OptimizerProperties.DYNAMIC_ALLOCATION_ENABLED,
+ OptimizerProperties.DYNAMIC_ALLOCATION_ENABLED_DEFAULT);
+ Integer minParallelism = parseMinParallelism(group);
+
+ Integer maxParallelism =
+ PropertyUtil.propertyAsNullableInt(
+ properties,
OptimizerProperties.DYNAMIC_ALLOCATION_MAX_PARALLELISM);
+
+ return new DynamicAllocationConfig(
+ group.getName(),
+ group.getContainer(),
+ enabled,
+ minParallelism,
+ maxParallelism,
+ parseDuration(
+ properties,
+ OptimizerProperties.DYNAMIC_ALLOCATION_SCHEDULER_BACKLOG_TIMEOUT,
+
OptimizerProperties.DYNAMIC_ALLOCATION_SCHEDULER_BACKLOG_TIMEOUT_DEFAULT),
+ parseDuration(
+ properties,
+ OptimizerProperties.DYNAMIC_ALLOCATION_SUSTAINED_BACKLOG_TIMEOUT,
+
OptimizerProperties.DYNAMIC_ALLOCATION_SUSTAINED_BACKLOG_TIMEOUT_DEFAULT),
+ parseDuration(
+ properties,
+ OptimizerProperties.DYNAMIC_ALLOCATION_EXECUTOR_IDLE_TIMEOUT,
+
OptimizerProperties.DYNAMIC_ALLOCATION_EXECUTOR_IDLE_TIMEOUT_DEFAULT),
+ parseDuration(
+ properties,
+ OptimizerProperties.DYNAMIC_ALLOCATION_SCALE_DOWN_COOLDOWN,
+
OptimizerProperties.DYNAMIC_ALLOCATION_SCALE_DOWN_COOLDOWN_DEFAULT),
+ parseDuration(
+ properties,
+ OptimizerProperties.DYNAMIC_ALLOCATION_DRAIN_TIMEOUT,
+ OptimizerProperties.DYNAMIC_ALLOCATION_DRAIN_TIMEOUT_DEFAULT));
+ }
+
+ /**
+ * Resolve the effective min-parallelism of a group, honoring the deprecated
flat {@code
+ * min-parallelism} as a fallback. Resolution order: {@link
+ * OptimizerProperties#DYNAMIC_ALLOCATION_MIN_PARALLELISM} → {@link
+ * OptimizerProperties#OPTIMIZER_GROUP_MIN_PARALLELISM} → {@code 0}.
Lenient: an unparsable value
+ * falls back to {@code 0} rather than throwing, preserving legacy behavior.
This is on the keeper
+ * hot path and therefore stays silent; deprecation is reported by {@link
+ * #warnDeprecatedMinParallelism(ResourceGroup)} at config-entry points
instead.
+ */
+ public static int resolveMinParallelism(ResourceGroup group) {
+ Map<String, String> properties = group.getProperties();
+ String namespaced =
properties.get(OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM);
+ if (namespaced != null) {
+ return parseIntOrDefault(group.getName(), namespaced);
+ }
+ String legacy =
properties.get(OptimizerProperties.OPTIMIZER_GROUP_MIN_PARALLELISM);
+ if (legacy != null) {
+ return parseIntOrDefault(group.getName(), legacy);
+ }
+ return OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM_DEFAULT;
+ }
+
+ /**
+ * Strictly parse the configured min-parallelism, or {@code null} when
unset. Follows the
+ * resolution order of {@link #resolveMinParallelism(ResourceGroup)} ({@link
+ * OptimizerProperties#DYNAMIC_ALLOCATION_MIN_PARALLELISM} → {@link
+ * OptimizerProperties#OPTIMIZER_GROUP_MIN_PARALLELISM}). Mirrors {@code
max-parallelism}: a
+ * malformed value throws at parse time (honoring {@link #parse}'s contract)
rather than being
+ * silently degraded to {@code 0} by the lenient {@link
#resolveMinParallelism(ResourceGroup)} on
+ * the keeper hot path. Shares its trim semantics so the value {@link
#validate()} accepts is the
+ * one the keeper resolves; the error cites the key the value actually came
from.
+ */
+ private static Integer parseMinParallelism(ResourceGroup group) {
+ Map<String, String> properties = group.getProperties();
+ String key = OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM;
+ String raw = properties.get(key);
+ if (raw == null) {
+ key = OptimizerProperties.OPTIMIZER_GROUP_MIN_PARALLELISM;
+ raw = properties.get(key);
+ }
+ if (raw == null) {
+ return null;
+ }
+ try {
+ return Integer.parseInt(raw.trim());
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Resource group:%s '%s'(%s) is not a valid integer.",
group.getName(), key, raw));
+ }
+ }
+
+ /**
+ * Log a one-off deprecation warning when a group still relies on the flat
{@code
+ * min-parallelism}. Intended for config-entry points (startup load, REST
create/update), not the
+ * keeper hot path.
+ */
+ public static void warnDeprecatedMinParallelism(ResourceGroup group) {
+ Map<String, String> properties = group.getProperties();
+ boolean hasNamespaced =
+
properties.containsKey(OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM);
+ boolean hasLegacy =
properties.containsKey(OptimizerProperties.OPTIMIZER_GROUP_MIN_PARALLELISM);
+ if (hasNamespaced && hasLegacy) {
+ LOG.warn(
+ "Resource group:{} sets both '{}' and the deprecated '{}'; the
namespaced value wins.",
+ group.getName(),
+ OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM,
+ OptimizerProperties.OPTIMIZER_GROUP_MIN_PARALLELISM);
+ } else if (hasLegacy) {
+ LOG.warn(
+ "Resource group:{} uses the deprecated '{}'; please migrate to
'{}'.",
+ group.getName(),
+ OptimizerProperties.OPTIMIZER_GROUP_MIN_PARALLELISM,
+ OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM);
+ }
+ }
+
+ /**
+ * Whether dynamic allocation is effectively enabled for the group: opted in
and the configuration
+ * is valid. An invalid configuration counts as disabled, mirroring the
startup fail-safe
+ * behavior.
+ */
+ public static boolean isEffectivelyEnabled(ResourceGroup group) {
+ try {
+ DynamicAllocationConfig config = parse(group);
+ config.validate();
+ return config.isEnabled();
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+
+ /**
+ * The min-parallelism property key that {@link
#resolveMinParallelism(ResourceGroup)} actually
+ * reads for this group. Writers updating the effective value (e.g. the
keeper's auto-reset) must
+ * target this key; writing the deprecated flat key while the namespaced one
is present would be
+ * shadowed.
+ */
+ public static String effectiveMinParallelismKey(ResourceGroup group) {
+ return
group.getProperties().containsKey(OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM)
+ ? OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM
+ : OptimizerProperties.OPTIMIZER_GROUP_MIN_PARALLELISM;
+ }
+
+ /** Enforce the AIP-5 constraints. No-op when DRA is disabled. */
+ public void validate() {
+ if (!enabled) {
+ return;
+ }
+ if (Constants.EXTERNAL_RESOURCE_CONTAINER.equals(container)) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Resource group:%s cannot enable dynamic allocation on an
externally-registered "
+ + "optimizer (container '%s'); AMS cannot scale optimizers
it did not launch.",
+ groupName, container));
+ }
+ if (maxParallelism == null) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Resource group:%s enables dynamic allocation but '%s' is not
set; it is required.",
+ groupName,
OptimizerProperties.DYNAMIC_ALLOCATION_MAX_PARALLELISM));
+ }
+ // min-parallelism: a malformed value already threw in parse(); an
opted-in group must also
+ // reject a negative floor. resolveMinParallelism() stays lenient on the
keeper hot path.
+ int minParallelism =
+ this.minParallelism == null
+ ? OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM_DEFAULT
+ : this.minParallelism;
+ if (minParallelism < 0) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Resource group:%s '%s'(%d) must not be negative.",
+ groupName,
OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM, minParallelism));
+ }
+ if (maxParallelism < minParallelism) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Resource group:%s '%s'(%d) must be >= '%s'(%d).",
+ groupName,
+ OptimizerProperties.DYNAMIC_ALLOCATION_MAX_PARALLELISM,
+ maxParallelism,
+ OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM,
+ minParallelism));
+ }
+ if (maxParallelism >
OptimizerProperties.DYNAMIC_ALLOCATION_MAX_PARALLELISM_LIMIT) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Resource group:%s '%s'(%d) must not exceed the hard limit %d.",
+ groupName,
+ OptimizerProperties.DYNAMIC_ALLOCATION_MAX_PARALLELISM,
+ maxParallelism,
+ OptimizerProperties.DYNAMIC_ALLOCATION_MAX_PARALLELISM_LIMIT));
+ }
+ Duration idleMin =
+ ConfigHelpers.TimeUtils.parseDuration(
+ OptimizerProperties.DYNAMIC_ALLOCATION_EXECUTOR_IDLE_TIMEOUT_MIN);
+ if (executorIdleTimeout.compareTo(idleMin) < 0) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Resource group:%s '%s'(%s) must be >= %s.",
+ groupName,
+ OptimizerProperties.DYNAMIC_ALLOCATION_EXECUTOR_IDLE_TIMEOUT,
+ executorIdleTimeout,
+
OptimizerProperties.DYNAMIC_ALLOCATION_EXECUTOR_IDLE_TIMEOUT_MIN));
+ }
+ // TODO AIP-5 Phase 2: these polling-oriented timeouts only enforce a
positive lower bound; a
+ // value like "1ns" passes here but would busy-spin the scale loop. Add
realistic minimums once
+ // the scaling loop that consumes them lands.
+ requirePositive(
+ OptimizerProperties.DYNAMIC_ALLOCATION_SCHEDULER_BACKLOG_TIMEOUT,
schedulerBacklogTimeout);
+ requirePositive(
+ OptimizerProperties.DYNAMIC_ALLOCATION_SUSTAINED_BACKLOG_TIMEOUT,
sustainedBacklogTimeout);
+
requirePositive(OptimizerProperties.DYNAMIC_ALLOCATION_SCALE_DOWN_COOLDOWN,
scaleDownCooldown);
+ requirePositive(OptimizerProperties.DYNAMIC_ALLOCATION_DRAIN_TIMEOUT,
drainTimeout);
+ }
+
+ private void requirePositive(String property, Duration value) {
+ if (value.isZero() || value.isNegative()) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Resource group:%s '%s'(%s) must be a positive duration.",
+ groupName, property, value));
+ }
+ }
+
+ private static Duration parseDuration(
+ Map<String, String> properties, String property, String defaultValue) {
+ String value = properties.getOrDefault(property, defaultValue);
+ return ConfigHelpers.TimeUtils.parseDuration(value);
+ }
+
+ private static int parseIntOrDefault(String groupName, String value) {
+ try {
+ return Integer.parseInt(value.trim());
+ } catch (NumberFormatException e) {
+ LOG.warn(
+ "Resource group:{} has an illegal min-parallelism value '{}', using
default {}.",
+ groupName,
+ value,
+ OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM_DEFAULT);
+ return OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM_DEFAULT;
+ }
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public int getMinParallelism() {
+ return minParallelism == null
+ ? OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM_DEFAULT
+ : minParallelism;
+ }
+
+ /**
+ * The configured max-parallelism. Precondition: only call on a config that
has passed {@link
+ * #validate()} with DRA enabled — {@code max-parallelism} is nullable and
only mandatory when
+ * enabled, so calling this on a disabled or unvalidated config may throw
{@link
+ * NullPointerException} on unboxing.
+ */
+ public int getMaxParallelism() {
+ return maxParallelism;
+ }
+
+ public Duration getSchedulerBacklogTimeout() {
+ return schedulerBacklogTimeout;
+ }
+
+ public Duration getSustainedBacklogTimeout() {
+ return sustainedBacklogTimeout;
+ }
+
+ public Duration getExecutorIdleTimeout() {
+ return executorIdleTimeout;
+ }
+
+ public Duration getScaleDownCooldown() {
+ return scaleDownCooldown;
+ }
+
+ public Duration getDrainTimeout() {
+ return drainTimeout;
+ }
+}
diff --git
a/amoro-ams/src/test/java/org/apache/amoro/server/TestOptimizerGroupController.java
b/amoro-ams/src/test/java/org/apache/amoro/server/TestOptimizerGroupController.java
index 6e40077c7..a4dfa4290 100644
---
a/amoro-ams/src/test/java/org/apache/amoro/server/TestOptimizerGroupController.java
+++
b/amoro-ams/src/test/java/org/apache/amoro/server/TestOptimizerGroupController.java
@@ -19,14 +19,19 @@
package org.apache.amoro.server;
import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import io.javalin.http.Context;
+import org.apache.amoro.OptimizerProperties;
+import org.apache.amoro.resource.ResourceGroup;
import org.apache.amoro.server.dashboard.controller.OptimizerGroupController;
import org.apache.amoro.server.resource.OptimizerManager;
import org.apache.amoro.server.table.TableManager;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@@ -77,4 +82,43 @@ public class TestOptimizerGroupController {
when(ctx.bodyAsClass(Map.class)).thenReturn(requestBody);
assertThrows(BadRequestException.class, () ->
controller.createResourceGroup(ctx));
}
+
+ private Map<String, Object> groupRequest(String name, Map<String, String>
properties) {
+ Map<String, Object> requestBody = new HashMap<>();
+ requestBody.put("name", name);
+ requestBody.put("container", "flink");
+ requestBody.put("properties", properties);
+ return requestBody;
+ }
+
+ @Test
+ void createWithInvalidDynamicAllocationIsRejected() {
+ Map<String, String> properties = new HashMap<>();
+ // enabled without the required max-parallelism
+ properties.put(OptimizerProperties.DYNAMIC_ALLOCATION_ENABLED, "true");
+
+ when(ctx.bodyAsClass(Map.class)).thenReturn(groupRequest("group1",
properties));
+ assertThrows(BadRequestException.class, () ->
controller.createResourceGroup(ctx));
+ }
+
+ @Test
+ void updateWithInvalidDynamicAllocationIsRejected() {
+ Map<String, String> properties = new HashMap<>();
+ properties.put(OptimizerProperties.DYNAMIC_ALLOCATION_ENABLED, "true");
+ properties.put(OptimizerProperties.DYNAMIC_ALLOCATION_MAX_PARALLELISM,
"2048");
+
+ when(ctx.bodyAsClass(Map.class)).thenReturn(groupRequest("group1",
properties));
+ assertThrows(BadRequestException.class, () ->
controller.updateResourceGroup(ctx));
+ }
+
+ @Test
+ void createWithValidDynamicAllocationSucceeds() {
+ Map<String, String> properties = new HashMap<>();
+ properties.put(OptimizerProperties.DYNAMIC_ALLOCATION_ENABLED, "true");
+ properties.put(OptimizerProperties.DYNAMIC_ALLOCATION_MAX_PARALLELISM,
"16");
+
+ when(ctx.bodyAsClass(Map.class)).thenReturn(groupRequest("group1",
properties));
+ controller.createResourceGroup(ctx);
+ verify(optimizerManager).createResourceGroup(any(ResourceGroup.class));
+ }
}
diff --git
a/amoro-ams/src/test/java/org/apache/amoro/server/TestOptimizerGroupKeeper.java
b/amoro-ams/src/test/java/org/apache/amoro/server/TestOptimizerGroupKeeper.java
index 889fb02cc..20d4504cc 100644
---
a/amoro-ams/src/test/java/org/apache/amoro/server/TestOptimizerGroupKeeper.java
+++
b/amoro-ams/src/test/java/org/apache/amoro/server/TestOptimizerGroupKeeper.java
@@ -313,6 +313,67 @@ public class TestOptimizerGroupKeeper extends
AMSTableTestBase {
+ ":min-parallelism should be reset to optimizer's current total
cores (1) when no more resources available");
}
+ /**
+ * Test scenario 5: min-parallelism auto-reset is skipped when dynamic
allocation is enabled.
+ *
+ * <p>When a group opts into dynamic allocation, DRA owns scale decisions
for the group; the
+ * keeper must not erode its min-parallelism floor even after exhausting
creation attempts.
+ */
+ @Test
+ public void testAutoResetSkippedWhenDynamicAllocationEnabled() throws
InterruptedException {
+ resourceAvailable.set(false);
+ scaleOutCallCount.set(0);
+ ResourceGroup resourceGroup = buildTestResourceGroup(TEST_GROUP_NAME +
"-5", 2);
+
resourceGroup.getProperties().put(OptimizerProperties.DYNAMIC_ALLOCATION_ENABLED,
"true");
+
resourceGroup.getProperties().put(OptimizerProperties.DYNAMIC_ALLOCATION_MAX_PARALLELISM,
"8");
+
+ optimizerManager().createResourceGroup(resourceGroup);
+ optimizingService().createResourceGroup(resourceGroup);
+
+ Thread.sleep(200);
+
+ ResourceGroup updatedGroup =
optimizerManager().getResourceGroup(resourceGroup.getName());
+ Assertions.assertEquals(
+ "2",
+
updatedGroup.getProperties().get(OptimizerProperties.OPTIMIZER_GROUP_MIN_PARALLELISM),
+ resourceGroup.getName()
+ + ":min-parallelism must not be auto-reset when dynamic allocation
is enabled");
+ }
+
+ /**
+ * Test scenario 6: auto-reset writes the namespaced min-parallelism key
when the group uses it.
+ *
+ * <p>A group configured with dynamic-allocation.min-parallelism (DRA not
enabled) must have the
+ * auto-reset applied to that key; writing the deprecated flat key would be
shadowed by the
+ * namespaced one, turning the reset into an endless no-op loop.
+ */
+ @Test
+ public void testAutoResetWritesNamespacedKeyWhenUsed() throws
InterruptedException {
+ resourceAvailable.set(false);
+ scaleOutCallCount.set(0);
+ String groupName = TEST_GROUP_NAME + "-6";
+ this.currentGroupName = groupName;
+ Map<String, String> properties = Maps.newHashMap();
+ properties.put(OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM,
"2");
+ properties.put("memory", "1024");
+ ResourceGroup resourceGroup =
+ new ResourceGroup.Builder(groupName,
MOCK_CONTAINER_NAME).addProperties(properties).build();
+
+ optimizerManager().createResourceGroup(resourceGroup);
+ optimizingService().createResourceGroup(resourceGroup);
+
+ Thread.sleep(200);
+
+ ResourceGroup updatedGroup =
optimizerManager().getResourceGroup(resourceGroup.getName());
+ Assertions.assertEquals(
+ "0",
+
updatedGroup.getProperties().get(OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM),
+ groupName + ":auto-reset must write the namespaced min-parallelism key
the group uses");
+ Assertions.assertNull(
+
updatedGroup.getProperties().get(OptimizerProperties.OPTIMIZER_GROUP_MIN_PARALLELISM),
+ groupName + ":auto-reset must not introduce the deprecated flat
min-parallelism key");
+ }
+
private static OptimizerRegisterInfo buildRegisterInfo(String groupName, int
threadCount) {
OptimizerRegisterInfo registerInfo = new OptimizerRegisterInfo();
Map<String, String> registerProperties = Maps.newHashMap();
diff --git
a/amoro-ams/src/test/java/org/apache/amoro/server/optimizing/dra/TestDynamicAllocationConfig.java
b/amoro-ams/src/test/java/org/apache/amoro/server/optimizing/dra/TestDynamicAllocationConfig.java
new file mode 100644
index 000000000..837584285
--- /dev/null
+++
b/amoro-ams/src/test/java/org/apache/amoro/server/optimizing/dra/TestDynamicAllocationConfig.java
@@ -0,0 +1,245 @@
+/*
+ * 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.amoro.server.optimizing.dra;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+import org.apache.amoro.OptimizerProperties;
+import org.apache.amoro.resource.ResourceGroup;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+
+public class TestDynamicAllocationConfig {
+
+ private static final String CONTAINER = "flink";
+
+ private ResourceGroup group(Map<String, String> properties) {
+ return new ResourceGroup.Builder("group1",
CONTAINER).addProperties(properties).build();
+ }
+
+ private Map<String, String> enabledProps() {
+ Map<String, String> props = new HashMap<>();
+ props.put(OptimizerProperties.DYNAMIC_ALLOCATION_ENABLED, "true");
+ props.put(OptimizerProperties.DYNAMIC_ALLOCATION_MAX_PARALLELISM, "16");
+ return props;
+ }
+
+ private void parseAndValidate(ResourceGroup group) {
+ DynamicAllocationConfig.parse(group).validate();
+ }
+
+ @Test
+ void enabledWithoutMaxParallelismIsRejected() {
+ Map<String, String> props = new HashMap<>();
+ props.put(OptimizerProperties.DYNAMIC_ALLOCATION_ENABLED, "true");
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
parseAndValidate(group(props)));
+ }
+
+ @Test
+ void maxParallelismBelowMinIsRejected() {
+ Map<String, String> props = enabledProps();
+ props.put(OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM, "32");
+ props.put(OptimizerProperties.DYNAMIC_ALLOCATION_MAX_PARALLELISM, "16");
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
parseAndValidate(group(props)));
+ }
+
+ @Test
+ void maxParallelismAboveHardLimitIsRejected() {
+ Map<String, String> props = enabledProps();
+ props.put(OptimizerProperties.DYNAMIC_ALLOCATION_MAX_PARALLELISM, "2048");
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
parseAndValidate(group(props)));
+ }
+
+ @Test
+ void enabledWithUnparsableMinParallelismIsRejected() {
+ // resolveMinParallelism() is lenient (legacy/keeper path), but an
opted-in group must not
+ // silently degrade an unparsable min-parallelism to 0.
+ Map<String, String> props = enabledProps();
+ props.put(OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM, "abc");
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
parseAndValidate(group(props)));
+ }
+
+ @Test
+ void enabledWithNegativeMinParallelismIsRejected() {
+ Map<String, String> props = enabledProps();
+ props.put(OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM, "-3");
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
parseAndValidate(group(props)));
+ }
+
+ @Test
+ void malformedMinParallelismIsRejectedAtParseEvenWhenDisabled() {
+ // Mirrors max-parallelism: a malformed numeric is rejected at parse()
regardless of enabled,
+ // honoring parse()'s documented contract rather than being silently
degraded to 0.
+ Map<String, String> props = new HashMap<>();
+ props.put(OptimizerProperties.OPTIMIZER_GROUP_MIN_PARALLELISM, "abc");
+ Assertions.assertThrows(
+ IllegalArgumentException.class, () ->
DynamicAllocationConfig.parse(group(props)));
+ }
+
+ @Test
+ void malformedMinParallelismErrorCitesTheKeyActuallyUsed() {
+ // Value comes only from the legacy key; the error must name that key, not
the namespaced one
+ // the user never set.
+ Map<String, String> props = new HashMap<>();
+ props.put(OptimizerProperties.OPTIMIZER_GROUP_MIN_PARALLELISM, "abc");
+ IllegalArgumentException e =
+ Assertions.assertThrows(
+ IllegalArgumentException.class, () ->
DynamicAllocationConfig.parse(group(props)));
+ Assertions.assertTrue(
+ e.getMessage().contains("'" +
OptimizerProperties.OPTIMIZER_GROUP_MIN_PARALLELISM + "'"),
+ "error should cite the legacy key actually used: " + e.getMessage());
+ }
+
+ @Test
+ void whitespacePaddedMinParallelismIsResolvedConsistently() {
+ // validate() trims, so it accepts " 5 " as 5; the keeper's
resolveMinParallelism() must agree.
+ // Otherwise a config validate() accepts silently degrades to a floor of 0
at runtime.
+ Map<String, String> props = enabledProps();
+ props.put(OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM, " 5 ");
+ ResourceGroup g = group(props);
+
+ DynamicAllocationConfig config = DynamicAllocationConfig.parse(g);
+ assertDoesNotThrow(config::validate);
+ Assertions.assertEquals(5, config.getMinParallelism());
+ Assertions.assertEquals(5,
DynamicAllocationConfig.resolveMinParallelism(g));
+ }
+
+ @Test
+ void executorIdleTimeoutBelowMinimumIsRejected() {
+ Map<String, String> props = enabledProps();
+ props.put(OptimizerProperties.DYNAMIC_ALLOCATION_EXECUTOR_IDLE_TIMEOUT,
"10s");
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
parseAndValidate(group(props)));
+ }
+
+ @Test
+ void unparsableDurationIsRejected() {
+ Map<String, String> props = enabledProps();
+
props.put(OptimizerProperties.DYNAMIC_ALLOCATION_SCHEDULER_BACKLOG_TIMEOUT,
"not-a-duration");
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
parseAndValidate(group(props)));
+ }
+
+ @Test
+ void zeroDurationIsRejected() {
+ Map<String, String> props = enabledProps();
+
props.put(OptimizerProperties.DYNAMIC_ALLOCATION_SCHEDULER_BACKLOG_TIMEOUT,
"0s");
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
parseAndValidate(group(props)));
+ }
+
+ @Test
+ void externalContainerWithEnabledIsRejected() {
+ ResourceGroup external =
+ new
ResourceGroup.Builder("group1").addProperties(enabledProps()).build();
+ Assertions.assertThrows(
+ IllegalArgumentException.class, () ->
DynamicAllocationConfig.parse(external).validate());
+ }
+
+ @Test
+ void validConfigIsParsedCorrectly() {
+ Map<String, String> props = enabledProps();
+ props.put(OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM, "4");
+
props.put(OptimizerProperties.DYNAMIC_ALLOCATION_SCHEDULER_BACKLOG_TIMEOUT,
"2min");
+ props.put(OptimizerProperties.DYNAMIC_ALLOCATION_EXECUTOR_IDLE_TIMEOUT,
"10min");
+
+ DynamicAllocationConfig config =
DynamicAllocationConfig.parse(group(props));
+ assertDoesNotThrow(config::validate);
+
+ Assertions.assertTrue(config.isEnabled());
+ Assertions.assertEquals(4, config.getMinParallelism());
+ Assertions.assertEquals(16, config.getMaxParallelism());
+ Assertions.assertEquals(Duration.ofMinutes(2),
config.getSchedulerBacklogTimeout());
+ Assertions.assertEquals(Duration.ofMinutes(10),
config.getExecutorIdleTimeout());
+ Assertions.assertEquals(Duration.ofSeconds(30),
config.getSustainedBacklogTimeout());
+ Assertions.assertEquals(Duration.ofMinutes(1),
config.getScaleDownCooldown());
+ Assertions.assertEquals(Duration.ofMinutes(15), config.getDrainTimeout());
+ }
+
+ @Test
+ void minParallelismResolutionPrefersNamespacedValue() {
+ Map<String, String> props = enabledProps();
+ props.put(OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM, "8");
+ props.put(OptimizerProperties.OPTIMIZER_GROUP_MIN_PARALLELISM, "2");
+ Assertions.assertEquals(8,
DynamicAllocationConfig.resolveMinParallelism(group(props)));
+ }
+
+ @Test
+ void minParallelismResolutionFallsBackToLegacyValue() {
+ Map<String, String> props = new HashMap<>();
+ props.put(OptimizerProperties.OPTIMIZER_GROUP_MIN_PARALLELISM, "5");
+ Assertions.assertEquals(5,
DynamicAllocationConfig.resolveMinParallelism(group(props)));
+ }
+
+ @Test
+ void minParallelismResolutionDefaultsToZero() {
+ Assertions.assertEquals(
+ 0, DynamicAllocationConfig.resolveMinParallelism(group(new
HashMap<>())));
+ }
+
+ @Test
+ void disabledConfigSkipsValidation() {
+ Map<String, String> props = new HashMap<>();
+ props.put(OptimizerProperties.DYNAMIC_ALLOCATION_ENABLED, "false");
+ // max-parallelism absent and an external container would fail if
validated, but disabled.
+ ResourceGroup external = new
ResourceGroup.Builder("group1").addProperties(props).build();
+ assertDoesNotThrow(() ->
DynamicAllocationConfig.parse(external).validate());
+ }
+
+ @Test
+ void effectivelyEnabledWhenValidConfig() {
+
Assertions.assertTrue(DynamicAllocationConfig.isEffectivelyEnabled(group(enabledProps())));
+ }
+
+ @Test
+ void notEffectivelyEnabledWhenDisabled() {
+
Assertions.assertFalse(DynamicAllocationConfig.isEffectivelyEnabled(group(new
HashMap<>())));
+ }
+
+ @Test
+ void notEffectivelyEnabledWhenConfigInvalid() {
+ Map<String, String> props = new HashMap<>();
+ props.put(OptimizerProperties.DYNAMIC_ALLOCATION_ENABLED, "true");
+ // enabled without max-parallelism is invalid, so DRA falls back to
disabled.
+
Assertions.assertFalse(DynamicAllocationConfig.isEffectivelyEnabled(group(props)));
+ }
+
+ @Test
+ void effectiveMinParallelismKeyPrefersNamespacedWhenPresent() {
+ Map<String, String> props = new HashMap<>();
+ props.put(OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM, "2");
+ props.put(OptimizerProperties.OPTIMIZER_GROUP_MIN_PARALLELISM, "2");
+ Assertions.assertEquals(
+ OptimizerProperties.DYNAMIC_ALLOCATION_MIN_PARALLELISM,
+ DynamicAllocationConfig.effectiveMinParallelismKey(group(props)));
+ }
+
+ @Test
+ void effectiveMinParallelismKeyFallsBackToLegacy() {
+ Map<String, String> props = new HashMap<>();
+ props.put(OptimizerProperties.OPTIMIZER_GROUP_MIN_PARALLELISM, "2");
+ Assertions.assertEquals(
+ OptimizerProperties.OPTIMIZER_GROUP_MIN_PARALLELISM,
+ DynamicAllocationConfig.effectiveMinParallelismKey(group(props)));
+ Assertions.assertEquals(
+ OptimizerProperties.OPTIMIZER_GROUP_MIN_PARALLELISM,
+ DynamicAllocationConfig.effectiveMinParallelismKey(group(new
HashMap<>())));
+ }
+}
diff --git
a/amoro-common/src/main/java/org/apache/amoro/OptimizerProperties.java
b/amoro-common/src/main/java/org/apache/amoro/OptimizerProperties.java
index 3c5e7f900..c69a8aa94 100644
--- a/amoro-common/src/main/java/org/apache/amoro/OptimizerProperties.java
+++ b/amoro-common/src/main/java/org/apache/amoro/OptimizerProperties.java
@@ -32,7 +32,13 @@ public class OptimizerProperties {
public static final String OPTIMIZER_EXECUTION_PARALLEL =
"execution-parallel";
public static final String OPTIMIZER_MEMORY_SIZE = "memory-size";
public static final String OPTIMIZER_GROUP_NAME = "group-name";
- public static final String OPTIMIZER_GROUP_MIN_PARALLELISM =
"min-parallelism";
+
+ /**
+ * @deprecated since 0.9.0, use {@link #DYNAMIC_ALLOCATION_MIN_PARALLELISM}
instead. Still honored
+ * as a fallback when the namespaced property is absent.
+ */
+ @Deprecated public static final String OPTIMIZER_GROUP_MIN_PARALLELISM =
"min-parallelism";
+
public static final String OPTIMIZER_HEART_BEAT_INTERVAL =
"heart-beat-interval";
public static final String OPTIMIZER_EXTEND_DISK_STORAGE =
"extend-disk-storage";
public static final boolean OPTIMIZER_EXTEND_DISK_STORAGE_DEFAULT = false;
@@ -51,4 +57,54 @@ public class OptimizerProperties {
public static final String OPTIMIZER_MASTER_SLAVE_MODE_ENABLED =
"master-slave-mode-enabled";
public static final String OPTIMIZER_SHUTDOWN_TIMEOUT_MS =
"shutdown-timeout-ms";
public static final long OPTIMIZER_SHUTDOWN_TIMEOUT_MS_DEFAULT = 600_000L;
// 10 min
+
+ // Dynamic resource allocation (DRA) properties (AIP-5), configured at the
resource group level.
+ // Semantics and validation rules are documented in DynamicAllocationConfig
(amoro-ams).
+
+ /** @since 0.9.0 */
+ public static final String DYNAMIC_ALLOCATION_ENABLED =
"dynamic-allocation.enabled";
+
+ public static final boolean DYNAMIC_ALLOCATION_ENABLED_DEFAULT = false;
+
+ /** @since 0.9.0 */
+ public static final String DYNAMIC_ALLOCATION_MIN_PARALLELISM =
+ "dynamic-allocation.min-parallelism";
+
+ public static final int DYNAMIC_ALLOCATION_MIN_PARALLELISM_DEFAULT = 0;
+
+ /** @since 0.9.0 */
+ public static final String DYNAMIC_ALLOCATION_MAX_PARALLELISM =
+ "dynamic-allocation.max-parallelism";
+
+ public static final int DYNAMIC_ALLOCATION_MAX_PARALLELISM_LIMIT = 1024;
+
+ /** @since 0.9.0 */
+ public static final String DYNAMIC_ALLOCATION_SCHEDULER_BACKLOG_TIMEOUT =
+ "dynamic-allocation.scheduler-backlog-timeout";
+
+ public static final String
DYNAMIC_ALLOCATION_SCHEDULER_BACKLOG_TIMEOUT_DEFAULT = "1min";
+
+ /** @since 0.9.0 */
+ public static final String DYNAMIC_ALLOCATION_SUSTAINED_BACKLOG_TIMEOUT =
+ "dynamic-allocation.sustained-backlog-timeout";
+
+ public static final String
DYNAMIC_ALLOCATION_SUSTAINED_BACKLOG_TIMEOUT_DEFAULT = "30s";
+
+ /** @since 0.9.0 */
+ public static final String DYNAMIC_ALLOCATION_EXECUTOR_IDLE_TIMEOUT =
+ "dynamic-allocation.executor-idle-timeout";
+
+ public static final String DYNAMIC_ALLOCATION_EXECUTOR_IDLE_TIMEOUT_DEFAULT
= "5min";
+ public static final String DYNAMIC_ALLOCATION_EXECUTOR_IDLE_TIMEOUT_MIN =
"30s";
+
+ /** @since 0.9.0 */
+ public static final String DYNAMIC_ALLOCATION_SCALE_DOWN_COOLDOWN =
+ "dynamic-allocation.scale-down-cooldown";
+
+ public static final String DYNAMIC_ALLOCATION_SCALE_DOWN_COOLDOWN_DEFAULT =
"1min";
+
+ /** @since 0.9.0 */
+ public static final String DYNAMIC_ALLOCATION_DRAIN_TIMEOUT =
"dynamic-allocation.drain-timeout";
+
+ public static final String DYNAMIC_ALLOCATION_DRAIN_TIMEOUT_DEFAULT =
"15min";
}