FixedSource
Move Type: Fixed Complexity: O(sources × sample_size) instead of O(objects × containers)
Move objects from specific fixed source containers to hot container. The inverse of FixedDest - you know where to pull from, not where to send.
Overview
FixedSource (also known as SINGLE_FIXED_SOURCE) evaluates moving objects from predetermined source containers to the hot (cold/underutilized) container. Instead of exploring all possible sources, it only considers objects from specific containers.
Use when:
- Know exactly which containers to pull objects from
- Draining specific servers/containers
- Hot container is underutilized and needs objects
- Testing "what if we pulled from container X"
Avoid when:
- Don't know sources (use Single)
- Need to explore all possible sources
- Sources keep changing
- Want solver to find best sources
Quick Example
- Python
- C++
# Move objects from specific source to hot container
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SingleFixedSourceMoveTypeSpec(
specialContainer="old_server", # Fixed source
),
]
)
)
)
void quickExample() {
// Move objects from specific source to hot container
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("server");
LocalSearchSolverSpec solverSpec;
SingleFixedSourceMoveTypeSpec fixedSourceSpec;
fixedSourceSpec.specialContainer() = "old_server"; // Fixed source
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().singleFixedSourceMoveTypeSpec() =
fixedSourceSpec;
solver.addSolver(solverSpec);
// Setup drain scenario
solver.setAssignment(
std::map<std::string, std::vector<std::string>>{
{"old_server", {"task0", "task1", "task2"}}, // Server to drain
{"server1", {"task3"}},
{"server2", {"task4"}},
{"server3", {}},
});
solver.addObjectDimension(
"cpu",
std::map<std::string, double>{
{"task0", 1.5},
{"task1", 2.0},
{"task2", 1.5},
{"task3", 2.0},
{"task4", 2.0},
});
CapacitySpec capacitySpec;
capacitySpec.name() = "cpu-capacity";
capacitySpec.scope() = "server";
capacitySpec.dimension() = "cpu";
Limit limit;
limit.globalLimit() = 5.0;
capacitySpec.limit() = limit;
solver.addConstraint(capacitySpec);
BalanceSpec balanceSpec;
balanceSpec.name() = "balance-cpu";
balanceSpec.scope() = "server";
balanceSpec.dimension() = "cpu";
solver.addGoal(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 |
|---|---|---|---|---|
scopeItemList | ScopeItemList | No* | null | List of scope items containing source containers |
specialContainer | string | No* | null | Single source container name |
sampleSize | SampleSize | No | null | Sample subset of objects (probabilistic) |
stopEarlyAtScopeItemThatImprovesObjective | bool | No | false | Stop after first scope item with improvement |
*Either scopeItemList or specialContainer must be specified. If both provided, scopeItemList is used.
Parameter Details
scopeItemList:
- List of scope items containing source containers
- All containers in these scope items become sources
- Evaluated in order specified
specialContainer:
- Single specific source container
- Simpler alternative to
scopeItemList - Used only if
scopeItemListnot provided
sampleSize:
- Optional sampling to reduce evaluations
- Each object sampled with probability =
sampleSize / (objects in source) - Applied per source container
stopEarlyAtScopeItemThatImprovesObjective:
- When
true: Stop after first scope item with improving move - When
false: Evaluate all scope items, pick best move - Only relevant when using
scopeItemList
How It Works
Given a hot container (underutilized destination):
- Select source: Pick one of the specified source containers
- Select object: Pick object from source container
- Sample: With probability
sampleSize / N, evaluate this object (if sampling enabled) - Evaluate move: Test moving object from source to hot container
- Repeat: Try all objects in all specified sources
- Apply best: Apply the move from source that improves objective most
Visual Example
Before move: After move from specialContainer:
┌──────────────┐ ┌──────────────┐
│ Hot │ │ Hot │
│ Container │ <────────── │ Container │
│ (empty) │ ┌──┤ • obj1 ←────┼── Pulled from source!
└──────────────┘ │ │ • obj2 │
│ └──────────────┘
┌──────────────┐ │
│ Special │ │ ┌──────────────┐
│ Container │ ────────────────┘ │ Special │
│ • obj1 ────┼──┐ │ Container │
│ • obj2 │ │ │ • obj2 │
│ • obj3 │ │ │ • obj3 │
└──────────────┘ └─ Source └──────────────┘
Only one source considered: specialContainer
Comparison with FixedDest
| Aspect | FixedDest | FixedSource |
|---|---|---|
| Fixed | Destination | Source |
| Hot container | Source (gives) | Destination (receives) |
| Use case | Know where to send | Know where to pull from |
Complexity
Without sampling: O(N × S) With sampling: O(Sample × S)
Where:
- N = average objects per source container
- S = number of source containers
- Sample = sample size
Example - Draining servers:
- Source containers: 5 servers to drain
- Objects per server: 1,000
- Without sampling: Evaluate 5,000 moves
- With sampleSize=100: Evaluate ~500 moves (100 per server)
Usage Patterns
Drain Specific Servers
Pull objects from servers being decommissioned:
- Python
- C++
# Drain tasks from servers being decommissioned
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SingleFixedSourceMoveTypeSpec(
scopeItemList=ScopeItemList(
itemList=["old_server_01", "old_server_02", "old_server_03"]
),
),
]
)
)
)
void drainServers() {
// Drain tasks from servers being decommissioned
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("server");
LocalSearchSolverSpec solverSpec;
SingleFixedSourceMoveTypeSpec fixedSourceSpec;
ScopeItemList scopeItemList;
scopeItemList.scopeItems() = {
"old_server_01", "old_server_02", "old_server_03"};
fixedSourceSpec.scopeItemList() = std::move(scopeItemList);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().singleFixedSourceMoveTypeSpec() =
fixedSourceSpec;
solver.addSolver(solverSpec);
// Setup multi-server drain
solver.setAssignment(
std::map<std::string, std::vector<std::string>>{
{"old_server_01", {"task0", "task1"}},
{"old_server_02", {"task2"}},
{"old_server_03", {"task3", "task4"}},
{"server1", {}},
{"server2", {}},
{"server3", {}},
});
solver.addObjectDimension(
"memory",
std::map<std::string, double>{
{"task0", 2.0},
{"task1", 2.0},
{"task2", 3.0},
{"task3", 1.5},
{"task4", 1.5},
});
CapacitySpec capacitySpec;
capacitySpec.name() = "memory-capacity";
capacitySpec.scope() = "server";
capacitySpec.dimension() = "memory";
Limit limit;
limit.globalLimit() = 6.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 Underutilized Container
Pull from specific sources to fill hot container:
- Python
- C++
# Fill underutilized container from specific sources
solver = ProblemSolver(service_name="example", service_scope="test")
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SingleFixedSourceMoveTypeSpec(
scopeItemList=ScopeItemList(
itemList=["overloaded_server_A", "overloaded_server_B"]
),
),
]
)
)
)
void fillContainer() {
// Fill underutilized container from specific sources
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
LocalSearchSolverSpec solverSpec;
SingleFixedSourceMoveTypeSpec fixedSourceSpec;
ScopeItemList scopeItemList;
scopeItemList.scopeItems() = {"overloaded_server_A", "overloaded_server_B"};
fixedSourceSpec.scopeItemList() = std::move(scopeItemList);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().singleFixedSourceMoveTypeSpec() =
fixedSourceSpec;
solver.addSolver(solverSpec);
}
With Sampling
Sample subset when sources are very large:
- Python
- C++
# Large source containers: sample 100 objects per source
solver = ProblemSolver(service_name="example", service_scope="test")
from rebalancer.interface.thrift.v2.SolverSpecs.thrift_types import SampleSize
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SingleFixedSourceMoveTypeSpec(
scopeItemList=ScopeItemList(
itemList=["large_source_1", "large_source_2"]
),
sampleSize=SampleSize(
defaultSampleSize=100
), # Sample ~100 per source
),
]
)
)
)
void sampling() {
// Large source containers: sample 100 objects per source
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
LocalSearchSolverSpec solverSpec;
SingleFixedSourceMoveTypeSpec fixedSourceSpec;
ScopeItemList scopeItemList;
scopeItemList.scopeItems() = {"large_source_1", "large_source_2"};
fixedSourceSpec.scopeItemList() = scopeItemList;
SampleSize sampleSize;
sampleSize.defaultSampleSize() = 100; // Sample ~100 per source
fixedSourceSpec.sampleSize() = sampleSize;
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().singleFixedSourceMoveTypeSpec() =
fixedSourceSpec;
solver.addSolver(solverSpec);
}
Early Exit Strategy
Stop after first good source:
- Python
- C++
# Stop after first source with improvement
solver = ProblemSolver(service_name="example", service_scope="test")
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SingleFixedSourceMoveTypeSpec(
scopeItemList=ScopeItemList(
itemList=["source_A", "source_B", "source_C"]
),
stopEarlyAtScopeItemThatImprovesObjective=True,
),
]
)
)
)
void earlyExit() {
// Stop after first source with improvement
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
LocalSearchSolverSpec solverSpec;
SingleFixedSourceMoveTypeSpec fixedSourceSpec;
ScopeItemList scopeItemList;
scopeItemList.scopeItems() = {"source_A", "source_B", "source_C"};
fixedSourceSpec.scopeItemList() = std::move(scopeItemList);
fixedSourceSpec.stopEarlyAtScopeItemThatImprovesObjective() = true;
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().singleFixedSourceMoveTypeSpec() =
fixedSourceSpec;
solver.addSolver(solverSpec);
}
Performance Characteristics
Speedup Analysis
| Source Containers | Objects/Source | FixedSource | Single | Speedup |
|---|---|---|---|---|
| 5 | 1K | 5K | ~100K | 20× |
| 10 | 10K | 100K | ~10M | 100× |
| 100 | 10K | 1M | ~100M | 100× |
When Does It Help?
FixedSource helps when:
- Known sources: Exactly which containers to drain
- Server decommissioning: Pulling from specific servers
- Filling underutilized: Hot container needs objects from specific sources
- Testing: "What if we pulled from these containers?"
- Avoiding exploration overhead: Don't need to search all sources
FixedSource does NOT help when:
- Unknown sources: Need solver to find best sources
- Exploring options: Want to try many sources
- General optimization: Use Single instead
- Sources change: Different sources each iteration
Comparison with Variants
| Move Type | Destination | Source | Use Case |
|---|---|---|---|
| Single | Any container | Hot container | General moves |
| FixedDest | Fixed specific | Hot container | Push to specific dest |
| FixedSource | Hot container | Fixed specific | Pull from specific sources |
| FixedSourceMultiMove | Multiple dests | Fixed specific | One source to many dests |
Decision tree:
Troubleshooting
Problem: No improving moves found
Diagnosis: Objects can't beneficially move from sources to hot container
Solutions:
- Verify source containers are correct
- Check capacity constraints on hot container
- May already be optimal
- Try different sources 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 sources
Diagnosis: Source containers too large
Solutions:
- Enable sampling with
sampleSizeparameter - Start with small sample (e.g., 100 per source)
- Use
stopEarlyAtScopeItemThatImprovesObjective=true - Check objective function efficiency
Problem: Hot container not filling as expected
Diagnosis: Capacity constraints or objective not favoring moves
Solutions:
- Verify hot container has capacity
- Check objective function rewards filling hot container
- May need different sources
- Review capacity constraints
When to Use FixedSource
DO use when:
- Know exactly which containers to pull from
- Draining specific servers/containers
- Hot container is underutilized and needs filling
- Testing specific source scenario
- Want to avoid source exploration overhead
DO NOT use when:
- Need solver to find best sources
- Want to explore multiple source options
- Sources are not predetermined
- General optimization (use Single)
Related Move Types
Fixed variants:
- FixedDest - Fixed destination, hot source
- FixedSource - Hot destination, fixed sources (this)
- FixedSourceMultiMove - Fixed source to multiple dests
- FixedDestMultiMove - Multiple sources to fixed dest
General alternatives:
- Single - Explore all sources
- SingleFast - Fast exploration with early exit
Use together:
- Try FixedSource for known sources
- Fall back to Single for exploration
Source Code
- Thrift definition:
interface/thrift/SolverSpecs.thrift:640 - Implementation:
solver/moves/SingleFixedSourceMoveType.h - Tests:
solver/moves/tests/
Next Steps
- Learn about FixedDest for pushing to specific destination
- Try FixedSourceMultiMove for multi-destination scenarios
- Review Move Types Overview for choosing move types
- See Single for general single moves