This is an automated email from the ASF dual-hosted git repository.
ayushtkn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hive.git
The following commit(s) were added to refs/heads/master by this push:
new 980064b76be HIVE-29855: Optimize K8s Operator LLAP scaling (#6723)
980064b76be is described below
commit 980064b76be20049edd4048ce9c2b411e1fba534
Author: Tanishq Chugh <[email protected]>
AuthorDate: Mon Aug 31 09:49:11 2026 +0530
HIVE-29855: Optimize K8s Operator LLAP scaling (#6723)
---
packaging/src/kubernetes/README.md | 31 +++++----
.../crds/hiveclusters.hive.apache.org-v1.yml | 52 +++++++++++----
.../helm/hive-operator/templates/hivecluster.yaml | 1 +
.../src/kubernetes/helm/hive-operator/values.yaml | 3 +-
.../operator/autoscaling/ComponentAutoscaler.java | 9 ++-
.../autoscaling/HiveClusterAutoscaler.java | 15 +++++
.../operator/autoscaling/LlapScalingStrategy.java | 74 +++++++++++++++-------
.../operator/dependent/HiveDependentResource.java | 7 ++
.../operator/model/spec/AutoscalingSpec.java | 7 +-
.../operator/model/spec/HiveServer2Spec.java | 2 +-
.../kubernetes/operator/model/spec/LlapSpec.java | 4 +-
.../operator/model/spec/MetastoreSpec.java | 2 +-
.../kubernetes/operator/model/spec/TezAmSpec.java | 2 +-
.../operator/reconciler/HiveClusterReconciler.java | 11 ++--
.../hive/kubernetes/operator/util/ConfigUtils.java | 2 +
.../operator/util/HiveConfigBuilder.java | 6 ++
16 files changed, 167 insertions(+), 61 deletions(-)
diff --git a/packaging/src/kubernetes/README.md
b/packaging/src/kubernetes/README.md
index 2202885d459..1135413f612 100644
--- a/packaging/src/kubernetes/README.md
+++ b/packaging/src/kubernetes/README.md
@@ -580,7 +580,8 @@ cluster:
autoscaling:
enabled: true
minReplicas: 2
- scaleUpThreshold: 2
+ scaleUpThreshold: 80
+ scaleDownThreshold: 20
- name: analytics
enabled: true
@@ -744,7 +745,7 @@ When `autoscaling.enabled: true` is set for a component,
the operator:
- HS2: deregisters from ZK, drains open sessions, kills JVM
- HMS: kills JVM (stateless HTTP — no drain needed)
- LLAP: waits until all executors become idle, kills JVM
- - TezAM: no drain (DAGAppMaster does not expose JMX metrics)
+ - TezAM: deregisters from ZK, drains running DAG, kills JVM
5. terminationGracePeriodSeconds = gracePeriodSeconds (safety cap)
6. Pod terminates immediately once drain completes (does NOT wait
the full grace period — it's only the upper safety bound)
@@ -762,14 +763,14 @@ The autoscaling system uses three independent timing
controls:
| Timer | Config Field | Default | Purpose |
|-------|-------------|---------|---------|
| **Metrics scrape interval** | `metricsScrapeIntervalSeconds` | `10` | How
often the operator scrapes JMX Exporter `/metrics` on each pod. This is the
**biggest bottleneck** for autoscaling reaction time. |
-| **Scale-up stabilization** | `scaleUpStabilizationSeconds` | `60` | Window:
picks the highest recommendation within this period before scaling up. Prevents
flapping when metrics oscillate. Set to `0` for LLAP and TezAM (reactive
dependents). |
+| **Scale-up stabilization** | `scaleUpStabilizationSeconds` | `60` | Window:
picks the highest recommendation within this period before scaling up. Prevents
flapping when metrics oscillate. Set to `0` for LLAP and TezAM (reactive
dependents). LLAP picks the lowest recommendation to prevent over-scaling due
to momentary Reducer Burst. |
| **Scale-down stabilization** | `scaleDownStabilizationSeconds` | `300-900` |
Window: picks the most conservative (highest) recommendation within this period
before scaling down. Also acts as the cooldown between consecutive scale-downs
— no separate cooldown needed. |
**How they interact:**
- Load spike detected → operator scrapes metrics within
`metricsScrapeIntervalSeconds` → waits `scaleUpStabilizationSeconds` then
scales up
- Load drops → operator waits `scaleDownStabilizationSeconds` (stabilization
window must confirm low demand consistently) then scales down
-**Tuning reaction time:** With defaults (`metricsScrapeIntervalSeconds: 10`,
`scaleUpStabilizationSeconds: 0` for LLAP/TezAM), scale-up latency is ~10-20s
(one scrape cycle). For HS2 with `scaleUpStabilizationSeconds: 60`, expect ~70s.
+**Tuning reaction time:** With defaults (`metricsScrapeIntervalSeconds: 10`,
`scaleUpStabilizationSeconds: 60`), expect ~70s for stabilized scale-up.
### Per-Component Scaling Logic
@@ -777,7 +778,7 @@ The autoscaling system uses three independent timing
controls:
|-----------|-----------------|------------|------------|
| **HiveServer2** | `max(ceil(sessions / threshold), cpu_desired)` | Sessions
drop to 0 AND CPU below threshold → scale to minReplicas | `hs2_open_sessions`,
`jvm_process_cpu_load` |
| **Metastore** | `max(ceil(api_rate / threshold), cpu_desired)` | Rate drops
to 0 AND CPU below threshold → scale to minReplicas | `api_*_total`,
`jvm_process_cpu_load` |
-| **LLAP** | `ceil(avg(queued + configured - available) / scaleUpThreshold)` |
All executors idle + no HS2 sessions | `hadoop_llapdaemon_executor*` |
+| **LLAP** | `ceil(totalClusterLoad / capacityPerDaemon)` when pending load is
above threshold | No TezAM pending tasks AND avg daemon load is below threshold
| `hadoop_llapdaemon_executor*`, `tez_am_pending_tasks` |
| **Tez AM** | `max(sum(hs2_open_sessions), count(HS2_pods) *
sessions_per_queue)` | All HS2 sessions closed | `hs2_open_sessions` (from HS2
pods) |
**TezAM Scaling Model:** TezAM uses demand-driven scaling with two formulas
(max wins):
@@ -1092,8 +1093,8 @@ status:
```
**Applicability:** CPU-based scaling only applies to HS2 and HMS. LLAP and
TezAM
-do not use CPU as a scaling signal (LLAP scales on busy executor slots which
already
-correlates with CPU; TezAM is demand-based from HS2 session count).
+do not use CPU as a scaling signal (LLAP scales on combined TezAM pending tasks
+and daemon busy slots; TezAM is demand-based from HS2 session count).
---
@@ -1187,7 +1188,8 @@ cluster:
autoscaling:
enabled: true
# minReplicas: 0 # default — scale to zero when no sessions
target this cluster
- # scaleUpThreshold: 1 # default — total busy slots (queued+running)
triggering scale-up
+ # scaleUpThreshold: 80 # default — pending load percent of total
cluster load triggers scale-up
+ # scaleDownThreshold: 20 # default — avg daemon utilization percent
triggering scale-down
# scaleUpStabilizationSeconds: 60 # default — scale-up window
# scaleDownStabilizationSeconds: 900 # default — scale-down window (long
— scaling down destroys cache)
# gracePeriodSeconds: 600 # default — 10 min drain for in-flight
fragments
@@ -1224,8 +1226,8 @@ When autoscaling is enabled, the operator automatically:
|-----------|---------|---------|
| **HiveServer2** | `hs2_open_sessions`, `jvm_process_cpu_load` | Session
count for primary scaling + CPU for secondary scaling signal |
| **Metastore** | `api_*_total`, `jvm_process_cpu_load` | API call counters
(operator computes request rate from deltas) + CPU for secondary scaling signal
|
-| **LLAP** | `hadoop_llapdaemon_executornumqueuedrequests`,
`hadoop_llapdaemon_executornumexecutorsconfigured`,
`hadoop_llapdaemon_executornumexecutorsavailable` | Total busy slots = queued +
configured - available |
-| **Tez AM** | N/A (scales on HS2 metrics) | TezAM scaling is demand-driven
from `hs2_open_sessions` — no TezAM-specific metrics needed |
+| **LLAP** | `hadoop_llapdaemon_executornumqueuedrequests`,
`hadoop_llapdaemon_executornumexecutorsconfigured`,
`hadoop_llapdaemon_executornumexecutorsavailable`,
`hadoop_llapdaemon_executormaxfreeslotsconfigured` | Daemon busy slots and
capacity; combined with TezAM pending tasks for scaling |
+| **Tez AM** | `tez_am_pending_tasks`, `tez_am_dag_running` | Pending task
count drives LLAP scale-up; DAG status ranks idle AMs for cost-based scale-down
|
### Enabling Autoscaling — Example
@@ -1248,7 +1250,7 @@ cluster:
scaleUpThreshold: 75 # API requests/sec threshold
```
-> **Note:** LLAP scales on total busy slots (queued + running executors).
+> **Note:** LLAP scales based on combined TezAM pending tasks and daemon busy
slots.
> TezAM scales on demand — the number of active HS2 pods multiplied by
> `hive.server2.tez.sessions.per.default.queue` (default 1).
@@ -1260,6 +1262,7 @@ cluster:
| `cluster.<component>.autoscaling.enabled` | `false` | Enable operator-driven
autoscaling |
| `cluster.<component>.autoscaling.minReplicas` | `1` (HS2/HMS), `0`
(LLAP/TezAM) | Minimum replica count. Set to 0 for scale-to-zero (LLAP, TezAM
only; HS2 minimum is 1) |
| `cluster.<component>.autoscaling.scaleUpThreshold` | varies | Metric
threshold triggering scale-up |
+| `cluster.<component>.autoscaling.scaleDownThreshold` | `20` (LLAP only) |
Average daemon utilization percent below which LLAP scale-down triggers |
| `cluster.<component>.autoscaling.scaleUpStabilizationSeconds` | `60` |
Stabilization window for scale-up (picks highest recommendation in window) |
| `cluster.<component>.autoscaling.scaleDownStabilizationSeconds` | `300-900`
| Stabilization window for scale-down (picks most conservative recommendation
in window). Also acts as cooldown between consecutive scale-downs. |
| `cluster.<component>.autoscaling.gracePeriodSeconds` | `3600` | Safety cap:
max drain time before forced termination. Pod exits immediately once drain
completes. |
@@ -1406,7 +1409,8 @@ and a paired TezAM Deployment (when `tezAm.enabled:
true`).
| `cluster.llapClusters[].extraVolumeMounts` | `[]` | Additional volume mounts
for LLAP containers |
| `cluster.llapClusters[].autoscaling.enabled` | `false` | Enable per-cluster
autoscaling |
| `cluster.llapClusters[].autoscaling.minReplicas` | `0` | Min replicas (0 =
scale to zero) |
-| `cluster.llapClusters[].autoscaling.scaleUpThreshold` | `1` | Busy-slot
threshold for scale-up |
+| `cluster.llapClusters[].autoscaling.scaleUpThreshold` | `80` | Pending load
percent of total cluster load for scale-up |
+| `cluster.llapClusters[].autoscaling.scaleDownThreshold` | `20` | Avg daemon
utilization percent for scale-down |
HS2 routes sessions to LLAP clusters server-side based on user/group identity:
@@ -1448,7 +1452,8 @@ controls shared settings (enabled flag, scratch PVC).
Per-LLAP TezAM settings
|-------|---------|-------------|
| `cluster.<component>.autoscaling.enabled` | `false` | Enable operator-driven
autoscaling for this component |
| `cluster.<component>.autoscaling.minReplicas` | `0` | Floor replica count. 0
enables scale-to-zero (LLAP, TezAM only; HS2 minimum is 1) |
-| `cluster.<component>.autoscaling.scaleUpThreshold` | `100` (HS2/HMS), `10`
(LLAP) | Metric threshold per pod triggering scale-up (sessions for HS2,
connections for HMS, busy slots for LLAP). TezAM scales 1:1 with demand (no
threshold). |
+| `cluster.<component>.autoscaling.scaleUpThreshold` | `100` (HS2/HMS), `80`
(LLAP) | Metric threshold per pod triggering scale-up (sessions for HS2, API
rate for HMS, pending load percent for LLAP). TezAM scales 1:1 with demand (no
threshold). |
+| `cluster.<component>.autoscaling.scaleDownThreshold` | `20` (LLAP) | Average
daemon utilization percent below which LLAP scale-down triggers |
| `cluster.<component>.autoscaling.scaleUpStabilizationSeconds` | `60` |
Stabilization window for scale-up decisions (prevents flapping) |
| `cluster.<component>.autoscaling.scaleDownStabilizationSeconds` | `300-900`
| Stabilization window for scale-down decisions (also acts as cooldown between
consecutive scale-downs) |
| `cluster.<component>.autoscaling.gracePeriodSeconds` | `3600` | Safety cap
(seconds) — pod terminates immediately once drain completes, this is only the
upper bound |
diff --git
a/packaging/src/kubernetes/helm/hive-operator/crds/hiveclusters.hive.apache.org-v1.yml
b/packaging/src/kubernetes/helm/hive-operator/crds/hiveclusters.hive.apache.org-v1.yml
index 9947f44b75b..9bdb3863c52 100644
---
a/packaging/src/kubernetes/helm/hive-operator/crds/hiveclusters.hive.apache.org-v1.yml
+++
b/packaging/src/kubernetes/helm/hive-operator/crds/hiveclusters.hive.apache.org-v1.yml
@@ -129,6 +129,12 @@ spec:
replicas before scale-down occurs. Also acts as the
cooldown
between consecutive scale-downs.
type: integer
+ scaleDownThreshold:
+ default: 20
+ description: "Threshold that triggers scale-down (LLAP
only):\
+ \ average daemon utilization percentage (0-100)
below which\
+ \ scale-down triggers."
+ type: integer
scaleUpStabilizationSeconds:
default: 60
description: Stabilization window in seconds for
scale-up
@@ -138,8 +144,8 @@ spec:
scaleUpThreshold:
default: 80
description: "Threshold that triggers scale-up
(component-specific:\
- \ sessions per pod for HS2, request rate for HMS,
busy slots\
- \ per daemon for LLAP). Not used by TezAM
(demand-based:\
+ \ sessions per pod for HS2, request rate for HMS,
load percentage\
+ \ pending in TezAM for LLAP). Not used by TezAM
(demand-based:\
\ 1 TezAM per session)."
type: integer
type: object
@@ -312,6 +318,12 @@ spec:
fewer replicas before scale-down occurs. Also acts
as
the cooldown between consecutive scale-downs.
type: integer
+ scaleDownThreshold:
+ default: 20
+ description: "Threshold that triggers scale-down
(LLAP only):\
+ \ average daemon utilization percentage (0-100)
below\
+ \ which scale-down triggers."
+ type: integer
scaleUpStabilizationSeconds:
default: 60
description: Stabilization window in seconds for
scale-up
@@ -321,9 +333,9 @@ spec:
scaleUpThreshold:
default: 80
description: "Threshold that triggers scale-up
(component-specific:\
- \ sessions per pod for HS2, request rate for HMS,
busy\
- \ slots per daemon for LLAP). Not used by TezAM
(demand-based:\
- \ 1 TezAM per session)."
+ \ sessions per pod for HS2, request rate for HMS,
load\
+ \ percentage pending in TezAM for LLAP). Not used
by TezAM\
+ \ (demand-based: 1 TezAM per session)."
type: integer
type: object
configOverrides:
@@ -467,6 +479,12 @@ spec:
fewer replicas before scale-down occurs. Also
acts
as the cooldown between consecutive
scale-downs.
type: integer
+ scaleDownThreshold:
+ default: 20
+ description: "Threshold that triggers scale-down
(LLAP\
+ \ only): average daemon utilization percentage
(0-100)\
+ \ below which scale-down triggers."
+ type: integer
scaleUpStabilizationSeconds:
default: 60
description: Stabilization window in seconds for
scale-up
@@ -477,8 +495,8 @@ spec:
default: 80
description: "Threshold that triggers scale-up
(component-specific:\
\ sessions per pod for HS2, request rate for
HMS,\
- \ busy slots per daemon for LLAP). Not used by
TezAM\
- \ (demand-based: 1 TezAM per session)."
+ \ load percentage pending in TezAM for LLAP).
Not\
+ \ used by TezAM (demand-based: 1 TezAM per
session)."
type: integer
type: object
replicas:
@@ -545,6 +563,12 @@ spec:
replicas before scale-down occurs. Also acts as the
cooldown
between consecutive scale-downs.
type: integer
+ scaleDownThreshold:
+ default: 20
+ description: "Threshold that triggers scale-down (LLAP
only):\
+ \ average daemon utilization percentage (0-100)
below which\
+ \ scale-down triggers."
+ type: integer
scaleUpStabilizationSeconds:
default: 60
description: Stabilization window in seconds for
scale-up
@@ -554,8 +578,8 @@ spec:
scaleUpThreshold:
default: 80
description: "Threshold that triggers scale-up
(component-specific:\
- \ sessions per pod for HS2, request rate for HMS,
busy slots\
- \ per daemon for LLAP). Not used by TezAM
(demand-based:\
+ \ sessions per pod for HS2, request rate for HMS,
load percentage\
+ \ pending in TezAM for LLAP). Not used by TezAM
(demand-based:\
\ 1 TezAM per session)."
type: integer
type: object
@@ -753,6 +777,12 @@ spec:
replicas before scale-down occurs. Also acts as the
cooldown
between consecutive scale-downs.
type: integer
+ scaleDownThreshold:
+ default: 20
+ description: "Threshold that triggers scale-down (LLAP
only):\
+ \ average daemon utilization percentage (0-100)
below which\
+ \ scale-down triggers."
+ type: integer
scaleUpStabilizationSeconds:
default: 60
description: Stabilization window in seconds for
scale-up
@@ -762,8 +792,8 @@ spec:
scaleUpThreshold:
default: 80
description: "Threshold that triggers scale-up
(component-specific:\
- \ sessions per pod for HS2, request rate for HMS,
busy slots\
- \ per daemon for LLAP). Not used by TezAM
(demand-based:\
+ \ sessions per pod for HS2, request rate for HMS,
load percentage\
+ \ pending in TezAM for LLAP). Not used by TezAM
(demand-based:\
\ 1 TezAM per session)."
type: integer
type: object
diff --git
a/packaging/src/kubernetes/helm/hive-operator/templates/hivecluster.yaml
b/packaging/src/kubernetes/helm/hive-operator/templates/hivecluster.yaml
index 278ae40e8df..a2b465ba0b3 100644
--- a/packaging/src/kubernetes/helm/hive-operator/templates/hivecluster.yaml
+++ b/packaging/src/kubernetes/helm/hive-operator/templates/hivecluster.yaml
@@ -157,6 +157,7 @@ spec:
enabled: true
minReplicas: {{ .autoscaling.minReplicas }}
scaleUpThreshold: {{ .autoscaling.scaleUpThreshold }}
+ scaleDownThreshold: {{ .autoscaling.scaleDownThreshold }}
scaleUpStabilizationSeconds: {{ .autoscaling.scaleUpStabilizationSeconds
}}
scaleDownStabilizationSeconds: {{
.autoscaling.scaleDownStabilizationSeconds }}
gracePeriodSeconds: {{ .autoscaling.gracePeriodSeconds }}
diff --git a/packaging/src/kubernetes/helm/hive-operator/values.yaml
b/packaging/src/kubernetes/helm/hive-operator/values.yaml
index c16f7240ea2..dda082695f8 100644
--- a/packaging/src/kubernetes/helm/hive-operator/values.yaml
+++ b/packaging/src/kubernetes/helm/hive-operator/values.yaml
@@ -200,7 +200,8 @@ cluster:
autoscaling:
enabled: false
minReplicas: 0
- scaleUpThreshold: 10
+ scaleUpThreshold: 80
+ scaleDownThreshold: 20
scaleUpStabilizationSeconds: 60
scaleDownStabilizationSeconds: 900
gracePeriodSeconds: 600
diff --git
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/autoscaling/ComponentAutoscaler.java
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/autoscaling/ComponentAutoscaler.java
index fcf4da2aefb..a55973bfe34 100644
---
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/autoscaling/ComponentAutoscaler.java
+++
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/autoscaling/ComponentAutoscaler.java
@@ -94,8 +94,13 @@ public EvaluationResult evaluate(List<PodMetrics> metrics,
AutoscalingSpec spec,
int target;
if (clamped > currentReplicas) {
- // Scale up: use stabilized max (highest recommendation in window —
don't under-scale)
- target = scaleUpWindow.stabilizedMax();
+ if (component.startsWith(ConfigUtils.COMPONENT_LLAP + "-")) {
+ // HS2 sessions activation gate scales up the LLAP pods to atleast 1
+ // in presence of sessions. Avoid stabilizedMin in this start-up case.
+ target = currentReplicas == 0 ? clamped :
scaleUpWindow.stabilizedMin();
+ } else {
+ target = scaleUpWindow.stabilizedMax();
+ }
} else if (clamped < currentReplicas) {
// Scale down: use stabilized max (highest/most conservative
recommendation in window —
// prevents premature scale-down, matches HPA selectPolicy: Max
behavior).
diff --git
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/autoscaling/HiveClusterAutoscaler.java
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/autoscaling/HiveClusterAutoscaler.java
index 3c69a957ef9..f6016043fc6 100644
---
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/autoscaling/HiveClusterAutoscaler.java
+++
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/autoscaling/HiveClusterAutoscaler.java
@@ -321,6 +321,21 @@ public List<PodMetrics> getHs2MetricsFromCache(HiveCluster
cluster) {
return metricsCache.getOrEmpty(key, maxStale);
}
+ /**
+ * Returns cached TezAM metrics for the given LLAP cluster (used by
LlapScalingStrategy).
+ */
+ public List<PodMetrics> getTezAmMetricsFromCache(HiveCluster cluster, String
llapName) {
+ String namespace = cluster.getMetadata().getNamespace();
+ String clusterName = cluster.getMetadata().getName();
+ int maxStale = cluster.getSpec().llapClusters().stream()
+ .filter(l -> llapName.equals(l.name()))
+ .findFirst()
+ .map(l -> l.tezAm().autoscaling().metricsScrapeIntervalSeconds() * 3)
+ .orElse(30);
+ return metricsCache.getOrEmpty(
+ cacheKey(namespace, clusterName,
ConfigUtils.tezAmComponentKey(llapName)), maxStale);
+ }
+
private void evaluateComponent(HiveCluster cluster, KubernetesClient client,
String namespace, String clusterName, String component,
AutoscalingSpec autoscaling, int maxReplicas,
diff --git
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/autoscaling/LlapScalingStrategy.java
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/autoscaling/LlapScalingStrategy.java
index 368d38474d6..546800403ac 100644
---
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/autoscaling/LlapScalingStrategy.java
+++
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/autoscaling/LlapScalingStrategy.java
@@ -28,11 +28,12 @@
/**
* Scaling strategy for LLAP daemons.
- * Formula: avg(QueuedRequests + Configured - Available) across all pods.
- * This represents average "busy slots + queued" per daemon.
- * desired = ceil(avg_busy / scaleUpThreshold)
+ * For Scale-Up: Pending Load across all TezAM pods should be above the
threshold.
+ * For Scale-Down: Running Load across all LLAP pods should be below the
threshold.
+ * desired = ceil(totalClusterLoad / capacityPerDaemon)
* <p>
- * Activation gate: only scale if HS2 has open sessions (prevents zombie
scaling).
+ * Activation gate: only scale if HS2 has open sessions, TezAM metrics are
present &
+ * cluster has load (prevents zombie scaling).
*/
public class LlapScalingStrategy implements ScalingStrategy {
@@ -41,7 +42,9 @@ public class LlapScalingStrategy implements ScalingStrategy {
static final String METRIC_QUEUED =
"hadoop_llapdaemon_executornumqueuedrequests";
static final String METRIC_CONFIGURED =
"hadoop_llapdaemon_executornumexecutorsconfigured";
static final String METRIC_AVAILABLE =
"hadoop_llapdaemon_executornumexecutorsavailable";
+ static final String METRIC_MAX_FREE_SLOTS_CONFIGURED =
"hadoop_llapdaemon_executormaxfreeslotsconfigured";
static final String METRIC_LLAP_TARGET_PREFIX = "hs2_llap_target_sessions_";
+ static final String METRIC_TEZ_PENDING_TASKS = "tez_am_pending_tasks";
private final HiveClusterAutoscaler orchestrator;
private final HiveCluster cluster;
@@ -67,7 +70,7 @@ public int computeDesiredReplicas(List<PodMetrics> podMetrics,
// HS2 scrape returned no data but LLAP is running — hold current state
LOG.debug("[llap] HS2 scrape returned no pods; preserving LLAP (has {}
running pods)", podMetrics.size());
lastMetric = 0;
- return Math.max(1, autoscaling.minReplicas());
+ return podMetrics.size();
}
if (sessionsDetected == null || !sessionsDetected) {
LOG.debug("[llap] HS2 has no open sessions, scaling to minReplicas");
@@ -83,36 +86,59 @@ public int computeDesiredReplicas(List<PodMetrics>
podMetrics,
return minReplica;
}
- // Compute average busy slots across all LLAP pods
- double totalBusy = 0;
- int podCount = 0;
+ List<PodMetrics> tezAmMetrics =
orchestrator.getTezAmMetricsFromCache(cluster, llapName);
+ double totalPending = 0;
+ for (PodMetrics pm : tezAmMetrics) {
+ totalPending += pm.metrics().getOrDefault(METRIC_TEZ_PENDING_TASKS, 0.0);
+ }
+
+ double totalLLAPCapacity = 0;
+ double totalLLAPLoad = 0;
for (PodMetrics pm : podMetrics) {
double queued = pm.metrics().getOrDefault(METRIC_QUEUED, 0.0);
double configured = pm.metrics().getOrDefault(METRIC_CONFIGURED, 0.0);
double available = pm.metrics().getOrDefault(METRIC_AVAILABLE, 0.0);
- double busy = queued + configured - available;
- totalBusy += busy;
- podCount++;
+ double capacity =
pm.metrics().getOrDefault(METRIC_MAX_FREE_SLOTS_CONFIGURED, 0.0);
+ if (capacity <= 0) {
+ capacity = configured;
+ }
+ totalLLAPCapacity += capacity;
+ totalLLAPLoad += queued + configured - available;
}
- double avgBusy = totalBusy / podCount;
- lastMetric = (int) Math.round(avgBusy);
-
- if (avgBusy <= 0) {
- // HS2 has sessions (passed activation gate above) but executors are
idle between queries.
- // Keep at least 1 daemon to avoid flapping: scaling to 0 here would
cause immediate
- // scale-back-up on the next evaluation when the empty-pod path triggers.
+ // HS2 has sessions (passed activation gate above) but either
+ // 1. there is no tezAM running, so LLAP running any work is zombie if any.
+ // 2. there are tezAMs running, but no tasks running or pending.
+ if (tezAmMetrics.isEmpty() || (totalPending + totalLLAPLoad) == 0) {
return Math.max(1, autoscaling.minReplicas());
}
- if (LOG.isDebugEnabled()) {
- LOG.debug("[llap] avgBusy={}, threshold={}", String.format("%.2f",
avgBusy),
- autoscaling.scaleUpThreshold());
+ double capacityPerDaemon = Math.max(1.0, totalLLAPCapacity /
podMetrics.size());
+ double avgLLAPLoadPercent = totalLLAPCapacity > 0 ? (totalLLAPLoad /
totalLLAPCapacity) * 100.00 : 0.0;
+
+ double totalClusterLoad = totalPending + totalLLAPLoad;
+ double pendingLoadPercent = (totalPending / totalClusterLoad) * 100.0;
+
+ int scaleUpThreshold = autoscaling.scaleUpThreshold();
+ int scaleDownThreshold = autoscaling.scaleDownThreshold();
+
+ lastMetric = (int) totalClusterLoad;
+
+ // Scale-up: pending load share of total load exceeds threshold
+ // Scale-down: no pending work AND daemon load below threshold
+ if (pendingLoadPercent >= scaleUpThreshold || (totalPending == 0 &&
avgLLAPLoadPercent <= scaleDownThreshold)) {
+ int desired = (int) Math.ceil(totalClusterLoad / capacityPerDaemon);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("[llap-{}] totalClusterLoad={}, capacityPerDaemon={},
pendingLoadPercent={}, avgLLAPLoadPercent={}",
+ llapName, totalClusterLoad, capacityPerDaemon,
String.format("%.2f", pendingLoadPercent),
+ String.format("%.2f", avgLLAPLoadPercent));
+ }
+ return desired;
}
- int threshold = Math.max(1, autoscaling.scaleUpThreshold());
- int desired = (int) Math.ceil(avgBusy / threshold);
- return Math.max(desired, autoscaling.minReplicas());
+ // Work is in flight (pending share below threshold) or daemons are
moderately loaded.
+ // Return current pod count so the stabilization window keeps the replica
count stable.
+ return podMetrics.size();
}
@Override
diff --git
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/dependent/HiveDependentResource.java
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/dependent/HiveDependentResource.java
index ebe89d06143..4cb348bfd47 100644
---
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/dependent/HiveDependentResource.java
+++
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/dependent/HiveDependentResource.java
@@ -854,14 +854,21 @@ private static String buildJmxExporterConfig(String
component) {
sb.append("- pattern:
'").append(llapBean).append("ExecutorNumExecutors'\n");
sb.append(" name: hadoop_llapdaemon_executornumexecutors\n");
sb.append(" type: GAUGE\n");
+ sb.append("- pattern:
'").append(llapBean).append("ExecutorMaxFreeSlotsConfigured'\n");
+ sb.append(" name: hadoop_llapdaemon_executormaxfreeslotsconfigured\n");
+ sb.append(" type: GAUGE\n");
break;
case ConfigUtils.COMPONENT_TEZAM:
// LlapMetricsSystem registers beans under LlapTaskScheduler service
// SchedulerDagStatus tracks if the AM is running a dag or is idle so
exported as GAUGE.
+ // SchedulerPendingTaskCount tracks currently waiting tasks in the AM to
be scheduled.
String schedulerBean = "Hadoop<service=LlapTaskScheduler,
name=LlapTaskSchedulerMetrics.+><>";
sb.append("- pattern:
'").append(schedulerBean).append("SchedulerDagStatus'\n");
sb.append(" name: tez_am_dag_running\n");
sb.append(" type: GAUGE\n");
+ sb.append("- pattern:
'").append(schedulerBean).append("SchedulerPendingTaskCount'\n");
+ sb.append(" name: tez_am_pending_tasks\n");
+ sb.append(" type: GAUGE\n");
break;
default:
sb.append("- pattern: '.*'\n");
diff --git
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/model/spec/AutoscalingSpec.java
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/model/spec/AutoscalingSpec.java
index 1cdb06bdad4..a1bc6ea3a10 100644
---
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/model/spec/AutoscalingSpec.java
+++
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/model/spec/AutoscalingSpec.java
@@ -33,10 +33,14 @@ public record AutoscalingSpec(
@Default("0")
Integer minReplicas,
@JsonPropertyDescription("Threshold that triggers scale-up
(component-specific: "
- + "sessions per pod for HS2, request rate for HMS, busy slots per
daemon for LLAP). "
+ + "sessions per pod for HS2, request rate for HMS, load percentage
pending in TezAM for LLAP). "
+ "Not used by TezAM (demand-based: 1 TezAM per session).")
@Default("80")
Integer scaleUpThreshold,
+ @JsonPropertyDescription("Threshold that triggers scale-down (LLAP only): "
+ + "average daemon utilization percentage (0-100) below which
scale-down triggers.")
+ @Default("20")
+ Integer scaleDownThreshold,
@JsonPropertyDescription("Stabilization window in seconds for scale-up
decisions. "
+ "Picks the highest recommendation within this window to prevent
flapping.")
@Default("60")
@@ -73,6 +77,7 @@ public record AutoscalingSpec(
enabled = enabled != null ? enabled : false;
minReplicas = minReplicas != null ? minReplicas : 0;
scaleUpThreshold = scaleUpThreshold != null ? scaleUpThreshold : 80;
+ scaleDownThreshold = scaleDownThreshold != null ? scaleDownThreshold : 20;
scaleUpStabilizationSeconds = scaleUpStabilizationSeconds != null ?
scaleUpStabilizationSeconds : 60;
scaleDownStabilizationSeconds = scaleDownStabilizationSeconds != null ?
scaleDownStabilizationSeconds : 600;
gracePeriodSeconds = gracePeriodSeconds != null ? gracePeriodSeconds :
3600;
diff --git
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/model/spec/HiveServer2Spec.java
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/model/spec/HiveServer2Spec.java
index 62855537bcd..b4962d7e6f3 100644
---
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/model/spec/HiveServer2Spec.java
+++
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/model/spec/HiveServer2Spec.java
@@ -63,6 +63,6 @@ public record HiveServer2Spec(
extraVolumeMounts = extraVolumeMounts != null ? extraVolumeMounts :
List.of();
externalJars = externalJars != null ? externalJars : List.of();
autoscaling = autoscaling != null ? autoscaling : new AutoscalingSpec(
- false, 1, 80, 60, 600, 300, 10, 90, 30, null);
+ false, 1, 80, 0, 60, 600, 300, 10, 90, 30, null);
}
}
diff --git
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/model/spec/LlapSpec.java
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/model/spec/LlapSpec.java
index 61279829c90..eb42d8c8c0e 100644
---
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/model/spec/LlapSpec.java
+++
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/model/spec/LlapSpec.java
@@ -81,7 +81,7 @@ public record LlapTezAmSpec(
public LlapTezAmSpec {
replicas = replicas != null ? replicas : 1;
autoscaling = autoscaling != null ? autoscaling : new AutoscalingSpec(
- false, 0, 0, 60, 600, 120, 10, 0, 0, null);
+ false, 0, 0, 0, 60, 600, 120, 10, 0, 0, null);
}
}
@@ -102,7 +102,7 @@ public record LlapTezAmSpec(
extraVolumes = extraVolumes != null ? extraVolumes : List.of();
extraVolumeMounts = extraVolumeMounts != null ? extraVolumeMounts :
List.of();
autoscaling = autoscaling != null ? autoscaling : new AutoscalingSpec(
- false, 0, 1, 60, 900, 600, 10, 0, 0, null);
+ false, 0, 1, 20, 60, 900, 600, 10, 0, 0, null);
tezAm = tezAm != null ? tezAm : new LlapTezAmSpec(null, null);
}
diff --git
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/model/spec/MetastoreSpec.java
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/model/spec/MetastoreSpec.java
index 956bac65bcb..6548d999918 100644
---
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/model/spec/MetastoreSpec.java
+++
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/model/spec/MetastoreSpec.java
@@ -70,7 +70,7 @@ public record MetastoreSpec(
extraVolumes = extraVolumes != null ? extraVolumes : List.of();
extraVolumeMounts = extraVolumeMounts != null ? extraVolumeMounts :
List.of();
autoscaling = autoscaling != null ? autoscaling : new AutoscalingSpec(
- false, 1, 75, 60, 300, 60, 10, 90, 30, null);
+ false, 1, 75, 0, 60, 300, 60, 10, 90, 30, null);
}
public boolean isEnabled() {
diff --git
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/model/spec/TezAmSpec.java
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/model/spec/TezAmSpec.java
index 98d1c976dd9..8165b9b829a 100644
---
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/model/spec/TezAmSpec.java
+++
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/model/spec/TezAmSpec.java
@@ -64,7 +64,7 @@ public record TezAmSpec(
extraVolumes = extraVolumes != null ? extraVolumes : List.of();
extraVolumeMounts = extraVolumeMounts != null ? extraVolumeMounts :
List.of();
autoscaling = autoscaling != null ? autoscaling : new AutoscalingSpec(
- false, 0, 0, 60, 600, 120, 10, 0, 0, null);
+ false, 0, 0, 0, 60, 600, 120, 10, 0, 0, null);
}
public boolean isEnabled() {
diff --git
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/reconciler/HiveClusterReconciler.java
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/reconciler/HiveClusterReconciler.java
index ebc5c4f1ae8..a7da524a3db 100644
---
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/reconciler/HiveClusterReconciler.java
+++
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/reconciler/HiveClusterReconciler.java
@@ -704,10 +704,13 @@ private int resolveTezAmReplicaCount(HiveCluster resource,
if (llapManaged != null && llapManaged == 0) {
return 0;
}
- if (llapSpec.autoscaling().isEnabled() && llapManaged == null) {
- // First reconcile, LLAP starts at minReplicas (likely 0) — TezAM matches
- return llapSpec.autoscaling().minReplicas() > 0
- ? tezAmSpec.replicas() : 0;
+ if (llapSpec.autoscaling().isEnabled() && llapManaged == null
+ && llapSpec.autoscaling().minReplicas() == 0) {
+ // First reconcile before autoscaler runs: LLAP starts at 0, so TezAM
stays down too.
+ return 0;
+ }
+ if (tezAmSpec.autoscaling().isEnabled()) {
+ return tezAmSpec.autoscaling().minReplicas();
}
return tezAmSpec.replicas();
}
diff --git
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/util/ConfigUtils.java
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/util/ConfigUtils.java
index 6253681455b..76dac7a640b 100644
---
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/util/ConfigUtils.java
+++
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/util/ConfigUtils.java
@@ -141,6 +141,8 @@ public static String tezAmComponentKey(String llapName) {
public static final String HIVE_LLAP_DAEMON_UMBILICAL_PORT_KEY =
"hive.llap.daemon.umbilical.port";
public static final String HIVE_LLAP_DAEMON_UMBILICAL_PORT_DEFAULT = "0";
+ public static final String HIVE_LLAP_TASK_SCHEDULER_LOCALITY_DELAY_KEY =
"hive.llap.task.scheduler.locality.delay";
+
public static final String METASTORE_SERVER_TRANSPORT_MODE_KEY =
"metastore.server.thrift.transport.mode";
public static final String METASTORE_SERVER_TRANSPORT_MODE_DEFAULT = "http";
diff --git
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/util/HiveConfigBuilder.java
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/util/HiveConfigBuilder.java
index d71011f9289..9c78fa15342 100644
---
a/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/util/HiveConfigBuilder.java
+++
b/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator/util/HiveConfigBuilder.java
@@ -204,6 +204,12 @@ public static Map<String, String>
getTezSite(HiveClusterSpec spec, LlapSpec llap
if (spec.tezAm().configOverrides() != null) {
tezProps.putAll(spec.tezAm().configOverrides());
}
+
+ // Disable Infinite locality Delay when LLAP Auto-scaling is enabled, as
they are mutually exclusive.
+ if (llap != null && llap.isEnabled() && llap.autoscaling().isEnabled() &&
+ ConfigUtils.getTimeMs(tezProps,
ConfigUtils.HIVE_LLAP_TASK_SCHEDULER_LOCALITY_DELAY_KEY, 0) == -1) {
+ tezProps.put(ConfigUtils.HIVE_LLAP_TASK_SCHEDULER_LOCALITY_DELAY_KEY,
"0ms");
+ }
return tezProps;
}