Skip to main content

Util Increase Cost

Type: Goal

Encourage objects to land on under-utilized scope items by penalizing utilization above a lower bound. A scope item below the lower bound can take on more load for free, while pushing a scope item past the lower bound incurs a cost---so objects are drawn toward the emptiest scope items. When several scope items are below the lower bound, the choice between them is arbitrary.

Parameters

ParameterTypeRequiredDefaultDescription
namestringYes-Descriptive name for logging/debugging
scopestringYes-Scope whose scope items are penalized (e.g. "host")
dimensionstringYes-Dimension whose relative utilization is penalized
lowerBounddoubleYes-Relative-utilization threshold; only utilization above it is penalized
squaresboolNofalseRaise the penalty to a superlinear power (1.1, despite the name), penalizing higher utilization disproportionately
filterFilterNoall scope itemsWhich scope items count toward the goal

Example

An example use: drain a host while filling the emptiest hosts first. Three hosts have a capacity of 10 and a lower bound of 0.5. host0 is at 0.2, host1 at 0.3, and host2 at 0.5 (exactly the bound). We drain host0; its task should land on host1 (which has room below the bound) rather than host2 (already at the bound, so any addition is penalized). (source)

(The diagram labels these from 1: Container 1 = host0, Task 1 = task0, and so on.)

Initial assignment:

Initial assignment: host0 at 0.2, host1 at 0.3, host2 at 0.5 (the lower bound)
solver.setObjectName("task");
solver.setContainerName("host");

solver.setAssignment(std::map<std::string, std::vector<std::string>>{
{"host0", {"task0"}}, // util 0.2
{"host1", {"task1"}}, // util 0.3
{"host2", {"task2"}}, // util 0.5 == lower bound
});

// cpu load per task; each host has a capacity of 10.
solver.addObjectDimension(
"cpu", std::map<std::string, double>{{"task0", 2}, {"task1", 3}, {"task2", 5}});
solver.addContainerDimension(
"cpu", std::map<std::string, double>{{"host0", 10}, {"host1", 10}, {"host2", 10}});

// Drain host0.
ToFreeSpec toFree;
toFree.containers() = {"host0"};
solver.addConstraint(toFree);

// Penalize utilization above the 0.5 lower bound.
UtilIncreaseCostSpec utilIncreaseCost;
utilIncreaseCost.scope() = "host";
utilIncreaseCost.dimension() = "cpu";
utilIncreaseCost.lowerBound() = 0.5;
utilIncreaseCost.squares() = true;
solver.addGoal(utilIncreaseCost);

task0 moves to host1, bringing it to exactly the lower bound (0.5) at no cost. Moving it to host2 instead would push that host above the bound and incur a penalty.

Final assignment:

Final assignment: host0 drained, its task moved to host1 which reaches the 0.5 lower bound

Goal only

UtilIncreaseCost can only be used as a goal; there is no constraint form. Its penalty competes with other goals, so it nudges placement toward under-utilized scope items rather than enforcing a hard rule.

Larger-capacity bias with squares

When every candidate scope item is already above the lower bound, the squares formula biases placement toward larger-capacity scope items, which is often not what you want. The reason: relative utilization is absoluteUtil / capacity, so adding an object raises a bigger scope item's relative utilization by less than it raises a smaller one's. The squared penalty therefore grows more slowly on the larger scope item, making it look cheaper---so load piles onto the biggest scope items. If you need balanced relative utilization instead, use Balance with its IDEAL formula, which compares scope items by relative utilization directly and does not over-load the larger ones.

More Examples

Both examples drain host0, which starts with 10 tasks (each cpu = 1) while host1 and host2 start empty.

Example 2: multiple scope items below the lower bound

With a lower bound of 0.5 and a capacity of 100 per host, both host1 and host2 stay below the bound no matter how the 10 tasks are split, so there is no penalty to distinguish them---the tasks are placed across the two arbitrarily. (source)

Initial assignment:

Example 2 initial: 10 tasks on host0, host1 and host2 empty

Final assignment:

Example 2 final: the 10 tasks spread arbitrarily across host1 and host2

Example 3: all scope items above the lower bound (larger-capacity bias)

With a lower bound of 0, every host is above the bound as soon as it holds a task. host1 has a far larger capacity than host2, so under the squares formula it absorbs the smaller relative-utilization increase, and all 10 tasks move there---an illustration of the larger-capacity bias. (source)

Initial assignment:

Example 3 initial: 10 tasks on host0; host1 has very large capacity, host2 small

Final assignment:

Example 3 final: all tasks moved to the larger-capacity host1

Source