FrankChen021 commented on code in PR #19687:
URL: https://github.com/apache/druid/pull/19687#discussion_r3728799562
##########
indexing-service/src/main/java/org/apache/druid/indexing/overlord/supervisor/SupervisorResource.java:
##########
@@ -534,6 +537,31 @@ public Response terminateAll(@Context final
HttpServletRequest req)
);
}
+ @POST
+ @Path("/{id}/autoscaler/simulate")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @ResourceFilters(SupervisorResourceFilter.class)
Review Comment:
[P2] Authorize simulation as read-only
`SupervisorResourceFilter` maps every POST to `Action.WRITE`, so read-only
users who can inspect this supervisor receive 403 for a computation that
changes no state. The prior authorization finding remains unresolved; use an
explicit datasource `READ` authorization path for this endpoint.
##########
indexing-service/src/main/java/org/apache/druid/indexing/overlord/supervisor/SupervisorManager.java:
##########
@@ -644,6 +648,90 @@ public boolean isAnotherTaskGroupPublishingToPartitions(
}
}
+ /**
+ * Simulates the effects of the {@code costBased} auto-scaler by computing
the optimal
+ * task count under various values of aggregate lag.
+ */
+ public Map<String, Object> simulateAutoscaling(
+ String supervisorId,
+ CostBasedAutoScalerConfig config,
+ int maxProcessingRatePerTask,
+ @Nullable Integer requestedTaskCount
+ )
+ {
+ // Validate that this is a SeekableStreamSupervisor
+ final Pair<Supervisor, SupervisorSpec> supervisorPair =
supervisors.get(supervisorId);
+ if (supervisorPair == null || supervisorPair.rhs == null ||
supervisorPair.lhs == null) {
+ throw NotFound.exception("Invalid supervisor[%s]", supervisorId);
+ } else if (!(supervisorPair.rhs instanceof SeekableStreamSupervisorSpec)) {
+ throw InvalidInput.exception(
+ "Cannot simulate autoscaling for supervisor[%s] of type[%s]",
+ supervisorId, supervisorPair.rhs.getType()
+ );
+ }
+
+ final SeekableStreamSupervisorSpec supervisorSpec =
(SeekableStreamSupervisorSpec) supervisorPair.rhs;
+
+ // Validate the inputs
+ final long criticalLag =
Configs.valueOrDefault(config.getCriticalLagThreshold(), 1_000_000);
+ InvalidInput.conditionalException(
+ criticalLag >= 1000,
+ "Value of critical lag[%d] must be 1000 or more",
+ criticalLag
+ );
+ InvalidInput.conditionalException(
+ maxProcessingRatePerTask >= 100,
+ "Value of maxProcessingRatePerTask[%d] must be 100 events per second
or more",
+ maxProcessingRatePerTask
+ );
+ InvalidInput.conditionalException(
+ requestedTaskCount == null
+ || (requestedTaskCount >= config.getTaskCountMin() &&
requestedTaskCount <= config.getTaskCountMax()),
+ "Value of currentTaskCount[%d] must be within taskCountMin[%d] and
taskCountMax[%d]",
+ requestedTaskCount, config.getTaskCountMin(), config.getTaskCountMax()
+ );
+
+ // Simulate from the supervisor's live task count unless the caller pins
one.
+ final int currentTaskCount = supervisorSpec.getIoConfig().getTaskCount();
+ final int simulationTaskCount = Math.max(
+ config.getTaskCountMin(),
+ Math.min(
+ Configs.valueOrDefault(requestedTaskCount, currentTaskCount),
+ config.getTaskCountMax()
+ )
+ );
+
+ // Use the partition count and task duration from the supervisor spec
+ final int partitionCount = ((SeekableStreamSupervisor<?, ?, ?>)
supervisorPair.lhs).getKnownPartitionCount();
+ final long taskDurationSeconds =
supervisorSpec.getIoConfig().getTaskDuration().getStandardSeconds();
+
+ // Assume that the tasks are fully used since there is some lag
+ final double idleRatio = config.getOptimalTaskIdleRatio();
+
+ // Invoke the cost function for lag in the range [0, 2 *
criticalLagThreshold)
+ final Object[] rows = new Object[200];
+ final int lagStepSize = (int) (criticalLag / 100);
Review Comment:
[P3] Preserve the long critical-lag range
`criticalLagThreshold` is a `long`, but its step is narrowed to `int`.
Thresholds above 214,748,364,700 therefore produce wrapped or truncated lag
points instead of the promised 0-to-2x range. Keep `lagStepSize` as `long` or
`double`.
##########
indexing-service/src/main/java/org/apache/druid/indexing/overlord/supervisor/SupervisorManager.java:
##########
@@ -644,6 +648,90 @@ public boolean isAnotherTaskGroupPublishingToPartitions(
}
}
+ /**
+ * Simulates the effects of the {@code costBased} auto-scaler by computing
the optimal
+ * task count under various values of aggregate lag.
+ */
+ public Map<String, Object> simulateAutoscaling(
+ String supervisorId,
+ CostBasedAutoScalerConfig config,
+ int maxProcessingRatePerTask,
+ @Nullable Integer requestedTaskCount
+ )
+ {
+ // Validate that this is a SeekableStreamSupervisor
+ final Pair<Supervisor, SupervisorSpec> supervisorPair =
supervisors.get(supervisorId);
+ if (supervisorPair == null || supervisorPair.rhs == null ||
supervisorPair.lhs == null) {
+ throw NotFound.exception("Invalid supervisor[%s]", supervisorId);
+ } else if (!(supervisorPair.rhs instanceof SeekableStreamSupervisorSpec)) {
+ throw InvalidInput.exception(
+ "Cannot simulate autoscaling for supervisor[%s] of type[%s]",
+ supervisorId, supervisorPair.rhs.getType()
+ );
+ }
+
+ final SeekableStreamSupervisorSpec supervisorSpec =
(SeekableStreamSupervisorSpec) supervisorPair.rhs;
+
+ // Validate the inputs
+ final long criticalLag =
Configs.valueOrDefault(config.getCriticalLagThreshold(), 1_000_000);
+ InvalidInput.conditionalException(
+ criticalLag >= 1000,
+ "Value of critical lag[%d] must be 1000 or more",
+ criticalLag
+ );
+ InvalidInput.conditionalException(
+ maxProcessingRatePerTask >= 100,
+ "Value of maxProcessingRatePerTask[%d] must be 100 events per second
or more",
+ maxProcessingRatePerTask
+ );
+ InvalidInput.conditionalException(
Review Comment:
[P2] Reject non-positive task-count bounds
This validation checks only the optional current count. The config still
accepts `taskCountMin=0`; with a positive live task count,
`computeValidTaskCounts` divides by that minimum and the request fails with
HTTP 500. Require both task-count bounds to be at least one before simulation.
##########
indexing-service/src/main/java/org/apache/druid/indexing/overlord/supervisor/SupervisorManager.java:
##########
@@ -644,6 +648,90 @@ public boolean isAnotherTaskGroupPublishingToPartitions(
}
}
+ /**
+ * Simulates the effects of the {@code costBased} auto-scaler by computing
the optimal
+ * task count under various values of aggregate lag.
+ */
+ public Map<String, Object> simulateAutoscaling(
+ String supervisorId,
+ CostBasedAutoScalerConfig config,
+ int maxProcessingRatePerTask,
+ @Nullable Integer requestedTaskCount
+ )
+ {
+ // Validate that this is a SeekableStreamSupervisor
+ final Pair<Supervisor, SupervisorSpec> supervisorPair =
supervisors.get(supervisorId);
+ if (supervisorPair == null || supervisorPair.rhs == null ||
supervisorPair.lhs == null) {
+ throw NotFound.exception("Invalid supervisor[%s]", supervisorId);
+ } else if (!(supervisorPair.rhs instanceof SeekableStreamSupervisorSpec)) {
+ throw InvalidInput.exception(
+ "Cannot simulate autoscaling for supervisor[%s] of type[%s]",
+ supervisorId, supervisorPair.rhs.getType()
+ );
+ }
+
+ final SeekableStreamSupervisorSpec supervisorSpec =
(SeekableStreamSupervisorSpec) supervisorPair.rhs;
+
+ // Validate the inputs
+ final long criticalLag =
Configs.valueOrDefault(config.getCriticalLagThreshold(), 1_000_000);
+ InvalidInput.conditionalException(
+ criticalLag >= 1000,
+ "Value of critical lag[%d] must be 1000 or more",
+ criticalLag
+ );
+ InvalidInput.conditionalException(
+ maxProcessingRatePerTask >= 100,
+ "Value of maxProcessingRatePerTask[%d] must be 100 events per second
or more",
+ maxProcessingRatePerTask
+ );
+ InvalidInput.conditionalException(
+ requestedTaskCount == null
+ || (requestedTaskCount >= config.getTaskCountMin() &&
requestedTaskCount <= config.getTaskCountMax()),
+ "Value of currentTaskCount[%d] must be within taskCountMin[%d] and
taskCountMax[%d]",
+ requestedTaskCount, config.getTaskCountMin(), config.getTaskCountMax()
+ );
+
+ // Simulate from the supervisor's live task count unless the caller pins
one.
+ final int currentTaskCount = supervisorSpec.getIoConfig().getTaskCount();
+ final int simulationTaskCount = Math.max(
+ config.getTaskCountMin(),
+ Math.min(
+ Configs.valueOrDefault(requestedTaskCount, currentTaskCount),
+ config.getTaskCountMax()
+ )
+ );
+
+ // Use the partition count and task duration from the supervisor spec
+ final int partitionCount = ((SeekableStreamSupervisor<?, ?, ?>)
supervisorPair.lhs).getKnownPartitionCount();
+ final long taskDurationSeconds =
supervisorSpec.getIoConfig().getTaskDuration().getStandardSeconds();
+
+ // Assume that the tasks are fully used since there is some lag
+ final double idleRatio = config.getOptimalTaskIdleRatio();
+
+ // Invoke the cost function for lag in the range [0, 2 *
criticalLagThreshold)
+ final Object[] rows = new Object[200];
+ final int lagStepSize = (int) (criticalLag / 100);
+ final CostBasedAutoScaler autoscaleSimulator =
CostBasedAutoScaler.createSimulator(config, supervisorId);
+ for (int i = 0; i < 200; ++i) {
+ final double observedAggregateLag = (double) lagStepSize * i;
+ final CostMetrics costMetrics = new CostMetrics(
+ observedAggregateLag / partitionCount,
+ observedAggregateLag,
+ simulationTaskCount,
+ partitionCount,
+ idleRatio,
+ taskDurationSeconds,
+ maxProcessingRatePerTask,
+ maxProcessingRatePerTask * 1.0
+ );
+ final int optimalTaskCount =
autoscaleSimulator.computeOptimalTaskCountInternal(costMetrics, true);
Review Comment:
[P2] Suppress every simulator log path
Passing `true` suppresses the normal computation logs, but the high- and
critical-lag branches in `computeOptimalTaskCountInternal` still log
unconditionally. Each request evaluates 200 points; with the standard 0-to-2x
range, 125 cross the 0.75x threshold and emit INFO records. Guard those
branches, and the minimum-cost-drop log path, during simulation.
##########
indexing-service/src/main/java/org/apache/druid/indexing/overlord/supervisor/SupervisorManager.java:
##########
@@ -644,6 +648,90 @@ public boolean isAnotherTaskGroupPublishingToPartitions(
}
}
+ /**
+ * Simulates the effects of the {@code costBased} auto-scaler by computing
the optimal
+ * task count under various values of aggregate lag.
+ */
+ public Map<String, Object> simulateAutoscaling(
+ String supervisorId,
+ CostBasedAutoScalerConfig config,
+ int maxProcessingRatePerTask,
+ @Nullable Integer requestedTaskCount
+ )
+ {
+ // Validate that this is a SeekableStreamSupervisor
+ final Pair<Supervisor, SupervisorSpec> supervisorPair =
supervisors.get(supervisorId);
+ if (supervisorPair == null || supervisorPair.rhs == null ||
supervisorPair.lhs == null) {
+ throw NotFound.exception("Invalid supervisor[%s]", supervisorId);
+ } else if (!(supervisorPair.rhs instanceof SeekableStreamSupervisorSpec)) {
+ throw InvalidInput.exception(
+ "Cannot simulate autoscaling for supervisor[%s] of type[%s]",
+ supervisorId, supervisorPair.rhs.getType()
+ );
+ }
+
+ final SeekableStreamSupervisorSpec supervisorSpec =
(SeekableStreamSupervisorSpec) supervisorPair.rhs;
+
+ // Validate the inputs
+ final long criticalLag =
Configs.valueOrDefault(config.getCriticalLagThreshold(), 1_000_000);
+ InvalidInput.conditionalException(
+ criticalLag >= 1000,
+ "Value of critical lag[%d] must be 1000 or more",
+ criticalLag
+ );
+ InvalidInput.conditionalException(
+ maxProcessingRatePerTask >= 100,
+ "Value of maxProcessingRatePerTask[%d] must be 100 events per second
or more",
+ maxProcessingRatePerTask
+ );
+ InvalidInput.conditionalException(
+ requestedTaskCount == null
+ || (requestedTaskCount >= config.getTaskCountMin() &&
requestedTaskCount <= config.getTaskCountMax()),
+ "Value of currentTaskCount[%d] must be within taskCountMin[%d] and
taskCountMax[%d]",
+ requestedTaskCount, config.getTaskCountMin(), config.getTaskCountMax()
+ );
+
+ // Simulate from the supervisor's live task count unless the caller pins
one.
+ final int currentTaskCount = supervisorSpec.getIoConfig().getTaskCount();
+ final int simulationTaskCount = Math.max(
+ config.getTaskCountMin(),
+ Math.min(
+ Configs.valueOrDefault(requestedTaskCount, currentTaskCount),
+ config.getTaskCountMax()
+ )
+ );
+
+ // Use the partition count and task duration from the supervisor spec
+ final int partitionCount = ((SeekableStreamSupervisor<?, ?, ?>)
supervisorPair.lhs).getKnownPartitionCount();
Review Comment:
[P2] Reject unavailable live partition topology
`getKnownPartitionCount()` is zero during startup and temporarily while
assignments are cleared. In that state all 200 samples return `taskCount=-1` in
a successful response, producing a bogus chart. Return a retryable or
invalid-input error until a positive live partition count is available.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]