SingleRandomObjectStratified
Move Type: Basic Complexity: O(object_strata × sample_size) instead of O(objects)
Sample objects from stratified groups for more intelligent source selection. Reduces search space while maintaining coverage across different object types.
Overview
SingleRandomObjectStratified evaluates single object moves but samples source objects in a stratified manner - taking samples from different object groups rather than uniformly. This is the inverse of SingleRandomStratified which stratifies destination containers.
Use when:
- Objects naturally group into strata (e.g., by size, type, priority)
- Want balanced exploration across object types
- Problem has many objects (millions)
- Random object selection gives poor coverage
Avoid when:
- Objects are homogeneous (no natural grouping)
- Problem has few objects (<10K, use Single)
- Don't have meaningful object partitions
- Need exhaustive object exploration
Quick Example
- Python
- C++
# Stratified object sampling across size classes
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SingleRandomObjectStratifiedMoveTypeSpec(
objectsToExploreOptions=ObjectsToExploreOptions(
objectsFromGroupSpec=ObjectsFromGroupsSpec(
groupList=GroupList(
partitionName="size_class",
),
),
),
stratifiedSampleSize=SampleSize(
defaultSampleSize=300, # 300 objects per size class
),
),
]
)
)
)
void quickExample() {
// Stratified object sampling across size classes
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("task");
solver.setContainerName("host");
// Setup problem
solver.setAssignment(
std::map<std::string, std::vector<std::string>>{
{"host0", {"task0", "task1", "task2", "task3", "task4"}},
{"host1", {}},
{"host2", {}},
});
// Define object groups
std::unordered_map<std::string, std::vector<std::string>> sizeGroups = {
{"small", {"task0", "task1", "task2"}},
{"medium", {"task3"}},
{"large", {"task4"}},
};
solver.addPartition("size_class", std::move(sizeGroups));
LocalSearchSolverSpec solverSpec;
SingleRandomObjectStratifiedMoveTypeSpec stratifiedSpec;
GroupList groupList;
groupList.partitionName() = "size_class";
ObjectsFromGroupsSpec objectsFromGroupsSpec;
objectsFromGroupsSpec.groupList() = std::move(groupList);
ObjectsToExploreOptions objectsToExploreOptions;
objectsToExploreOptions.objectsFromGroupsSpec() = objectsFromGroupsSpec;
stratifiedSpec.objectsToExploreOptions() = std::move(objectsToExploreOptions);
SampleSize sampleSize;
sampleSize.defaultSampleSize() = 300; // 300 objects per size class
stratifiedSpec.stratifiedSampleSize() = std::move(sampleSize);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().singleRandomObjectStratifiedMoveTypeSpec() =
stratifiedSpec;
solver.addSolver(solverSpec);
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
stratifiedSampleSize | SampleSize | Yes | - | Sample size per object stratum |
objectsToExploreOptions | ObjectsToExploreOptions | Yes | - | Defines object strata (groups/partitions) |
Parameter Details
stratifiedSampleSize:
- Number of objects to sample per object group
- Uses
SampleSizestruct withdefaultSampleSize - Total objects sampled =
sample_size × number_of_object_groups
objectsToExploreOptions:
- Defines the object stratification - how to group objects
- Typically uses
objectsFromGroupSpecwith partition name - Example:
grouppartition creates strata by object group
How It Works
Given a cold container (underutilized destination):
- Define object strata: Group objects by partition (e.g., size, type)
- Sample per stratum: Take
sample_sizerandom objects from each group - Evaluate samples: Test moving each sampled object to cold container
- Apply best: Apply the move that improves objective most
Visual Example
System with 3 million objects stratified into 5 groups by size:
Group 1 (Very Small) Group 2 (Small) Group 3 (Medium) Group 4 (Large) Group 5 (Very Large)
+------------------+ +------------------+ +------------------+ +------------------+ +------------------+
| 600K objects | | 600K objects | | 600K objects | | 600K objects | | 600K objects |
| | | | | | | | | |
| Sample 300 | | Sample 300 | | Sample 300 | | Sample 300 | | Sample 300 |
+------------------+ +------------------+ +------------------+ +------------------+ +------------------+
Total objects evaluated: 300 × 5 = 1,500 (instead of 3,000,000!)
Coverage: Guaranteed samples from each size class
Comparison with Container Stratification
| Aspect | SingleRandomStratified | SingleRandomObjectStratified |
|---|---|---|
| What's stratified | Destination containers | Source objects |
| Sample from | Container groups | Object groups |
| Use case | Many heterogeneous containers | Many heterogeneous objects |
| Best for | Destination diversity | Source diversity |
Complexity
Moves evaluated per iteration: O(S × K × C)
Where:
- S = sample size per object group (
stratifiedSampleSize) - K = number of object groups
- C = number of destination containers (per sampled object)
Effective complexity: O(S × K) for object sampling
Example - Large object problem:
- Total objects: 3,000,000
- Object groups: 5
- Sample per group: 300
- Objects sampled: 300 × 5 = 1,500 (vs 3M for full Single)
- Speedup: 2000x
Usage Patterns
Size-Based Object Stratification
Sample across object sizes:
- Python
- C++
# Ensure coverage across object size classes
# Assumes objects are partitioned into size-based groups
solver.addPartition(
"size_class",
{
"very_small": ["task0"],
"small": ["task1"],
"medium": ["task2"],
"large": ["task3"],
"very_large": ["task4"],
},
)
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SingleRandomObjectStratifiedMoveTypeSpec(
objectsToExploreOptions=ObjectsToExploreOptions(
objectsFromGroupSpec=ObjectsFromGroupsSpec(
groupList=GroupList(
partitionName="size_class",
),
),
),
stratifiedSampleSize=SampleSize(
defaultSampleSize=300, # Sample 300 per size class
),
),
]
)
)
)
void sizeStratified() {
// Ensure coverage across object size classes
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
std::unordered_map<std::string, std::vector<std::string>> sizeGroups = {
{"very_small", {}}, // Objects populated elsewhere
{"small", {}},
{"medium", {}},
{"large", {}},
{"very_large", {}},
};
solver.addPartition("size_class", std::move(sizeGroups));
LocalSearchSolverSpec solverSpec;
SingleRandomObjectStratifiedMoveTypeSpec stratifiedSpec;
GroupList groupList;
groupList.partitionName() = "size_class";
ObjectsFromGroupsSpec objectsFromGroupsSpec;
objectsFromGroupsSpec.groupList() = std::move(groupList);
ObjectsToExploreOptions objectsToExploreOptions;
objectsToExploreOptions.objectsFromGroupsSpec() = objectsFromGroupsSpec;
stratifiedSpec.objectsToExploreOptions() = std::move(objectsToExploreOptions);
SampleSize sampleSize;
sampleSize.defaultSampleSize() = 300; // Sample 300 per size class
stratifiedSpec.stratifiedSampleSize() = std::move(sampleSize);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().singleRandomObjectStratifiedMoveTypeSpec() =
stratifiedSpec;
solver.addSolver(solverSpec);
Type-Based Stratification
Ensure coverage across object types:
- Python
- C++
# Stratify by object type/workload
solver.addPartition(
"workload_type",
{
"batch": ["task0"],
"realtime": ["task1"],
"ml_training": ["task2"],
"serving": ["task3"],
},
)
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SingleRandomObjectStratifiedMoveTypeSpec(
objectsToExploreOptions=ObjectsToExploreOptions(
objectsFromGroupSpec=ObjectsFromGroupsSpec(
groupList=GroupList(
partitionName="workload_type",
),
),
),
stratifiedSampleSize=SampleSize(
defaultSampleSize=200, # Sample 200 per workload type
),
),
]
)
)
)
void typeStratified() {
// Stratify by object type/workload
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
std::unordered_map<std::string, std::vector<std::string>> workloadGroups = {
{"batch", {}}, // Batch processing objects
{"realtime", {}}, // Real-time objects
{"ml_training", {}}, // ML training objects
{"serving", {}}, // Serving objects
};
solver.addPartition("workload_type", std::move(workloadGroups));
LocalSearchSolverSpec solverSpec;
SingleRandomObjectStratifiedMoveTypeSpec stratifiedSpec;
GroupList groupList;
groupList.partitionName() = "workload_type";
ObjectsFromGroupsSpec objectsFromGroupsSpec;
objectsFromGroupsSpec.groupList() = std::move(groupList);
ObjectsToExploreOptions objectsToExploreOptions;
objectsToExploreOptions.objectsFromGroupsSpec() = objectsFromGroupsSpec;
stratifiedSpec.objectsToExploreOptions() = std::move(objectsToExploreOptions);
SampleSize sampleSize;
sampleSize.defaultSampleSize() = 200; // Sample 200 per workload type
stratifiedSpec.stratifiedSampleSize() = std::move(sampleSize);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().singleRandomObjectStratifiedMoveTypeSpec() =
stratifiedSpec;
solver.addSolver(solverSpec);
Very Large Object Sets
Millions of objects with stratification:
- Python
- C++
# Very large: 3M objects across 5 size groups
# Sample 300 per group = 1500 total (2000x reduction)
solver.addPartition(
"size_class",
{
"xs": ["task0"],
"s": ["task1"],
"m": ["task2"],
"l": ["task3"],
"xl": ["task4"],
},
)
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SingleRandomObjectStratifiedMoveTypeSpec(
objectsToExploreOptions=ObjectsToExploreOptions(
objectsFromGroupSpec=ObjectsFromGroupsSpec(
groupList=GroupList(
partitionName="size_class",
),
),
),
stratifiedSampleSize=SampleSize(
defaultSampleSize=300, # Small sample, many strata
),
),
]
)
)
)
void largeScale() {
// Very large: 3M objects across 5 size groups
// Sample 300 per group = 1500 total (2000x reduction)
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
std::unordered_map<std::string, std::vector<std::string>> sizeGroups = {
{"xs", {}}, // Very small objects
{"s", {}}, // Small objects
{"m", {}}, // Medium objects
{"l", {}}, // Large objects
{"xl", {}}, // Very large objects
};
solver.addPartition("size_class", std::move(sizeGroups));
LocalSearchSolverSpec solverSpec;
SingleRandomObjectStratifiedMoveTypeSpec stratifiedSpec;
GroupList groupList;
groupList.partitionName() = "size_class";
ObjectsFromGroupsSpec objectsFromGroupsSpec;
objectsFromGroupsSpec.groupList() = std::move(groupList);
ObjectsToExploreOptions objectsToExploreOptions;
objectsToExploreOptions.objectsFromGroupsSpec() = objectsFromGroupsSpec;
stratifiedSpec.objectsToExploreOptions() = std::move(objectsToExploreOptions);
SampleSize sampleSize;
sampleSize.defaultSampleSize() = 300; // Small sample, many strata
stratifiedSpec.stratifiedSampleSize() = std::move(sampleSize);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().singleRandomObjectStratifiedMoveTypeSpec() =
stratifiedSpec;
solver.addSolver(solverSpec);
Priority-Based Stratification
Sample by object priority:
- Python
- C++
# Stratify by priority level, with higher sampling for critical objects
solver.addPartition(
"priority",
{
"critical": ["task0"],
"high": ["task1"],
"medium": ["task2"],
"low": ["task3"],
},
)
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
SingleRandomObjectStratifiedMoveTypeSpec(
objectsToExploreOptions=ObjectsToExploreOptions(
objectsFromGroupSpec=ObjectsFromGroupsSpec(
groupList=GroupList(
partitionName="priority",
),
),
),
stratifiedSampleSize=SampleSize(
defaultSampleSize=100, # Default sample
# Can override per priority if needed
),
),
SingleMoveTypeSpec(), # Final exhaustive pass
]
)
)
)
void priorityStratified() {
// Stratify by priority level
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
std::unordered_map<std::string, std::vector<std::string>> priorityGroups = {
{"critical", {}}, // P0 critical objects
{"high", {}}, // P1 high priority
{"medium", {}}, // P2 medium priority
{"low", {}}, // P3 low priority
};
solver.addPartition("priority", std::move(priorityGroups));
LocalSearchSolverSpec solverSpec;
SingleRandomObjectStratifiedMoveTypeSpec stratifiedSpec;
GroupList groupList;
groupList.partitionName() = "priority";
ObjectsFromGroupsSpec objectsFromGroupsSpec;
objectsFromGroupsSpec.groupList() = std::move(groupList);
ObjectsToExploreOptions objectsToExploreOptions;
objectsToExploreOptions.objectsFromGroupsSpec() = objectsFromGroupsSpec;
stratifiedSpec.objectsToExploreOptions() = std::move(objectsToExploreOptions);
SampleSize sampleSize;
sampleSize.defaultSampleSize() = 100; // Default sample
stratifiedSpec.stratifiedSampleSize() = std::move(sampleSize);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().singleRandomObjectStratifiedMoveTypeSpec() =
stratifiedSpec;
// Add Single for final exhaustive pass
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().singleMoveTypeSpec() = SingleMoveTypeSpec{};
solver.addSolver(solverSpec);
Performance Characteristics
Object Stratification Benefit
| Object Groups | Sample/Group | Total Samples | Total Objects | Speedup | Coverage |
|---|---|---|---|---|---|
| 5 | 300 | 1.5K | 3M | 2000x | Excellent |
| 10 | 100 | 1K | 1M | 1000x | Excellent |
| 20 | 50 | 1K | 5M | 5000x | Good |
| 1 | 1K | 1K | 3M | 3000x | Poor (no stratification) |
Key insight: Object stratification is most valuable when you have many heterogeneous objects.
When Does It Help?
SingleRandomObjectStratified helps when:
- Heterogeneous objects: Different sizes/types/priorities
- Need coverage: Important to try all object types
- Very large object sets: Millions of objects
- Object partitions exist: Natural object grouping
- Biased objectives: Some object types heavily preferred
SingleRandomObjectStratified does NOT help when:
- Homogeneous objects: All similar, no natural grouping
- Small object sets: Overhead not worth it
- No partitions: Can't define meaningful object groups
- Container stratification more important: Use SingleRandomStratified
Comparison with Variants
| Move Type | What's Stratified | Sample From | Use Case |
|---|---|---|---|
| Single | Nothing | All objects, all containers | Small problems |
| SingleFast | Nothing (early exit) | Objects in order | Medium problems |
| SingleRandomBatches | Nothing (batches) | Random containers | Large + multi-core |
| SingleRandomStratified | Containers | Containers per stratum | Large + container diversity |
| SingleColdestStratified | Containers (coldest) | Coldest containers per stratum | Capacity balancing |
| SingleRandomObjectStratified | Objects | Objects per group | Large + object diversity |
Decision tree:
- Many heterogeneous objects? → SingleRandomObjectStratified
- Many heterogeneous containers? → SingleRandomStratified
- Both? → Combine both strategies
- Neither? → Single or SingleFast
Troubleshooting
Problem: Poor solution quality despite object stratification
Diagnosis: Sample size too small per group OR grouping not meaningful
Solutions:
- Increase
stratifiedSampleSize - Verify object groups are meaningful (similar objects in same group)
- Check if objects vary significantly within groups
- Try different partition/grouping
- Combine with Single for final refinement
Problem: Not seeing speedup vs uniform sampling
Diagnosis: Objects are homogeneous OR too few groups
Solutions:
- Check if objects naturally group by partition
- May not need object stratification (use Single)
- Verify number of groups is reasonable (5-20 ideal)
- Ensure groups have balanced sizes
Problem: Missing good moves for rare object types
Diagnosis: Some groups have few objects but small sample size
Solutions:
- Increase sample size for small groups
- Use per-group sample size overrides in
SampleSize - Consider group size when setting samples
- May need different grouping approach
Problem: Non-deterministic results
Diagnosis: Random sampling produces different results
Solutions:
- Set
randomSeedin LocalSearchSolverSpec - Increase sample size for stability
- Run multiple times and pick best
- Accept variance as trade-off for speed
Choosing Object Stratification
Good object stratification characteristics:
- Meaningful groups: Objects in same group are similar
- Balanced sizes: Groups have roughly equal number of objects
- Diverse outcomes: Moving different groups has different effects
- Reasonable count: 5-20 groups ideal
Common object stratification strategies:
- Size: Small/medium/large objects
- Type: Object type, class, category
- Priority: High/medium/low priority
- Resource usage: CPU/memory/disk bins
Example partition hierarchies:
size_class > object_type > object
priority_level > team > object
resource_bin > workload_type > object
Related Move Types
Stratified variants:
- SingleRandomStratified - Stratify containers
- SingleColdestStratified - Coldest containers per stratum
- SingleRandomObjectStratified - Stratify objects (this)
Sampling alternatives:
- Single - Full exploration (no sampling)
- SingleRandomBatches - Uniform random container batches
- SingleFast - Early exit
Use together:
- SingleRandomObjectStratified for object diversity
- Can combine with container stratification for double stratification
- Follow with Single for final refinement
Source Code
- Thrift definition:
interface/thrift/SolverSpecs.thrift:656 - Implementation:
solver/moves/SingleRandomObjectStratifiedMoveType.h - Tests:
solver/moves/tests/
Next Steps
- Learn about SingleRandomStratified for container stratification
- Try SingleColdestStratified for capacity-focused stratification
- Review Groups and Partitions for partition design
- See Move Types Overview for choosing move types