FixedDest
Move Type: Fixed Complexity: O(sample_size) instead of O(objects × containers)
Move objects from hot container to a specific fixed destination. Useful when you know exactly where objects should go.
Overview
FixedDest (also known as SINGLE_FIXED_DEST) evaluates moving objects from the hot container to a single predetermined destination container. Instead of exploring all possible destinations, it only considers moves to one specific container.
Use when:
- Know exactly where objects should move (e.g., new server, specific region)
- Migrating workload to specific destination
- Filling a specific underutilized container
- Testing "what if we moved to container X"
Avoid when:
- Don't know destination (use Single)
- Need to explore multiple destinations
- Destination keeps changing
- Want solver to find best destination
Quick Example
- Python
- C++
# Move objects from hot container to specific destination
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
FixedDestMoveTypeSpec(
specialContainer="new_server", # Fixed destination
),
]
)
)
)
void quickExample() {
// Move objects from hot container to specific destination
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("server");
LocalSearchSolverSpec solverSpec;
FixedDestMoveTypeSpec fixedDestSpec;
fixedDestSpec.specialContainer() = "new_server"; // Fixed destination
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().fixedDestMoveTypeSpec() = fixedDestSpec;
solver.addSolver(solverSpec);
// Setup problem with imbalanced load
solver.setAssignment(
std::map<std::string, std::vector<std::string>>{
{"server0", {"task0", "task1", "task2", "task3"}}, // Overloaded
{"server1", {"task4", "task5"}},
{"new_server", {}}, // New server to fill
});
solver.addObjectDimension(
"cpu",
std::map<std::string, double>{
{"task0", 1.5},
{"task1", 2.0},
{"task2", 1.0},
{"task3", 1.5},
{"task4", 2.0},
{"task5", 2.0},
});
CapacitySpec capacitySpec;
capacitySpec.name() = "cpu-capacity";
capacitySpec.scope() = "server";
capacitySpec.dimension() = "cpu";
Limit limit;
limit.globalLimit() = 6.0;
capacitySpec.limit() = std::move(limit);
solver.addConstraint(std::move(capacitySpec));
BalanceSpec balanceSpec;
balanceSpec.name() = "balance-cpu";
balanceSpec.scope() = "server";
balanceSpec.dimension() = "cpu";
solver.addGoal(std::move(balanceSpec), 1.0);
// Solve and print results
auto solution = solver.solve();
std::cout << " Initial objective: " << std::fixed << std::setprecision(4)
<< *solution.initialObjective()->value() << "\n";
std::cout << " Final objective: " << *solution.finalObjective()->value()
<< "\n";
std::cout << " Improvement: "
<< (*solution.initialObjective()->value() -
*solution.finalObjective()->value())
<< "\n";
}
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
specialContainer | string | Yes | null | Fixed destination container name |
sampleSize | SampleSize | No | null | Sample subset of objects (probabilistic) |
Parameter Details
specialContainer:
- Name of the specific destination container
- All object moves will target this container only
- Must be a valid container name
sampleSize:
- Optional sampling to reduce evaluations
- Each object sampled with probability =
sampleSize / (objects in hot container) - Useful for very large hot containers
How It Works
Given a hot container (most broken):
- Select object: Pick object from hot container
- Sample: With probability
sampleSize / N, evaluate this object (if sampling enabled) - Evaluate move: Test moving object to
specialContainer - Repeat: Try all objects in hot container
- Apply best: Apply the move to
specialContainerthat improves objective most
Visual Example
Before move: After move to specialContainer:
┌──────────────┐ ┌──────────────┐
│ Hot │ │ Hot │
│ Container │ ───────────> │ Container │
│ • obj1 ────┼──┐ │ • obj2 │
│ • obj2 │ │ │ • obj3 │
│ • obj3 │ │ └──────────────┘
└──────────────┘ │
│ ┌──────────────┐
┌──────────────┐ │ │ Special │
│ Special │ │ │ Container │
│ Container │ │ │ • objA │
│ • objA │ │ │ • objB │
│ • objB <───┼──┘ │ • obj1 ←────┼── Moved here!
└──────────────┘ └──────────────┘
Only one destination considered: specialContainer
Comparison with Regular Single
| Aspect | Single | FixedDest |
|---|---|---|
| Destinations explored | All containers | One specific container |
| Complexity | O(N × C) | O(N) or O(S) if sampled |
| Flexibility | Finds best destination | Tests specific destination |
| Use case | Explore all options | Know where to move |
Complexity
Without sampling: O(N) With sampling: O(S)
Where:
- N = number of objects in hot container
- S = sample size
Example - Directed migration:
- Hot container: 10,000 objects
- Without sampling: Evaluate 10,000 moves to special container
- With sampleSize=100: Evaluate ~100 moves to special container
Speedup vs Single: C× (where C = number of containers in system)
Usage Patterns
Server Migration
Move objects to new server:
- Python
- C++
# Migrate tasks from overloaded servers to new server
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
FixedDestMoveTypeSpec(
specialContainer="new_server_01", # New server
),
SingleMoveTypeSpec(), # Then explore all moves
]
)
)
)
void serverMigration() {
// Migrate tasks from overloaded servers to new server
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("server");
LocalSearchSolverSpec solverSpec;
FixedDestMoveTypeSpec fixedDestSpec;
fixedDestSpec.specialContainer() = "new_server_01"; // New server
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().fixedDestMoveTypeSpec() = fixedDestSpec;
solver.addSolver(solverSpec);
// Setup migration scenario
solver.setAssignment(
std::map<std::string, std::vector<std::string>>{
{"server0", {"task0", "task1", "task2"}},
{"server1", {"task3", "task4"}},
{"server2", {"task5"}},
{"new_server_01", {}},
});
solver.addObjectDimension(
"memory",
std::map<std::string, double>{
{"task0", 3.0},
{"task1", 2.0},
{"task2", 1.0},
{"task3", 2.5},
{"task4", 2.5},
{"task5", 3.0},
});
CapacitySpec capacitySpec;
capacitySpec.name() = "memory-capacity";
capacitySpec.scope() = "server";
capacitySpec.dimension() = "memory";
Limit limit;
limit.globalLimit() = 7.0;
capacitySpec.limit() = std::move(limit);
solver.addConstraint(std::move(capacitySpec));
BalanceSpec balanceSpec;
balanceSpec.name() = "balance-memory";
balanceSpec.scope() = "server";
balanceSpec.dimension() = "memory";
solver.addGoal(std::move(balanceSpec), 1.0);
// Solve and print results
auto solution = solver.solve();
std::cout << " Initial objective: " << std::fixed << std::setprecision(4)
<< *solution.initialObjective()->value() << "\n";
std::cout << " Final objective: " << *solution.finalObjective()->value()
<< "\n";
std::cout << " Improvement: "
<< (*solution.initialObjective()->value() -
*solution.finalObjective()->value())
<< "\n";
}
Fill Specific Container
Target specific underutilized container:
- Python
- C++
# Fill specific underutilized container
solver = ProblemSolver(service_name="example", service_scope="test")
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
FixedDestMoveTypeSpec(
specialContainer="underutilized_server",
),
]
)
)
)
void fillContainer() {
// Fill specific underutilized container
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
LocalSearchSolverSpec solverSpec;
FixedDestMoveTypeSpec fixedDestSpec;
fixedDestSpec.specialContainer() = "underutilized_server";
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().fixedDestMoveTypeSpec() = fixedDestSpec;
solver.addSolver(solverSpec);
}
With Sampling for Large Containers
Sample subset when hot container is very large:
- Python
- C++
# Large hot container: sample 100 objects
solver = ProblemSolver(service_name="example", service_scope="test")
from rebalancer.interface.thrift.v2.SolverSpecs.thrift_types import SampleSize
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
FixedDestMoveTypeSpec(
specialContainer="destination_server",
sampleSize=SampleSize(
defaultSampleSize=100
), # Sample ~100 objects
),
]
)
)
)
void sampling() {
// Large hot container: sample 100 objects
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
LocalSearchSolverSpec solverSpec;
FixedDestMoveTypeSpec fixedDestSpec;
fixedDestSpec.specialContainer() = "destination_server";
SampleSize sampleSize;
sampleSize.defaultSampleSize() = 100; // Sample ~100 objects
fixedDestSpec.sampleSize() = std::move(sampleSize);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().fixedDestMoveTypeSpec() = fixedDestSpec;
solver.addSolver(solverSpec);
}
Multi-Destination Strategy
Use multiple FixedDest in sequence for different destinations:
- Python
- C++
# Try multiple specific destinations in sequence
solver = ProblemSolver(service_name="example", service_scope="test")
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
FixedDestMoveTypeSpec(
specialContainer="server_A"
), # Try server A first
FixedDestMoveTypeSpec(specialContainer="server_B"), # Then server B
]
)
)
)
void multiDest() {
// Try multiple specific destinations in sequence
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
LocalSearchSolverSpec solverSpec;
// Try server A first
FixedDestMoveTypeSpec fixedDestSpecA;
fixedDestSpecA.specialContainer() = "server_A";
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().fixedDestMoveTypeSpec() = fixedDestSpecA;
// Then server B
FixedDestMoveTypeSpec fixedDestSpecB;
fixedDestSpecB.specialContainer() = "server_B";
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().fixedDestMoveTypeSpec() = fixedDestSpecB;
// Then server C
FixedDestMoveTypeSpec fixedDestSpecC;
fixedDestSpecC.specialContainer() = "server_C";
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().fixedDestMoveTypeSpec() = fixedDestSpecC;
// Finally explore all options
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().singleMoveTypeSpec() = SingleMoveTypeSpec{};
solver.addSolver(solverSpec);
}
Performance Characteristics
Speedup Analysis
| Containers | Objects | FixedDest | Single | Speedup |
|---|---|---|---|---|
| 100 | 1K | 1K | 100K | 100× |
| 1K | 10K | 10K | 10M | 1000× |
| 10K | 10K | 10K | 100M | 10000× |
When Does It Help?
FixedDest helps when:
- Known destination: Exactly where objects should go
- Directed migration: Moving to specific new server/region
- Testing: "What if we moved to container X?"
- Filling specific container: Target underutilized container
- Avoiding exploration overhead: Don't need to search all destinations
FixedDest does NOT help when:
- Unknown destination: Need solver to find best destination
- Exploring options: Want to try multiple destinations
- General optimization: Use Single instead
- Destination changes: Different destination each iteration
Comparison with Variants
| Move Type | Destination | Source | Use Case |
|---|---|---|---|
| Single | Any container | Hot container | General moves |
| FixedDest | Fixed specific | Hot container | Directed migration |
| FixedSource | Hot container | Fixed specific | Pull from specific source |
| FixedDestMultiMove | Fixed specific | Multiple sources | Multi-source to one dest |
Decision tree:
- Know destination? → FixedDest
- Know source? → FixedSource
- Neither fixed? → Single
Troubleshooting
Problem: No improving moves found
Diagnosis: Objects can't beneficially move to special container
Solutions:
- Verify
specialContaineris correct destination - Check capacity constraints on special container
- May already be optimal for this destination
- Try different destination or use Single
Problem: Wrong objects moving
Diagnosis: Objective function or constraints issue
Solutions:
- Verify objective function rewards correct moves
- Check constraints (capacity, affinity, etc.)
- May need different objective or constraints
- Review which objects the solver is selecting
Problem: Too slow even with fixed destination
Diagnosis: Hot container too large
Solutions:
- Enable sampling with
sampleSizeparameter - Start with small sample (e.g., 100)
- Verify special container can accept objects
- Check objective function efficiency
Problem: Sampling missing good moves
Diagnosis: Sample size too small, random sampling misses best objects
Solutions:
- Increase
sampleSize - Remove sampling (evaluate all objects)
- Run multiple times with different random seeds
- May need deterministic selection (use Single)
When to Use FixedDest
DO use when:
- Know exactly where objects should move
- Migrating to new server/container
- Filling specific underutilized container
- Testing specific destination scenario
- Want to avoid destination exploration overhead
DO NOT use when:
- Need solver to find best destination
- Want to explore multiple destinations
- Destination is not predetermined
- General optimization (use Single)
Related Move Types
Fixed variants:
- FixedDest - Fixed destination, hot source (this)
- FixedSource - Hot destination, fixed source
- FixedDestMultiMove - Multiple sources to fixed dest
- FixedSourceMultiMove - Fixed source to multiple dests
General alternatives:
- Single - Explore all destinations
- SingleFast - Fast exploration with early exit
Use together:
- Try FixedDest for known destinations
- Fall back to Single for exploration
Source Code
- Thrift definition:
interface/thrift/SolverSpecs.thrift:578 - Implementation:
solver/moves/FixedDestMoveType.h - Tests:
solver/moves/tests/
Next Steps
- Learn about FixedSource for pulling from specific source
- Try FixedDestMultiMove for multi-source scenarios
- Review Move Types Overview for choosing move types
- See Single for general single moves