Skip to content

Commit aee4db2

Browse files
committed
Fix ML autoscaling (classic cloud) for models with zero allocations
1 parent 6f60880 commit aee4db2

File tree

3 files changed

+77
-1
lines changed

3 files changed

+77
-1
lines changed

x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/autoscaling/MlAutoscalingContext.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,13 @@ public boolean isEmpty() {
180180
&& modelAssignments.isEmpty();
181181
}
182182

183+
public boolean hasOnlyZeroAllocationModels() {
184+
return anomalyDetectionTasks.isEmpty()
185+
&& snapshotUpgradeTasks.isEmpty()
186+
&& dataframeAnalyticsTasks.isEmpty()
187+
&& modelAssignments.values().stream().allMatch(assignment -> assignment.totalTargetAllocations() == 0);
188+
}
189+
183190
public List<String> findPartiallyAllocatedModels() {
184191
return modelAssignments.entrySet()
185192
.stream()

x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/autoscaling/MlAutoscalingDeciderService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public AutoscalingDeciderResult scale(Settings configuration, AutoscalingDecider
131131
.setPassedConfiguration(configuration);
132132

133133
// We don't need to check anything as there are no tasks
134-
if (mlContext.isEmpty()) {
134+
if (mlContext.isEmpty() || mlContext.hasOnlyZeroAllocationModels()) {
135135
// This is a quick path to downscale.
136136
// simply return `0` for scale down if delay is satisfied
137137
return downscaleToZero(configuration, context, currentNativeMemoryCapacity, reasonBuilder);

x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/autoscaling/MlAutoscalingDeciderServiceTests.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@
2929
import org.elasticsearch.xpack.autoscaling.capacity.AutoscalingDeciderContext;
3030
import org.elasticsearch.xpack.autoscaling.capacity.AutoscalingDeciderResult;
3131
import org.elasticsearch.xpack.core.ml.MachineLearningField;
32+
import org.elasticsearch.xpack.core.ml.action.StartTrainedModelDeploymentAction;
33+
import org.elasticsearch.xpack.core.ml.inference.assignment.AdaptiveAllocationsSettings;
34+
import org.elasticsearch.xpack.core.ml.inference.assignment.AssignmentState;
35+
import org.elasticsearch.xpack.core.ml.inference.assignment.Priority;
36+
import org.elasticsearch.xpack.core.ml.inference.assignment.TrainedModelAssignment;
37+
import org.elasticsearch.xpack.core.ml.inference.assignment.TrainedModelAssignmentMetadata;
3238
import org.elasticsearch.xpack.core.ml.job.config.JobState;
3339
import org.elasticsearch.xpack.ml.MachineLearning;
3440
import org.elasticsearch.xpack.ml.job.NodeLoad;
@@ -262,6 +268,69 @@ public void testScale_GivenUndeterminedMemory_ShouldReturnNullCapacity() {
262268
assertThat(result.requiredCapacity(), is(nullValue()));
263269
}
264270

271+
public void testScale_GivenModelWithZeroAllocations() {
272+
MlAutoscalingDeciderService service = buildService();
273+
service.onMaster();
274+
275+
ClusterState clusterState = new ClusterState.Builder(new ClusterName("cluster")).metadata(
276+
Metadata.builder()
277+
.putCustom(
278+
TrainedModelAssignmentMetadata.NAME,
279+
new TrainedModelAssignmentMetadata(
280+
Map.of(
281+
"model-with-zero-allocations",
282+
TrainedModelAssignment.Builder.empty(
283+
new StartTrainedModelDeploymentAction.TaskParams(
284+
"model-with-zero-allocations",
285+
"model-with-zero-allocations-deployment",
286+
400,
287+
0,
288+
2,
289+
100,
290+
null,
291+
Priority.NORMAL,
292+
0L,
293+
0L
294+
),
295+
new AdaptiveAllocationsSettings(true, 0, 4)
296+
).setAssignmentState(AssignmentState.STARTED).build()
297+
)
298+
)
299+
)
300+
.build()
301+
).nodes(DiscoveryNodes.builder().add(buildNode("ml-node", ByteSizeValue.ofGb(4), 8)).build()).build();
302+
303+
AutoscalingDeciderResult result = service.scale(
304+
Settings.EMPTY,
305+
new DeciderContext(
306+
clusterState,
307+
new AutoscalingCapacity(
308+
new AutoscalingCapacity.AutoscalingResources(null, ByteSizeValue.ofGb(4), null),
309+
new AutoscalingCapacity.AutoscalingResources(null, ByteSizeValue.ofGb(4), null)
310+
)
311+
)
312+
);
313+
// First call doesn't downscale as delay has not been satisfied
314+
assertThat(result.reason().summary(), containsString("down scale delay has not been satisfied"));
315+
316+
// Let's move time forward 1 hour
317+
timeSupplier.setOffset(TimeValue.timeValueHours(1));
318+
319+
result = service.scale(
320+
Settings.EMPTY,
321+
new DeciderContext(
322+
clusterState,
323+
new AutoscalingCapacity(
324+
new AutoscalingCapacity.AutoscalingResources(null, ByteSizeValue.ofGb(4), null),
325+
new AutoscalingCapacity.AutoscalingResources(null, ByteSizeValue.ofGb(4), null)
326+
)
327+
)
328+
);
329+
assertThat(result.reason().summary(), equalTo("Requesting scale down as tier and/or node size could be smaller"));
330+
assertThat(result.requiredCapacity().total().memory().getBytes(), equalTo(0L));
331+
assertThat(result.requiredCapacity().node().memory().getBytes(), equalTo(0L));
332+
}
333+
265334
private DiscoveryNode buildNode(String id, ByteSizeValue machineMemory, int allocatedProcessors) {
266335
return DiscoveryNodeUtils.create(
267336
id,

0 commit comments

Comments
 (0)