From: Michael Ryom <Michael@RyomHerold.dk>
To: pve-devel@lists.proxmox.com
Cc: Michael Ryom <Michael@RyomHerold.dk>
Subject: [PATCH ha-manager 3/7] manager: auto rebalance: require a minimum absolute imbalance improvement
Date: Sun, 20 Sep 2026 18:22:10 +0200 [thread overview]
Message-ID: <20260920162220.574802-4-Michael@RyomHerold.dk> (raw)
In-Reply-To: <20260920162220.574802-1-Michael@RyomHerold.dk>
The automatic load balancer only requires a relative improvement of the
node imbalance (default 10%). When a single resource dominates the
cluster load, the relative margin provides almost no hysteresis: with
two nodes, a resource load v and a node base load difference d, the
relative improvement of moving the resource is 2d / (v + d), so it
exceeds a margin m as soon as d > v * m / (2 - m). For a mid-size VM
that is a base load difference of about half a percentage point, well
within the normal fluctuation of e.g. the ZFS ARC or Ceph daemons, and
each fluctuation then qualifies a motion (and, after it was carried
out, its reverse).
Additionally require the imbalance improvement to exceed a minimum
absolute value of 5 percentage points. This creates a dead band for
motions that shuffle load without meaningfully improving the cluster
balance. No expected motion of an existing regression test is affected;
the smallest occurring improvement there is 9.6 percentage points.
Like the backoff and cooldown constants, this could be exposed as a
ha-auto-rebalance-* option later.
Signed-off-by: Michael Ryom <Michael@RyomHerold.dk>
---
src/PVE/HA/Manager.pm | 8 +++++
.../test-crs-dynamic-auto-rebalance7/README | 12 ++++++++
.../test-crs-dynamic-auto-rebalance7/cmdlist | 3 ++
.../datacenter.cfg | 6 ++++
.../dynamic_service_stats | 5 ++++
.../hardware_status | 4 +++
.../log.expect | 30 +++++++++++++++++++
.../manager_status | 1 +
.../service_config | 5 ++++
.../static_service_stats | 5 ++++
10 files changed, 79 insertions(+)
create mode 100644 src/test/test-crs-dynamic-auto-rebalance7/README
create mode 100644 src/test/test-crs-dynamic-auto-rebalance7/cmdlist
create mode 100644 src/test/test-crs-dynamic-auto-rebalance7/datacenter.cfg
create mode 100644 src/test/test-crs-dynamic-auto-rebalance7/dynamic_service_stats
create mode 100644 src/test/test-crs-dynamic-auto-rebalance7/hardware_status
create mode 100644 src/test/test-crs-dynamic-auto-rebalance7/log.expect
create mode 100644 src/test/test-crs-dynamic-auto-rebalance7/manager_status
create mode 100644 src/test/test-crs-dynamic-auto-rebalance7/service_config
create mode 100644 src/test/test-crs-dynamic-auto-rebalance7/static_service_stats
diff --git a/src/PVE/HA/Manager.pm b/src/PVE/HA/Manager.pm
index 30b9ae2..2c4c273 100644
--- a/src/PVE/HA/Manager.pm
+++ b/src/PVE/HA/Manager.pm
@@ -151,6 +151,13 @@ my $auto_rebalance_failure_backoff_max = 3600;
# minimum time before a resource is considered for rebalancing again after it
# was successfully moved by the load balancer
my $auto_rebalance_resource_cooldown = 600;
+# minimum absolute improvement of the node imbalance for a rebalance motion;
+# the relative margin alone provides almost no hysteresis when a single
+# resource dominates the cluster load: with two nodes, a resource load v and
+# a node base load difference d, the relative improvement of a motion is
+# 2d / (v + d), so for large v tiny fluctuations of d around zero are enough
+# to make the motion (and afterwards its reverse) qualify
+my $auto_rebalance_min_imbalance_improvement = 0.05;
my $is_on_rebalance_cooldown = sub {
my ($self, $sid, $now) = @_;
@@ -387,6 +394,7 @@ sub load_balance {
my $relative_change = ($imbalance - $target_imbalance) / $imbalance;
return if $relative_change < $margin;
+ return if ($imbalance - $target_imbalance) < $auto_rebalance_min_imbalance_improvement;
my ($sid, $source, $target) = $migration->@{qw(sid source-node target-node)};
diff --git a/src/test/test-crs-dynamic-auto-rebalance7/README b/src/test/test-crs-dynamic-auto-rebalance7/README
new file mode 100644
index 0000000..488a018
--- /dev/null
+++ b/src/test/test-crs-dynamic-auto-rebalance7/README
@@ -0,0 +1,12 @@
+Test that the auto rebalance system does not act on a rebalance motion whose
+absolute improvement of the node imbalance is below the minimum, even though
+the relative improvement exceeds the configured margin.
+
+The cluster has two nodes with a HA resource excluded from auto rebalancing
+on each (vm:100 on node1 and vm:200 on node2) acting as node base loads, and
+one movable HA resource vm:101 on node1. The imbalance is 35.6% and stays
+above the default trigger threshold of 30%, and migrating vm:101 to node2
+would improve the imbalance to 31.1%, i.e. by 12.5% relative, which exceeds
+the default margin of 10%. However, the absolute improvement of 4.4
+percentage points is below the required minimum of 5 percentage points, so
+the motion must not be issued.
diff --git a/src/test/test-crs-dynamic-auto-rebalance7/cmdlist b/src/test/test-crs-dynamic-auto-rebalance7/cmdlist
new file mode 100644
index 0000000..76f0313
--- /dev/null
+++ b/src/test/test-crs-dynamic-auto-rebalance7/cmdlist
@@ -0,0 +1,3 @@
+[
+ [ "power node1 on", "power node2 on" ]
+]
diff --git a/src/test/test-crs-dynamic-auto-rebalance7/datacenter.cfg b/src/test/test-crs-dynamic-auto-rebalance7/datacenter.cfg
new file mode 100644
index 0000000..01c8114
--- /dev/null
+++ b/src/test/test-crs-dynamic-auto-rebalance7/datacenter.cfg
@@ -0,0 +1,6 @@
+{
+ "crs": {
+ "ha": "dynamic",
+ "ha-auto-rebalance": 1
+ }
+}
diff --git a/src/test/test-crs-dynamic-auto-rebalance7/dynamic_service_stats b/src/test/test-crs-dynamic-auto-rebalance7/dynamic_service_stats
new file mode 100644
index 0000000..231af30
--- /dev/null
+++ b/src/test/test-crs-dynamic-auto-rebalance7/dynamic_service_stats
@@ -0,0 +1,5 @@
+{
+ "vm:100": { "cpu": 7.44, "mem": 0 },
+ "vm:101": { "cpu": 7.2, "mem": 0 },
+ "vm:200": { "cpu": 6.96, "mem": 0 }
+}
diff --git a/src/test/test-crs-dynamic-auto-rebalance7/hardware_status b/src/test/test-crs-dynamic-auto-rebalance7/hardware_status
new file mode 100644
index 0000000..864adb3
--- /dev/null
+++ b/src/test/test-crs-dynamic-auto-rebalance7/hardware_status
@@ -0,0 +1,4 @@
+{
+ "node1": { "power": "off", "network": "off", "maxcpu": 24, "maxmem": 34359738368 },
+ "node2": { "power": "off", "network": "off", "maxcpu": 24, "maxmem": 34359738368 }
+}
diff --git a/src/test/test-crs-dynamic-auto-rebalance7/log.expect b/src/test/test-crs-dynamic-auto-rebalance7/log.expect
new file mode 100644
index 0000000..6c84e42
--- /dev/null
+++ b/src/test/test-crs-dynamic-auto-rebalance7/log.expect
@@ -0,0 +1,30 @@
+info 0 hardware: starting simulation
+info 20 cmdlist: execute power node1 on
+info 20 node1/crm: status change startup => wait_for_quorum
+info 20 node1/lrm: status change startup => wait_for_agent_lock
+info 20 cmdlist: execute power node2 on
+info 20 node2/crm: status change startup => wait_for_quorum
+info 20 node2/lrm: status change startup => wait_for_agent_lock
+info 20 node1/crm: got lock 'ha_manager_lock'
+info 20 node1/crm: status change wait_for_quorum => master
+info 20 node1/crm: using scheduler mode 'dynamic'
+info 20 node1/crm: node 'node1': state changed from 'unknown' => 'online'
+info 20 node1/crm: node 'node2': state changed from 'unknown' => 'online'
+info 20 node1/crm: adding new service 'vm:100' on node 'node1'
+info 20 node1/crm: adding new service 'vm:101' on node 'node1'
+info 20 node1/crm: adding new service 'vm:200' on node 'node2'
+info 20 node1/crm: service 'vm:100': state changed from 'request_start' to 'started' (node = node1)
+info 20 node1/crm: service 'vm:101': state changed from 'request_start' to 'started' (node = node1)
+info 20 node1/crm: service 'vm:200': state changed from 'request_start' to 'started' (node = node2)
+info 21 node1/lrm: got lock 'ha_agent_node1_lock'
+info 21 node1/lrm: status change wait_for_agent_lock => active
+info 21 node1/lrm: starting service vm:100
+info 21 node1/lrm: service status vm:100 started
+info 21 node1/lrm: starting service vm:101
+info 21 node1/lrm: service status vm:101 started
+info 22 node2/crm: status change wait_for_quorum => slave
+info 23 node2/lrm: got lock 'ha_agent_node2_lock'
+info 23 node2/lrm: status change wait_for_agent_lock => active
+info 23 node2/lrm: starting service vm:200
+info 23 node2/lrm: service status vm:200 started
+info 620 hardware: exit simulation - done
diff --git a/src/test/test-crs-dynamic-auto-rebalance7/manager_status b/src/test/test-crs-dynamic-auto-rebalance7/manager_status
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/src/test/test-crs-dynamic-auto-rebalance7/manager_status
@@ -0,0 +1 @@
+{}
diff --git a/src/test/test-crs-dynamic-auto-rebalance7/service_config b/src/test/test-crs-dynamic-auto-rebalance7/service_config
new file mode 100644
index 0000000..83bf42e
--- /dev/null
+++ b/src/test/test-crs-dynamic-auto-rebalance7/service_config
@@ -0,0 +1,5 @@
+{
+ "vm:100": { "node": "node1", "state": "started", "auto-rebalance": 0 },
+ "vm:101": { "node": "node1", "state": "started" },
+ "vm:200": { "node": "node2", "state": "started", "auto-rebalance": 0 }
+}
diff --git a/src/test/test-crs-dynamic-auto-rebalance7/static_service_stats b/src/test/test-crs-dynamic-auto-rebalance7/static_service_stats
new file mode 100644
index 0000000..300fb7a
--- /dev/null
+++ b/src/test/test-crs-dynamic-auto-rebalance7/static_service_stats
@@ -0,0 +1,5 @@
+{
+ "vm:100": { "maxcpu": 8.0, "maxmem": 8589934592 },
+ "vm:101": { "maxcpu": 8.0, "maxmem": 8589934592 },
+ "vm:200": { "maxcpu": 8.0, "maxmem": 8589934592 }
+}
--
2.47.3
next prev parent reply other threads:[~2026-09-20 16:39 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-20 16:22 [PATCH ha-manager 0/7] auto rebalance: fix failure retry loop, oscillation and idle-cluster churn Michael Ryom
2026-09-20 16:22 ` [PATCH ha-manager 1/7] fix #8059: manager: auto rebalance: back off failed motions and add per-resource cooldown Michael Ryom
2026-09-20 16:22 ` [PATCH ha-manager 2/7] env: dynamic service stats: use host-side memory footprint of guests Michael Ryom
2026-09-21 9:18 ` Dominik Rusovac
2026-09-20 16:22 ` Michael Ryom [this message]
2026-09-20 16:22 ` [PATCH ha-manager 4/7] sim: hardware: report actual running state in cluster service stats Michael Ryom
2026-09-20 16:22 ` [PATCH ha-manager 5/7] sim: hardware: allow setting a base load for nodes Michael Ryom
2026-09-20 16:22 ` [PATCH ha-manager 6/7] usage: dynamic: smooth the unaccounted node load Michael Ryom
2026-09-20 16:22 ` [PATCH ha-manager 7/7] manager: auto rebalance: only balance under actual node resource pressure Michael Ryom
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260920162220.574802-4-Michael@RyomHerold.dk \
--to=michael@ryomherold.dk \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.