bhouse-nexthop opened a new pull request, #14111:
URL: https://github.com/apache/cloudstack/pull/14111
> [!WARNING]
> **Draft — stacked on two other PRs. Please do not review the whole diff.**
>
> This branch contains the commits from #14108 and #14109 as well as its
own, because it depends on
> both. Only the **last four commits** are this PR:
>
> | Commit | |
> |---|---|
> | `Stop DRS planning from redoing the same work` | performance |
> | `Add 'weighted' DRS algorithm` | the feature |
> | `Fix defects found in review of the weighted DRS algorithm` | review
fixes |
> | `Verify equivalent VMs share one candidate host lookup` | test |
>
> Everything before those belongs to:
>
> - **#14108** — DRS anti-affinity fixes (targets `4.22`; the commits appear
here cherry-picked
> forward, and will disappear from this diff once that merges forward)
> - **#14109** — the `balancedweighted` allocation algorithm, which this
reuses for its
> utilisation figures
>
> It will be taken out of draft and rebased once both have merged. Raising
it now so the design can
> be discussed alongside them, since the three are easier to judge together
than apart.
### Description
Adds an opt-in DRS algorithm, `weighted`, for `drs.algorithm`, alongside
`balanced` and `condensed`.
The default is unchanged and the existing algorithms are untouched.
**The problem.** The existing algorithms balance a single metric chosen by
`drs.metric`, so whichever
is not chosen goes unwatched. A cluster can sit comfortably inside its
imbalance threshold on memory
while CPU load varies several fold across its hosts, and DRS will correctly
do nothing.
Allocation is also a poor stand-in for load under overprovisioning.
Allocated figures are measured
against a total that has already been multiplied by the overprovisioning
factor, so a host whose
cores are saturated can still report a small percentage allocated.
**What it balances.** A blend of four figures per host - CPU and memory
allocated, CPU and memory in
use:
```
w_ca*cpu_alloc + w_ma*mem_alloc + w_cu*cpu_util + w_mu*mem_util
metric(h) = -------------------------------------------------------------
w_ca + w_ma + w_cu + w_mu
imbalance = stddev( metric(h) for h in cluster ) / mean( metric(h) )
```
where the allocated fractions are measured against what a host can hand out
- its total scaled by
the cluster's overcommit ratio, with reserved capacity taken off that.
Imbalance keeps the shape the other algorithms use, so `drs.imbalance` still
behaves the same way,
but it is now computed over a blend rather than over one metric and is
therefore **not calibrated
the same**. Worth re-checking that setting after switching.
`drs.metric`, `drs.metric.type` and `drs.metric.use.ratio` choose and shape
the single metric the
other algorithms balance. They do not apply here and are ignored.
**Utilisation is used only when every host in the cluster has been
sampled.** Imbalance compares
hosts against each other, so measuring one host on a blend and another on
allocation alone would
report a difference that is an artefact of the monitoring rather than of the
load - and would
evacuate whichever host had stopped reporting. When any host is unmeasured
the cluster falls back to
allocation figures for all of them.
**Cost and benefit.** Both are expressed in units of imbalance, so the
caller's `benefit > cost` test
means "is this worth what it costs". A migration that has to move storage
has to earn more than one
that does not, by a configurable margin.
### Settings
| Setting | Default | Scope |
|---|---|---|
| `drs.weighted.cpu.allocated.weight` | `1.0` | Cluster |
| `drs.weighted.cpu.used.weight` | `2.0` | Cluster |
| `drs.weighted.memory.allocated.weight` | `1.0` | Cluster |
| `drs.weighted.memory.used.weight` | `2.0` | Cluster |
| `drs.weighted.storage.motion.cost` | `0.02` | Cluster |
Weights are relative to each other; `0` disables a term.
### Also: DRS plan generation is much faster
Independent of the algorithm, and useful on its own. Plan generation took
minutes on a large
cluster, for two reasons that are both about repeating work:
- Working out where a VM could go runs the host allocators and inspects
every volume, and it was
done once per VM. VMs that would get the same answer now share one pass. A
VM is only grouped when
it carries none of the per-VM inputs that change the answer - a custom
offering whose size comes
from the VM, a boot mode, a device setting - rather than trying to
enumerate everything that could
matter and risk missing one.
- Affinity was re-evaluated for every VM in the cluster on every iteration,
up to
`drs.max.migrations` times. Only VMs sharing a group with the one just
moved can have changed.
### Types of changes
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] Enhancement (improves an existing feature and functionality)
- [ ] Cleanup (Code refactoring and cleanup, that may add test cases)
- [ ] Build/CI
- [ ] Test (unit or integration test code)
### Feature/Enhancement Scale or Bug Severity
#### Feature/Enhancement Scale
- [x] Major
- [ ] Minor
### How Has This Been Tested?
- `WeightedTest` - 13 cases: an even cluster reads as balanced; CPU load
imbalance is seen when
memory is even and vice versa; allocation beyond what a host can hand out
stays distinguishable;
reserved capacity does not make a host look fuller; utilisation is dropped
when any host cannot be
measured and used when all can; moving a VM off the busier host is an
improvement and onto it is
not; storage motion has to earn more.
- `ClusterDrsEquivalenceTest` - 11 cases on what may and may not share a
candidate-host lookup,
including custom offerings and boot modes, which must never be grouped.
- `ClusterDrsServiceImplTest` - 34 cases, including one asserting that two
interchangeable VMs cost
one lookup rather than two, so the grouping cannot be silently disabled.
Full `mvn test` on `api`, `server` and all three DRS plugins with checkstyle
and license checks
enabled: 0 failures.
#### How did you try to break this feature and the system with this change?
Adversarial review found four defects that would have made this unusable,
all fixed:
- **The plugin had no `module.properties`**, so it was never discovered,
never instantiated and
never registered. Selecting it would have failed every DRS run with
"Invalid algorithm
configured".
- **It injected a service from a sibling Spring module**, which is not
visible to it. Once the
module did load, the management server would have failed to start - for
anyone with the plugin on
the classpath, regardless of configuration. The caller now works out the
load once per plan and
passes it in.
- **`getMetrics` read two cluster details and four settings from the
database on every call**, and
it is called for every candidate VM and host - hundreds of thousands of
times for a large cluster.
That would have swamped the performance work in the same PR.
- **Grouping VMs was unsound**: the key enumerated the inputs it thought
mattered and missed several
that are held per VM rather than per offering. Two VMs could share a host
list neither should have
had. Inverted to only group VMs that provably carry none of them.
Also found and fixed: reserved capacity was multiplied by the overcommit
ratio instead of subtracted
from the scaled total, so a host 11% fuller than its peer read 67% fuller
and DRS would have
evacuated it for no reason; hosts with and without utilisation samples were
compared on different
bases; and the cost and benefit terms cancelled out, making storage motion
free despite the claim
otherwise.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]