SwapFullWithEmpty
Move Type: Swap Complexity: O(empty_containers) with full container move
Move ALL objects from hot container to an empty container. Ideal for consolidation, decommissioning, and bin-packing scenarios.
Overview
SwapFullWithEmpty evaluates moving all objects from the hot container to every empty container in the system. This is a specialized move type for consolidation - reducing the number of active containers by emptying overloaded or problematic containers.
Use when:
- Consolidating workload (reducing active containers)
- Decommissioning servers/containers
- Bin-packing optimization
- Have spare capacity in empty containers
- Want to empty specific containers
Avoid when:
- No empty containers available
- Objects can't all fit in one container
- Need fine-grained object placement
- Container capacities would be violated
Quick Example
- Python
- C++
# Move all objects from hot container to empty containers
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapFullWithEmptyContainersMoveTypeSpec(), # Consolidate to empties
]
)
)
)
void quickExample() {
// Move all objects from hot container to empty 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().swapFullWithEmptyContainersMoveTypeSpec() =
SwapFullWithEmptyContainersMoveTypeSpec{};
solver.addSolver(solverSpec);
}
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| (none) | - | - | - | SwapFullWithEmpty has no configuration parameters |
How It Works
Given a hot container (the container contributing most to the objective):
- Find empty containers: Identify all containers with zero objects
- Try each empty: For each empty container:
- Move ALL objects from hot container to empty container
- Evaluate resulting objective
- Parallel evaluation: All empty containers evaluated in parallel
- Apply best: Apply the move to empty container that improves objective most
Visual Example
Before move: After full move to empty:
┌─────────────────┐ ┌─────────────────┐
│ Hot Container │ │ Hot Container │
│ • obj1 ───────┼──┐ │ (empty) │
│ • obj2 │ │ │ │
│ • obj3 │ │ │ │
└─────────────────┘ │ └─────────────────┘
│
│ ┌─────────────────┐
┌─────────────────┐ │ │ Empty Container │
│ Empty Container │ │ │ • obj1 ←───────┼─┘
│ (empty) │ │ │ • obj2 │
│ │ │ │ • obj3 │
└─────────────────┘ │ └─────────────────┘
All objects moved in ONE move
Comparison with Alternatives
| Move Type | Source | Destination | Use Case |
|---|---|---|---|
| Single | Any object | Any container | General placement |
| Swap | Object pairs | Occupied containers | Capacity-constrained |
| SwapFullContainers | All objects | Any container (swap) | Container migration |
| SwapFullWithEmpty | All objects | Empty containers only | Consolidation |
Complexity
Moves evaluated per iteration: O(E)
Where:
- E = number of empty containers
Example:
- System: 1,000 containers total
- Empty containers: 100
- Hot container: 1,000 objects
- Moves evaluated: 100 (one per empty container)
Note: Much faster than regular moves since only empty containers are considered.
Usage Patterns
Container Consolidation
Reduce number of active containers:
- Python
- C++
# Consolidate workload to reduce active servers
solver.addGoal(
GoalSpecs(
minimizeContainersSpec=MinimizeContainersSpec(
name="minimize-servers",
scope="server",
),
),
weight=1.0,
)
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapFullWithEmptyContainersMoveTypeSpec(), # Empty servers
SingleMoveTypeSpec(), # Pack remaining efficiently
]
)
)
)
void consolidation() {
// Consolidate workload to reduce active servers
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("server");
MinimizeContainersSpec minimizeSpec;
minimizeSpec.name() = "minimize-servers";
minimizeSpec.scope() = "server";
solver.addGoal(std::move(minimizeSpec), 1.0);
LocalSearchSolverSpec solverSpec;
// Add SwapFullWithEmpty
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapFullWithEmptyContainersMoveTypeSpec() =
SwapFullWithEmptyContainersMoveTypeSpec{};
// Add Single
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().singleMoveTypeSpec() = SingleMoveTypeSpec{};
solver.addSolver(solverSpec);
}
Server Decommissioning
Empty servers for maintenance:
- Python
- C++
# Decommission specific servers by moving all shards to empties
# Use nonAccepting or toFree constraints to mark servers for decommission
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapFullWithEmptyContainersMoveTypeSpec(), # Move to empty servers
]
)
)
)
void decommission() {
// Decommission specific servers by moving all shards to empties
// Use nonAccepting or toFree constraints to mark servers for decommission
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().swapFullWithEmptyContainersMoveTypeSpec() =
SwapFullWithEmptyContainersMoveTypeSpec{};
solver.addSolver(solverSpec);
}
Bin-Packing Optimization
Minimize number of active bins:
- Python
- C++
# Bin-packing: minimize active containers
solver.addGoal(
GoalSpecs(
minimizeContainersSpec=MinimizeContainersSpec(
name="minimize-bins",
scope="container",
),
),
weight=10.0, # High weight for consolidation
)
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapFullWithEmptyContainersMoveTypeSpec(), # Empty containers
SingleMoveTypeSpec(), # Pack efficiently
]
)
)
)
void binPacking() {
// Bin-packing: minimize active containers
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("container");
MinimizeContainersSpec minimizeSpec;
minimizeSpec.name() = "minimize-bins";
minimizeSpec.scope() = "container";
solver.addGoal(
std::move(minimizeSpec), 10.0); // High weight for consolidation
LocalSearchSolverSpec solverSpec;
// Add SwapFullWithEmpty
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapFullWithEmptyContainersMoveTypeSpec() =
SwapFullWithEmptyContainersMoveTypeSpec{};
// Add Single
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().singleMoveTypeSpec() = SingleMoveTypeSpec{};
solver.addSolver(solverSpec);
}
Combined Strategy
Use with other moves for complete consolidation:
- Python
- C++
# Multi-stage consolidation strategy
solver.addGoal(
GoalSpecs(
minimizeContainersSpec=MinimizeContainersSpec(
name="minimize-containers",
scope="container",
),
),
weight=5.0,
)
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SwapFullWithEmptyContainersMoveTypeSpec(), # Empty containers quickly
SingleMoveTypeSpec(), # Efficient packing
]
)
)
)
void combined() {
// Multi-stage consolidation strategy
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("host");
MinimizeContainersSpec minimizeSpec;
minimizeSpec.name() = "minimize-containers";
minimizeSpec.scope() = "container";
solver.addGoal(std::move(minimizeSpec), 5.0);
LocalSearchSolverSpec solverSpec;
// Add SwapFullWithEmpty
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().swapFullWithEmptyContainersMoveTypeSpec() =
SwapFullWithEmptyContainersMoveTypeSpec{};
// Add Single
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().singleMoveTypeSpec() = SingleMoveTypeSpec{};
solver.addSolver(solverSpec);
}
Performance Characteristics
Scalability
| Empty Containers | Objects to Move | Evaluation Time | Practical? |
|---|---|---|---|
| 10 | 1K | <1s | ✓ Yes |
| 100 | 10K | 1-5s | ✓ Yes |
| 1000 | 10K | 10-60s | ✓ Yes |
| 10K | 100K | 2-10 min | △ Depends on objective |
Note: Complexity is O(empty_containers), independent of total containers.
When Does It Help?
SwapFullWithEmpty helps when:
- Consolidation goal: Want to reduce active containers
- Spare capacity: Have empty containers available
- Decommissioning: Need to empty specific containers
- Bin-packing: Minimize container usage
- Hot container is problematic: Want it completely empty
SwapFullWithEmpty does NOT help when:
- No empty containers: Can't move anywhere
- Capacity issues: Objects won't fit in one container
- Need fine-grained placement: Full move too coarse
- Hot container should stay active: Just needs rebalancing
Comparison with Variants
| Move Type | Destination Type | Granularity | Primary Use Case |
|---|---|---|---|
| Single | Any container | Individual objects | General optimization |
| Swap | Occupied containers | Object pairs | Capacity-constrained |
| SwapFullContainers | Any container | Full swap | Container migration |
| SwapFullWithEmpty | Empty only | All objects | Consolidation |
Consolidation strategy order:
- SwapFullWithEmpty - Empty problematic containers
- Single - Pack remaining objects efficiently
MinimizeContainersgoal - Drive toward fewer containers
Troubleshooting
Problem: No improving moves found
Diagnosis: No empty containers OR moving to empty doesn't improve objective
Solutions:
- Check if empty containers exist (
containers - used_containers) - Verify objective function rewards consolidation
- May need
MinimizeContainersgoal - Use Single to create empty containers first
Problem: Capacity violations when moving to empty
Diagnosis: All objects don't fit in empty container
Solutions:
- Check container capacity constraints
- Objects may exceed single container capacity
- Use Single to move objects individually
- Split hot container load across multiple destinations
Problem: Empties wrong containers
Diagnosis: Hot container selection not aligned with decommissioning goals
Solutions:
- Use
avoidMovingto protect containers that should stay active - Use
nonAcceptingto mark containers for decommissioning - Set
toFreeconstraints on containers to empty - Adjust objective to penalize specific containers
Problem: Too slow despite few empty containers
Diagnosis: Objective evaluation is expensive for full container moves
Solutions:
- Profile objective function evaluation
- Each move evaluates all objects moving together
- Consider if full move is necessary
- May be inherently expensive for this problem
When to Use SwapFullWithEmpty
DO use when:
- Consolidating workload to reduce container count
- Decommissioning servers or containers
- Bin-packing optimization
- Have empty containers available
- Want to completely empty specific containers
DO NOT use when:
- No empty containers in system
- Need fine-grained object placement
- Objects won't fit in single container
- Hot container should remain active
Related Move Types
Consolidation moves:
- SwapFullWithEmpty - Move all to empty (this)
MinimizeContainers- Goal to reduce containers- Single - Move individual objects
Full container moves:
- SwapFullContainers - Swap entire containers
- SwapFullWithEmpty - Move to empty only (this)
Use together:
- SwapFullWithEmpty to empty problematic containers
- Single to pack efficiently
MinimizeContainersgoal to drive consolidation
Source Code
- Thrift definition:
interface/thrift/SolverSpecs.thrift:520 - Implementation:
solver/moves/SwapFullWithEmptyContainersMoveType.h - Tests:
solver/moves/tests/
Next Steps
- Learn about SwapFullContainers for full container swaps
- Try
MinimizeContainersgoal for consolidation - See Move Types Overview for choosing move types