public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
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




  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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal