CP-SAT based VM placement solver for ProxLB
Project description
ProxLB CP-SAT Solver
The ProxLB Solver is a mathematically exact scheduler for Proxmox VE clusters. It uses Google's OR-Tools CP-SAT to find the provably global optimum for VM and Container placement, moving beyond simple greedy heuristics.
Algorithmic Overview
graph TD
A[Input: Cluster Snapshot] --> B{Validator}
B -- Conflict detected --> C[Status: RULE_CONFLICT]
B -- OK --> D[CP-SAT Modeling]
subgraph Solver [Optimization Loop]
D --> E[Solver: Find Optimum]
E -- Infeasible --> F[Status: INFEASIBLE]
E -- Optimal State --> G[Planner: Calculate Path]
G -- Blocked cycle? --> H{Reachable?}
H -- No --> I[Add No-Good Clause]
I --> E
H -- Yes --> J[Solution: Optimal & Executable]
end
1. Mathematical Core
The solver treats guest placement as an Integer Linear Programming (ILP) problem.
Decision Variables
For every guest $i$ (VM or Container) and node $j$, a binary variable $x_{i,j}$ is defined:
- $x_{i,j} = 1$: Guest $i$ is assigned to node $j$.
- $x_{i,j} = 0$: Guest $i$ is not assigned to node $j$.
Every guest must be assigned to exactly one node: $\sum_{j} x_{i,j} = 1$.
Integer Arithmetic & Scaling
CP-SAT is an integer-only solver. To handle fractional values (like 0.5 CPU cores or 12.5% PSI), the solver internally scales all metrics by a factor of 10,000 (_LOAD_SCALE).
- 100% load is represented as 10,000.
- 0.5% PSI is represented as 50.
- This scaling explains the large objective values seen in technical solver logs.
Objective Function
The solver minimizes a weighted cost function: $$\text{Minimize: } (w_\text{balance} \cdot \text{Spread}) + (w_\text{stickiness} \cdot \text{MigrationCost}) + \text{Penalty}_\text{SoftRules}$$
- Spread: The difference between the most and least utilized node ($\text{Max} - \text{Min}$), scaled by total capacity.
- MigrationCost: A weighted sum over all migrated guests — see §2 below.
- Penalty: A massive malus ($1{,}000{,}000$) for every violated soft constraint.
2. Migration Cost Model
Migrating a guest has a real cost: RAM must be copied live over the network (dirty-page tracking); local disk requires a full sequential copy. The cost model reflects this:
$$\text{cost}(\text{Guest}) = \max(1,\ \lfloor \text{RAM} / 256,\text{MiB} \rfloor) + 4 \times \lfloor \text{LocalDisk} / 256,\text{MiB} \rfloor$$
The 256 MiB base unit gives enough granularity for the solver to distinguish between a 512 MiB guest (cost 2) and a 1 GiB guest (cost 4). The max(1, …) floor ensures tiny containers still have a non-zero weight.
The 4× local disk factor reflects that copying a local disk (LVM/ZFS) is significantly slower than a RAM live-migration.
3. Resource Metrics & Strategy Modes
ProxLB supports multiple optimization dimensions via the method parameter:
| Method | Balance objective | Use Case |
|---|---|---|
memory |
RAM allocation | Classic memory-based balancing (default). |
cpu |
CPU load (cores) | Throughput optimization. |
disk |
Local storage usage | Disk capacity balancing across nodes. |
cpu_psi |
CPU stall time (PSI) | Latency optimization (PVE 9+). |
cpu_smart |
CPU load + PSI | Balance of throughput and responsiveness. |
global_smart |
RAM + CPU + IO | Holistic cluster-wide optimization. |
RAM: configured allocation vs. actual RSS
The solver balances RAM by configured allocation, not actual RSS. This is correct for capacity planning — a VM configured with 4 GiB must be placed on a node that has 4 GiB reserved, regardless of its current usage.
CPU: Usage vs. Assigned
- Used Mode (Default): Balances based on the actual measured CPU load (
cpu_used). - Assigned Mode: Balances based on the configured number of vCPUs. This ensures that reserved compute capacity is distributed evenly.
The PSI Footprint Model (CPU, RAM, IO)
For PSI metrics, the solver uses an additive footprint model. It tries to spread these contributions so that the aggregate pressure on each node stays as low and uniform as possible.
4. Weight Hierarchy
Optimization is fine-tuned via three distinct tiers:
- Global Level (
w_global_*): Importance of resource pools (e.g., "RAM balance is 10x more important than IO"). - Resource Level (
w_*_usagevsw_*_psi): Weighting raw utilization against dynamic pressure stalls. - Guest Level (
priority):- Priority 3 (High): Footprint counts 3× towards the spread calculation.
- Priority 1 (Low): Footprint counts 1×.
- Effect: High-priority guests "force" their way onto nodes with the most free resources by artificially inflating their perceived load during the optimization phase.
5. Constraints
Hard Constraints (Strict)
Violations result in INFEASIBLE.
- Capacity: RAM, CPU cores (with overcommit), and named storage pools (ZFS, LVM).
- Pinning: Binding guests to specific hardware. Pinning is always hard.
- Maintenance: Nodes in maintenance mode are forbidden targets.
- Hard Rules: Affinity/Anti-Affinity marked as
hard: true.
Rule Origins & Specialized Handling
The solver distinguishes between rules based on their origin:
| Origin | Type | Handling | Rationale |
|---|---|---|---|
pve |
Native HA | Atomic / Strict | Proxmox enforces these rules automatically. |
plb |
Internal Tags | Granular / Soft | ProxLB manages these; allows flexible transitions. |
- PVE Affinity (Atomic): Members of a native Proxmox affinity group are moved in the same execution step.
- PVE Anti-Affinity (Strict): The planner ensures partners never share a node even for a split second during transitions.
6. Reachability Guarantee & Migration Planning
An optimal state is worthless if it cannot be executed (e.g., no buffer space for a swap). The Solver finds the target state, but the Planner determines the safe path to get there.
- Dependency Analysis: The Planner builds a directed graph where an edge $A \to B$ means "Guest A must move before Guest B can fit on its target node".
- Step-by-Step Simulation: Migrations are grouped into execution steps. For every step, the Planner simulates node capacities to ensure that no host is oversubscribed even during the transition.
- Cycle Breaking (Temp-Moves): Circular dependencies (e.g., Guest-A $\leftrightarrow$ Guest-B swap) are detected. If a third "spare" node with sufficient capacity exists, the Planner inserts a temp-move (parking) to break the loop.
- Atomic PVE Affinity: For native Proxmox affinity groups, the Planner picks one "trigger" guest for the API call. It assumes Proxmox will co-migrate the other group members atomically and accounts for their total resource footprint in that single step.
- Strict PVE Anti-Affinity: To satisfy native PVE anti-affinity, the Planner ensures that partners never share a node, even temporarily. One must fully vacate the target before the other is allowed to land.
- No-Good Feedback: If a dependency cycle is mathematically unbreakable (e.g., cluster is too full for parking), the Planner rejects the solution. The Solver is then re-triggered with a "No-Good" constraint to find the next-best reachable state.
7. ProxLB Integration (Shadow & Active Mode)
The solver integrates with ProxLB via two operating modes:
Shadow mode (default, read-only)
The solver runs alongside ProxLB's built-in balancer without changing anything. It produces a structured JSONL log and an HTML report for validation.
Active mode
The solver takes over execution. ProxLB's Balancing() class is still used for API calls, but the solver determines the plan. A feedback loop handles migration failures by pinning failed guests and re-solving.
Administrator Guide: Configuration & Defaults
The ProxLB Solver is tuned for Stability over Agility by default.
1. Operational Safety
max_node_inflow(Default: 1): Only one guest at a time can migrate into a host. This prevents memory or CPU peaks that could trigger OOM on the target host.max_parallel_migrations(Default: 2): Limits simultaneous migrations cluster-wide.balanciness(Default: 3 — Moderate):- Level 1–2: Only moves guests for maintenance or hard rule violations.
- Level 3: Rebalances only if the spread exceeds ~15%.
- Level 5: Chases perfect balance.
2. Resource Balancing Strategy
method(Default:memory): RAM is usually the hardest bottleneck. Start with memory balancing before exploring CPU or Smart modes.cpu_overcommit(Default: 2.0): Allows assigning more vCPUs than physical cores exist.
Usage for Developers
Installation
make install
Running Tests
make test
Generating Reports
Integration results and shadow-mode comparisons can be visualized as interactive reports:
make report
This produces the following artifacts in the project root:
results.html: Full interactive report with sidebar, Mermaid dependency graphs, and per-run detail pages.results.md: A concise Markdown summary of all processed runs.results.xml: JUnit-compatible XML for CI/CD integration.
Individual run logs are stored as .jsonl files in the configured log_dir (e.g., /tmp/proxlb-solver-logs).
Internal Architecture
models.py: Strict type definitions for the cluster state.adapter.py: Bridge between ProxLB's runtime data and the solver models.solver.py: Mathematical model and CP-SAT integration.planner.py: Topological sort and dependency resolution for migrations.shadow.py: Non-intrusive "shadow mode" for live cluster observation.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file proxlb_solver-0.1.0rc1.tar.gz.
File metadata
- Download URL: proxlb_solver-0.1.0rc1.tar.gz
- Upload date:
- Size: 70.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9687ab37e39873b2263867389e9ba19bb49baa9b44152fff7e83e8f0daf8b6c
|
|
| MD5 |
33c03f34a7e6a631db1d4fce28747050
|
|
| BLAKE2b-256 |
6484ebf392b83df8711eac7f8b368af45b6d8837b69c6a2c90bb6b3e211e252f
|
File details
Details for the file proxlb_solver-0.1.0rc1-py3-none-any.whl.
File metadata
- Download URL: proxlb_solver-0.1.0rc1-py3-none-any.whl
- Upload date:
- Size: 64.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e4d2b361defca6dcede9b7c8c22b88b3a936942fe2a3296e323dae43de9bee50
|
|
| MD5 |
5eedfe13d1d142fc0659f677ac630f3c
|
|
| BLAKE2b-256 |
23a88e521c8665cdf148884b9f872261405a1e49d6c02351f8cec56d395c2d93
|