Skip to main content

Group Move Limit

Type: Constraint

Limit how many objects of the same group are allowed to move. For example, cap how many tasks of a job may be reassigned in a single rebalance, so a job is never disrupted too much at once.

Container scope only

GroupMoveLimit currently supports only the container scope, so every scope item referred to below is a container.

Parameters

ParameterTypeRequiredDefaultDescription
namestringYes-Descriptive name for logging/debugging
partitionNamestringYes-Partition whose groups' moves are limited (e.g. "group")
limitLimitNoABSOLUTE, globalLimit 1Max number of moves allowed per group (see Limit)
sourceScopeItemsAffectingLimitFilterFilterNoall scope itemsOnly moves out of these scope items count toward a group's limit (see Which moves count)
destinationScopeItemsAffectingLimitFilterFilterNoall scope itemsOnly moves into these scope items count toward a group's limit (see Which moves count)
dimensionstringNoobject countWhat a move's size is measured by; defaults to the number of objects moved

Example

All examples share this setup: 4 tasks split into two groups (group0 = task0, task1; group1 = task2, task3), placed across 3 hosts.

Partition:

Partition: group0 holds task0 and task1; group1 holds task2 and task3

Initial assignment: host0 holds task0, task1, task2; host1 holds task3; host2 is empty. group0 prefers host1 and group1 prefers host2 (expressed with an AssignmentAffinities goal), so without any limit every task would move toward its preferred host.

Initial assignment: host0 holds three tasks, host1 holds one, host2 is empty

(The diagrams number tasks and containers from 1, so Task 1 = task0, Container 1 = host0, and so on.)

A GroupMoveLimit constraint with a global limit of 1 lets each group move at most one of its objects, so each group can satisfy only part of its preference:

solver.setObjectName("task");
solver.setContainerName("host");

solver.setAssignment(std::map<std::string, std::vector<std::string>>{
{"host0", {"task0", "task1", "task2"}},
{"host1", {"task3"}},
{"host2", {}},
});

// Two groups of two tasks each.
solver.addPartition(
"group",
std::map<std::string, std::string>{
{"task0", "group0"}, {"task1", "group0"},
{"task2", "group1"}, {"task3", "group1"}});

// (group0 prefers host1 and group1 prefers host2 via an AssignmentAffinities goal.)

// Each group may move at most one of its objects.
GroupMoveLimitSpec groupMoveLimit;
groupMoveLimit.partitionName() = "group";

Limit limit;
limit.type() = LimitType::ABSOLUTE;
limit.globalLimit() = 1;
groupMoveLimit.limit() = limit;

solver.addConstraint(groupMoveLimit);

There are several valid solutions; each leaves each group having moved at most once:

Two valid solutions, each with every group having moved at most once

Constraint only

GroupMoveLimit can only be used as a constraint; there is no goal form. Add it with solver.addConstraint. As with any constraint, if the initial assignment already satisfies it the final one will too, and if it is initially broken the general constraint policy applies.

Limit

The limit (Limit) caps the number of moves per group; the default is an absolute global limit of 1. Use groupLimits to set a different cap per group---for example, allow group0 up to 2 moves while forbidding group1 from moving at all (Example 2). With a dimension, the limit bounds the summed dimension of the moved objects rather than their count.

Which moves count

By default every move of a group's object counts toward that group's limit. The two filters narrow which moves are counted, by the move's source and destination scope items:

  • sourceScopeItemsAffectingLimitFilter --- a move counts only if it leaves a scope item selected by this filter.
  • destinationScopeItemsAffectingLimitFilter --- a move counts only if it enters a scope item selected by this filter.

A move counts toward a group's limit only when both conditions hold. Moves that fall outside the filters are always allowed, regardless of the limit (Example 3).

More Examples

All examples build on the shared setup above. Each links to its runnable unit test.

Example 2: per-group limit

A GroupMoveLimit with groupLimits of 2 for group0 and 0 for group1: group0 may move freely toward its preferred host while group1 is pinned in place. Only group0 ends up moving. (source)

Per-group limit: only group0 moved

Example 3: filtering which moves count

A global limit of 0 (normally pinning everything in place), but with sourceScopeItemsAffectingLimitFilter whitelisting {host0, host2} and destinationScopeItemsAffectingLimitFilter blacklisting {host1}. group0 can still move to host1 because moves into host1 do not count; and task3 can move out of host1 because moves out of host1 (not in the source whitelist) do not count either. (source)

Filtering which moves count: moves to or from host1 are unaffected by the limit

Source