FixedDestSwapMultiMove
Move Type: Fixed Complexity: Traditional O(B_src × B_dst × sizes), Adaptive O(B_src × k) Primary Use: RAS stackable solve with swap support
Swap bundles of related objects between hot container and specific fixed destination. Supports both traditional 1:1 swaps and adaptive 1:k uneven swaps based on dimension ratios.
Overview
FixedDestSwapMultiMove (also known as FIXED_DEST_SWAP_MULTIPLE) evaluates swapping bundles of related objects between the hot container and a single predetermined destination container. This move type supports two distinct modes:
- Traditional Swap Mode: 1:1 swaps between bundles
- Adaptive 1:k Swap Mode: One object for k objects based on dimension ratios
Use when:
- Using RAS stackable solve with swaps
- Swapping bundles between containers
- Know exactly which destination to swap with
- Objects have different dimension values (e.g., capacity)
- Need 1:k uneven swaps for heterogeneous resources
- Want parallel evaluation of swap sets
Avoid when:
- Not using RAS local search
- Don't need swaps (use FixedDestMultiMove)
- Objects all have same dimension values
- Don't know destination
- Need to explore multiple destinations
Quick Example
- Python
- C++
# Swap object bundles between hot container and specific destination
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
FixedDestSwapMultiMoveTypeSpec(
specialContainer="swap_partner", # Fixed destination
),
]
)
)
)
void quickExample() {
// Swap object bundles between hot container and specific destination
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("server");
LocalSearchSolverSpec solverSpec;
FixedDestSwapMultiMoveTypeSpec swapSpec;
swapSpec.specialContainer() = "swap_partner"; // Fixed destination
// RasLocalSearchMetadata is required for multi-move types
auto rasMetadata = std::make_shared<const RasLocalSearchMetadata>();
swapSpec.rasLocalSearchMetadata() = std::move(rasMetadata);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().fixedDestSwapMultiMoveTypeSpec() = swapSpec;
solver.addSolver(solverSpec);
// Setup swap scenario
solver.setAssignment(
std::map<std::string, std::vector<std::string>>{
{"server0", {"task0", "task1", "task2"}},
{"swap_partner", {"task3", "task4", "task5"}},
{"server2", {}},
});
solver.addObjectDimension(
"cpu",
std::map<std::string, double>{
{"task0", 2.0},
{"task1", 1.0},
{"task2", 1.0},
{"task3", 1.5},
{"task4", 1.5},
{"task5", 1.0},
});
BalanceSpec balanceSpec;
balanceSpec.name() = "balance-cpu";
balanceSpec.scope() = "server";
balanceSpec.dimension() = "cpu";
solver.addGoal(balanceSpec, 1.0);
// Note: solve() is not called here because multi-move types require
// additional bundle/partition setup (e.g., search space partitioning)
// that is beyond the scope of this basic example.
}
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
specialContainer | string | Yes | null | Fixed destination container for swaps |
greedyOnSrc | bool | No | false | Greedy source selection (required for 1:k) |
maxSamplesPerEquivSet | int | No | 5 | Max move sets per equivalent set |
maxSampleSizeOnSrc | int | No | null | Max source bundles to consider |
maxSampleSizeOnDst | int | No | null | Max destination bundles to consider |
rasLocalSearchMetadata | RasLocalSearchMetadata | No | null | RAS metadata with swap config |
Parameter Details
specialContainer:
- Name of the specific destination container
- All swaps target this container only
- Must be a valid container name
greedyOnSrc:
- When
false: Evaluate all source × destination combinations - When
true: Try each source object, exit early on success - Required for adaptive 1:k swap mode
- Enables greedy termination for faster search
maxSamplesPerEquivSet:
- Limits move sets evaluated per equivalent set
- Equivalent sets are groups identical from local search perspective
- Default: 5 samples per equivalent set
maxSampleSizeOnSrc:
- Optional limit on source bundles to consider
- Reduces evaluations for large hot containers
maxSampleSizeOnDst:
- Optional limit on destination bundles to consider
- Reduces evaluations for large destination containers
rasLocalSearchMetadata:
- Metadata specific to RAS stackable solve
- swapRatioDimension: Dimension name for calculating swap ratios (e.g., "capacity")
- useAdaptiveAllotments: Enables adaptive 1:k swap mode
- Both must be set to activate adaptive swaps
How It Works
Traditional Swap Mode (Default)
When swapRatioDimension is not configured or useAdaptiveAllotments is disabled:
- Select source bundle: Pick bundle from hot container
- Select dest bundle: Pick bundle from special container
- Evaluate swap: Test swapping all source with all dest objects
- Cartesian product: All source × dest combinations evaluated
- Parallel evaluation: All swaps evaluated concurrently
- Apply best: Apply the swap that improves objective most
Adaptive 1:k Swap Mode
When swapRatioDimension is configured and useAdaptiveAllotments is enabled:
- Select source object: Pick single object from hot (greedy required)
- Calculate ratio: For each dest equiv set:
k = ceil(hot_dim / cold_dim)- If hot_dim ≤ cold_dim, k = 1
- Form dest bundle: Create bundle with k objects from equiv set
- Evaluate swap: Test swapping 1 hot for k cold objects
- Greedy exit: Stop immediately when beneficial swap found
- Apply swap: Apply the 1:k swap
Visual Example - Traditional Mode
Before swap: After 1:1 bundle swap:
┌──────────────┐ ┌──────────────┐
│ Hot │ │ Hot │
│ Container │ ↔──────────> │ Container │
│ Bundle1 ────┼──┐ │ Bundle2 ←───┼── From dest!
│ • obj1 │ │ │ • obj3 │
│ • obj2 ────┼──┘ Swap │ • obj4 │
└──────────────┘ └──────────────┘
┌──────────────┐ ┌──────────────┐
│ Special │ │ Special │
│ Container │ <──────────↔ │ Container │
│ Bundle2 <───┼──┐ │ Bundle1 ←───┼── From hot!
│ • obj3 │ │ │ • obj1 │
│ • obj4 ────┼──┘ Swap │ • obj2 │
└──────────────┘ └──────────────┘
1:1 bundle swap between hot and specialContainer
Visual Example - Adaptive 1:k Mode
Before adaptive swap (k=2): After 1:2 adaptive swap:
┌──────────────┐ ┌──────────────┐
│ Hot │ │ Hot │
│ Container │ ───────────> │ Container │
│ • obj1 ────┼──┐ dim=2.0 │ • obj2 │
│ • obj2 │ │ │ (obj3, obj4)┼← Received 2 objects!
└──────────────┘ │ └──────────────┘
│
┌──────────────┐ │ ┌──────────────┐
│ Special │ │ │ Special │
│ Container │ │ │ Container │
│ • obj3 <───┼──┤ dim=1.0 │ • obj1 ←───┼── Received 1 object!
│ • obj4 <───┼──┘ dim=1.0 └──────────────┘
└──────────────┘
Swap 1 obj (dim=2.0) for 2 objs (dim=1.0 each)
k = ceil(2.0/1.0) = 2
Complexity
Traditional Swap Mode
Per iteration: O(S × D × |S_bundle| × |D_bundle|)
Where:
- S = source bundles (limited by
maxSampleSizeOnSrc) - D = destination bundles (limited by
maxSampleSizeOnDst) - |S_bundle| = objects per source bundle
- |D_bundle| = objects per destination bundle
Adaptive 1:k Swap Mode
Per iteration: O(S × k̄)
Where:
- S = source objects (limited by
maxSampleSizeOnSrc) - k̄ = average swap ratio across equiv sets
- Significantly fewer due to greedy termination and no cartesian product
Example - Heterogeneous servers:
- Hot container: 10 high-capacity objects (dim=2.0)
- Special container: 50 low-capacity objects (dim=1.0)
- Traditional: 10 × 50 = 500 swap evaluations
- Adaptive: ~10 evaluations (k=2 each, greedy exit)
Usage Patterns
Traditional 1:1 Swaps
Standard bundle swaps between containers:
- Python
- C++
# Traditional 1:1 bundle swaps
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
FixedDestSwapMultiMoveTypeSpec(
specialContainer="destination_server",
greedyOnSrc=False, # Evaluate all combinations
),
]
)
)
)
void traditionalSwap() {
// Traditional 1:1 bundle swaps
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("server");
LocalSearchSolverSpec solverSpec;
FixedDestSwapMultiMoveTypeSpec swapSpec;
swapSpec.specialContainer() = "destination_server";
swapSpec.greedyOnSrc() = false; // Evaluate all combinations
auto rasMetadata = std::make_shared<const RasLocalSearchMetadata>();
swapSpec.rasLocalSearchMetadata() = std::move(rasMetadata);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().fixedDestSwapMultiMoveTypeSpec() = swapSpec;
solver.addSolver(solverSpec);
// Setup balanced swap problem
solver.setAssignment(
std::map<std::string, std::vector<std::string>>{
{"server0", {"task0", "task1"}},
{"destination_server", {"task2", "task3"}},
{"server2", {"task4"}},
});
solver.addObjectDimension(
"memory",
std::map<std::string, double>{
{"task0", 3.0},
{"task1", 2.0},
{"task2", 1.5},
{"task3", 1.5},
{"task4", 2.0},
});
BalanceSpec balanceSpec;
balanceSpec.name() = "balance-memory";
balanceSpec.scope() = "server";
balanceSpec.dimension() = "memory";
solver.addGoal(std::move(balanceSpec), 1.0);
// Note: solve() is not called here because multi-move types require
// additional bundle/partition setup (e.g., search space partitioning)
// that is beyond the scope of this basic example.
}
Adaptive 1:k Swaps
Uneven swaps based on dimension ratios:
- Python
- C++
# Adaptive 1:k swaps based on capacity dimension
solver = ProblemSolver(service_name="example", service_scope="test")
from rebalancer.interface.thrift.v2.SolverSpecs.thrift_types import (
RasLocalSearchMetadata,
)
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
FixedDestSwapMultiMoveTypeSpec(
specialContainer="new_server_type",
greedyOnSrc=True, # Required for adaptive
rasLocalSearchMetadata=RasLocalSearchMetadata(
swapRatioDimension="capacity", # Dimension for ratios
useAdaptiveAllotments=True, # Enable adaptive mode
),
),
]
)
)
)
void adaptiveSwap() {
// Adaptive 1:k swaps based on capacity dimension
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
LocalSearchSolverSpec solverSpec;
auto rasMetadata = std::make_shared<RasLocalSearchMetadata>();
StringKeyValueMap swapRatioDim;
swapRatioDim.defaultValue() = "capacity";
rasMetadata->swapRatioDimension() =
std::move(swapRatioDim); // Dimension for ratios
rasMetadata->useAdaptiveAllotments() = true; // Enable adaptive mode
FixedDestSwapMultiMoveTypeSpec swapSpec;
swapSpec.specialContainer() = "new_server_type";
swapSpec.greedyOnSrc() = true; // Required for adaptive
swapSpec.rasLocalSearchMetadata() = std::move(rasMetadata);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().fixedDestSwapMultiMoveTypeSpec() = swapSpec;
solver.addSolver(solverSpec);
}
Greedy Source Selection
Fast greedy search with early exit:
- Python
- C++
# Greedy source selection for faster search
solver = ProblemSolver(service_name="example", service_scope="test")
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
FixedDestSwapMultiMoveTypeSpec(
specialContainer="swap_destination",
greedyOnSrc=True, # Exit early on first improvement
),
]
)
)
)
void greedy() {
// Greedy source selection for faster search
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
LocalSearchSolverSpec solverSpec;
FixedDestSwapMultiMoveTypeSpec swapSpec;
swapSpec.specialContainer() = "swap_destination";
swapSpec.greedyOnSrc() = true; // Exit early on first improvement
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().fixedDestSwapMultiMoveTypeSpec() = swapSpec;
solver.addSolver(solverSpec);
}
Sampling Control
Control sampling on both source and destination:
- Python
- C++
# Limit evaluations with source and destination sampling
solver = ProblemSolver(service_name="example", service_scope="test")
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
FixedDestSwapMultiMoveTypeSpec(
specialContainer="swap_partner",
maxSamplesPerEquivSet=10, # Sample equiv sets
maxSampleSizeOnSrc=20, # Max 20 source bundles
maxSampleSizeOnDst=30, # Max 30 dest bundles
),
]
)
)
)
void sampling() {
// Limit evaluations with source and destination sampling
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
LocalSearchSolverSpec solverSpec;
FixedDestSwapMultiMoveTypeSpec swapSpec;
swapSpec.specialContainer() = "swap_partner";
swapSpec.maxSamplesPerEquivSet() = 10; // Sample equiv sets
swapSpec.maxSampleSizeOnSrc() = 20; // Max 20 source bundles
swapSpec.maxSampleSizeOnDst() = 30; // Max 30 dest bundles
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().fixedDestSwapMultiMoveTypeSpec() = swapSpec;
solver.addSolver(solverSpec);
}
Performance Characteristics
Mode Comparison
| Mode | Evaluations | Early Exit | Use Case |
|---|---|---|---|
| Traditional | O(S × D × sizes) | No | Uniform objects |
| Adaptive 1:k | O(S × k̄) | Yes (greedy) | Heterogeneous objects |
When Does It Help?
FixedDestSwapMultiMove helps when:
- RAS stackable with swaps: Designed for RAS swap scenarios
- Bundle swaps: Need to swap groups of objects
- Known destination: Exactly which container to swap with
- Heterogeneous resources: Objects have different capacities/values
- Capacity matching: 1:k swaps balance different resource types
FixedDestSwapMultiMove does NOT help when:
- Not RAS: Use simpler move types
- Don't need swaps: Use FixedDestMultiMove
- Uniform objects: All objects have same dimension values
- Unknown destination: Need solver to find swap partner
Comparison with Variants
| Move Type | Operation | Mode | Use Case |
|---|---|---|---|
| FixedDestMultiMove | Move bundles | Push | RAS push to dest |
| FixedSourceMultiMove | Move bundles | Pull | RAS pull from source |
| FixedDestSwapMultiMove | Swap bundles | 1:1 or 1:k | RAS swaps (traditional or adaptive) |
Decision tree:
- Need swaps between hot and fixed dest? → FixedDestSwapMultiMove
- Just push to dest? → FixedDestMultiMove
- Just pull from source? → FixedSourceMultiMove
- Neither? → Single
Troubleshooting
Problem: No improving swaps found
Diagnosis: Swaps can't beneficially exchange objects
Solutions:
- Verify
specialContaineris correct swap partner - Check capacity constraints on both containers
- Bundle sizes may not fit after swap
- May already be optimal
- Try different destination
Problem: Adaptive mode not working
Diagnosis: Configuration issue with adaptive settings
Solutions:
- Verify
greedyOnSrc=true(required for adaptive) - Check
rasLocalSearchMetadata.swapRatioDimensionis set - Ensure
rasLocalSearchMetadata.useAdaptiveAllotments=true - All three must be configured for adaptive mode
Problem: Wrong swap ratios
Diagnosis: Dimension values or calculation issue
Solutions:
- Verify dimension values are correct
- Check
swapRatioDimensionmatches actual dimension name - Ratio k = ceil(hot_dim / cold_dim)
- If hot_dim ≤ cold_dim, ratio is 1
- Review dimension setup
Problem: Too many evaluations
Diagnosis: Large source or destination bundles
Solutions:
- Enable
greedyOnSrc=truefor early exit - Reduce
maxSampleSizeOnSrcandmaxSampleSizeOnDst - Reduce
maxSamplesPerEquivSet - Consider adaptive mode (fewer evaluations)
When to Use FixedDestSwapMultiMove
DO use when:
- Using RAS stackable solve with swaps
- Swapping bundles between containers
- Know exactly which destination to swap with
- Objects have heterogeneous dimensions
- Need 1:k uneven swaps
DO NOT use when:
- Not using RAS local search
- Don't need swaps (use FixedDestMultiMove)
- All objects uniform
- Don't know destination
- General optimization
Related Move Types
RAS Fixed bundle variants:
- FixedDestMultiMove - Bundle to fixed dest
- FixedSourceMultiMove - Bundle from fixed source
- FixedDestSwapMultiMove - Bundle swaps (this)
All three used exclusively for RAS stackable solve
General swap alternatives:
- Swap - General 2-object swaps
- SwapSampled - Sampled swaps
Source Code
- Thrift definition:
interface/thrift/SolverSpecs.thrift:602 - Implementation:
solver/moves/FixedDestSwapMultiMoveType.h - Tests:
solver/moves/tests/
Next Steps
- Review FixedDestMultiMove for moves without swaps
- Try FixedSourceMultiMove for pulling bundles
- See Move Types Overview for choosing move types
- Learn about RAS local search in the solver strategies guide