SwapFullContainers
Move Type: Swap Complexity: O(containers) with full container exchange
Exchange ALL objects between two containers in one move. Much simpler than pairwise swaps - useful for container rebalancing and migration scenarios.
Overview
SwapFullContainers evaluates exchanging all objects between the hot container and every other container. Unlike regular Swap which tries individual object pairs, this move type swaps entire container contents at once.
Use when:
- Containers have similar sizes
- Want to relocate entire workload (e.g., migrate server)
- Capacity constraints block individual moves
- Container-level rebalancing more important than object-level
Avoid when:
- Containers have very different sizes
- Need fine-grained object placement
- Most objects in containers are well-placed
- Container capacities would be violated
Quick Example
- Python
- C++
# Exchange entire container contents
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapFullContainersMoveTypeSpec(), # Full container swaps
]
)
)
)
void quickExample() {
// Exchange entire container contents
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("host");
LocalSearchSolverSpec solverSpec;
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapFullContainersMoveTypeSpec() =
SwapFullContainersMoveTypeSpec{};
solver.addSolver(solverSpec);
}
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| (none) | - | - | - | SwapFullContainers has no configuration parameters |
How It Works
Given a hot container (the container contributing most to the objective):
- Select cold container: Pick a different container
- Evaluate full swap: Test exchanging ALL objects between hot and cold containers
- Repeat: Try all other containers as potential swap partners
- Apply best: Apply the full container swap that improves objective most
Visual Example
Before swap: After full container swap:
┌─────────────────┐ ┌─────────────────┐
│ Hot Container │ │ Hot Container │
│ • obj1 │ │ • objA ←───────┼─┐
│ • obj2 │ <========> │ • objB │ │
│ • obj3 │ │ • objC │ │
└─────────────────┘ └─────────────────┘ │
│
┌─────────────────┐ ┌─────────────────┐ │
│ Cold Container │ │ Cold Container │ │
│ • objA ───────┼──┐ │ • obj1 │ │
│ • objB │ │ │ • obj2 │ │
│ • objC │ │ │ • obj3 ←───────┼─┘
└─────────────────┘ │ └─────────────────┘
│
All objects exchanged in ONE move
Comparison with Regular Swap
| Aspect | Swap | SwapFullContainers |
|---|---|---|
| Granularity | Individual object pairs | Entire containers |
| Complexity | O(N² × C) | O(C) |
| Moves per iteration | Thousands-millions | Tens-hundreds |
| Use case | Fine-grained optimization | Container-level rebalancing |
Complexity
Moves evaluated per iteration: O(C)
Where:
- C = number of containers
Example:
- System: 100 containers
- Regular Swap with 1000 objects each: 1000² × 100 = 100 million pairwise swaps
- SwapFullContainers: 100 full container swaps
Note: While fewer moves are evaluated, each full container swap is more expensive to evaluate since all objects in both containers must be considered.
Usage Patterns
Server Migration
Move entire server workload:
- Python
- C++
# Migrate all shards from overloaded server to another
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapFullContainersMoveTypeSpec(), # Swap all shards between servers
]
)
)
)
void serverMigration() {
// Migrate all shards from overloaded server to another
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("shard");
solver.setContainerName("server");
LocalSearchSolverSpec solverSpec;
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapFullContainersMoveTypeSpec() =
SwapFullContainersMoveTypeSpec{};
solver.addSolver(solverSpec);
}
Container Consolidation
Rebalance container-level load:
- Python
- C++
# Container-level rebalancing for similar-sized containers
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapFullContainersMoveTypeSpec(), # Swap entire containers
]
)
)
)
void consolidation() {
// Container-level rebalancing for similar-sized containers
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("host");
LocalSearchSolverSpec solverSpec;
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapFullContainersMoveTypeSpec() =
SwapFullContainersMoveTypeSpec{};
solver.addSolver(solverSpec);
}
Combined with Object-Level Moves
Use for coarse adjustment, then fine-tune:
- Python
- C++
# Multi-stage: coarse container rebalancing, then fine object placement
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapFullContainersMoveTypeSpec(), # Container-level rebalancing
SwapMoveTypeSpec(), # Fine-tune with object swaps
SingleMoveTypeSpec(), # Final object placement
]
)
)
)
void combined() {
// Multi-stage: coarse container rebalancing, then fine object placement
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("host");
LocalSearchSolverSpec solverSpec;
// Add SwapFullContainers
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapFullContainersMoveTypeSpec() =
SwapFullContainersMoveTypeSpec{};
// Add Swap
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapMoveTypeSpec() = SwapMoveTypeSpec{};
// Add Single
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().singleMoveTypeSpec() = SingleMoveTypeSpec{};
solver.addSolver(solverSpec);
}
Capacity-Constrained Full Swaps
When individual moves blocked by capacity:
- Python
- C++
# Full swaps can work when individual moves are capacity-blocked
# Note: Full swaps may violate capacity if container sizes differ
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapFullContainersMoveTypeSpec(), # Try full container exchanges
SwapMoveTypeSpec(), # Fall back to capacity-neutral object swaps
]
)
)
)
void capacityConstrained() {
// Full swaps can work when individual moves are capacity-blocked
// Note: Full swaps may violate capacity if container sizes differ
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("host");
LocalSearchSolverSpec solverSpec;
// Add SwapFullContainers
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapFullContainersMoveTypeSpec() =
SwapFullContainersMoveTypeSpec{};
// Add Swap as fallback
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapMoveTypeSpec() = SwapMoveTypeSpec{};
solver.addSolver(solverSpec);
}
Performance Characteristics
Scalability
| Containers | Objects per Container | Full Swaps Evaluated | Evaluation Time |
|---|---|---|---|
| 10 | 1K | 10 | <1s |
| 100 | 1K | 100 | 1-5s |
| 1000 | 1K | 1000 | 10-60s |
| 10K | 1K | 10K | 2-10 min |
Note: Evaluation time depends heavily on objective function complexity.
When Does It Help?
SwapFullContainers helps when:
- Similar container sizes: Swapping makes sense
- Container-level imbalance: Distribution across containers is wrong
- Capacity-blocked: Individual moves violate capacity
- Migration scenarios: Moving entire workloads
- Coarse rebalancing: Need big changes quickly
SwapFullContainers does NOT help when:
- Variable container sizes: Swapping creates more imbalance
- Object-level issues: Problem is specific object placement
- Most objects well-placed: Full swap undoes good placements
- Capacity highly varied: Full swaps likely to violate constraints
Comparison with Variants
| Move Type | Granularity | Complexity | Capacity Handling | Use Case |
|---|---|---|---|---|
| Swap | Object pairs | O(N²) | Capacity-neutral | General swapping |
| SwapSampled | Sampled pairs | O(S²) | Capacity-neutral | Large-scale swapping |
| SwapFullContainers | Full containers | O(C) | May violate | Container migration |
| SwapFullWithEmpty | Full to empty | O(C_empty) | Consolidation-friendly | Emptying containers |
Decision tree:
- Need to empty containers? → SwapFullWithEmpty
- Container-level rebalancing? → SwapFullContainers
- Object-level optimization? → Swap or SwapSampled
- Capacity-constrained? → Swap (capacity-neutral by definition)
Troubleshooting
Problem: SwapFullContainers not finding good moves
Diagnosis: Container sizes too different or no beneficial full swaps exist
Solutions:
- Check container size distribution
- Use Swap for fine-grained object swaps instead
- Combine with Single to move objects first
- May already be at good container-level balance
Problem: Capacity violations after full swap
Diagnosis: Full container swap violated capacity constraints
Solutions:
- This is expected - SwapFullContainers doesn't guarantee capacity respect
- Set tight capacity constraints to prevent violations
- Use Swap instead (capacity-neutral)
- Follow up with Single to fix violations
Problem: Undoing good object placements
Diagnosis: Full swap moves well-placed objects
Solutions:
- Use SwapFullContainers only for initial coarse rebalancing
- Follow with object-level moves (Single, Swap)
- Consider if full container swap is appropriate for this problem
- Use
avoidMovingconstraints for critical objects
Problem: Too few improving moves found
Diagnosis: Only C moves evaluated, may miss opportunities
Solutions:
- This is expected - trade-off for simplicity
- Combine with Swap or SwapSampled
- Use multi-stage: full swap first, then object swaps
- Consider if problem needs container-level or object-level rebalancing
When to Use SwapFullContainers
DO use when:
- Migrating entire server/container workloads
- Container-level rebalancing (similar-sized containers)
- Coarse initial rebalancing before fine-tuning
- Capacity constraints block most individual moves
- Testing "what if we swapped these two servers"
DO NOT use when:
- Containers have very different sizes
- Need fine-grained object placement
- Capacity constraints must be strictly respected
- Most objects are already well-placed
Related Move Types
Swap variants:
- Swap - Fine-grained object pair swaps
- SwapSampled - Sampled object swaps for scale
- SwapFullWithEmpty - Move all to empty container
Complementary:
- Single - Use before/after for object-level tuning
- SingleEndChain - Alternative for capacity issues
Use together:
- SwapFullContainers for coarse container rebalancing
- Swap or Single for fine object placement
- Achieves both container and object level optimization
Source Code
- Thrift definition:
interface/thrift/SolverSpecs.thrift:533 - Implementation:
solver/moves/SwapFullContainersMoveType.h - Tests:
solver/moves/tests/
Next Steps
- Learn about SwapFullWithEmpty for container consolidation
- Try Swap for fine-grained object swaps
- Review Move Types Overview for choosing move types
- See Capacity for capacity constraints