This is an automated email from the ASF dual-hosted git repository.
cxzl25 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/auron.git
The following commit(s) were added to refs/heads/master by this push:
new 377e53242 [AURON #2422] Make RSS spill memory triggers configurable
(#2423)
377e53242 is described below
commit 377e53242a0089971d3167716b9e2972c55532e2
Author: cxzl25 <[email protected]>
AuthorDate: Tue Aug 4 14:02:21 2026 +0800
[AURON #2422] Make RSS spill memory triggers configurable (#2423)
# Which issue does this PR close?
Closes #2422
# Rationale for this change
Configurable spill trigger (rss_sort_repartitioner.rs). Replace the
hardcoded `mem_used_percent() > 0.4` with two tunable thresholds; spill
fires when either is exceeded (whichever first):
- `spark.auron.rss.spill.memoryFraction` (relative, default 0.4 =
previous behavior)
- `spark.auron.rss.spill.memorySize` (absolute bytes, default 0 =
disabled; acts as a deterministic cap on the per-spill burst regardless
of executor memory)
Both are read once per executor via OnceCell (no per-batch JNI conf
reads).
Adds `MemConsumer::mem_used()` (absolute bytes accessor) to support the
absolute threshold.
# What changes are included in this PR?
Auron's rss shuffle buffers records in native memory and spills to
celeborn in bursts. At a shuffle-stage peak, many tasks spill
simultaneously; if each spill is large, the aggregate burst can overrun
a celeborn worker's inbound capacity and push the worker into a
push-pause: it stops reading, holds inbound buffers, and only recovers
after the idle-connection timeout / connection close (observed as a
multi-minute stall — tasks survive via celeborn's client-side revive but
throughput degrades).
The previous hardcoded 0.4 fraction made the per-spill burst size depend
on executor memory and not directly tunable against this.
# Are there any user-facing changes?
No
# How was this patch tested?
Production environment verification
# Was this patch authored or co-authored using generative AI tooling?
- [x] Yes
- [ ] No
---
.../apache/auron/configuration/ConfigOption.java | 37 ++++++++++++++++++++++
native-engine/auron-jni-bridge/src/conf.rs | 2 ++
native-engine/auron-memmgr/src/lib.rs | 5 +++
.../src/shuffle/rss_sort_repartitioner.rs | 15 ++++++++-
.../configuration/SparkAuronConfiguration.java | 34 ++++++++++++++++++--
5 files changed, 90 insertions(+), 3 deletions(-)
diff --git
a/auron-core/src/main/java/org/apache/auron/configuration/ConfigOption.java
b/auron-core/src/main/java/org/apache/auron/configuration/ConfigOption.java
index 04a4e0f22..e564d7abc 100644
--- a/auron-core/src/main/java/org/apache/auron/configuration/ConfigOption.java
+++ b/auron-core/src/main/java/org/apache/auron/configuration/ConfigOption.java
@@ -18,7 +18,9 @@ package org.apache.auron.configuration;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.function.Function;
+import java.util.function.Predicate;
import org.apache.auron.jni.AuronAdaptor;
/**
@@ -53,6 +55,12 @@ public class ConfigOption<T> {
/** The function to compute the default value. */
private Function<AuronConfiguration, T> dynamicDefaultValueFunction;
+ /**
+ * Optional validator. Returns a non-empty error message (wrapped in
{@link Optional}) when the
+ * value is invalid, or an empty {@link Optional} when it is valid.
+ */
+ private Function<T, Optional<String>> validator;
+
/**
* Type of the value that this ConfigOption describes.
*
@@ -98,6 +106,35 @@ public class ConfigOption<T> {
return this;
}
+ /**
+ * Registers a validator that rejects values for which {@code check}
returns {@code false}. When
+ * the engine-side configuration backend reads a user-supplied value that
fails the check, it
+ * fails fast with an {@link IllegalArgumentException} carrying {@code
errorMsg}.
+ *
+ * @param check predicate that returns {@code true} for valid values.
+ * @param errorMsg human-readable message used when the check fails.
+ * @return this {@code ConfigOption} for chaining.
+ */
+ public ConfigOption<T> checkValue(Predicate<T> check, String errorMsg) {
+ this.validator = v -> check.test(v) ? Optional.empty() :
Optional.of(errorMsg);
+ return this;
+ }
+
+ /**
+ * Validates a resolved value against the registered validator, if any.
Called by the
+ * engine-side configuration backend when it reads a user-supplied value.
+ *
+ * @param value the value to validate.
+ * @return an empty {@link Optional} if valid or no validator is
registered, otherwise an
+ * {@link Optional} carrying the error message.
+ */
+ public Optional<String> validate(T value) {
+ if (validator == null) {
+ return Optional.empty();
+ }
+ return validator.apply(value);
+ }
+
/**
* Gets the configuration key.
*
diff --git a/native-engine/auron-jni-bridge/src/conf.rs
b/native-engine/auron-jni-bridge/src/conf.rs
index fa6c1d57a..9f160beb1 100644
--- a/native-engine/auron-jni-bridge/src/conf.rs
+++ b/native-engine/auron-jni-bridge/src/conf.rs
@@ -50,6 +50,8 @@ define_conf!(IntConf, TOKIO_WORKER_THREADS_PER_CPU);
define_conf!(IntConf, TASK_CPUS);
define_conf!(IntConf, SHUFFLE_COMPRESSION_TARGET_BUF_SIZE);
define_conf!(StringConf, SPILL_COMPRESSION_CODEC);
+define_conf!(DoubleConf, RSS_SPILL_MEMORY_FRACTION);
+define_conf!(LongConf, RSS_SPILL_MEMORY_SIZE);
define_conf!(BooleanConf, SMJ_FALLBACK_ENABLE);
define_conf!(IntConf, SMJ_FALLBACK_ROWS_THRESHOLD);
define_conf!(IntConf, SMJ_FALLBACK_MEM_SIZE_THRESHOLD);
diff --git a/native-engine/auron-memmgr/src/lib.rs
b/native-engine/auron-memmgr/src/lib.rs
index e9cb90311..32c89971d 100644
--- a/native-engine/auron-memmgr/src/lib.rs
+++ b/native-engine/auron-memmgr/src/lib.rs
@@ -224,6 +224,11 @@ pub trait MemConsumer: Send + Sync {
mem_used as f64 / consumer_mem_max as f64
}
+ /// Absolute bytes currently used by this consumer.
+ fn mem_used(&self) -> usize {
+ self.consumer_info().status.lock().mem_used
+ }
+
fn set_spillable(&self, spillable: bool) {
let consumer_info = self.consumer_info();
let mut consumer_status = consumer_info.status.lock();
diff --git
a/native-engine/datafusion-ext-plans/src/shuffle/rss_sort_repartitioner.rs
b/native-engine/datafusion-ext-plans/src/shuffle/rss_sort_repartitioner.rs
index b0b2c825c..61617fe57 100644
--- a/native-engine/datafusion-ext-plans/src/shuffle/rss_sort_repartitioner.rs
+++ b/native-engine/datafusion-ext-plans/src/shuffle/rss_sort_repartitioner.rs
@@ -17,11 +17,16 @@ use std::sync::Weak;
use arrow::record_batch::RecordBatch;
use async_trait::async_trait;
+use auron_jni_bridge::{
+ conf,
+ conf::{DoubleConf, LongConf},
+};
use auron_memmgr::{MemConsumer, MemConsumerInfo, MemManager};
use datafusion::{common::Result, physical_plan::metrics::Time};
use datafusion_ext_commons::arrow::array_size::BatchSize;
use futures::lock::Mutex;
use jni::objects::GlobalRef;
+use once_cell::sync::OnceCell;
use crate::shuffle::{Partitioning, ShuffleRepartitioner,
buffered_data::BufferedData};
@@ -102,7 +107,15 @@ impl ShuffleRepartitioner for RssSortShuffleRepartitioner {
// we are likely to spill more frequently because the cost of spilling
a shuffle
// repartition is lower than other consumers.
// rss shuffle spill has even lower cost than normal shuffle
- if self.mem_used_percent() > 0.4 {
+ static PCT_THRESHOLD: OnceCell<f64> = OnceCell::new();
+ static SIZE_THRESHOLD: OnceCell<i64> = OnceCell::new();
+ let pct_threshold =
+ *PCT_THRESHOLD.get_or_init(||
conf::RSS_SPILL_MEMORY_FRACTION.value().unwrap_or(0.4));
+ let size_threshold =
+ *SIZE_THRESHOLD.get_or_init(||
conf::RSS_SPILL_MEMORY_SIZE.value().unwrap_or(0));
+ if self.mem_used_percent() > pct_threshold
+ || (size_threshold > 0 && self.mem_used() > size_threshold as
usize)
+ {
self.force_spill().await?;
}
Ok(())
diff --git
a/spark-extension/src/main/java/org/apache/auron/spark/configuration/SparkAuronConfiguration.java
b/spark-extension/src/main/java/org/apache/auron/spark/configuration/SparkAuronConfiguration.java
index 366f32c13..97da32d4f 100644
---
a/spark-extension/src/main/java/org/apache/auron/spark/configuration/SparkAuronConfiguration.java
+++
b/spark-extension/src/main/java/org/apache/auron/spark/configuration/SparkAuronConfiguration.java
@@ -227,6 +227,28 @@ public class SparkAuronConfiguration extends
AuronConfiguration {
+ "Common options include lz4, snappy, and gzip.
The choice affects both spill performance and disk space usage.")
.withDefaultValue("lz4");
+ public static final ConfigOption<Double> RSS_SPILL_MEMORY_FRACTION = new
ConfigOption<>(Double.class)
+ .withKey("auron.rss.spill.memoryFraction")
+ .withCategory("Runtime Configuration")
+ .withDescription(
+ "RSS shuffle spill trigger: spill when this consumer's
memory fraction exceeds the threshold. "
+ + "A lower value makes auron spill sooner with a
smaller buffer each time, so each spill pushes "
+ + "a smaller burst to the celeborn worker and
reduces the peak in-flight bytes that can trigger "
+ + "a worker push pause. Default 0.4 preserves the
previous hardcoded behavior.")
+ .withDefaultValue(0.4)
+ .checkValue(v -> v != null && v >= 0.0 && v <= 1.0, "must be a
number in [0.0, 1.0]");
+
+ public static final ConfigOption<Long> RSS_SPILL_MEMORY_SIZE = new
ConfigOption<>(Long.class)
+ .withKey("auron.rss.spill.memorySize")
+ .withCategory("Runtime Configuration")
+ .withDescription(
+ "RSS shuffle spill trigger (absolute): force spill when
this consumer's buffered bytes exceed "
+ + "this size, regardless of the relative fraction.
This caps the per-spill burst size pushed to "
+ + "the celeborn worker deterministically (the
fraction-based threshold varies with executor memory). "
+ + "0 means disabled (only the fraction threshold
is used). Default 0 preserves previous behavior.")
+ .withDefaultValue(0L)
+ .checkValue(v -> v != null && v >= 0L, "must be >= 0");
+
public static final ConfigOption<Boolean> SMJ_FALLBACK_ENABLE = new
ConfigOption<>(Boolean.class)
.withKey("auron.smjfallback.enable")
.withCategory("Operator Supports")
@@ -544,12 +566,20 @@ public class SparkAuronConfiguration extends
AuronConfiguration {
: option instanceof SparkContextOption
? GetFromSparkType.FROM_SPARK_CONTEXT
: GetFromSparkType.FROM_SPARK_ENV;
- return Optional.ofNullable(getFromSpark(
+ T value = getFromSpark(
option.key(),
option.altKeys(),
option.getValueClass(),
() -> getOptionDefaultValue(option),
- getFromSparkType));
+ getFromSparkType);
+ if (value != null) {
+ Optional<String> error = option.validate(value);
+ if (error.isPresent()) {
+ throw new IllegalArgumentException(
+ "Invalid value for config '" + option.key() + "': " +
error.get() + " (value=" + value + ")");
+ }
+ }
+ return Optional.ofNullable(value);
}
enum GetFromSparkType {